diff --git a/com.unity.renderstreaming/Runtime/Scripts/PeerConnection.cs b/com.unity.renderstreaming/Runtime/Scripts/PeerConnection.cs
index 72316752d..fbe622ebb 100644
--- a/com.unity.renderstreaming/Runtime/Scripts/PeerConnection.cs
+++ b/com.unity.renderstreaming/Runtime/Scripts/PeerConnection.cs
@@ -1,52 +1,104 @@
using System;
+using System.Collections;
+using System.Collections.Generic;
using Unity.WebRTC;
+using UnityEngine;
+using UnityEngine.Assertions;
namespace Unity.RenderStreaming
{
internal class PeerConnection : IDisposable
{
- public readonly RTCPeerConnection peer;
- public readonly bool polite;
+ public delegate void OnConnectEvent();
- public bool makingOffer;
- public bool ignoreOffer;
- public bool srdAnswerPending;
- public bool makingAnswer;
+ public delegate void OnDisconnectEvent();
- bool disposed = false;
+ public delegate void OnDataChannelEvent(RTCDataChannel channel);
+
+ public delegate void OnTrackEvent(RTCTrackEvent trackEvent);
+
+ public delegate void SendOfferEvent(RTCSessionDescription description);
+
+ public delegate void SendAnswerEvent(RTCSessionDescription description);
+
+ public delegate void SendCandidateEvent(RTCIceCandidate candidate);
+
+ public OnConnectEvent OnConnectHandler;
+ public OnDisconnectEvent OnDisconnectHandler;
+ public OnDataChannelEvent OnDataChannelHandler;
+ public OnTrackEvent OnTrackEventHandler;
+ public SendOfferEvent SendOfferHandler;
+ public SendAnswerEvent SendAnswerHandler;
+ public SendCandidateEvent SendCandidateHandler;
+
+ public RTCPeerConnection peer => _peer;
///
- ///
+ ///
///
public bool waitingAnswer
{
get => _waitingAnswer;
- set {
+ private set
+ {
_waitingAnswer = value;
- timeSinceStartWaitingAnswer =
- _waitingAnswer ? UnityEngine.Time.realtimeSinceStartup : 0;
+ _timeSinceStartWaitingAnswer =
+ _waitingAnswer ? Time.realtimeSinceStartup : 0;
}
}
- ///
- /// see Time.realtimeSinceStartup
- ///
- public float timeSinceStartWaitingAnswer { get; private set; }
+ private readonly RTCPeerConnection _peer;
+ private readonly bool _polite;
+ private readonly Func _startCoroutine;
+ private readonly Action _stopCoroutine;
+ private readonly HashSet> _processingCoroutineList = new HashSet>();
+ // resend offer
+ private readonly float _resendInterval;
private bool _waitingAnswer;
+ private float _timeSinceStartWaitingAnswer;
+
+ // processing set description
+ private bool _processingSetDescription;
+
+ // processing got description
+ private bool _ignoreOffer;
+ private bool _srdAnswerPending;
+
- public PeerConnection(RTCPeerConnection peer, bool polite)
+ private bool _disposed = false;
+
+ public PeerConnection(bool polite, RTCConfiguration config, float resendInterval, Func startCoroutine, Action stopCoroutine)
{
- this.peer = peer;
- this.polite = polite;
+ _polite = polite;
+ _resendInterval = resendInterval;
+ _startCoroutine = startCoroutine;
+ _stopCoroutine = stopCoroutine;
+
+ _peer = new RTCPeerConnection(ref config);
+ _peer.OnDataChannel = channel => OnDataChannelHandler?.Invoke(channel);
+ _peer.OnIceCandidate = candidate => SendCandidateHandler?.Invoke(candidate);
+ _peer.OnTrack = trackEvent => OnTrackEventHandler?.Invoke(trackEvent);
+ _peer.OnConnectionStateChange = state =>
+ {
+ switch (state)
+ {
+ case RTCPeerConnectionState.Connected:
+ OnConnectHandler?.Invoke();
+ break;
+ case RTCPeerConnectionState.Disconnected:
+ OnDisconnectHandler?.Invoke();
+ break;
+ }
+ };
+ _peer.OnNegotiationNeeded = () => StartCoroutine(OnNegotiationNeeded());
}
- ///
- ///
- ///
- public void RestartTimerForWaitingAnswer()
+ private void StartCoroutine(IEnumerator enumerator)
{
- timeSinceStartWaitingAnswer = UnityEngine.Time.realtimeSinceStartup;
+ var co = _startCoroutine(enumerator);
+ _processingCoroutineList.RemoveWhere(x => !x.TryGetTarget(out _));
+ _processingCoroutineList.Add(new WeakReference(co));
}
~PeerConnection()
@@ -56,30 +108,192 @@ public void RestartTimerForWaitingAnswer()
public override string ToString()
{
- var str = polite ? "polite" : "impolite";
- return $"[{str}-{base.ToString()}]";
+ var str = _polite ? "polite" : "impolite";
+ return
+ $"[{str}-{nameof(PeerConnection)} {nameof(_peer.ConnectionState)}:{_peer.ConnectionState} {nameof(_peer.IceConnectionState)}:{_peer.IceConnectionState} {nameof(_peer.SignalingState)}:{_peer.SignalingState} {nameof(_peer.GatheringState)}:{_peer.GatheringState}]";
}
public void Dispose()
{
- if (peer == null)
+ if (_disposed)
+ return;
+
+ foreach (var weakCo in _processingCoroutineList)
{
+ if (weakCo.TryGetTarget(out var co))
+ {
+ _stopCoroutine?.Invoke(co);
+ }
+ }
+ _processingCoroutineList.Clear();
+
+ if (_peer != null)
+ {
+ _peer.OnTrack = null;
+ _peer.OnDataChannel = null;
+ _peer.OnIceCandidate = null;
+ _peer.OnNegotiationNeeded = null;
+ _peer.OnConnectionStateChange = null;
+ _peer.OnIceConnectionChange = null;
+ _peer.OnIceGatheringStateChange = null;
+ _peer.Dispose();
+ }
+
+ _disposed = true;
+ GC.SuppressFinalize(this);
+ }
+
+ private IEnumerator OnNegotiationNeeded()
+ {
+ var waitProcessSetDescription = new WaitWhile(() => _processingSetDescription);
+ yield return waitProcessSetDescription;
+ SendOffer();
+ }
+
+ public bool IsConnected()
+ {
+ return _peer.ConnectionState == RTCPeerConnectionState.Connected;
+ }
+
+ public bool IsStable()
+ {
+ return _peer.SignalingState == RTCSignalingState.Stable ||
+ (_peer.SignalingState == RTCSignalingState.HaveLocalOffer && _srdAnswerPending);
+ }
+
+ public void SendOffer()
+ {
+ if (_processingSetDescription)
+ {
+ Debug.LogWarning($"{this} already processing other set description");
return;
}
- if (disposed)
+
+ if (!IsStable())
+ {
+ if (!_waitingAnswer)
+ {
+ throw new InvalidOperationException(
+ $"{this} sendoffer needs in stable state, current state is {_peer.SignalingState}");
+ }
+
+ var timeout = _timeSinceStartWaitingAnswer + _resendInterval;
+
+ if (timeout < Time.realtimeSinceStartup)
+ {
+ SendOfferHandler?.Invoke(_peer.LocalDescription);
+ _timeSinceStartWaitingAnswer = Time.realtimeSinceStartup;
+ }
return;
+ }
- peer.OnTrack = null;
- peer.OnDataChannel = null;
- peer.OnIceCandidate = null;
- peer.OnNegotiationNeeded = null;
- peer.OnConnectionStateChange = null;
- peer.OnIceConnectionChange = null;
- peer.OnIceGatheringStateChange = null;
- peer.Dispose();
+ StartCoroutine(SendOfferCoroutine());
+ }
- disposed = true;
- GC.SuppressFinalize(this);
+ private IEnumerator SendOfferCoroutine()
+ {
+ Assert.AreEqual(_peer.SignalingState, RTCSignalingState.Stable);
+ Assert.AreEqual(_processingSetDescription, false);
+ Assert.AreEqual(waitingAnswer, false);
+
+ _processingSetDescription = true;
+
+ var opLocalDesc = _peer.SetLocalDescription();
+ yield return opLocalDesc;
+
+ if (opLocalDesc.IsError)
+ {
+ Debug.LogError($"{this} {opLocalDesc.Error.message}");
+ _processingSetDescription = false;
+ yield break;
+ }
+
+ Assert.AreEqual(_peer.LocalDescription.type, RTCSdpType.Offer);
+ Assert.AreEqual(_peer.SignalingState, RTCSignalingState.HaveLocalOffer);
+ _processingSetDescription = false;
+ waitingAnswer = true;
+
+ SendOfferHandler?.Invoke(_peer.LocalDescription);
+ }
+
+ public void SendAnswer()
+ {
+ if (_processingSetDescription)
+ {
+ Debug.LogWarning($"{this} already processing other set description");
+ return;
+ }
+
+ StartCoroutine(SendAnswerCoroutine());
+ }
+
+ private IEnumerator SendAnswerCoroutine()
+ {
+ Assert.AreEqual(_peer.SignalingState, RTCSignalingState.HaveRemoteOffer);
+ Assert.AreEqual(_processingSetDescription, false);
+
+ _processingSetDescription = true;
+
+ var opLocalDesc = _peer.SetLocalDescription();
+ yield return opLocalDesc;
+
+ if (opLocalDesc.IsError)
+ {
+ Debug.LogError($"{this} {opLocalDesc.Error.message}");
+ _processingSetDescription = false;
+ yield break;
+ }
+
+ Assert.AreEqual(_peer.LocalDescription.type, RTCSdpType.Answer);
+ Assert.AreEqual(_peer.SignalingState, RTCSignalingState.Stable);
+ _processingSetDescription = false;
+
+ SendAnswerHandler?.Invoke(_peer.LocalDescription);
+ }
+
+ public IEnumerator OnGotDescription(RTCSessionDescription description, Action onComplete)
+ {
+ var waitOtherProcess = new WaitWhile(() => _processingSetDescription);
+ yield return waitOtherProcess;
+
+ _ignoreOffer = description.type == RTCSdpType.Offer && !_polite && (_processingSetDescription || !IsStable());
+
+ if (_ignoreOffer)
+ {
+ Debug.LogWarning($"{this} glare - ignoreOffer.");
+ yield break;
+ }
+
+ waitingAnswer = false;
+ _srdAnswerPending = description.type == RTCSdpType.Answer;
+ _processingSetDescription = true;
+
+ var remoteDescOp = _peer.SetRemoteDescription(ref description);
+ yield return remoteDescOp;
+ if (remoteDescOp.IsError)
+ {
+ Debug.LogError($"{this} {remoteDescOp.Error.message}");
+ _srdAnswerPending = false;
+ _processingSetDescription = false;
+ yield break;
+ }
+
+ _srdAnswerPending = false;
+ _processingSetDescription = false;
+ onComplete?.Invoke();
+ }
+
+ public bool OnGotIceCandidate(RTCIceCandidate candidate)
+ {
+ if (!_peer.AddIceCandidate(candidate))
+ {
+ if (!_ignoreOffer)
+ Debug.LogWarning($"{this} this candidate can't accept on state.");
+
+ return false;
+ }
+
+ return true;
}
}
}
diff --git a/com.unity.renderstreaming/Runtime/Scripts/RenderStreaming.cs b/com.unity.renderstreaming/Runtime/Scripts/RenderStreaming.cs
index 9eac06e2d..2584227b7 100644
--- a/com.unity.renderstreaming/Runtime/Scripts/RenderStreaming.cs
+++ b/com.unity.renderstreaming/Runtime/Scripts/RenderStreaming.cs
@@ -162,6 +162,7 @@ private void _Run(
config = _conf,
signaling = _signaling,
startCoroutine = StartCoroutine,
+ stopCoroutine = StopCoroutine,
resentOfferInterval = interval,
};
var _handlers = (handlers ?? this.handlers.AsEnumerable()).Where(_ => _ != null);
diff --git a/com.unity.renderstreaming/Runtime/Scripts/RenderStreamingInternal.cs b/com.unity.renderstreaming/Runtime/Scripts/RenderStreamingInternal.cs
index d73818bdc..b4fb998e9 100644
--- a/com.unity.renderstreaming/Runtime/Scripts/RenderStreamingInternal.cs
+++ b/com.unity.renderstreaming/Runtime/Scripts/RenderStreamingInternal.cs
@@ -29,6 +29,11 @@ internal struct RenderStreamingDependencies
///
public Func startCoroutine;
+ ///
+ ///
+ ///
+ public Action stopCoroutine;
+
///
/// unit is second;
///
@@ -90,6 +95,7 @@ internal class RenderStreamingInternal : IDisposable,
private readonly ISignaling _signaling;
private RTCConfiguration _config;
private readonly Func _startCoroutine;
+ private readonly Action _stopCoroutine;
private readonly Dictionary _mapConnectionIdAndPeer =
new Dictionary();
private bool _runningResendCoroutine;
@@ -118,6 +124,7 @@ public RenderStreamingInternal(ref RenderStreamingDependencies dependencies)
_config = dependencies.config;
_startCoroutine = dependencies.startCoroutine;
+ _stopCoroutine = dependencies.stopCoroutine;
_resendInterval = dependencies.resentOfferInterval;
_signaling = dependencies.signaling;
_signaling.OnStart += OnStart;
@@ -190,23 +197,12 @@ public bool ExistConnection(string connectionId)
public bool IsConnected(string connectionId)
{
- if (!_mapConnectionIdAndPeer.TryGetValue(connectionId, out var peer))
- return false;
-
- return peer.peer.ConnectionState == RTCPeerConnectionState.Connected;
+ return _mapConnectionIdAndPeer.TryGetValue(connectionId, out var peer) && peer.IsConnected();
}
public bool IsStable(string connectionId)
{
- if (!_mapConnectionIdAndPeer.TryGetValue(connectionId, out var peer))
- return false;
-
- if (peer.makingOffer || peer.waitingAnswer)
- {
- return false;
- }
-
- return peer.peer.SignalingState == RTCSignalingState.Stable;
+ return _mapConnectionIdAndPeer.TryGetValue(connectionId, out var peer) && peer.IsStable();
}
///
@@ -301,18 +297,7 @@ public void SendOffer(string connectionId)
{
if (!_mapConnectionIdAndPeer.TryGetValue(connectionId, out var pc))
return;
- if (!IsStable(connectionId))
- {
- if (!pc.waitingAnswer)
- {
- throw new InvalidOperationException(
- $"{pc} sendoffer needs in stable state, current state is {pc.peer.SignalingState}");
- }
-
- _signaling.SendOffer(connectionId, pc.peer.LocalDescription);
- return;
- }
- _startCoroutine(SendOfferCoroutine(connectionId, pc));
+ pc.SendOffer();
}
///
@@ -321,22 +306,18 @@ public void SendOffer(string connectionId)
///
public void SendAnswer(string connectionId)
{
- _startCoroutine(SendAnswerCoroutine(connectionId, _mapConnectionIdAndPeer[connectionId]));
+ if (!_mapConnectionIdAndPeer.TryGetValue(connectionId, out var pc))
+ return;
+ pc.SendAnswer();
}
IEnumerator ResendOfferCoroutine()
{
while (_runningResendCoroutine)
{
- foreach (var pair in _mapConnectionIdAndPeer.Where(x => x.Value.waitingAnswer))
+ foreach (var peer in _mapConnectionIdAndPeer.Where(x => x.Value.waitingAnswer))
{
- float timeout = pair.Value.timeSinceStartWaitingAnswer + _resendInterval;
-
- if (timeout < Time.realtimeSinceStartup)
- {
- _signaling.SendOffer(pair.Key, pair.Value.peer.LocalDescription);
- pair.Value.RestartTimerForWaitingAnswer();
- }
+ peer.Value.SendOffer();
}
yield return 0;
}
@@ -368,25 +349,19 @@ PeerConnection CreatePeerConnection(string connectionId, bool polite)
{
if (_mapConnectionIdAndPeer.TryGetValue(connectionId, out var peer))
{
- peer.peer.Close();
+ peer.Dispose();
}
- var pc = new RTCPeerConnection();
- peer = new PeerConnection(pc, polite);
+ peer = new PeerConnection(polite, _config, _resendInterval, _startCoroutine, _stopCoroutine);
_mapConnectionIdAndPeer[connectionId] = peer;
- pc.SetConfiguration(ref _config);
- pc.OnDataChannel = channel => { OnDataChannel(connectionId, channel); };
- pc.OnIceCandidate = candidate =>
- {
- _signaling.SendCandidate(connectionId, candidate);
- };
- pc.OnConnectionStateChange = state => OnConnectionStateChange(connectionId, state);
- pc.OnTrack = trackEvent =>
- {
- onAddTransceiver?.Invoke(connectionId, trackEvent.Transceiver);
- };
- pc.OnNegotiationNeeded = () => _startCoroutine(OnNegotiationNeeded(connectionId));
+ 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;
}
@@ -401,62 +376,6 @@ void DeletePeerConnection(string connectionId)
_mapConnectionIdAndPeer.Remove(connectionId);
}
- void OnDataChannel(string connectionId, RTCDataChannel channel)
- {
- onAddChannel?.Invoke(connectionId, channel);
- }
-
- void OnConnectionStateChange(string connectionId, RTCPeerConnectionState state)
- {
- switch (state)
- {
- case RTCPeerConnectionState.Connected:
- onConnect?.Invoke(connectionId);
- break;
- case RTCPeerConnectionState.Disconnected:
- onDisconnect?.Invoke(connectionId);
- break;
- }
- }
-
- IEnumerator OnNegotiationNeeded(string connectionId)
- {
- yield return new WaitWhile(() => !IsStable(connectionId));
- SendOffer(connectionId);
- }
-
- IEnumerator SendOfferCoroutine(string connectionId, PeerConnection pc)
- {
- // waiting other setLocalDescription process
- yield return new WaitWhile(() => !IsStable(connectionId));
-
- if (!ExistConnection(connectionId))
- yield break;
-
- Assert.AreEqual(pc.peer.SignalingState, RTCSignalingState.Stable,
- $"{pc} negotiationneeded always fires in stable state");
- Assert.AreEqual(pc.makingOffer, false, $"{pc} negotiationneeded not already in progress");
-
- pc.makingOffer = true;
- var opLocalDesc = pc.peer.SetLocalDescription();
- yield return opLocalDesc;
-
- if (opLocalDesc.IsError)
- {
- Debug.LogError($"{pc} {opLocalDesc.Error.message}");
- pc.makingOffer = false;
- yield break;
- }
-
- Assert.AreEqual(pc.peer.SignalingState, RTCSignalingState.HaveLocalOffer,
- $"{pc} negotiationneeded not racing with onmessage");
- Assert.AreEqual(pc.peer.LocalDescription.type, RTCSdpType.Offer, $"{pc} negotiationneeded SLD worked");
- pc.makingOffer = false;
- pc.waitingAnswer = true;
-
- _signaling.SendOffer(connectionId, pc.peer.LocalDescription);
- }
-
void OnAnswer(ISignaling signaling, DescData e)
{
if (!_mapConnectionIdAndPeer.TryGetValue(e.connectionId, out var pc))
@@ -465,36 +384,8 @@ void OnAnswer(ISignaling signaling, DescData e)
return;
}
- _startCoroutine(GotAnswerCoroutine(e.connectionId, pc, e.sdp));
- }
-
- IEnumerator GotAnswerCoroutine(string connectionId, PeerConnection pc, string sdp)
- {
- var description = new RTCSessionDescription();
- description.type = RTCSdpType.Answer;
- description.sdp = sdp;
-
- // waiting other setLocalDescription process
- yield return new WaitWhile(() => pc.makingOffer || pc.makingAnswer);
-
- pc.waitingAnswer = false;
- pc.srdAnswerPending = true;
-
- var opRemoteDesc = pc.peer.SetRemoteDescription(ref description);
- yield return opRemoteDesc;
-
- if (opRemoteDesc.IsError)
- {
- Debug.LogError($"{pc} {opRemoteDesc.Error.message}");
- pc.srdAnswerPending = false;
- yield break;
- }
-
- Assert.AreEqual(pc.peer.RemoteDescription.type, RTCSdpType.Answer, $"{pc} Answer was set");
- Assert.AreEqual(pc.peer.SignalingState, RTCSignalingState.Stable, $"{pc} answered");
- pc.srdAnswerPending = false;
-
- onGotAnswer?.Invoke(connectionId, sdp);
+ 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)
@@ -508,11 +399,7 @@ void OnIceCandidate(ISignaling signaling, CandidateData e)
{
candidate = e.candidate, sdpMLineIndex = e.sdpMLineIndex, sdpMid = e.sdpMid
};
-
- if (!pc.peer.AddIceCandidate(new RTCIceCandidate(option)) && !pc.ignoreOffer)
- {
- Debug.LogWarning($"{pc} this candidate can't accept current signaling state {pc.peer.SignalingState}.");
- }
+ pc.OnGotIceCandidate(new RTCIceCandidate(option));
}
void OnOffer(ISignaling signaling, DescData e)
@@ -523,64 +410,8 @@ void OnOffer(ISignaling signaling, DescData e)
pc = CreatePeerConnection(connectionId, e.polite);
}
- _startCoroutine(GotOfferCoroutine(connectionId, pc, e.sdp));
- }
-
- IEnumerator GotOfferCoroutine(string connectionId, PeerConnection pc, string sdp)
- {
- RTCSessionDescription description;
- description.type = RTCSdpType.Offer;
- description.sdp = sdp;
-
- var isStable =
- pc.peer.SignalingState == RTCSignalingState.Stable ||
- (pc.peer.SignalingState == RTCSignalingState.HaveLocalOffer && pc.srdAnswerPending);
- pc.ignoreOffer = !pc.polite && (pc.makingOffer || !isStable);
- if (pc.ignoreOffer || pc.makingAnswer)
- {
- Debug.LogWarning($"{pc} glare - ignoreOffer {nameof(pc.peer.SignalingState)}:{pc.peer.SignalingState}");
- yield break;
- }
-
- // waiting other setRemoteDescription process
- yield return new WaitWhile(() => pc.srdAnswerPending);
- pc.waitingAnswer = false;
-
- var opRemoteDesc = pc.peer.SetRemoteDescription(ref description);
- yield return opRemoteDesc;
-
- if (opRemoteDesc.IsError)
- {
- Debug.LogError($"{pc} {opRemoteDesc.Error.message}");
- yield break;
- }
-
- Assert.AreEqual(pc.peer.RemoteDescription.type, RTCSdpType.Offer, $"{pc} SRD worked");
- Assert.AreEqual(pc.peer.SignalingState, RTCSignalingState.HaveRemoteOffer, $"{pc} Remote offer");
-
- onGotOffer?.Invoke(connectionId, sdp);
- }
-
- IEnumerator SendAnswerCoroutine(string connectionId, PeerConnection pc)
- {
- pc.makingAnswer = true;
-
- var opLocalDesc = pc.peer.SetLocalDescription();
- yield return opLocalDesc;
-
- if (opLocalDesc.IsError)
- {
- Debug.LogError($"{pc} {opLocalDesc.Error.message}");
- pc.makingAnswer = false;
- yield break;
- }
-
- Assert.AreEqual(pc.peer.LocalDescription.type, RTCSdpType.Answer, $"{pc} onmessage SLD worked");
- Assert.AreEqual(pc.peer.SignalingState, RTCSignalingState.Stable,
- $"{pc} onmessage not racing with negotiationneeded");
- pc.makingAnswer = false;
-
- _signaling.SendAnswer(connectionId, pc.peer.LocalDescription);
+ RTCSessionDescription description = new RTCSessionDescription {type = RTCSdpType.Offer, sdp = e.sdp};
+ _startCoroutine(pc.OnGotDescription(description, () => onGotOffer?.Invoke(connectionId, e.sdp)));
}
}
}
diff --git a/com.unity.renderstreaming/Tests/Runtime/InputSystem/InputRemotingTest.cs b/com.unity.renderstreaming/Tests/Runtime/InputSystem/InputRemotingTest.cs
index ae9814bb9..2bffbbd2f 100644
--- a/com.unity.renderstreaming/Tests/Runtime/InputSystem/InputRemotingTest.cs
+++ b/com.unity.renderstreaming/Tests/Runtime/InputSystem/InputRemotingTest.cs
@@ -63,6 +63,7 @@ private RenderStreamingDependencies CreateDependencies()
iceServers = new[] { new RTCIceServer { urls = new[] { "stun:stun.l.google.com:19302" } } },
},
startCoroutine = _test.component.StartCoroutine,
+ stopCoroutine = _test.component.StopCoroutine,
resentOfferInterval = ResendOfferInterval,
};
}
@@ -176,7 +177,7 @@ public void Receiver()
receiverDisposer.Dispose();
}
- /// todo(kazuki): This test is failed for timeout on macOS
+ /// todo(kazuki): This test is failed for timeout on macOS
[UnityTest, Timeout(3000)]
[UnityPlatform(exclude = new[] { RuntimePlatform.OSXPlayer })]
public IEnumerator AddDevice()
diff --git a/com.unity.renderstreaming/Tests/Runtime/PeerConnectionTest.cs b/com.unity.renderstreaming/Tests/Runtime/PeerConnectionTest.cs
new file mode 100644
index 000000000..a1a6ae201
--- /dev/null
+++ b/com.unity.renderstreaming/Tests/Runtime/PeerConnectionTest.cs
@@ -0,0 +1,964 @@
+using System.Collections;
+using System.Collections.Generic;
+using NUnit.Framework;
+using Unity.WebRTC;
+using UnityEngine;
+using UnityEngine.TestTools;
+
+namespace Unity.RenderStreaming.RuntimeTest
+{
+ class PeerConnectionTest
+ {
+ class MyMonoBehaviourTest : MonoBehaviour, IMonoBehaviourTest
+ {
+ public bool IsTestFinished
+ {
+ get { return true; }
+ }
+ }
+
+ private const float ResendOfferInterval = 1.0f;
+ private MonoBehaviourTest test;
+ private RTCConfiguration config;
+
+ [SetUp]
+ public void SetUp()
+ {
+ test = new MonoBehaviourTest();
+ config = new RTCConfiguration
+ {
+ iceServers = new[] { new RTCIceServer { urls = new[] { "stun:stun.l.google.com:19302" } } },
+ };
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ test.component.StopAllCoroutines();
+ Object.Destroy(test.gameObject);
+ }
+
+ [Test, Timeout(5000)]
+ public void Construct()
+ {
+ var peer = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer, Is.Not.Null);
+ var rtcPeer = peer.peer;
+ Assert.That(rtcPeer, Is.Not.Null);
+ Assert.That(rtcPeer.OnTrack, Is.Not.Null);
+ Assert.That(rtcPeer.OnIceCandidate, Is.Not.Null);
+ Assert.That(rtcPeer.OnNegotiationNeeded, Is.Not.Null);
+ Assert.That(rtcPeer.OnDataChannel, Is.Not.Null);
+ Assert.That(rtcPeer.OnConnectionStateChange, Is.Not.Null);
+
+ peer.Dispose();
+ }
+
+ [UnityTest, Timeout(5000)]
+ public IEnumerator CalledSendOfferWhenAddTrack()
+ {
+ var peer = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer, Is.Not.Null);
+
+ var isGotSendOffer = false;
+ RTCSessionDescription offerDesc = default;
+ peer.SendOfferHandler += description =>
+ {
+ isGotSendOffer = true;
+ offerDesc = description;
+ };
+
+ var track = new AudioStreamTrack();
+ peer.peer.AddTrack(track);
+
+ yield return new WaitUntil(() => isGotSendOffer);
+ Assert.That(isGotSendOffer, Is.True);
+ Assert.That(offerDesc.type, Is.EqualTo(RTCSdpType.Offer));
+ Assert.That(offerDesc.sdp, Is.Not.Null.Or.Empty);
+
+ track.Dispose();
+ peer.Dispose();
+ }
+
+ [UnityTest, Timeout(5000)]
+ public IEnumerator CalledSendOfferWhenAddTransceiver()
+ {
+ var peer = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer, Is.Not.Null);
+
+ var isGotSendOffer = false;
+ RTCSessionDescription offerDesc = default;
+ peer.SendOfferHandler += description =>
+ {
+ isGotSendOffer = true;
+ offerDesc = description;
+ };
+
+ var transceiver = peer.peer.AddTransceiver(TrackKind.Video);
+ Assert.That(transceiver, Is.Not.Null);
+
+ yield return new WaitUntil(() => isGotSendOffer);
+ Assert.That(isGotSendOffer, Is.True);
+ Assert.That(offerDesc.type, Is.EqualTo(RTCSdpType.Offer));
+ Assert.That(offerDesc.sdp, Is.Not.Null.Or.Empty);
+
+ transceiver.Dispose();
+ peer.Dispose();
+ }
+
+ [UnityTest, Timeout(5000)]
+ public IEnumerator CalledSendOfferWhenCreateChannel()
+ {
+ var peer = new PeerConnection(
+ true,
+ config,
+ ResendOfferInterval,
+ test.component.StartCoroutine,
+ test.component.StopCoroutine
+ );
+
+ var isGotSendOffer = false;
+ RTCSessionDescription offerDesc = default;
+ peer.SendOfferHandler += description =>
+ {
+ isGotSendOffer = true;
+ offerDesc = description;
+ };
+
+ var channel = peer.peer.CreateDataChannel("test");
+ Assert.That(channel, Is.Not.Null);
+
+ yield return new WaitUntil(() => isGotSendOffer);
+ Assert.That(isGotSendOffer, Is.True);
+ Assert.That(offerDesc.type, Is.EqualTo(RTCSdpType.Offer));
+ Assert.That(offerDesc.sdp, Is.Not.Null.Or.Empty);
+
+ channel.Dispose();
+ peer.Dispose();
+ }
+
+ [UnityTest, Timeout(5000)]
+ public IEnumerator CalledSendOfferTwiceIfGetAnswerNotYet()
+ {
+ var peer = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer, Is.Not.Null);
+
+ var sendOfferCount = 0;
+ RTCSessionDescription offerDesc = default;
+ peer.SendOfferHandler += description =>
+ {
+ sendOfferCount++;
+ offerDesc = description;
+ };
+
+ var track = new AudioStreamTrack();
+ peer.peer.AddTrack(track);
+
+ while (sendOfferCount <= 2)
+ {
+ yield return new WaitForSeconds(ResendOfferInterval);
+ Assert.That(peer.waitingAnswer, Is.True);
+ peer.SendOffer();
+ }
+
+ yield return new WaitUntil(() => sendOfferCount > 2);
+ Assert.That(sendOfferCount, Is.GreaterThan(2));
+ Assert.That(offerDesc.type, Is.EqualTo(RTCSdpType.Offer));
+ Assert.That(offerDesc.sdp, Is.Not.Null.Or.Empty);
+
+ track.Dispose();
+ peer.Dispose();
+ }
+
+ [UnityTest, Timeout(5000)]
+ [TestCase(true, ExpectedResult = null)]
+ [TestCase(false, ExpectedResult = null)]
+ public IEnumerator CalledSendAnswerWhenGotDescriptionWhenStable(bool polite)
+ {
+ var peer1 = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer1, Is.Not.Null);
+ var peer2 = new PeerConnection(polite, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer2, Is.Not.Null);
+
+ var isGotSendOffer = false;
+ RTCSessionDescription offerDesc = default;
+ peer1.SendOfferHandler += description =>
+ {
+ isGotSendOffer = true;
+ offerDesc = description;
+ };
+
+ var isGotSendAnswer = false;
+ RTCSessionDescription answerDesc = default;
+ peer2.SendAnswerHandler += description =>
+ {
+ isGotSendAnswer = true;
+ answerDesc = description;
+ };
+
+ var track = new AudioStreamTrack();
+ peer1.peer.AddTrack(track);
+
+ yield return new WaitUntil(() => isGotSendOffer);
+ Assert.That(isGotSendOffer, Is.True);
+ Assert.That(offerDesc.type, Is.EqualTo(RTCSdpType.Offer));
+ Assert.That(offerDesc.sdp, Is.Not.Null.Or.Empty);
+
+ var isComplete = false;
+ yield return peer2.OnGotDescription(offerDesc, () => isComplete = true);
+ Assert.That(isComplete, Is.True);
+ peer2.SendAnswer();
+
+ yield return new WaitUntil(() => isGotSendAnswer);
+ Assert.That(isGotSendAnswer, Is.True);
+ Assert.That(answerDesc.type, Is.EqualTo(RTCSdpType.Answer));
+ Assert.That(answerDesc.sdp, Is.Not.Null.Or.Empty);
+
+ track.Dispose();
+ peer1.Dispose();
+ peer2.Dispose();
+ }
+
+ [UnityTest, Timeout(5000)]
+ public IEnumerator CalledSendAnswerWhenGotDescriptionThatHaveLocalOfferInPolite()
+ {
+ var peer1 = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer1, Is.Not.Null);
+ var peer2 = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer2, Is.Not.Null);
+
+ var isGotSendOffer1 = false;
+ RTCSessionDescription offerDesc1 = default;
+ peer1.SendOfferHandler += description =>
+ {
+ isGotSendOffer1 = true;
+ offerDesc1 = description;
+ };
+
+ var isGotSendOffer2 = false;
+ RTCSessionDescription offerDesc2 = default;
+ peer2.SendOfferHandler += description =>
+ {
+ isGotSendOffer2 = true;
+ offerDesc2 = description;
+ };
+
+ var isGotSendAnswer = false;
+ RTCSessionDescription answerDesc = default;
+ peer2.SendAnswerHandler += description =>
+ {
+ isGotSendAnswer = true;
+ answerDesc = description;
+ };
+
+ var track1 = new AudioStreamTrack();
+ peer1.peer.AddTrack(track1);
+ var track2 = new AudioStreamTrack();
+ peer2.peer.AddTrack(track2);
+
+ yield return new WaitUntil(() => isGotSendOffer1);
+ Assert.That(isGotSendOffer1, Is.True);
+ Assert.That(offerDesc1.type, Is.EqualTo(RTCSdpType.Offer));
+ Assert.That(offerDesc1.sdp, Is.Not.Null.Or.Empty);
+
+ yield return new WaitUntil(() => isGotSendOffer2);
+ Assert.That(isGotSendOffer1, Is.True);
+ Assert.That(offerDesc2.type, Is.EqualTo(RTCSdpType.Offer));
+ Assert.That(offerDesc2.sdp, Is.Not.Null.Or.Empty);
+ Assert.That(peer2.peer.SignalingState, Is.EqualTo(RTCSignalingState.HaveLocalOffer));
+
+ var isComplete = false;
+ yield return peer2.OnGotDescription(offerDesc1, () => isComplete = true);
+ Assert.That(isComplete, Is.True);
+ peer2.SendAnswer();
+
+ yield return new WaitUntil(() => isGotSendAnswer);
+ Assert.That(isGotSendAnswer, Is.True);
+ Assert.That(answerDesc.type, Is.EqualTo(RTCSdpType.Answer));
+ Assert.That(answerDesc.sdp, Is.Not.Null.Or.Empty);
+ Assert.That(peer2.peer.SignalingState, Is.EqualTo(RTCSignalingState.Stable));
+
+ track1.Dispose();
+ track2.Dispose();
+ peer1.Dispose();
+ peer2.Dispose();
+ }
+
+ [UnityTest, Timeout(5000)]
+ public IEnumerator NotCalledSendAnswerWhenGotDescriptionThatHaveLocalOfferInImpolite()
+ {
+ var peer1 = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer1, Is.Not.Null);
+ var peer2 = new PeerConnection(false, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer2, Is.Not.Null);
+
+ var isGotSendOffer1 = false;
+ RTCSessionDescription offerDesc1 = default;
+ peer1.SendOfferHandler += description =>
+ {
+ isGotSendOffer1 = true;
+ offerDesc1 = description;
+ };
+
+ var isGotSendOffer2 = false;
+ RTCSessionDescription offerDesc2 = default;
+ peer2.SendOfferHandler += description =>
+ {
+ isGotSendOffer2 = true;
+ offerDesc2 = description;
+ };
+
+ var track1 = new AudioStreamTrack();
+ peer1.peer.AddTrack(track1);
+ var track2 = new AudioStreamTrack();
+ peer2.peer.AddTrack(track2);
+
+ yield return new WaitUntil(() => isGotSendOffer1);
+ Assert.That(isGotSendOffer1, Is.True);
+ Assert.That(offerDesc1.type, Is.EqualTo(RTCSdpType.Offer));
+ Assert.That(offerDesc1.sdp, Is.Not.Null.Or.Empty);
+
+ yield return new WaitUntil(() => isGotSendOffer2);
+ Assert.That(isGotSendOffer1, Is.True);
+ Assert.That(offerDesc2.type, Is.EqualTo(RTCSdpType.Offer));
+ Assert.That(offerDesc2.sdp, Is.Not.Null.Or.Empty);
+ Assert.That(peer2.peer.SignalingState, Is.EqualTo(RTCSignalingState.HaveLocalOffer));
+
+ var isComplete = false;
+ yield return peer2.OnGotDescription(offerDesc1, () => isComplete = true);
+ Assert.That(isComplete, Is.False);
+ Assert.That(peer2.peer.SignalingState, Is.EqualTo(RTCSignalingState.HaveLocalOffer));
+
+ track1.Dispose();
+ track2.Dispose();
+ peer1.Dispose();
+ peer2.Dispose();
+ }
+
+ [UnityTest, Timeout(5000)]
+ public IEnumerator CalledTrackEventWhenGotSdpIncludeTrack()
+ {
+ var peer1 = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer1, Is.Not.Null);
+ var peer2 = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer2, Is.Not.Null);
+
+ var isGotSendOffer = false;
+ RTCSessionDescription offerDesc = default;
+ peer1.SendOfferHandler += description =>
+ {
+ isGotSendOffer = true;
+ offerDesc = description;
+ };
+
+ var isGotTrackEvent = false;
+ RTCTrackEvent trackEvent = null;
+ peer2.OnTrackEventHandler += e =>
+ {
+ isGotTrackEvent = true;
+ trackEvent = e;
+ };
+
+ var track = new AudioStreamTrack();
+ peer1.peer.AddTrack(track);
+
+ yield return new WaitUntil(() => isGotSendOffer);
+ Assert.That(isGotSendOffer, Is.True);
+ Assert.That(offerDesc.type, Is.EqualTo(RTCSdpType.Offer));
+ Assert.That(offerDesc.sdp, Is.Not.Null.Or.Empty);
+
+ var isComplete = false;
+ yield return peer2.OnGotDescription(offerDesc, () => isComplete = true);
+ Assert.That(isComplete, Is.True);
+
+ yield return new WaitUntil(() => isGotTrackEvent);
+ Assert.That(isGotTrackEvent, Is.True);
+ Assert.That(trackEvent, Is.Not.Null);
+ Assert.That(trackEvent.Track.Id, Is.EqualTo(track.Id));
+
+ track.Dispose();
+ peer1.Dispose();
+ peer2.Dispose();
+ }
+
+ [UnityTest, Timeout(5000)]
+ public IEnumerator CalledOnDataChannelWhenGotSdpIncludeDataChannel()
+ {
+ var peer1 = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer1, Is.Not.Null);
+ var peer2 = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer2, Is.Not.Null);
+
+ var isGotSendOffer = false;
+ RTCSessionDescription offerDesc = default;
+ peer1.SendOfferHandler += description =>
+ {
+ isGotSendOffer = true;
+ offerDesc = description;
+ };
+
+ var isGotSendAnswer = false;
+ RTCSessionDescription answerDesc = default;
+ peer2.SendAnswerHandler += description =>
+ {
+ isGotSendAnswer = true;
+ answerDesc = description;
+ };
+
+ var isGotDataChannel = false;
+ RTCDataChannel dataChannel = null;
+ peer2.OnDataChannelHandler += e =>
+ {
+ isGotDataChannel = true;
+ dataChannel = e;
+ };
+
+ var channel = peer1.peer.CreateDataChannel("test");
+ Assert.That(channel, Is.Not.Null);
+
+ yield return new WaitUntil(() => isGotSendOffer);
+ Assert.That(isGotSendOffer, Is.True);
+ Assert.That(offerDesc.type, Is.EqualTo(RTCSdpType.Offer));
+ Assert.That(offerDesc.sdp, Is.Not.Null.Or.Empty);
+
+ var isComplete = false;
+ yield return peer2.OnGotDescription(offerDesc, () => isComplete = true);
+ Assert.That(isComplete, Is.True);
+
+ peer2.SendAnswer();
+ yield return new WaitUntil(() => isGotSendAnswer);
+ Assert.That(isGotSendAnswer, Is.True);
+ Assert.That(answerDesc.type, Is.EqualTo(RTCSdpType.Answer));
+ Assert.That(answerDesc.sdp, Is.Not.Null.Or.Empty);
+
+ isComplete = false;
+ yield return peer1.OnGotDescription(answerDesc, () => isComplete = true);
+ Assert.That(isComplete, Is.True);
+
+ yield return new WaitUntil(() => isGotDataChannel);
+ Assert.That(isGotDataChannel, Is.True);
+ Assert.That(dataChannel, Is.Not.Null);
+ Assert.That(dataChannel.Id, Is.EqualTo(channel.Id));
+
+ channel.Dispose();
+ peer1.Dispose();
+ peer2.Dispose();
+ }
+
+ [UnityTest, Timeout(5000)]
+ public IEnumerator CalledSendCandidateWhenAddTransceiver()
+ {
+ var peer = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer, Is.Not.Null);
+
+ var isGotSendCandidate = false;
+ RTCIceCandidate candidate = null;
+ peer.SendCandidateHandler += e =>
+ {
+ isGotSendCandidate = true;
+ candidate = e;
+ };
+
+ var transceiver = peer.peer.AddTransceiver(TrackKind.Video);
+ Assert.That(transceiver, Is.Not.Null);
+
+ yield return new WaitUntil(() => isGotSendCandidate);
+ Assert.That(isGotSendCandidate, Is.True);
+ Assert.That(candidate, Is.Not.Null);
+
+ transceiver.Dispose();
+ peer.Dispose();
+ }
+
+ [UnityTest, Timeout(5000)]
+ public IEnumerator AcceptWhenGotCandidateThatHaveRemoteDescription()
+ {
+ var peer1 = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer1, Is.Not.Null);
+ var peer2 = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer2, Is.Not.Null);
+
+ var isGotSendOffer1 = false;
+ RTCSessionDescription offerDesc1 = default;
+ peer1.SendOfferHandler += description =>
+ {
+ isGotSendOffer1 = true;
+ offerDesc1 = description;
+ };
+
+ var isGotSendCandidate = false;
+ RTCIceCandidate candidate = null;
+ peer1.SendCandidateHandler += e =>
+ {
+ isGotSendCandidate = true;
+ candidate = e;
+ };
+
+ var transceiver = peer1.peer.AddTransceiver(TrackKind.Video);
+ Assert.That(transceiver, Is.Not.Null);
+
+ yield return new WaitUntil(() => isGotSendOffer1);
+ Assert.That(isGotSendOffer1, Is.True);
+ Assert.That(offerDesc1.type, Is.EqualTo(RTCSdpType.Offer));
+ Assert.That(offerDesc1.sdp, Is.Not.Null.Or.Empty);
+
+ yield return new WaitUntil(() => isGotSendCandidate);
+ Assert.That(isGotSendCandidate, Is.True);
+ Assert.That(candidate, Is.Not.Null);
+
+ var isComplete = false;
+ yield return peer2.OnGotDescription(offerDesc1, () => isComplete = true);
+ Assert.That(isComplete, Is.True);
+
+ Assert.That(peer2.OnGotIceCandidate(candidate), Is.True);
+
+ transceiver.Dispose();
+ peer1.Dispose();
+ peer2.Dispose();
+ }
+
+ [UnityTest, Timeout(5000)]
+ public IEnumerator NotAcceptWhenGotCandidateThatDontHaveRemoteDescription()
+ {
+ var peer1 = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer1, Is.Not.Null);
+ var peer2 = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer2, Is.Not.Null);
+
+ var isGotSendOffer1 = false;
+ RTCSessionDescription offerDesc1 = default;
+ peer1.SendOfferHandler += description =>
+ {
+ isGotSendOffer1 = true;
+ offerDesc1 = description;
+ };
+
+ var isGotSendCandidate = false;
+ RTCIceCandidate candidate = null;
+ peer1.SendCandidateHandler += e =>
+ {
+ isGotSendCandidate = true;
+ candidate = e;
+ };
+
+ var transceiver = peer1.peer.AddTransceiver(TrackKind.Video);
+ Assert.That(transceiver, Is.Not.Null);
+
+ yield return new WaitUntil(() => isGotSendOffer1);
+ Assert.That(isGotSendOffer1, Is.True);
+ Assert.That(offerDesc1.type, Is.EqualTo(RTCSdpType.Offer));
+ Assert.That(offerDesc1.sdp, Is.Not.Null.Or.Empty);
+
+ yield return new WaitUntil(() => isGotSendCandidate);
+ Assert.That(isGotSendCandidate, Is.True);
+ Assert.That(candidate, Is.Not.Null);
+
+ Assert.That(peer2.OnGotIceCandidate(candidate), Is.False);
+
+ transceiver.Dispose();
+ peer1.Dispose();
+ peer2.Dispose();
+ }
+
+ [UnityTest, Timeout(5000)]
+ public IEnumerator CalledOnConnect()
+ {
+ var peer1 = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer1, Is.Not.Null);
+ var peer2 = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer2, Is.Not.Null);
+
+ var isGotSendOffer = false;
+ RTCSessionDescription offerDesc = default;
+ peer1.SendOfferHandler += description =>
+ {
+ isGotSendOffer = true;
+ offerDesc = description;
+ };
+ var isGotConnect1 = false;
+ peer1.OnConnectHandler += () => isGotConnect1 = true;
+
+ var isGotSendAnswer = false;
+ RTCSessionDescription answerDesc = default;
+ peer2.SendAnswerHandler += description =>
+ {
+ isGotSendAnswer = true;
+ answerDesc = description;
+ };
+ var isGotConnect2 = false;
+ peer2.OnConnectHandler += () => isGotConnect2 = true;
+
+ var transceiver = peer1.peer.AddTransceiver(TrackKind.Video);
+ Assert.That(transceiver, Is.Not.Null);
+
+ yield return new WaitUntil(() => isGotSendOffer);
+ Assert.That(isGotSendOffer, Is.True);
+ Assert.That(offerDesc.type, Is.EqualTo(RTCSdpType.Offer));
+ Assert.That(offerDesc.sdp, Is.Not.Null.Or.Empty);
+
+ var isComplete = false;
+ yield return peer2.OnGotDescription(offerDesc, () => isComplete = true);
+ Assert.That(isComplete, Is.True);
+
+ peer2.SendAnswer();
+ yield return new WaitUntil(() => isGotSendAnswer);
+ Assert.That(isGotSendAnswer, Is.True);
+ Assert.That(answerDesc.type, Is.EqualTo(RTCSdpType.Answer));
+ Assert.That(answerDesc.sdp, Is.Not.Null.Or.Empty);
+
+ isComplete = false;
+ yield return peer1.OnGotDescription(answerDesc, () => isComplete = true);
+ Assert.That(isComplete, Is.True);
+
+ yield return new WaitUntil(() => isGotConnect1 && isGotConnect2);
+ Assert.That(isGotConnect1, Is.True);
+ Assert.That(isGotConnect2, Is.True);
+
+ transceiver.Dispose();
+ peer1.Dispose();
+ peer2.Dispose();
+ }
+
+ [UnityTest, Timeout(5000)]
+ public IEnumerator SendOfferTwiceImmediately()
+ {
+ var peer1 = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer1, Is.Not.Null);
+
+ var isGotSendOffer = false;
+ RTCSessionDescription offerDesc = default;
+ peer1.SendOfferHandler += description =>
+ {
+ isGotSendOffer = true;
+ offerDesc = description;
+ };
+
+ var transceiver = peer1.peer.AddTransceiver(TrackKind.Video);
+ Assert.That(transceiver, Is.Not.Null);
+
+ peer1.SendOffer();
+ peer1.SendOffer();
+
+ yield return new WaitUntil(() => isGotSendOffer);
+ Assert.That(isGotSendOffer, Is.True);
+ Assert.That(offerDesc.type, Is.EqualTo(RTCSdpType.Offer));
+ Assert.That(offerDesc.sdp, Is.Not.Null.Or.Empty);
+
+ transceiver.Dispose();
+ peer1.Dispose();
+ }
+
+ [UnityTest, Timeout(5000)]
+ public IEnumerator SendAnswerTwiceImmediately()
+ {
+ var peer1 = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer1, Is.Not.Null);
+ var peer2 = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer2, Is.Not.Null);
+
+ var isGotSendOffer = false;
+ RTCSessionDescription offerDesc = default;
+ peer1.SendOfferHandler += description =>
+ {
+ isGotSendOffer = true;
+ offerDesc = description;
+ };
+
+ var isGotSendAnswer = false;
+ RTCSessionDescription answerDesc = default;
+ peer2.SendAnswerHandler += description =>
+ {
+ isGotSendAnswer = true;
+ answerDesc = description;
+ };
+
+ var transceiver = peer1.peer.AddTransceiver(TrackKind.Video);
+ Assert.That(transceiver, Is.Not.Null);
+
+ yield return new WaitUntil(() => isGotSendOffer);
+ Assert.That(isGotSendOffer, Is.True);
+ Assert.That(offerDesc.type, Is.EqualTo(RTCSdpType.Offer));
+ Assert.That(offerDesc.sdp, Is.Not.Null.Or.Empty);
+
+ var isComplete = false;
+ yield return peer2.OnGotDescription(offerDesc, () => isComplete = true);
+ Assert.That(isComplete, Is.True);
+
+ peer2.SendAnswer();
+ peer2.SendAnswer();
+
+ yield return new WaitUntil(() => isGotSendAnswer);
+ Assert.That(isGotSendAnswer, Is.True);
+ Assert.That(answerDesc.type, Is.EqualTo(RTCSdpType.Answer));
+ Assert.That(answerDesc.sdp, Is.Not.Null.Or.Empty);
+
+ transceiver.Dispose();
+ peer1.Dispose();
+ peer2.Dispose();
+ }
+
+ [UnityTest, Timeout(5000)]
+ public IEnumerator OnGotOfferDescriptionAfterSendOfferImmediatelyInPolite()
+ {
+ var peer1 = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer1, Is.Not.Null);
+ var peer2 = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer2, Is.Not.Null);
+
+ var isGotSendOffer1 = false;
+ RTCSessionDescription offerDesc1 = default;
+ peer1.SendOfferHandler += description =>
+ {
+ isGotSendOffer1 = true;
+ offerDesc1 = description;
+ };
+
+ var isGotSendOffer2 = false;
+ RTCSessionDescription offerDesc2 = default;
+ peer2.SendOfferHandler += description =>
+ {
+ isGotSendOffer2 = true;
+ offerDesc2 = description;
+ };
+
+ var isGotSendAnswer = false;
+ RTCSessionDescription answerDesc = default;
+ peer2.SendAnswerHandler += description =>
+ {
+ isGotSendAnswer = true;
+ answerDesc = description;
+ };
+
+ var transceiver1 = peer1.peer.AddTransceiver(TrackKind.Video);
+ Assert.That(transceiver1, Is.Not.Null);
+
+ yield return new WaitUntil(() => isGotSendOffer1);
+ Assert.That(isGotSendOffer1, Is.True);
+ Assert.That(offerDesc1.type, Is.EqualTo(RTCSdpType.Offer));
+ Assert.That(offerDesc1.sdp, Is.Not.Null.Or.Empty);
+
+ var transceiver2 = peer2.peer.AddTransceiver(TrackKind.Video);
+ Assert.That(transceiver2, Is.Not.Null);
+
+ //workaround: Need to wait 1 frame for negotiationneeded to be processed
+ yield return 0;
+
+ var isComplete = false;
+ yield return peer2.OnGotDescription(offerDesc1, () => isComplete = true);
+ Assert.That(isComplete, Is.True);
+
+ yield return new WaitForSeconds(ResendOfferInterval);
+ Assert.That(isGotSendOffer2, Is.False, "need waiting offer cause receive remote offer");
+
+ peer2.SendAnswer();
+ yield return new WaitUntil(() => isGotSendAnswer);
+ Assert.That(isGotSendAnswer, Is.True);
+ Assert.That(answerDesc.type, Is.EqualTo(RTCSdpType.Answer));
+ Assert.That(answerDesc.sdp, Is.Not.Null.Or.Empty);
+
+ yield return new WaitUntil(() => isGotSendOffer2);
+ Assert.That(isGotSendOffer2, Is.True);
+ Assert.That(offerDesc2.type, Is.EqualTo(RTCSdpType.Offer));
+ Assert.That(offerDesc2.sdp, Is.Not.Null.Or.Empty);
+
+ transceiver1.Dispose();
+ transceiver2.Dispose();
+ peer1.Dispose();
+ peer2.Dispose();
+ }
+
+ [UnityTest, Timeout(5000)]
+ public IEnumerator OnGotOfferDescriptionAfterSendOfferImmediatelyInImPolite()
+ {
+ var peer1 = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer1, Is.Not.Null);
+ var peer2 = new PeerConnection(false, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer2, Is.Not.Null);
+
+ var isGotSendOffer1 = false;
+ RTCSessionDescription offerDesc1 = default;
+ peer1.SendOfferHandler += description =>
+ {
+ isGotSendOffer1 = true;
+ offerDesc1 = description;
+ };
+
+ var isGotSendOffer2 = false;
+ RTCSessionDescription offerDesc2 = default;
+ peer2.SendOfferHandler += description =>
+ {
+ isGotSendOffer2 = true;
+ offerDesc2 = description;
+ };
+
+ var transceiver1 = peer1.peer.AddTransceiver(TrackKind.Video);
+ Assert.That(transceiver1, Is.Not.Null);
+
+ yield return new WaitUntil(() => isGotSendOffer1);
+ Assert.That(isGotSendOffer1, Is.True);
+ Assert.That(offerDesc1.type, Is.EqualTo(RTCSdpType.Offer));
+ Assert.That(offerDesc1.sdp, Is.Not.Null.Or.Empty);
+
+ var transceiver2 = peer2.peer.AddTransceiver(TrackKind.Video);
+ Assert.That(transceiver2, Is.Not.Null);
+
+ //workaround: Need to wait 1 frame for negotiationneeded to be processed
+ yield return 0;
+
+ var isComplete = false;
+ yield return peer2.OnGotDescription(offerDesc1, () => isComplete = true);
+ Assert.That(isComplete, Is.False, "need ignore offer cause peer2 is impolite");
+
+ yield return new WaitUntil(() => isGotSendOffer2);
+ Assert.That(isGotSendOffer2, Is.True);
+ Assert.That(offerDesc2.type, Is.EqualTo(RTCSdpType.Offer));
+ Assert.That(offerDesc2.sdp, Is.Not.Null.Or.Empty);
+
+ transceiver1.Dispose();
+ transceiver2.Dispose();
+ peer1.Dispose();
+ peer2.Dispose();
+ }
+
+ [UnityTest, Timeout(5000)]
+ [TestCase(true, ExpectedResult = null)]
+ [TestCase(false, ExpectedResult = null)]
+ public IEnumerator OnGotAnswerDescriptionAfterSendOfferImmediately(bool polite)
+ {
+ var peer1 = new PeerConnection(polite, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer1, Is.Not.Null);
+ var peer2 = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer2, Is.Not.Null);
+
+ var isGotSendOffer1 = false;
+ RTCSessionDescription offerDesc1 = default;
+ peer1.SendOfferHandler += description =>
+ {
+ isGotSendOffer1 = true;
+ offerDesc1 = description;
+ };
+
+ var isGotSendAnswer = false;
+ RTCSessionDescription answerDesc = default;
+ peer2.SendAnswerHandler += description =>
+ {
+ isGotSendAnswer = true;
+ answerDesc = description;
+ };
+
+ var transceiver1 = peer1.peer.AddTransceiver(TrackKind.Video);
+ Assert.That(transceiver1, Is.Not.Null);
+
+ yield return new WaitUntil(() => isGotSendOffer1);
+ Assert.That(isGotSendOffer1, Is.True);
+ Assert.That(offerDesc1.type, Is.EqualTo(RTCSdpType.Offer));
+ Assert.That(offerDesc1.sdp, Is.Not.Null.Or.Empty);
+
+ var isComplete = false;
+ yield return peer2.OnGotDescription(offerDesc1, () => isComplete = true);
+ Assert.That(isComplete, Is.True);
+
+ peer2.SendAnswer();
+ yield return new WaitUntil(() => isGotSendAnswer);
+ Assert.That(isGotSendAnswer, Is.True);
+ Assert.That(answerDesc.type, Is.EqualTo(RTCSdpType.Answer));
+ Assert.That(answerDesc.sdp, Is.Not.Null.Or.Empty);
+
+ yield return new WaitForSeconds(ResendOfferInterval);
+ peer1.SendOffer();
+
+ isComplete = false;
+ yield return peer1.OnGotDescription(answerDesc, () => isComplete = true);
+ Assert.That(isComplete, Is.True);
+
+ transceiver1.Dispose();
+ peer1.Dispose();
+ peer2.Dispose();
+ }
+
+ [UnityTest, Timeout(5000)]
+ [TestCase(true, ExpectedResult = null)]
+ [TestCase(false, ExpectedResult = null)]
+ public IEnumerator OnGotOfferDescriptionAfterSendAnswerImmediately(bool polite)
+ {
+ var peer1 = new PeerConnection(polite, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer1, Is.Not.Null);
+ var peer2 = new PeerConnection(true, config, ResendOfferInterval,
+ test.component.StartCoroutine, test.component.StopCoroutine);
+ Assert.That(peer2, Is.Not.Null);
+
+ var isGotSendOffer1 = false;
+ RTCSessionDescription offerDesc1 = default;
+ peer1.SendOfferHandler += description =>
+ {
+ isGotSendOffer1 = true;
+ offerDesc1 = description;
+ };
+
+ var isGotSendAnswer = false;
+ RTCSessionDescription answerDesc = default;
+ peer2.SendAnswerHandler += description =>
+ {
+ isGotSendAnswer = true;
+ answerDesc = description;
+ };
+
+ var transceiver1 = peer1.peer.AddTransceiver(TrackKind.Video);
+ Assert.That(transceiver1, Is.Not.Null);
+
+ yield return new WaitUntil(() => isGotSendOffer1);
+ Assert.That(isGotSendOffer1, Is.True);
+ Assert.That(offerDesc1.type, Is.EqualTo(RTCSdpType.Offer));
+ Assert.That(offerDesc1.sdp, Is.Not.Null.Or.Empty);
+
+ var isComplete = false;
+ yield return peer2.OnGotDescription(offerDesc1, () => isComplete = true);
+ Assert.That(isComplete, Is.True);
+ peer2.SendAnswer();
+
+ isComplete = false;
+ yield return peer2.OnGotDescription(offerDesc1, () => isComplete = true);
+ Assert.That(isComplete, Is.True);
+
+ yield return new WaitUntil(() => isGotSendAnswer);
+ Assert.That(isGotSendAnswer, Is.True);
+ Assert.That(answerDesc.type, Is.EqualTo(RTCSdpType.Answer));
+ Assert.That(answerDesc.sdp, Is.Not.Null.Or.Empty);
+
+ transceiver1.Dispose();
+ peer1.Dispose();
+ peer2.Dispose();
+ }
+ }
+}
diff --git a/com.unity.renderstreaming/Tests/Runtime/PeerConnectionTest.cs.meta b/com.unity.renderstreaming/Tests/Runtime/PeerConnectionTest.cs.meta
new file mode 100644
index 000000000..d29b5c2aa
--- /dev/null
+++ b/com.unity.renderstreaming/Tests/Runtime/PeerConnectionTest.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: ba9cd9098d364eecbabbf6d1c678b2d5
+timeCreated: 1661734624
\ No newline at end of file
diff --git a/com.unity.renderstreaming/Tests/Runtime/RenderStreamingInternalTest.cs b/com.unity.renderstreaming/Tests/Runtime/RenderStreamingInternalTest.cs
index badac2480..a00eb5323 100644
--- a/com.unity.renderstreaming/Tests/Runtime/RenderStreamingInternalTest.cs
+++ b/com.unity.renderstreaming/Tests/Runtime/RenderStreamingInternalTest.cs
@@ -62,6 +62,7 @@ private RenderStreamingDependencies CreateDependencies()
iceServers = new[] { new RTCIceServer { urls = new[] { "stun:stun.l.google.com:19302" } } },
},
startCoroutine = test.component.StartCoroutine,
+ stopCoroutine = test.component.StopCoroutine,
resentOfferInterval = ResendOfferInterval,
};
}
diff --git a/com.unity.renderstreaming/Tests/Runtime/SignalingHandlerTest.cs b/com.unity.renderstreaming/Tests/Runtime/SignalingHandlerTest.cs
index 4ae8e6151..78669b507 100644
--- a/com.unity.renderstreaming/Tests/Runtime/SignalingHandlerTest.cs
+++ b/com.unity.renderstreaming/Tests/Runtime/SignalingHandlerTest.cs
@@ -101,6 +101,7 @@ private static RenderStreamingDependencies CreateDependencies(MonoBehaviour beha
iceServers = new[] { new RTCIceServer { urls = new[] { "stun:stun.l.google.com:19302" } } },
},
startCoroutine = behaviour.StartCoroutine,
+ stopCoroutine = behaviour.StopCoroutine,
resentOfferInterval = ResendOfferInterval,
};
}