using System; using AVFoundation; using CoreGraphics; using UIKit; using OpenTok; namespace Xamarin.Forms.Clinical6.iOS.Services { /// /// Opentok streaming service. /// public class OpentokStreamingService { #region private state protected object _syncRoot = new object(); protected string _apiKey; protected string _sessionId; protected string _userToken; protected bool _subscriberConnected; protected bool _sessionTerminationIsInProgress = false; #endregion public event Action OnSessionEnded = delegate { }; public event Action OnPublishStarted = delegate { }; #region singletone private static OpentokStreamingService _instance = new OpentokStreamingService(); public static OpentokStreamingService Instance { get { return _instance; } } #endregion #region private state private OTSession _session; private OTPublisher _publisher; private OTSubscriber _subscriber; private UIView _myStreamContainer; private UIView _otherStreamContainer; private bool _lastKnownVideoPublishingState; #endregion /// /// Sets the config. /// /// API key. /// Session identifier. /// User token. public void SetConfig(string apiKey, string sessionId, string userToken) { _apiKey = apiKey; _sessionId = sessionId; _userToken = userToken; } /// /// Inits the new session. /// public void InitNewSession() { InitNewSession(_apiKey, _sessionId, _userToken); } /// /// Inits the new session. /// /// API key. /// Session identifier. /// User token. public void InitNewSession(string apiKey, string sessionId, string userToken) { if (_session != null) { //stop running session if any and dispose all the resources EndSession(); } _apiKey = apiKey; _sessionId = sessionId; _userToken = userToken; IsVideoPublishingEnabled = true; IsAudioPublishingEnabled = true; IsVideoSubscriptionEnabled = true; IsAudioSubscriptionEnabled = true; IsSubscriberVideoEnabled = true; _session = new OTSession(_apiKey, _sessionId, null); SubscribeForSessionEvents(_session); _session.Init(); OTError error; _session.ConnectWithToken(_userToken, out error); } /// /// Publish this instance. /// private void Publish() { lock (_syncRoot) { if (_publisher != null || _session == null) { return; } OTError error; _publisher = new OTPublisher(null, new OTPublisherSettings { Name = "XamarinOpenTok", CameraFrameRate = OTCameraCaptureFrameRate.OTCameraCaptureFrameRate15FPS, CameraResolution = OTCameraCaptureResolution.High, }); SubscribeForPublisherEvents(_publisher, true); _session.Publish(_publisher, out error); ActivateStreamContainer(_myStreamContainer, _publisher.View); OnPublishStarted(); } } /// /// Subscribe the specified stream. /// /// Stream. private void Subscribe(OTStream stream) { lock (_syncRoot) { if (_subscriber != null || _session == null) return; OTError error; _subscriber = new OTSubscriber(stream, null); SubscribeForSubscriberEvents(_subscriber); _session.Subscribe(_subscriber, out error); } } /// /// Ends the session. /// public void EndSession() { lock (_syncRoot) { try { _sessionTerminationIsInProgress = true; if (_subscriber != null) { if (_subscriber.SubscribeToAudio) _subscriber.SubscribeToAudio = false; if (_subscriber.SubscribeToVideo) _subscriber.SubscribeToVideo = false; SubscribeForSubscriberEvents(_subscriber, false); _subscriber.Dispose(); _subscriber = null; } if (_publisher != null) { if (_publisher.PublishAudio) _publisher.PublishAudio = false; if (_publisher.PublishVideo) _publisher.PublishVideo = false; SubscribeForPublisherEvents(_publisher, false); _publisher.Dispose(); _publisher = null; } if (_session != null) { SubscribeForSessionEvents(_session, false); _session.Disconnect(); _session.Dispose(); _session = null; } _myStreamContainer.InvokeOnMainThread(() => { DeactivateStreamContainer(_myStreamContainer); _myStreamContainer = null; DeactivateStreamContainer(_otherStreamContainer); _otherStreamContainer = null; }); _apiKey = null; _sessionId = null; _userToken = null; IsVideoPublishingEnabled = false; IsAudioPublishingEnabled = false; IsVideoSubscriptionEnabled = false; IsAudioSubscriptionEnabled = false; IsSubscriberVideoEnabled = false; _subscriberConnected = false; } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { _sessionTerminationIsInProgress = false; OnSessionEnded(); } } } /// /// Releases all resource used by the /// object. /// /// Call when you are finished using the /// . The /// method leaves the in an /// unusable state. After calling , you must release all references to the /// so the garbage collector can /// reclaim the memory that the was occupying. public void Dispose() { EndSession(); } private bool _isVideoPublishingEnabled; /// /// Gets or sets a value indicating whether this /// is video publishing enabled. /// /// true if is video publishing enabled; otherwise, false. public bool IsVideoPublishingEnabled { get { return _isVideoPublishingEnabled; } set { if (_isVideoPublishingEnabled == value) return; _isVideoPublishingEnabled = value; OnVideoPublishingChanged(); } } private bool _isAudioPublishingEnabled; public bool IsAudioPublishingEnabled { get { return _isAudioPublishingEnabled; } set { if (_isAudioPublishingEnabled == value) return; _isAudioPublishingEnabled = value; OnAudioPublishingChanged(); } } private bool _isVideoSubscriptionEnabled; public bool IsVideoSubscriptionEnabled { get { return _isVideoSubscriptionEnabled; } set { if (_isVideoSubscriptionEnabled == value) return; _isVideoSubscriptionEnabled = value; OnVideoSubscriptionChanged(); } } private bool _isAudioSubscriptionEnabled; public bool IsAudioSubscriptionEnabled { get { return _isAudioSubscriptionEnabled; } set { if (_isAudioSubscriptionEnabled == value) return; _isAudioSubscriptionEnabled = value; OnAudioSubscriptionChanged(); } } private bool _isSubscriberVideoEnabled; public bool IsSubscriberVideoEnabled { get { return _isSubscriberVideoEnabled; } protected set { if (_isSubscriberVideoEnabled == value) return; _isSubscriberVideoEnabled = value; OnSubscriberVideoChanged(); } } /// /// Ons the subscriber video changed. /// protected virtual void OnSubscriberVideoChanged() { //we have to keep and raise the enabled/disabled flag because Stream.HasVideo property doesn't reflect the change at once } /// /// Swaps the camera. /// public void SwapCamera() { if (_publisher == null) return; if (_publisher.CameraPosition != AVCaptureDevicePosition.Front) _publisher.CameraPosition = AVCaptureDevicePosition.Front; else _publisher.CameraPosition = AVCaptureDevicePosition.Back; } /// /// Ons the video publishing changed. /// protected void OnVideoPublishingChanged() { if (_publisher == null) return; if (_publisher.PublishVideo != IsVideoPublishingEnabled) _publisher.PublishVideo = IsVideoPublishingEnabled; } /// /// Ons the audio publishing changed. /// protected void OnAudioPublishingChanged() { if (_publisher == null) return; if (_publisher.PublishAudio != IsAudioPublishingEnabled) _publisher.PublishAudio = IsAudioPublishingEnabled; } /// /// Ons the video subscription changed. /// protected void OnVideoSubscriptionChanged() { if (_subscriber == null) return; if (_subscriber.SubscribeToVideo != IsVideoSubscriptionEnabled) _subscriber.SubscribeToVideo = IsVideoSubscriptionEnabled; } /// /// Ons the audio subscription changed. /// protected void OnAudioSubscriptionChanged() { if (_subscriber == null) return; if (_subscriber.SubscribeToAudio != IsAudioSubscriptionEnabled) _subscriber.SubscribeToAudio = IsAudioSubscriptionEnabled; } /// /// Sets the stream container. /// /// Container. /// If set to true my stream. public void SetStreamContainer(object container, bool myStream) { var streamContainer = ((UIView)container); UIView streamView = null; if (myStream) { _myStreamContainer = streamContainer; if (_publisher != null) streamView = _publisher.View; } else { _otherStreamContainer = streamContainer; if (_subscriber != null) streamView = _subscriber.View; } ActivateStreamContainer(streamContainer, streamView); } #region private helpers /// /// Activates the stream container. /// /// Stream container. /// Stream view. private void ActivateStreamContainer(UIView streamContainer, UIView streamView) { DeactivateStreamContainer(streamContainer); if (streamContainer == null || streamView == null) return; if (streamView.Superview != null) streamView.RemoveFromSuperview(); streamView.Frame = new CGRect(0, 0, streamContainer.Frame.Width, streamContainer.Frame.Height); streamContainer.Add(streamView); } /// /// Deactivates the stream container. /// /// Stream container. private void DeactivateStreamContainer(UIView streamContainer) { if (streamContainer == null || streamContainer.Subviews == null) return; while (streamContainer.Subviews.Length > 0) { var view = streamContainer.Subviews[0]; view.RemoveFromSuperview(); } } #endregion #region events subscription /// /// Subscribes for session events. /// /// Session. /// If set to true subscribe. private void SubscribeForSessionEvents(OTSession session, bool subscribe = true) { if (session == null) return; if (subscribe) { session.ConnectionDestroyed += OnConnectionDestroyed; session.DidConnect += OnDidConnect; session.StreamCreated += OnStreamCreated; session.StreamDestroyed += OnStreamDestroyed; } else { session.ConnectionDestroyed -= OnConnectionDestroyed; session.DidConnect -= OnDidConnect; session.StreamCreated -= OnStreamCreated; session.StreamDestroyed -= OnStreamDestroyed; } } /// /// Subscribes for subscriber events. /// /// Subscriber. /// If set to true subscribe. private void SubscribeForSubscriberEvents(OTSubscriber subscriber, bool subscribe = true) { if (subscriber == null) return; if (subscribe) { subscriber.DidConnectToStream += OnSubscriberDidConnectToStream; subscriber.VideoDataReceived += OnSubscriberVideoDataReceived; subscriber.VideoEnabled += OnSubscriberVideoEnabled; } else { subscriber.DidConnectToStream -= OnSubscriberDidConnectToStream; subscriber.VideoDataReceived -= OnSubscriberVideoDataReceived; subscriber.VideoEnabled -= OnSubscriberVideoEnabled; } } /// /// Subscribes for publisher events. /// /// Publisher. /// If set to true subscribe. private void SubscribeForPublisherEvents(OTPublisher publisher, bool subscribe = true) { if (publisher == null) return; if (subscribe) { publisher.StreamCreated += OnPublisherStreamCreated; } else { publisher.StreamCreated += OnPublisherStreamCreated; } } #endregion #region session events /// /// Ons the connection destroyed. /// /// Sender. /// E. private void OnConnectionDestroyed(object sender, OTSessionDelegateConnectionEventArgs e) { EndSession(); } /// /// Ons the did connect. /// /// Sender. /// E. private void OnDidConnect(object sender, EventArgs e) { Publish(); } /// /// Ons the stream created. /// /// Sender. /// E. private void OnStreamCreated(object sender, OTSessionDelegateStreamEventArgs e) { Subscribe(e.Stream); } /// /// Ons the stream destroyed. /// /// Sender. /// E. private void OnStreamDestroyed(object sender, OTSessionDelegateStreamEventArgs e) { _myStreamContainer?.InvokeOnMainThread(() => { DeactivateStreamContainer(_myStreamContainer); DeactivateStreamContainer(_otherStreamContainer); }); } #endregion #region subscriber evenets /// /// Ons the subscriber video data received. /// /// Sender. /// E. private void OnSubscriberVideoDataReceived(object sender, EventArgs e) { ActivateStreamContainer(_otherStreamContainer, _subscriber.View); } /// /// Ons the subscriber video enabled. /// /// Sender. /// E. private void OnSubscriberVideoEnabled(object sender, OTSubscriberKitDelegateVideoEventReasonEventArgs e) { lock (_syncRoot) { if (_subscriber != null && _subscriber.Stream != null && _subscriber.Stream.HasVideo) IsSubscriberVideoEnabled = true; } } /// /// Ons the subscriber did connect to stream. /// /// Sender. /// E. private void OnSubscriberDidConnectToStream(object sender, EventArgs e) { lock (_syncRoot) { if (_subscriber != null) { _subscriberConnected = true; ActivateStreamContainer(_otherStreamContainer, _subscriber.View); if (_subscriber.Stream != null && _subscriber.Stream.HasVideo) IsSubscriberVideoEnabled = true; } } } #endregion #region subscriber evenets /// /// Ons the publisher stream created. /// /// Sender. /// E. void OnPublisherStreamCreated(object sender, OTPublisherDelegateStreamEventArgs e) { OnPublishStarted(); } #endregion } }