// 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); } } }