using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Unity.RenderStreaming.Signaling;
using Unity.WebRTC;
namespace Unity.RenderStreaming
{
///
///
///
internal struct RenderStreamingDependencies
{
///
///
///
public ISignaling signaling;
///
///
///
public RTCConfiguration config;
///
///
///
public Func startCoroutine;
///
///
///
public Action stopCoroutine;
///
/// unit is second;
///
public float resentOfferInterval;
}
///
///
///
internal class SignalingManagerInternal : IDisposable,
IRenderStreamingHandler, IRenderStreamingDelegate
{
///
///
///
public event Action onStart;
///
///
///
public event Action onCreatedConnection;
///
///
///
public event Action onDeletedConnection;
///
///
///
public event Action onGotOffer;
///
///
///
public event Action onGotAnswer;
///
///
///
public event Action onConnect;
///
///
///
public event Action onDisconnect;
///
///
///
public event Action onAddTransceiver;
///
///
///
public event Action onAddChannel;
private bool _disposed;
private readonly ISignaling _signaling;
private RTCConfiguration _config;
private readonly Func _startCoroutine;
private readonly Action _stopCoroutine;
private readonly Dictionary _mapConnectionIdAndPeer =
new Dictionary();
private bool _runningResendCoroutine;
private float _resendInterval = 3.0f;
///
///
///
///
public SignalingManagerInternal(ref RenderStreamingDependencies dependencies)
{
if (dependencies.signaling == null)
throw new ArgumentException("Signaling instance is null.");
if (dependencies.startCoroutine == null)
throw new ArgumentException("Coroutine action instance is null.");
_config = dependencies.config;
_startCoroutine = dependencies.startCoroutine;
_stopCoroutine = dependencies.stopCoroutine;
_resendInterval = dependencies.resentOfferInterval;
_signaling = dependencies.signaling;
_signaling.OnStart += OnStart;
_signaling.OnCreateConnection += OnCreateConnection;
_signaling.OnDestroyConnection += OnDestroyConnection;
_signaling.OnOffer += OnOffer;
_signaling.OnAnswer += OnAnswer;
_signaling.OnIceCandidate += OnIceCandidate;
_signaling.Start();
_startCoroutine(WebRTC.WebRTC.Update());
}
///
///
///
~SignalingManagerInternal()
{
Dispose();
}
///
///
///
public void Dispose()
{
if (this._disposed)
{
return;
}
_runningResendCoroutine = false;
_signaling.Stop();
_signaling.OnStart -= OnStart;
_signaling.OnCreateConnection -= OnCreateConnection;
_signaling.OnDestroyConnection -= OnDestroyConnection;
_signaling.OnOffer -= OnOffer;
_signaling.OnAnswer -= OnAnswer;
_signaling.OnIceCandidate -= OnIceCandidate;
foreach(var pair in _mapConnectionIdAndPeer)
pair.Value.Dispose();
this._disposed = true;
GC.SuppressFinalize(this);
}
///
///
///
///
public void CreateConnection(string connectionId)
{
_signaling.OpenConnection(connectionId);
}
///
///
///
///
public void DeleteConnection(string connectionId)
{
_signaling.CloseConnection(connectionId);
}
public bool ExistConnection(string connectionId)
{
return _mapConnectionIdAndPeer.ContainsKey(connectionId);
}
public bool IsConnected(string connectionId)
{
return _mapConnectionIdAndPeer.TryGetValue(connectionId, out var peer) && peer.IsConnected();
}
public bool IsStable(string connectionId)
{
return _mapConnectionIdAndPeer.TryGetValue(connectionId, out var peer) && peer.IsStable();
}
///
///
///
///
///
public void RemoveSenderTrack(string connectionId, MediaStreamTrack track)
{
var sender = GetSenders(connectionId).First(s => s.Track == track);
_mapConnectionIdAndPeer[connectionId].peer.RemoveTrack(sender);
}
///
///
///
///
///
///
///
public RTCRtpTransceiver AddTransceiver(string connectionId, MediaStreamTrack track, RTCRtpTransceiverInit init = null)
{
var transceiver = _mapConnectionIdAndPeer[connectionId].peer.AddTransceiver(track, init);
return transceiver;
}
///
///
///
///
///
///
///
public RTCRtpTransceiver AddTransceiver(string connectionId, TrackKind kind, RTCRtpTransceiverInit init = null)
{
var transceiver = _mapConnectionIdAndPeer[connectionId].peer.AddTransceiver(kind, init);
return transceiver;
}
///
///
///
///
///
///
public RTCDataChannel CreateChannel(string connectionId, string name)
{
RTCDataChannelInit conf = new RTCDataChannelInit();
if (string.IsNullOrEmpty(name))
name = Guid.NewGuid().ToString();
return _mapConnectionIdAndPeer[connectionId].peer.CreateDataChannel(name, conf);
}
///
///
///
///
///
///
public IEnumerable GetSenders(string connectionId)
{
return _mapConnectionIdAndPeer[connectionId].peer.GetSenders();
}
///
///
///
///
///
///
public IEnumerable GetReceivers(string connectionId)
{
return _mapConnectionIdAndPeer[connectionId].peer.GetReceivers();
}
///
///
///
///
///
///
public IEnumerable GetTransceivers(string connectionId)
{
return _mapConnectionIdAndPeer[connectionId].peer.GetTransceivers();
}
///
///
///
///
public void SendOffer(string connectionId)
{
if (!_mapConnectionIdAndPeer.TryGetValue(connectionId, out var pc))
return;
pc.SendOffer();
}
///
///
///
///
public void SendAnswer(string connectionId)
{
if (!_mapConnectionIdAndPeer.TryGetValue(connectionId, out var pc))
return;
pc.SendAnswer();
}
IEnumerator ResendOfferCoroutine()
{
HashSet failedConnections = new HashSet();
while (_runningResendCoroutine)
{
failedConnections.Clear();
foreach (var peer in _mapConnectionIdAndPeer)
{
if (peer.Value.peer.ConnectionState == RTCPeerConnectionState.Failed)
{
failedConnections.Add(peer.Key);
}
else if(peer.Value.waitingAnswer)
{
peer.Value.SendOffer();
}
}
foreach (var connectionId in failedConnections)
{
DestroyConnection(connectionId);
}
yield return 0;
}
}
void OnStart(ISignaling signaling)
{
if (!_runningResendCoroutine)
{
_runningResendCoroutine = true;
_startCoroutine(ResendOfferCoroutine());
}
onStart?.Invoke();
}
void OnCreateConnection(ISignaling signaling, string connectionId, bool polite)
{
CreatePeerConnection(connectionId, polite);
onCreatedConnection?.Invoke(connectionId);
}
void OnDestroyConnection(ISignaling signaling, string connectionId)
{
DestroyConnection(connectionId);
}
void DestroyConnection(string connectionId)
{
DeletePeerConnection(connectionId);
onDeletedConnection?.Invoke(connectionId);
}
PeerConnection CreatePeerConnection(string connectionId, bool polite)
{
if (_mapConnectionIdAndPeer.TryGetValue(connectionId, out var peer))
{
peer.Dispose();
}
peer = new PeerConnection(polite, _config, _resendInterval, _startCoroutine, _stopCoroutine);
_mapConnectionIdAndPeer[connectionId] = peer;
peer.OnConnectHandler += () => onConnect?.Invoke(connectionId);
peer.OnDisconnectHandler += () => onDisconnect?.Invoke(connectionId);
peer.OnDataChannelHandler += channel => onAddChannel?.Invoke(connectionId, channel);;
peer.OnTrackEventHandler += e => onAddTransceiver?.Invoke(connectionId, e.Transceiver);
peer.SendOfferHandler += desc => _signaling?.SendOffer(connectionId, desc);
peer.SendAnswerHandler += desc => _signaling?.SendAnswer(connectionId, desc);
peer.SendCandidateHandler += candidate => _signaling?.SendCandidate(connectionId, candidate);
return peer;
}
void DeletePeerConnection(string connectionId)
{
if (!_mapConnectionIdAndPeer.TryGetValue(connectionId, out var peer))
{
return;
}
peer.Dispose();
_mapConnectionIdAndPeer.Remove(connectionId);
}
void OnAnswer(ISignaling signaling, DescData e)
{
if (!_mapConnectionIdAndPeer.TryGetValue(e.connectionId, out var pc))
{
Debug.LogWarning($"connectionId:{e.connectionId}, peerConnection not exist");
return;
}
RTCSessionDescription description = new RTCSessionDescription {type = RTCSdpType.Answer, sdp = e.sdp};
_startCoroutine(pc.OnGotDescription(description, () => onGotAnswer?.Invoke(e.connectionId, e.sdp)));
}
void OnIceCandidate(ISignaling signaling, CandidateData e)
{
if (!_mapConnectionIdAndPeer.TryGetValue(e.connectionId, out var pc))
{
return;
}
RTCIceCandidateInit option = new RTCIceCandidateInit
{
candidate = e.candidate, sdpMLineIndex = e.sdpMLineIndex, sdpMid = e.sdpMid
};
pc.OnGotIceCandidate(new RTCIceCandidate(option));
}
void OnOffer(ISignaling signaling, DescData e)
{
var connectionId = e.connectionId;
if (!_mapConnectionIdAndPeer.TryGetValue(connectionId, out var pc))
{
pc = CreatePeerConnection(connectionId, e.polite);
}
RTCSessionDescription description = new RTCSessionDescription {type = RTCSdpType.Offer, sdp = e.sdp};
_startCoroutine(pc.OnGotDescription(description, () => onGotOffer?.Invoke(connectionId, e.sdp)));
}
}
}