// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Collections; using System.Linq; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.TestTools; namespace Microsoft.MixedReality.WebRTC.Unity.Tests.Runtime { public class VideoSourceTests { [SetUp] public void Setup() { } [TearDown] public void Shutdown() { // Force shutdown in case a test failure prevented cleaning-up some // native resources, thereby locking the native module and preventing // it from being unloaded/reloaded in the Unity editor. Library.ReportLiveObjects(); //Library.ForceShutdown(); } private IEnumerator SingleTwoWaysImpl(bool withSender1, bool withReceiver1, bool withSender2, bool withReceiver2) { // Create the peer connections var pc1_go = new GameObject("pc1"); pc1_go.SetActive(false); // prevent auto-activation of components var pc1 = pc1_go.AddComponent(); var pc2_go = new GameObject("pc2"); pc2_go.SetActive(false); // prevent auto-activation of components var pc2 = pc2_go.AddComponent(); // Batch changes manually pc1.AutoCreateOfferOnRenegotiationNeeded = false; pc2.AutoCreateOfferOnRenegotiationNeeded = false; // Create the signaler var sig_go = new GameObject("signaler"); var sig = sig_go.AddComponent(); sig.Peer1 = pc1; sig.Peer2 = pc2; // Create the video sources on peer #1 VideoTrackSource source1 = null; VideoReceiver receiver1 = null; if (withSender1) { source1 = pc1_go.AddComponent(); } if (withReceiver1) { receiver1 = pc1_go.AddComponent(); } MediaLine ml1 = pc1.AddMediaLine(MediaKind.Video); ml1.SenderTrackName = "video_track_1"; ml1.Source = source1; ml1.Receiver = receiver1; // Create the video sources on peer #2 VideoTrackSource source2 = null; VideoReceiver receiver2 = null; if (withSender2) { source2 = pc2_go.AddComponent(); } if (withReceiver2) { receiver2 = pc1_go.AddComponent(); } MediaLine ml2 = pc2.AddMediaLine(MediaKind.Video); ml2.SenderTrackName = "video_track_2"; ml2.Source = source2; ml2.Receiver = receiver2; // Initialize yield return PeerConnectionTests.InitializeAndWait(pc1); yield return PeerConnectionTests.InitializeAndWait(pc2); // Confirm the sources are ready if (withSender1) { Assert.IsTrue(source1.IsLive); } if (withSender2) { Assert.IsTrue(source2.IsLive); } // Confirm the sender track is not created yet; it will be when the connection starts Assert.IsNull(ml1.LocalTrack); Assert.IsNull(ml2.LocalTrack); // Confirm the receiver track is not added yet, since remote tracks are only instantiated // as the result of a session negotiation. if (withReceiver1) { Assert.IsNull(receiver1.Track); } if (withReceiver2) { Assert.IsNull(receiver2.Track); } // Connect Assert.IsTrue(sig.StartConnection()); yield return sig.WaitForConnection(millisecondsTimeout: 10000); Assert.IsTrue(sig.IsConnected); // Wait a frame so that the Unity events for streams started can propagate yield return null; // Check pairing { bool hasSend1 = false; bool hasSend2 = false; bool hasRecv1 = false; bool hasRecv2 = false; // Local tracks exist if manually added (independently of negotiation) Assert.AreEqual(withSender1 ? 1 : 0, pc1.Peer.LocalVideoTracks.Count()); Assert.AreEqual(withSender2 ? 1 : 0, pc2.Peer.LocalVideoTracks.Count()); // Remote tracks exist if paired with a sender on the remote peer if (withReceiver1 && withSender2) // R <= S { Assert.IsNotNull(receiver1.Track); Assert.IsNotNull(ml2.LocalTrack); hasRecv1 = true; hasSend2 = true; } if (withSender1 && withReceiver2) // S => R { Assert.IsNotNull(ml1.LocalTrack); Assert.IsNotNull(receiver2.Track); hasSend1 = true; hasRecv2 = true; } Assert.AreEqual(hasRecv1 ? 1 : 0, pc1.Peer.RemoteVideoTracks.Count()); Assert.AreEqual(hasRecv2 ? 1 : 0, pc2.Peer.RemoteVideoTracks.Count()); // Transceivers are consistent with pairing Assert.IsTrue(ml1.Transceiver.NegotiatedDirection.HasValue); Assert.AreEqual(hasSend1, Transceiver.HasSend(ml1.Transceiver.NegotiatedDirection.Value)); Assert.AreEqual(hasRecv1, Transceiver.HasRecv(ml1.Transceiver.NegotiatedDirection.Value)); Assert.IsTrue(ml2.Transceiver.NegotiatedDirection.HasValue); Assert.AreEqual(hasSend2, Transceiver.HasSend(ml2.Transceiver.NegotiatedDirection.Value)); Assert.AreEqual(hasRecv2, Transceiver.HasRecv(ml2.Transceiver.NegotiatedDirection.Value)); } Object.Destroy(pc1_go); Object.Destroy(pc2_go); Object.Destroy(sig_go); } [UnityTest] public IEnumerator SingleMissingAll() // _ = _ { yield return SingleTwoWaysImpl(withSender1: false, withReceiver1: false, withSender2: false, withReceiver2: false); } [UnityTest] public IEnumerator SingleOneWay() // S => R { yield return SingleTwoWaysImpl(withSender1: true, withReceiver1: false, withSender2: false, withReceiver2: true); } [UnityTest] public IEnumerator SingleOneWayMissingRecvOffer() // S = _ { yield return SingleTwoWaysImpl(withSender1: true, withReceiver1: false, withSender2: false, withReceiver2: false); } [UnityTest] public IEnumerator SingleOneWayMissingSenderOffer() // _ = R { yield return SingleTwoWaysImpl(withSender1: false, withReceiver1: false, withSender2: false, withReceiver2: true); } [UnityTest] public IEnumerator SingleTwoWaysMissingSenderOffer() // _R <= SR { yield return SingleTwoWaysImpl(withSender1: false, withReceiver1: true, withSender2: true, withReceiver2: true); } [UnityTest] public IEnumerator SingleTwoWaysMissingReceiverOffer() // SR <= S_ { yield return SingleTwoWaysImpl(withSender1: true, withReceiver1: true, withSender2: true, withReceiver2: false); } [UnityTest] public IEnumerator SingleTwoWaysMissingReceiverAnswer() // S_ => SR { yield return SingleTwoWaysImpl(withSender1: true, withReceiver1: false, withSender2: true, withReceiver2: true); } [UnityTest] public IEnumerator SingleTwoWaysMissingSenderAnswer() // SR => _R { yield return SingleTwoWaysImpl(withSender1: true, withReceiver1: true, withSender2: false, withReceiver2: true); } [UnityTest] public IEnumerator SingleTwoWays() // SR <=> SR { yield return SingleTwoWaysImpl(withSender1: true, withReceiver1: true, withSender2: true, withReceiver2: true); } class PeerConfig { // Input public Transceiver.Direction desiredDirection; public MediaLine mediaLine; public UniformColorVideoSource source; public VideoReceiver receiver; // Output public bool expectSender; public bool expectReceiver; } class MultiConfig { public PeerConfig peer1; public PeerConfig peer2; }; [UnityTest] public IEnumerator Multi() { // Create the peer connections var pc1_go = new GameObject("pc1"); pc1_go.SetActive(false); // prevent auto-activation of components var pc1 = pc1_go.AddComponent(); var pc2_go = new GameObject("pc2"); pc2_go.SetActive(false); // prevent auto-activation of components var pc2 = pc2_go.AddComponent(); // Batch changes manually pc1.AutoCreateOfferOnRenegotiationNeeded = false; pc2.AutoCreateOfferOnRenegotiationNeeded = false; // Create the signaler var sig_go = new GameObject("signaler"); var sig = sig_go.AddComponent(); sig.Peer1 = pc1; sig.Peer2 = pc2; // Create the senders and receivers // P1 P2 // 0 : S => R // 1 : SR <=> SR // 2 : S => SR // 3 : R <= SR // 4 : S => R const int NumTransceivers = 5; // P1 has 4 senders added to it int numLocal1 = 4; // P1 receives 2 tracks from the 3 P2 senders (one is refused) int numRemote1 = 2; // P2 has 3 senders added to it int numLocal2 = 3; // P2 receives 4 tracks from the 4 P1 senders int numRemote2 = 4; var cfgs = new MultiConfig[NumTransceivers] { new MultiConfig { peer1 = new PeerConfig { desiredDirection = Transceiver.Direction.SendOnly, expectSender = true, expectReceiver = false, }, peer2 = new PeerConfig { desiredDirection = Transceiver.Direction.ReceiveOnly, expectSender = false, expectReceiver = true, } }, new MultiConfig { peer1 = new PeerConfig { desiredDirection = Transceiver.Direction.SendReceive, expectSender = true, expectReceiver = true, }, peer2 = new PeerConfig { desiredDirection = Transceiver.Direction.SendReceive, expectSender = true, expectReceiver = true, }, }, new MultiConfig { peer1 = new PeerConfig { desiredDirection = Transceiver.Direction.SendOnly, expectSender = true, expectReceiver = false, }, peer2 = new PeerConfig { desiredDirection = Transceiver.Direction.SendReceive, expectSender = true, expectReceiver = true, }, }, new MultiConfig { peer1 = new PeerConfig { desiredDirection = Transceiver.Direction.ReceiveOnly, expectSender = false, expectReceiver = true, }, peer2 = new PeerConfig { desiredDirection = Transceiver.Direction.SendReceive, expectSender = true, expectReceiver = false, }, }, new MultiConfig { peer1 = new PeerConfig { desiredDirection = Transceiver.Direction.SendOnly, expectSender = true, expectReceiver = false, }, peer2 = new PeerConfig { desiredDirection = Transceiver.Direction.ReceiveOnly, expectSender = false, expectReceiver = true, }, }, }; for (int i = 0; i < NumTransceivers; ++i) { var cfg = cfgs[i]; { MediaLine ml1 = pc1.AddMediaLine(MediaKind.Video); var peer = cfg.peer1; peer.mediaLine = ml1; if (Transceiver.HasSend(peer.desiredDirection)) { var source1 = pc1_go.AddComponent(); peer.source = source1; ml1.Source = source1; ml1.SenderTrackName = $"track{i}"; } if (Transceiver.HasRecv(peer.desiredDirection)) { var receiver1 = pc1_go.AddComponent(); peer.receiver = receiver1; ml1.Receiver = receiver1; } } { MediaLine ml2 = pc2.AddMediaLine(MediaKind.Video); var peer = cfg.peer2; peer.mediaLine = ml2; if (Transceiver.HasSend(peer.desiredDirection)) { var source2 = pc2_go.AddComponent(); peer.source = source2; ml2.Source = source2; ml2.SenderTrackName = $"track{i}"; } if (Transceiver.HasRecv(peer.desiredDirection)) { var receiver2 = pc2_go.AddComponent(); peer.receiver = receiver2; ml2.Receiver = receiver2; } } } // Initialize yield return PeerConnectionTests.InitializeAndWait(pc1); yield return PeerConnectionTests.InitializeAndWait(pc2); // Confirm the sources are ready for (int i = 0; i < NumTransceivers; ++i) { var cfg = cfgs[i]; if (cfg.peer1.expectSender) { Assert.IsNotNull(cfg.peer1.source, $"Missing source #{i} on Peer #1"); Assert.IsTrue(cfg.peer1.source.IsLive, $"Source #{i} is not ready on Peer #1"); Assert.IsNull(cfg.peer1.mediaLine.LocalTrack); // created during connection } if (cfg.peer2.expectSender) { Assert.IsNotNull(cfg.peer2.source, $"Missing source #{i} on Peer #2"); Assert.IsTrue(cfg.peer2.source.IsLive, $"Source #{i} is not ready on Peer #2"); Assert.IsNull(cfg.peer2.mediaLine.LocalTrack); // created during connection } } // Connect Assert.IsTrue(sig.StartConnection()); yield return sig.WaitForConnection(millisecondsTimeout: 60000); Assert.IsTrue(sig.IsConnected); // Wait a frame so that the Unity events for streams started can propagate yield return null; // Check pairing Assert.AreEqual(numLocal1, pc1.Peer.LocalVideoTracks.Count()); Assert.AreEqual(numRemote1, pc1.Peer.RemoteVideoTracks.Count()); Assert.AreEqual(numLocal2, pc2.Peer.LocalVideoTracks.Count()); Assert.AreEqual(numRemote2, pc2.Peer.RemoteVideoTracks.Count()); for (int i = 0; i < NumTransceivers; ++i) { var cfg = cfgs[i]; if (cfg.peer1.expectSender) { Assert.IsNotNull(cfg.peer1.mediaLine.LocalTrack, $"Transceiver #{i} missing local sender track on Peer #1"); } if (cfg.peer1.expectReceiver) { Assert.IsNotNull(cfg.peer1.receiver.Track, $"Transceiver #{i} missing remote track on Peer #1"); } if (cfg.peer2.expectSender) { Assert.IsNotNull(cfg.peer2.mediaLine.LocalTrack, $"Transceiver #{i} missing local sender track on Peer #2"); } if (cfg.peer2.expectReceiver) { Assert.IsNotNull(cfg.peer2.receiver.Track, $"Transceiver #{i} Missing remote track on Peer #2"); } } // Change the senders and receivers and transceivers direction // old new // P1 P2 P1 P2 // 0 : S => R = R P1 stops sending // 1 : SR <=> SR SR => R P2 stops sending // 2 : S => SR SR <=> SR P1 starts receiving // 3 : R <= SR SR <=> SR P1 starts sending // 4 : S => R S = P2 stops receiving numLocal1 = 4; numRemote1 = 2; numLocal2 = 2; numRemote2 = 3; // #0 - P1 stops sending { var cfg = cfgs[0]; cfg.peer1.mediaLine.Source = null; cfg.peer1.expectSender = false; cfg.peer1.expectReceiver = false; cfg.peer2.expectSender = false; cfg.peer2.expectReceiver = false; } // #1 - P2 stops sending { var cfg = cfgs[1]; cfg.peer2.mediaLine.Source = null; cfg.peer1.expectSender = true; cfg.peer1.expectReceiver = false; cfg.peer2.expectSender = false; cfg.peer2.expectReceiver = true; } // #2 - P1 starts receiving { var cfg = cfgs[2]; var receiver2 = pc2_go.AddComponent(); cfg.peer1.receiver = receiver2; cfg.peer1.mediaLine.Receiver = receiver2; cfg.peer1.expectSender = true; cfg.peer1.expectReceiver = true; cfg.peer2.expectSender = true; cfg.peer2.expectReceiver = true; } // #3 - P1 starts sending { var cfg = cfgs[3]; var source1 = pc1_go.AddComponent(); cfg.peer1.source = source1; cfg.peer1.mediaLine.Source = source1; cfg.peer1.expectSender = true; cfg.peer1.expectReceiver = true; cfg.peer2.expectSender = true; cfg.peer2.expectReceiver = true; } // #4 - P2 stops receiving { var cfg = cfgs[4]; cfg.peer2.mediaLine.Receiver = null; cfg.peer1.expectSender = false; cfg.peer1.expectReceiver = false; cfg.peer2.expectSender = false; cfg.peer2.expectReceiver = false; } // Renegotiate Assert.IsTrue(sig.StartConnection()); yield return sig.WaitForConnection(millisecondsTimeout: 60000); Assert.IsTrue(sig.IsConnected); // Wait a frame so that the Unity events for streams started can propagate yield return null; // Check pairing Assert.AreEqual(numLocal1, pc1.Peer.LocalVideoTracks.Count()); Assert.AreEqual(numRemote1, pc1.Peer.RemoteVideoTracks.Count()); Assert.AreEqual(numLocal2, pc2.Peer.LocalVideoTracks.Count()); Assert.AreEqual(numRemote2, pc2.Peer.RemoteVideoTracks.Count()); for (int i = 0; i < NumTransceivers; ++i) { var cfg = cfgs[i]; if (cfg.peer1.expectReceiver) { Assert.IsNotNull(cfg.peer1.receiver.Track, $"Transceiver #{i} missing remote track on Peer #1"); } if (cfg.peer2.expectReceiver) { Assert.IsNotNull(cfg.peer2.receiver.Track, $"Transceiver #{i} Missing remote track on Peer #2"); } } Object.Destroy(pc1_go); Object.Destroy(pc2_go); Object.Destroy(sig_go); } /// Negotiate 3 sessions in a row: /// P1 P2 /// a. S => R One way P1 => P2 /// b. = R Stop sending; transceiver goes to inactive /// c. S => R Resume sending; transceiver re-activate [UnityTest] public IEnumerator Negotiate_SxS_to_R() { // Create the peer connections var pc1_go = new GameObject("pc1"); pc1_go.SetActive(false); // prevent auto-activation of components var pc1 = pc1_go.AddComponent(); var pc2_go = new GameObject("pc2"); pc2_go.SetActive(false); // prevent auto-activation of components var pc2 = pc2_go.AddComponent(); // Batch changes manually pc1.AutoCreateOfferOnRenegotiationNeeded = false; pc2.AutoCreateOfferOnRenegotiationNeeded = false; // Create the signaler var sig_go = new GameObject("signaler"); var sig = sig_go.AddComponent(); sig.Peer1 = pc1; sig.Peer2 = pc2; // Create the sender video source var source1 = pc1_go.AddComponent(); //source1.SenderTrackName = "track_name"; MediaLine ml1 = pc1.AddMediaLine(MediaKind.Video); Assert.IsNotNull(ml1); Assert.AreEqual(MediaKind.Video, ml1.MediaKind); ml1.Source = source1; // Create the receiver video source var receiver2 = pc2_go.AddComponent(); MediaLine ml2 = pc2.AddMediaLine(MediaKind.Video); Assert.IsNotNull(ml2); Assert.AreEqual(MediaKind.Video, ml2.MediaKind); ml2.Receiver = receiver2; // Initialize yield return PeerConnectionTests.InitializeAndWait(pc1); yield return PeerConnectionTests.InitializeAndWait(pc2); // Confirm the source is ready, but the sender track is not created yet Assert.IsTrue(source1.IsLive); Assert.IsNull(ml1.LocalTrack); // Connect Assert.IsTrue(sig.StartConnection()); yield return sig.WaitForConnection(millisecondsTimeout: 60000); Assert.IsTrue(sig.IsConnected); // Wait a frame so that the Unity events for streams started can propagate yield return null; // Check transceiver update Assert.IsNotNull(ml1.Transceiver); // first negotiation creates this Assert.IsNotNull(ml2.Transceiver); // first negotiation creates this Assert.AreEqual(Transceiver.Direction.SendOnly, ml1.Transceiver.DesiredDirection); Assert.AreEqual(Transceiver.Direction.ReceiveOnly, ml2.Transceiver.DesiredDirection); Assert.IsTrue(ml1.Transceiver.NegotiatedDirection.HasValue); Assert.IsTrue(ml2.Transceiver.NegotiatedDirection.HasValue); Assert.AreEqual(Transceiver.Direction.SendOnly, ml1.Transceiver.NegotiatedDirection.Value); Assert.AreEqual(Transceiver.Direction.ReceiveOnly, ml2.Transceiver.NegotiatedDirection.Value); var video_tr1 = ml1.Transceiver; Assert.IsNotNull(video_tr1); Assert.AreEqual(MediaKind.Video, video_tr1.MediaKind); var video_tr2 = ml2.Transceiver; Assert.IsNotNull(video_tr2); Assert.AreEqual(MediaKind.Video, video_tr2.MediaKind); // Check track pairing Assert.IsNotNull(ml1.LocalTrack); // created during connection Assert.IsNotNull(receiver2.Track); // paired Assert.AreEqual(video_tr1.LocalTrack, ml1.LocalTrack); // sender attached Assert.AreEqual(video_tr2.RemoteTrack, receiver2.Track); // receiver paired // ====== Remove sender ============================== // Remove the sender from #1 ml1.Source = null; // Renegotiate Assert.IsTrue(sig.StartConnection()); yield return sig.WaitForConnection(millisecondsTimeout: 60000); Assert.IsTrue(sig.IsConnected); // Wait a frame so that the Unity events for streams started can propagate yield return null; // Check transceiver update Assert.IsNotNull(ml1.Transceiver); // immutable Assert.IsNotNull(ml2.Transceiver); // immutable Assert.AreEqual(Transceiver.Direction.Inactive, ml1.Transceiver.DesiredDirection); // set ml1 sender to null above Assert.AreEqual(Transceiver.Direction.ReceiveOnly, ml2.Transceiver.DesiredDirection); // no change Assert.IsTrue(ml1.Transceiver.NegotiatedDirection.HasValue); Assert.IsTrue(ml2.Transceiver.NegotiatedDirection.HasValue); Assert.AreEqual(Transceiver.Direction.Inactive, ml1.Transceiver.NegotiatedDirection.Value); // desired Assert.AreEqual(Transceiver.Direction.Inactive, ml2.Transceiver.NegotiatedDirection.Value); // inactive * recvonly = inactive Assert.AreEqual(video_tr1, ml1.Transceiver); // immutable Assert.AreEqual(video_tr2, ml2.Transceiver); // immutable // Check track pairing Assert.IsNull(ml1.LocalTrack); // no source, so media line destroyed its sender track... Assert.IsNull(video_tr1.LocalTrack); // ...after detaching it from the transceiver Assert.IsNull(receiver2.Track); // transceiver is inactive and remote tracks are not owned Assert.IsNull(video_tr2.RemoteTrack); // unpaired // ====== Re-add sender ============================== // Re-add the sender on #1 ml1.Source = source1; // Renegotiate Assert.IsTrue(sig.StartConnection()); yield return sig.WaitForConnection(millisecondsTimeout: 60000); Assert.IsTrue(sig.IsConnected); // Wait a frame so that the Unity events for streams started can propagate yield return null; // Check transceiver update Assert.IsNotNull(ml1.Transceiver); // immutable Assert.IsNotNull(ml2.Transceiver); // immutable Assert.AreEqual(Transceiver.Direction.SendOnly, ml1.Transceiver.DesiredDirection); // set ml1 sender above Assert.AreEqual(Transceiver.Direction.ReceiveOnly, ml2.Transceiver.DesiredDirection); // no change Assert.IsTrue(ml1.Transceiver.NegotiatedDirection.HasValue); Assert.IsTrue(ml2.Transceiver.NegotiatedDirection.HasValue); Assert.AreEqual(Transceiver.Direction.SendOnly, ml1.Transceiver.NegotiatedDirection.Value); // desired Assert.AreEqual(Transceiver.Direction.ReceiveOnly, ml2.Transceiver.NegotiatedDirection.Value); // accepted Assert.AreEqual(video_tr1, ml1.Transceiver); // immutable Assert.AreEqual(video_tr2, ml2.Transceiver); // immutable // Check track pairing Assert.IsNotNull(ml1.LocalTrack); // new source again, media line re-created a sender track Assert.IsNotNull(ml1.LocalTrack.Transceiver); // ...and attached it to the transceiver... Assert.AreEqual(video_tr1, ml1.LocalTrack.Transceiver); Assert.IsNotNull(video_tr1.LocalTrack); // ...and conversely Assert.AreEqual(ml1.LocalTrack, video_tr1.LocalTrack); Assert.IsNotNull(receiver2.Track); // transceiver is active again and remote track was re-created Assert.IsNotNull(receiver2.Track.Transceiver); Assert.AreEqual(video_tr2, receiver2.Track.Transceiver); Assert.IsNotNull(video_tr2.RemoteTrack); // re-paired Assert.AreEqual(receiver2.Track, video_tr2.RemoteTrack); Object.Destroy(pc1_go); Object.Destroy(pc2_go); Object.Destroy(sig_go); } /// /// Test interleaving of media transceivers and data channels, which produce a discontinuity in /// the media line indices of the media transceivers since data channels also consume some media /// line. This test ensures the transceiver indexing and pairing is robust to those discontinuities. /// [UnityTest] public IEnumerator InterleavedMediaAndData() { // Create the peer connections var pc1_go = new GameObject("pc1"); pc1_go.SetActive(false); // prevent auto-activation of components var pc1 = pc1_go.AddComponent(); var pc2_go = new GameObject("pc2"); pc2_go.SetActive(false); // prevent auto-activation of components var pc2 = pc2_go.AddComponent(); // Batch changes manually pc1.AutoCreateOfferOnRenegotiationNeeded = false; pc2.AutoCreateOfferOnRenegotiationNeeded = false; // Create the signaler var sig_go = new GameObject("signaler"); var sig = sig_go.AddComponent(); sig.Peer1 = pc1; sig.Peer2 = pc2; // Create the sender video source var source1 = pc1_go.AddComponent(); //sender1.SenderTrackName = "track_name"; MediaLine ml1 = pc1.AddMediaLine(MediaKind.Video); Assert.IsNotNull(ml1); ml1.Source = source1; // Create the receiver video source var receiver2 = pc2_go.AddComponent(); MediaLine ml2 = pc2.AddMediaLine(MediaKind.Video); Assert.IsNotNull(ml2); ml2.Receiver = receiver2; // Initialize yield return PeerConnectionTests.InitializeAndWait(pc1); yield return PeerConnectionTests.InitializeAndWait(pc2); // Confirm the source is ready, but the sender track is not created yet Assert.IsTrue(source1.IsLive); Assert.IsNull(ml1.LocalTrack); // Create some dummy out-of-band data channel to force SCTP negotiation // during the first offer, and be able to add some in-band data channels // later via subsequent SDP session negotiations. { Task t1 = pc1.Peer.AddDataChannelAsync(42, "dummy", ordered: true, reliable: true); Task t2 = pc2.Peer.AddDataChannelAsync(42, "dummy", ordered: true, reliable: true); Assert.IsTrue(t1.Wait(millisecondsTimeout: 10000)); Assert.IsTrue(t2.Wait(millisecondsTimeout: 10000)); } // Connect Assert.IsTrue(sig.StartConnection()); yield return sig.WaitForConnection(millisecondsTimeout: 60000); Assert.IsTrue(sig.IsConnected); // Wait a frame so that the Unity events for streams started can propagate yield return null; // Check transceiver update var video_tr1 = ml1.Transceiver; Assert.IsNotNull(video_tr1); var video_tr2 = ml2.Transceiver; Assert.IsNotNull(video_tr2); Assert.AreEqual(0, video_tr1.MlineIndex); Assert.AreEqual(0, video_tr2.MlineIndex); // ====== Add in-band data channel ==================================== // Add an in-band data channel on peer #1 DataChannel dc1; { Task t1 = pc1.Peer.AddDataChannelAsync("test_data_channel", ordered: true, reliable: true); Assert.IsTrue(t1.Wait(millisecondsTimeout: 10000)); dc1 = t1.Result; } // Prepare to receive a new data channel on peer #2 DataChannel dc2 = null; var dc2_added_ev = new ManualResetEventSlim(initialState: false); pc2.Peer.DataChannelAdded += (DataChannel channel) => { dc2 = channel; dc2_added_ev.Set(); }; // Renegotiate; data channel will consume media line #1 Assert.IsTrue(sig.StartConnection()); yield return sig.WaitForConnection(millisecondsTimeout: 60000); Assert.IsTrue(sig.IsConnected); // Do not assume that connecting is enough to get the data channel, as callbacks are // asynchronously invoked. Instead explicitly wait for the created event to be raised. Assert.IsTrue(dc2_added_ev.Wait(millisecondsTimeout: 10000)); // Check the data channel is ready Assert.IsNotNull(dc2); Assert.AreEqual(dc1.ID, dc2.ID); Assert.AreEqual(DataChannel.ChannelState.Open, dc1.State); Assert.AreEqual(DataChannel.ChannelState.Open, dc2.State); // ====== Add an extra media transceiver ============================== // Create the receiver video source var receiver1b = pc1_go.AddComponent(); MediaLine ml1b = pc1.AddMediaLine(MediaKind.Video); Assert.IsNotNull(ml1b); ml1b.Receiver = receiver1b; // Create the sender video source var source2b = pc2_go.AddComponent(); //sender2b.SenderTrackName = "track_name_2"; MediaLine ml2b = pc2.AddMediaLine(MediaKind.Video); Assert.IsNotNull(ml2b); ml2b.Source = source2b; // Renegotiate Assert.IsTrue(sig.StartConnection()); yield return sig.WaitForConnection(millisecondsTimeout: 60000); Assert.IsTrue(sig.IsConnected); // Wait a frame so that the Unity events for streams started can propagate yield return null; // Check transceiver update var video_tr1b = ml1b.Transceiver; Assert.IsNotNull(video_tr1b); var video_tr2b = ml2b.Transceiver; Assert.IsNotNull(video_tr2b); Assert.AreEqual(2, video_tr1b.MlineIndex); Assert.AreEqual(2, video_tr2b.MlineIndex); Object.Destroy(pc1_go); Object.Destroy(pc2_go); Object.Destroy(sig_go); } [UnityTest] public IEnumerator SwapSource() { // Create the peer connections var pc1_go = new GameObject("pc1"); pc1_go.SetActive(false); // prevent auto-activation of components var pc1 = pc1_go.AddComponent(); var pc2_go = new GameObject("pc2"); pc2_go.SetActive(false); // prevent auto-activation of components var pc2 = pc2_go.AddComponent(); // Batch changes manually pc1.AutoCreateOfferOnRenegotiationNeeded = false; pc2.AutoCreateOfferOnRenegotiationNeeded = false; // Create the signaler var sig_go = new GameObject("signaler"); var sig = sig_go.AddComponent(); sig.Peer1 = pc1; sig.Peer2 = pc2; // Create the video sources on peer #1 VideoTrackSource source1 = pc1_go.AddComponent(); VideoTrackSource source2 = pc1_go.AddComponent(); MediaLine ml = pc1.AddMediaLine(MediaKind.Video); ml.SenderTrackName = "video_track_1"; ml.Source = source1; // Create the receiver on peer #2 { VideoReceiver receiver = pc2_go.AddComponent(); MediaLine receiverMl = pc2.AddMediaLine(MediaKind.Video); receiverMl.Receiver = receiver; } // Initialize yield return PeerConnectionTests.InitializeAndWait(pc1); yield return PeerConnectionTests.InitializeAndWait(pc2); // Connect Assert.IsTrue(sig.StartConnection()); yield return sig.WaitForConnection(millisecondsTimeout: 10000); // Wait a frame so that the Unity events for streams started can propagate yield return null; // source1 is correctly wired. Assert.AreEqual(ml.Transceiver.DesiredDirection, Transceiver.Direction.SendOnly); Assert.AreEqual(pc1.Peer.LocalVideoTracks.Count(), 1); Assert.IsNotNull(ml.LocalTrack); Assert.AreEqual(((LocalVideoTrack)ml.LocalTrack).Source, source1.Source); Assert.AreEqual(source1.MediaLines.Single(), ml); // Reset source ml.Source = null; // source1 has been detached. Assert.IsNull(ml.LocalTrack); Assert.AreEqual(ml.Transceiver.DesiredDirection, Transceiver.Direction.Inactive); Assert.AreEqual(pc1.Peer.LocalVideoTracks.Count(), 0); Assert.IsEmpty(source1.MediaLines); // Set source2. ml.Source = source2; // source2 is correctly wired. Assert.IsNotNull(ml.LocalTrack); Assert.AreEqual(ml.Transceiver.DesiredDirection, Transceiver.Direction.SendOnly); Assert.AreEqual(pc1.Peer.LocalVideoTracks.Count(), 1); Assert.AreEqual(source2.MediaLines.Single(), ml); Assert.AreEqual(((LocalVideoTrack)ml.LocalTrack).Source, source2.Source); // Swap source2 with source1. ml.Source = source1; // source1 is correctly wired. Assert.IsNotNull(ml.LocalTrack); Assert.AreEqual(ml.Transceiver.DesiredDirection, Transceiver.Direction.SendOnly); Assert.AreEqual(pc1.Peer.LocalVideoTracks.Count(), 1); Assert.AreEqual(source1.MediaLines.Single(), ml); Assert.AreEqual(((LocalVideoTrack)ml.LocalTrack).Source, source1.Source); // source2 has been detached. Assert.IsEmpty(source2.MediaLines); Object.Destroy(pc1_go); Object.Destroy(pc2_go); } [UnityTest] public IEnumerator SwapReceiver() { // Create the peer connections var pc1_go = new GameObject("pc1"); pc1_go.SetActive(false); // prevent auto-activation of components var pc1 = pc1_go.AddComponent(); var pc2_go = new GameObject("pc2"); pc2_go.SetActive(false); // prevent auto-activation of components var pc2 = pc2_go.AddComponent(); // Batch changes manually pc1.AutoCreateOfferOnRenegotiationNeeded = false; pc2.AutoCreateOfferOnRenegotiationNeeded = false; // Create the signaler var sig_go = new GameObject("signaler"); var sig = sig_go.AddComponent(); sig.Peer1 = pc1; sig.Peer2 = pc2; // Create the video source on peer #1 { VideoTrackSource source = pc1_go.AddComponent(); MediaLine senderMl = pc1.AddMediaLine(MediaKind.Video); senderMl.SenderTrackName = "video_track_1"; senderMl.Source = source; } // Create the receivers on peer #2 VideoReceiver receiver1 = pc2_go.AddComponent(); VideoReceiver receiver2 = pc2_go.AddComponent(); MediaLine ml = pc2.AddMediaLine(MediaKind.Video); ml.Receiver = receiver1; // Initialize yield return PeerConnectionTests.InitializeAndWait(pc1); yield return PeerConnectionTests.InitializeAndWait(pc2); // Connect Assert.IsTrue(sig.StartConnection()); yield return sig.WaitForConnection(millisecondsTimeout: 10000); // Wait a frame so that the Unity events for streams started can propagate yield return null; // receiver1 is correctly wired. Assert.AreEqual(ml.Transceiver.DesiredDirection, Transceiver.Direction.ReceiveOnly); Assert.AreEqual(pc2.Peer.RemoteVideoTracks.Count(), 1); Assert.IsTrue(receiver1.IsLive); Assert.AreEqual(receiver1.Track, ml.Transceiver.RemoteTrack); Assert.AreEqual(receiver1.MediaLine, ml); // Reset receiver ml.Receiver = null; // receiver1 has been detached. Assert.AreEqual(ml.Transceiver.DesiredDirection, Transceiver.Direction.Inactive); Assert.IsFalse(receiver1.IsLive); Assert.IsNull(receiver1.MediaLine); // Set receiver2. ml.Receiver = receiver2; // receiver2 is correctly wired. Assert.AreEqual(ml.Transceiver.DesiredDirection, Transceiver.Direction.ReceiveOnly); Assert.AreEqual(pc2.Peer.RemoteVideoTracks.Count(), 1); Assert.IsTrue(receiver2.IsLive); Assert.AreEqual(receiver2.Track, ml.Transceiver.RemoteTrack); Assert.AreEqual(receiver2.MediaLine, ml); // Swap receiver2 with receiver1. ml.Receiver = receiver1; // receiver1 is correctly wired. Assert.AreEqual(ml.Transceiver.DesiredDirection, Transceiver.Direction.ReceiveOnly); Assert.AreEqual(pc2.Peer.RemoteVideoTracks.Count(), 1); Assert.IsTrue(receiver1.IsLive); Assert.AreEqual(receiver1.Track, ml.Transceiver.RemoteTrack); Assert.AreEqual(receiver1.MediaLine, ml); // receiver2 has been detached. Assert.IsFalse(receiver2.IsLive); Assert.IsNull(receiver2.MediaLine); Object.Destroy(pc1_go); Object.Destroy(pc2_go); } } }