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
}
}