// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using UnityEngine;
namespace Microsoft.MixedReality.WebRTC.Unity
{
///
/// Abstract base class to simplify implementing a WebRTC signaling solution in Unity.
///
/// There is no requirement to use this class as a base class for a custom implementation,
/// but it handles automatically registering the necessary
/// event handlers, as well as dispatching free-threaded callbacks to the main Unity app thread
/// for simplicity and safety, and leaves the implementation with instead with two sending methods
/// and to
/// implement, as well as handling received messages.
///
public abstract class Signaler : MonoBehaviour
{
///
/// The this signaler needs to work for.
///
public PeerConnection PeerConnection;
#region Signaler interface
///
/// Asynchronously send an SDP message to the remote peer.
///
/// The SDP message to send to the remote peer.
///
/// A object completed once the message has been sent,
/// but not necessarily delivered.
///
public abstract Task SendMessageAsync(SdpMessage message);
///
/// Asynchronously send an ICE candidate to the remote peer.
///
/// The ICE candidate to send to the remote peer.
///
/// A object completed once the message has been sent,
/// but not necessarily delivered.
///
public abstract Task SendMessageAsync(IceCandidate candidate);
#endregion
///
/// Native object from the underlying
/// WebRTC C# library, available once the peer has been initialized.
///
protected WebRTC.PeerConnection _nativePeer = null;
///
/// Task queue used to defer actions to the main Unity app thread, which is the only thread
/// with access to Unity objects.
///
protected ConcurrentQueue _mainThreadWorkQueue = new ConcurrentQueue();
///
/// Callback fired from the when it finished
/// initializing, to subscribe to signaling-related events.
///
/// The peer connection to attach to
public void OnPeerInitialized()
{
_nativePeer = PeerConnection.Peer;
// Register handlers for the SDP events
_nativePeer.IceCandidateReadytoSend += OnIceCandidateReadyToSend_Listener;
_nativePeer.LocalSdpReadytoSend += OnLocalSdpReadyToSend_Listener;
}
///
/// Callback fired from the before it starts
/// uninitializing itself and disposing of the underlying implementation object.
///
/// The peer connection about to be deinitialized
public void OnPeerUninitializing()
{
// Unregister handlers for the SDP events
//_nativePeer.IceCandidateReadytoSend -= OnIceCandidateReadyToSend_Listener;
//_nativePeer.LocalSdpReadytoSend -= OnLocalSdpReadyToSend_Listener;
}
private void OnIceCandidateReadyToSend_Listener(IceCandidate candidate)
{
_mainThreadWorkQueue.Enqueue(() => OnIceCandidateReadyToSend(candidate));
}
///
/// Helper to split SDP offer and answer messages and dispatch to the appropriate handler.
///
/// The SDP message ready to be sent to the remote peer.
private void OnLocalSdpReadyToSend_Listener(SdpMessage message)
{
if (message.Type == SdpMessageType.Offer)
{
_mainThreadWorkQueue.Enqueue(() => OnSdpOfferReadyToSend(message));
}
else if (message.Type == SdpMessageType.Answer)
{
_mainThreadWorkQueue.Enqueue(() => OnSdpAnswerReadyToSend(message));
}
}
protected virtual void OnEnable()
{
PeerConnection.OnInitialized.AddListener(OnPeerInitialized);
PeerConnection.OnShutdown.AddListener(OnPeerUninitializing);
}
///
/// Unity Engine Update() hook
///
///
/// https://docs.unity3d.com/ScriptReference/MonoBehaviour.Update.html
///
protected virtual void Update()
{
// Process workloads queued from background threads
while (_mainThreadWorkQueue.TryDequeue(out Action action))
{
action();
}
}
protected virtual void OnDisable()
{
PeerConnection.OnInitialized.RemoveListener(OnPeerInitialized);
PeerConnection.OnShutdown.RemoveListener(OnPeerUninitializing);
}
///
/// Callback invoked when an ICE candidate message has been generated and is ready to
/// be sent to the remote peer by the signaling object.
///
/// ICE candidate to send to the remote peer.
protected virtual void OnIceCandidateReadyToSend(IceCandidate candidate)
{
SendMessageAsync(candidate);
}
///
/// Callback invoked when a local SDP offer has been generated and is ready to
/// be sent to the remote peer by the signaling object.
///
/// The SDP offer message to send.
protected virtual void OnSdpOfferReadyToSend(SdpMessage offer)
{
SendMessageAsync(offer);
}
///
/// Callback invoked when a local SDP answer has been generated and is ready to
/// be sent to the remote peer by the signaling object.
///
/// The SDP answer message to send.
protected virtual void OnSdpAnswerReadyToSend(SdpMessage answer)
{
SendMessageAsync(answer);
}
}
}