Example #1
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>();

            pc1.AutoInitializeOnStart = false;
            var pc2_go = new GameObject("pc2");

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

            pc2.AutoInitializeOnStart = false;

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

            // Activate
            pc1_go.SetActive(true);
            pc2_go.SetActive(true);

            // Initialize
            var initializedEvent1 = new ManualResetEventSlim(initialState: false);

            pc1.OnInitialized.AddListener(() => initializedEvent1.Set());
            Assert.IsNull(pc1.Peer);
            pc1.InitializeAsync().Wait(millisecondsTimeout: 50000);
            var initializedEvent2 = new ManualResetEventSlim(initialState: false);

            pc2.OnInitialized.AddListener(() => initializedEvent2.Set());
            Assert.IsNull(pc2.Peer);
            pc2.InitializeAsync().Wait(millisecondsTimeout: 50000);

            // Wait a frame so that the Unity event OnInitialized can propagate
            yield return(null);

            // Check the event was raised
            Assert.IsTrue(initializedEvent1.Wait(millisecondsTimeout: 50000));
            Assert.IsNotNull(pc1.Peer);
            Assert.IsTrue(initializedEvent2.Wait(millisecondsTimeout: 50000));
            Assert.IsNotNull(pc2.Peer);

            // Confirm the sources are ready
            if (withSender1)
            {
                Assert.IsTrue(source1.IsStreaming);
            }
            if (withSender2)
            {
                Assert.IsTrue(source2.IsStreaming);
            }

            // Confirm the sender track is not created yet; it will be when the connection starts
            Assert.IsNull(ml1.SenderTrack);
            Assert.IsNull(ml2.SenderTrack);

            // 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.SenderTrack);
                    hasRecv1 = true;
                    hasSend2 = true;
                }
                if (withSender1 && withReceiver2) // S => R
                {
                    Assert.IsNotNull(ml1.SenderTrack);
                    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));
            }
        }
Example #2
0
        /// <summary>
        /// Pass the given SDP description received from the remote peer via signaling to the
        /// underlying WebRTC implementation, which will parse and use it.
        ///
        /// This must be called by the signaler when receiving a message. Once this operation
        /// has completed, it is safe to call <see xref="WebRTC.PeerConnection.CreateAnswer"/>.
        ///
        /// <div class="IMPORTANT alert alert-important">
        /// <h5>IMPORTANT</h5>
        /// <p>
        /// This method is very similar to the <c>SetRemoteDescriptionAsync()</c> method available in the
        /// underlying C# library, and actually calls it. However it also performs additional work in order
        /// to pair the transceivers of the local and remote peer. Therefore Unity applications must call
        /// this method instead of the C# library one to ensure transceiver pairing works as intended.
        /// </p>
        /// </div>
        /// </summary>
        /// <param name="message">The SDP message to handle.</param>
        /// <returns>A task which completes once the remote description has been applied and transceivers
        /// have been updated.</returns>
        /// <exception xref="InvalidOperationException">The peer connection is not intialized.</exception>
        /// <remarks>
        /// This method can only be called from the main Unity application thread, where Unity objects can
        /// be safely accessed.
        /// </remarks>
        public async Task HandleConnectionMessageAsync(SdpMessage message)
        {
            // MediaLine manipulates some MonoBehaviour objects when managing senders and receivers
            EnsureIsMainAppThread();

            // First apply the remote description
            await Peer.SetRemoteDescriptionAsync(message);

            // Sort associated transceiver by media line index. The media line index is not the index of
            // the transceiver, but they are both monotonically increasing, so sorting by one or the other
            // yields the same ordered collection, which allows pairing transceivers and media lines.
            // TODO - Ensure PeerConnection.Transceivers is already sorted
            var transceivers = new List <Transceiver>(_nativePeer.AssociatedTransceivers);

            transceivers.Sort((tr1, tr2) => (tr1.MlineIndex - tr2.MlineIndex));
            int numAssociatedTransceivers = transceivers.Count;
            int numMatching = Math.Min(numAssociatedTransceivers, _mediaLines.Count);

            // Once applied, try to pair transceivers and remote tracks with the Unity receiver components
            if (message.Type == SdpMessageType.Offer)
            {
                // Match transceivers with media line, in order
                for (int i = 0; i < numMatching; ++i)
                {
                    var tr        = transceivers[i];
                    var mediaLine = _mediaLines[i];

                    // Associate the transceiver with the media line, if not already done, and associate
                    // the track components of the media line to the tracks of the transceiver.
                    try
                    {
                        mediaLine.UpdateOnReceiveOffer(tr);
                    }
                    catch (Exception ex)
                    {
                        LogErrorOnMediaLineException(ex, mediaLine, tr);
                    }

                    // Check if the remote peer was planning to send something to this peer, but cannot.
                    bool wantsRecv = (mediaLine.Receiver != null);
                    if (!wantsRecv)
                    {
                        var desDir = tr.DesiredDirection;
                        if (Transceiver.HasRecv(desDir))
                        {
                            string peerName = name;
                            int    idx      = i;
                            InvokeOnAppThread(() => LogWarningOnMissingReceiver(peerName, idx));
                        }
                    }
                }

                // Ignore extra transceivers without a registered component to attach
                if (numMatching < numAssociatedTransceivers)
                {
                    string peerName = name;
                    InvokeOnAppThread(() =>
                    {
                        for (int i = numMatching; i < numAssociatedTransceivers; ++i)
                        {
                            LogWarningOnIgnoredTransceiver(peerName, i);
                        }
                    });
                }
            }
            else if (message.Type == SdpMessageType.Answer)
            {
                // Associate registered media senders/receivers with existing transceivers
                for (int i = 0; i < numMatching; ++i)
                {
                    Transceiver tr        = transceivers[i];
                    var         mediaLine = _mediaLines[i];
                    Debug.Assert(mediaLine.Transceiver == transceivers[i]);
                    mediaLine.UpdateOnReceiveAnswer();
                }

                // Ignore extra transceivers without a registered component to attach
                if (numMatching < numAssociatedTransceivers)
                {
                    string peerName = name;
                    InvokeOnAppThread(() =>
                    {
                        for (int i = numMatching; i < numAssociatedTransceivers; ++i)
                        {
                            LogWarningOnIgnoredTransceiver(peerName, i);
                        }
                    });
                }
            }
        }
Example #3
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>();

            pc1.AutoInitializeOnStart = false;
            var pc2_go = new GameObject("pc2");

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

            pc2.AutoInitializeOnStart = false;

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

            // Activate
            pc1_go.SetActive(true);
            pc2_go.SetActive(true);

            // Initialize
            var initializedEvent1 = new ManualResetEventSlim(initialState: false);

            pc1.OnInitialized.AddListener(() => initializedEvent1.Set());
            Assert.IsNull(pc1.Peer);
            pc1.InitializeAsync().Wait(millisecondsTimeout: 50000);
            var initializedEvent2 = new ManualResetEventSlim(initialState: false);

            pc2.OnInitialized.AddListener(() => initializedEvent2.Set());
            Assert.IsNull(pc2.Peer);
            pc2.InitializeAsync().Wait(millisecondsTimeout: 50000);

            // Wait a frame so that the Unity event OnInitialized can propagate
            yield return(null);

            // Check the event was raised
            Assert.IsTrue(initializedEvent1.Wait(millisecondsTimeout: 50000));
            Assert.IsNotNull(pc1.Peer);
            Assert.IsTrue(initializedEvent2.Wait(millisecondsTimeout: 50000));
            Assert.IsNotNull(pc2.Peer);

            // 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.IsNotNull(cfg.peer1.source.IsStreaming, $"Source #{i} is not ready on Peer #1");
                    Assert.IsNull(cfg.peer1.mediaLine.SenderTrack); // created during connection
                }
                if (cfg.peer2.expectSender)
                {
                    Assert.IsNotNull(cfg.peer2.source, $"Missing source #{i} on Peer #2");
                    Assert.IsNotNull(cfg.peer2.source.IsStreaming, $"Source #{i} is not ready on Peer #2");
                    Assert.IsNull(cfg.peer2.mediaLine.SenderTrack); // 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.SenderTrack, $"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.SenderTrack, $"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");
                }
            }
        }