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