Example #1
0
        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 <PeerConnection>();
            var pc2_go = new GameObject("pc2");

            pc2_go.SetActive(false); // prevent auto-activation of components
            var pc2 = pc2_go.AddComponent <PeerConnection>();

            // Batch changes manually
            pc1.AutoCreateOfferOnRenegotiationNeeded = false;
            pc2.AutoCreateOfferOnRenegotiationNeeded = false;

            // Create the signaler
            var sig_go = new GameObject("signaler");
            var sig    = sig_go.AddComponent <LocalOnlySignaler>();

            sig.Peer1 = pc1;
            sig.Peer2 = pc2;

            // Create the video source on peer #1
            {
                VideoTrackSource source   = pc1_go.AddComponent <UniformColorVideoSource>();
                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>();
            VideoReceiver receiver2 = pc2_go.AddComponent <VideoReceiver>();
            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);
        }
Example #2
0
        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 <PeerConnection>();
            var pc2_go = new GameObject("pc2");

            pc2_go.SetActive(false); // prevent auto-activation of components
            var pc2 = pc2_go.AddComponent <PeerConnection>();

            // Batch changes manually
            pc1.AutoCreateOfferOnRenegotiationNeeded = false;
            pc2.AutoCreateOfferOnRenegotiationNeeded = false;

            // Create the signaler
            var sig_go = new GameObject("signaler");
            var sig    = sig_go.AddComponent <LocalOnlySignaler>();

            sig.Peer1 = pc1;
            sig.Peer2 = pc2;

            // Create the sender video source
            var source1 = pc1_go.AddComponent <UniformColorVideoSource>();
            //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 <VideoReceiver>();
            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 <DataChannel> t1 = pc1.Peer.AddDataChannelAsync(42, "dummy", ordered: true, reliable: true);
                Task <DataChannel> 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 <DataChannel> 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 <VideoReceiver>();
            MediaLine ml1b       = pc1.AddMediaLine(MediaKind.Video);

            Assert.IsNotNull(ml1b);
            ml1b.Receiver = receiver1b;

            // Create the sender video source
            var source2b = pc2_go.AddComponent <UniformColorVideoSource>();
            //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);
        }
Example #3
0
        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 <PeerConnection>();
            var pc2_go = new GameObject("pc2");

            pc2_go.SetActive(false); // prevent auto-activation of components
            var pc2 = pc2_go.AddComponent <PeerConnection>();

            // Batch changes manually
            pc1.AutoCreateOfferOnRenegotiationNeeded = false;
            pc2.AutoCreateOfferOnRenegotiationNeeded = false;

            // Create the signaler
            var sig_go = new GameObject("signaler");
            var sig    = sig_go.AddComponent <LocalOnlySignaler>();

            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 <UniformColorVideoSource>();
            }
            if (withReceiver1)
            {
                receiver1 = pc1_go.AddComponent <VideoReceiver>();
            }
            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 <UniformColorVideoSource>();
            }
            if (withReceiver2)
            {
                receiver2 = pc1_go.AddComponent <VideoReceiver>();
            }
            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);
        }
Example #4
0
        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 <PeerConnection>();
            var pc2_go = new GameObject("pc2");

            pc2_go.SetActive(false); // prevent auto-activation of components
            var pc2 = pc2_go.AddComponent <PeerConnection>();

            // Batch changes manually
            pc1.AutoCreateOfferOnRenegotiationNeeded = false;
            pc2.AutoCreateOfferOnRenegotiationNeeded = false;

            // Create the signaler
            var sig_go = new GameObject("signaler");
            var sig    = sig_go.AddComponent <LocalOnlySignaler>();

            sig.Peer1 = pc1;
            sig.Peer2 = pc2;

            // Create the sender video source
            var source1 = pc1_go.AddComponent <UniformColorVideoSource>();
            //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 <VideoReceiver>();
            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);
        }
Example #5
0
        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 <PeerConnection>();
            var pc2_go = new GameObject("pc2");

            pc2_go.SetActive(false); // prevent auto-activation of components
            var pc2 = pc2_go.AddComponent <PeerConnection>();

            // Batch changes manually
            pc1.AutoCreateOfferOnRenegotiationNeeded = false;
            pc2.AutoCreateOfferOnRenegotiationNeeded = false;

            // Create the signaler
            var sig_go = new GameObject("signaler");
            var sig    = sig_go.AddComponent <LocalOnlySignaler>();

            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 <UniformColorVideoSource>();
                        peer.source         = source1;
                        ml1.Source          = source1;
                        ml1.SenderTrackName = $"track{i}";
                    }
                    if (Transceiver.HasRecv(peer.desiredDirection))
                    {
                        var receiver1 = pc1_go.AddComponent <VideoReceiver>();
                        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 <UniformColorVideoSource>();
                        peer.source         = source2;
                        ml2.Source          = source2;
                        ml2.SenderTrackName = $"track{i}";
                    }
                    if (Transceiver.HasRecv(peer.desiredDirection))
                    {
                        var receiver2 = pc2_go.AddComponent <VideoReceiver>();
                        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 <VideoReceiver>();
                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 <UniformColorVideoSource>();
                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);
        }