示例#1
0
        public async Task Connect()
        {
            TaskCompletionSource <bool> dcAOpened = new TaskCompletionSource <bool>(TaskCreationOptions.RunContinuationsAsynchronously);

            DC = await PCSrc.createDataChannel($"{DATACHANNEL_LABEL_PREFIX}-{ID}-a");

            DC.onopen += () =>
            {
                Console.WriteLine($"Peer connection pair {Name} A data channel opened.");
                StreamSendConfirmed.TryAdd(DC.id.Value, new ManualResetEventSlim());
                dcAOpened.TrySetResult(true);
            };

            var offer = PCSrc.createOffer();
            await PCSrc.setLocalDescription(offer);

            if (PCDst.setRemoteDescription(offer) != SetDescriptionResultEnum.OK)
            {
                throw new ApplicationException($"SDP negotiation failed for peer connection pair {Name}.");
            }

            var answer = PCDst.createAnswer();
            await PCDst.setLocalDescription(answer);

            if (PCSrc.setRemoteDescription(answer) != SetDescriptionResultEnum.OK)
            {
                throw new ApplicationException($"SDP negotiation failed for peer connection pair {Name}.");
            }

            await Task.WhenAll(dcAOpened.Task);
        }
示例#2
0
        public void Connect(string url)
        {
            if (!string.IsNullOrEmpty(mHost))
            {
                throw new Exception("The host has connected. Please disconnect at first.");
            }

            mHost = url;

            var conf = GetSelectedSdpSemantics();

            mPeer = new RTCPeerConnection(ref conf);
            mPeer.OnIceCandidate        = OnIceCandidate;
            mPeer.OnIceConnectionChange = OnIceConnectionChange;
            mPeer.OnDataChannel         = OnDataChannel;

            DebugUtility.Log(LoggerTags.Online, "Create peer connection : {0}", host);

            var init = new RTCDataChannelInit(true);

            mSendDataChannel         = mPeer.CreateDataChannel("data", ref init);
            mSendDataChannel.OnOpen  = OnDataChannelOpen;
            mSendDataChannel.OnClose = OnDataChannelClose;

            currentTask = StartCoroutine(StartCreationWorkflow(mPeer));
        }
示例#3
0
    IEnumerator Call()
    {
        callButton.interactable = false;
        Debug.Log("GetSelectedSdpSemantics");
        var configuration = GetSelectedSdpSemantics();

        pc1 = new RTCPeerConnection(ref configuration);
        Debug.Log("Created local peer connection object pc1");
        pc1.OnIceCandidate        = pc1OnIceCandidate;
        pc1.OnIceConnectionChange = pc1OnIceConnectionChange;
        pc2 = new RTCPeerConnection(ref configuration);
        Debug.Log("Created remote peer connection object pc2");
        pc2.OnIceCandidate        = pc2OnIceCandidate;
        pc2.OnIceConnectionChange = pc2OnIceConnectionChange;
        pc2.OnDataChannel         = onDataChannel;

        RTCDataChannelInit conf = new RTCDataChannelInit(true);

        dataChannel        = pc1.CreateDataChannel("data", ref conf);
        dataChannel.OnOpen = onDataChannelOpen;

        Debug.Log("pc1 createOffer start");
        var op = pc1.CreateOffer(ref OfferOptions);

        yield return(op);

        if (!op.IsError)
        {
            yield return(StartCoroutine(OnCreateOfferSuccess(op.Desc)));
        }
        else
        {
            OnCreateSessionDescriptionError(op.Error);
        }
    }
示例#4
0
        private void InitializeWebRtc()
        {
            WebRTC.Initialize();

            var config = new RTCConfiguration
            {
                iceServers = new[]
                {
                    new RTCIceServer
                    {
                        urls = new[] { "stun:stun.l.google.com:19302", "stun:stun1.l.google.com:19302" }
                    }
                }
            };

            _peerConnection = new RTCPeerConnection(ref config);
            _peerConnection.OnIceConnectionChange = state => Debug.LogError("State changed: " + state);
            Debug.LogError("PeerConnection created.");

            var dcInit = new RTCDataChannelInit(true);

            DataChannel         = _peerConnection.CreateDataChannel("dataChannel", ref dcInit);
            DataChannel.OnOpen  = () => Debug.LogError("Data channel opened.");
            DataChannel.OnClose = () => Debug.LogError("Data channel closed.");

            DataChannel.OnMessage = bytes => Debug.LogError("Data channel received data: " + Encoding.UTF8.GetString(bytes));
            Debug.LogError("Data channel created.");
        }
示例#5
0
 /// <summary>
 ///
 /// </summary>
 /// <param name="track"></param>
 public override void SetChannel(string connectionId, RTCDataChannel channel)
 {
     if (channel == null)
     {
         if (remoteInput != null)
         {
             onDeviceChange.Invoke(remoteInput.RemoteGamepad, InputDeviceChange.Removed);
             onDeviceChange.Invoke(remoteInput.RemoteKeyboard, InputDeviceChange.Removed);
             onDeviceChange.Invoke(remoteInput.RemoteMouse, InputDeviceChange.Removed);
             onDeviceChange.Invoke(remoteInput.RemoteTouchscreen, InputDeviceChange.Removed);
             remoteInput.Dispose();
             remoteInput = null;
         }
     }
     else
     {
         remoteInput = RemoteInputReceiver.Create();
         remoteInput.ActionButtonClick = OnButtonClick;
         channel.OnMessage            += remoteInput.ProcessInput;
         onDeviceChange.Invoke(remoteInput.RemoteGamepad, InputDeviceChange.Added);
         onDeviceChange.Invoke(remoteInput.RemoteKeyboard, InputDeviceChange.Added);
         onDeviceChange.Invoke(remoteInput.RemoteMouse, InputDeviceChange.Added);
         onDeviceChange.Invoke(remoteInput.RemoteTouchscreen, InputDeviceChange.Added);
     }
     base.SetChannel(connectionId, channel);
 }
示例#6
0
        void OnCloseChannel(RTCDataChannel channel)
        {
            RemoteInput input = m_mapChannelAndRemoteInput[channel];

            RemoteInputReceiver.Delete(input);

            // device.current must be changed after removing devices
            m_defaultInput.MakeCurrent();

            // reassign remote input to controller
            if (m_remoteInputAndCameraController.TryGetValue(input, out var controller))
            {
                RemoteInput newInput = FindPrioritizedInput();
                if (newInput == null)
                {
                    controller.SetInput(m_defaultInput);
                }
                else
                {
                    controller.SetInput(newInput);
                    m_remoteInputAndCameraController.Add(newInput, controller);
                }
            }

            m_remoteInputAndCameraController.Remove(input);

            m_mapChannelAndRemoteInput.Remove(channel);
        }
        protected virtual void Dispose(bool disposing)
        {
            // clean all unmanaged resources (if any)

            if (disposing)
            {
                // clean all managed resources
                if (_dataChannel != null)
                {
                    _dataChannel.Close();
                    _dataChannel = null;
                }
                if (_sctp != null)
                {
                    _sctp.Stop();
                    _sctp = null;
                }
                if (_dtls != null)
                {
                    _dtls.Stop();
                    _dtls = null;
                }
                if (_ice != null)
                {
                    _ice.Stop();
                    _ice = null;
                }
                if (_gatherer != null)
                {
                    _gatherer.Close();
                    _gatherer = null;
                }
            }
        }
 public Observer(RTCDataChannel channel)
 {
     _channel          = channel ?? throw new ArgumentNullException("channel is null");
     _channel.OnOpen  += () => { _isOpen = true; };
     _channel.OnClose += () => { _isOpen = false; };
     _isOpen           = _channel.ReadyState == RTCDataChannelState.Open;
 }
        public RTCDataChannel CreateDataChannel(int indexPeer, string label, RTCDataChannelInit option = null)
        {
            RTCDataChannel channel = peers[indexPeer].CreateDataChannel(label, option);

            dataChannels[peers[indexPeer]].Add(channel);
            return(channel);
        }
示例#10
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="channel"></param>
        public Receiver(RTCDataChannel channel)
        {
            _channel            = channel ?? throw new ArgumentNullException("channel is null");
            _channel.OnMessage += OnMessage;

            _onEvent   = (InputEventPtr ptr, InputDevice device) => { base.QueueEvent(ptr); };
            _corrector = new InputPositionCorrector(_onEvent);
        }
示例#11
0
        private static void DoLoadTestIteration(RTCDataChannel dc, uint payloadSize)
        {
            var rndBuffer = new byte[payloadSize];

            Crypto.GetRandomBytes(rndBuffer);
            logger.LogInformation($"Data channel sending {payloadSize} random bytes, hash {DoJavscriptSHA256(rndBuffer)}.");
            dc.send(rndBuffer);
        }
        public IEnumerator UnitySetUp()
        {
            MockSignaling.Reset(true);
            _test = new MonoBehaviourTest <MyMonoBehaviourTest>();

            var dependencies1 = CreateDependencies();
            var dependencies2 = CreateDependencies();

            _target1 = new RenderStreamingInternal(ref dependencies1);
            _target2 = new RenderStreamingInternal(ref dependencies2);

            bool isStarted1 = false;
            bool isStarted2 = false;

            _target1.onStart += () => { isStarted1 = true; };
            _target2.onStart += () => { isStarted2 = true; };
            yield return(new WaitUntil(() => isStarted1 && isStarted2));

            bool isCreatedConnection1 = false;
            bool isCreatedConnection2 = false;

            _target1.onCreatedConnection += _ => { isCreatedConnection1 = true; };
            _target2.onCreatedConnection += _ => { isCreatedConnection2 = true; };

            // _target1 is Receiver in private mode
            _target1.CreateConnection(connectionId);
            yield return(new WaitUntil(() => isCreatedConnection1));

            // _target2 is sender in private mode
            _target2.CreateConnection(connectionId);
            yield return(new WaitUntil(() => isCreatedConnection2));

            _target1.onAddChannel += (_, channel) => { _channel1 = channel; };

            // send offer automatically after creating channel
            _channel2 = _target2.CreateChannel(connectionId, "_test");

            bool isGotOffer1  = false;
            bool isGotAnswer2 = false;

            _target1.onGotOffer  += (_, sdp) => { isGotOffer1 = true; };
            _target2.onGotAnswer += (_, sdp) => { isGotAnswer2 = true; };

            // each peer are not stable, signaling process not complete.
            yield return(new WaitUntil(() => isGotOffer1));

            _target1.SendAnswer(connectionId);
            yield return(new WaitUntil(() => isGotAnswer2));

            Assert.That(isGotAnswer2, Is.True);

            // If target1 processes resent Offer from target2, target1 is not stable.
            Assert.That(_target2.IsStable(connectionId), Is.True);

            yield return(new WaitUntil(() => _channel1 != null));

            Assert.That(_channel1.ReadyState, Is.EqualTo(RTCDataChannelState.Open));
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="track"></param>
        public override void SetChannel(string connectionId, RTCDataChannel channel)
        {
            sender         = new Sender();
            senderInput    = new InputRemoting(sender);
            senderDisposer = senderInput.Subscribe(new Observer(channel));
            senderInput.StartSending();

            base.SetChannel(connectionId, channel);
        }
示例#14
0
        public void CreateDataChannel()
        {
            RTCDataChannelInit DataChannelInit = new RTCDataChannelInit();
            var            _dataChannel        = PeerConnection.CreateDataChannel("Deneme", DataChannelInit);
            RTCDataChannel rTCDataChannel      = (RTCDataChannel)_dataChannel;

            _rTCDataChannel         = rTCDataChannel;
            _rTCDataChannel.OnOpen += _rTCDataChannel_OnOpen;
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="track"></param>
        public override void SetChannel(string connectionId, RTCDataChannel channel)
        {
            receiver = new Receiver(channel);
            receiver.onDeviceChange += OnDeviceChange;
            receiverInput            = new InputRemoting(receiver);
            receiverDisposer         = receiverInput.Subscribe(receiverInput);
            receiverInput.StartSending();

            base.SetChannel(connectionId, channel);
        }
示例#16
0
        private void OnAddChannel(string connectionId, RTCDataChannel channel)
        {
            var data = new SignalingEventData(EventSystem.current)
            {
                connectionId = connectionId,
                channel      = channel
            };

            ExecuteEventToAllTargets(data, ExecuteSignalingEvents.addChannelHandler);
        }
示例#17
0
        /// <summary>
        /// Closes a peer connection.
        /// </summary>
        //private async Task ClosePeerConnection()
        private void ClosePeerConnection()
        {
            lock (MediaLock)
            {
                if (_peerConnection != null)
                {
                    _peerId = -1;
                    if (_mediaStream != null)
                    {
                        foreach (var track in _mediaStream.GetTracks())
                        {
                            // Check Track Status before action to avoid reference errors
                            // CRASH condition previously on non-XAML usage
                            if (track != null)
                            {
                                if (track.Enabled)
                                {
                                    track.Stop();
                                }
                                _mediaStream.RemoveTrack(track);
                            }
                        }
                    }
                    _mediaStream = null;

                    // TODO: Cleanup DataChannel
                    if (_peerSendDataChannel != null)
                    {
                        _peerSendDataChannel.Close();
                        _peerSendDataChannel = null;
                    }

                    if (_peerReceiveDataChannel != null)
                    {
                        _peerReceiveDataChannel.Close();
                        _peerReceiveDataChannel = null;
                    }

                    OnPeerConnectionClosed?.Invoke();

                    _peerConnection.Close(); // Slow, so do this after UI updated and camera turned off

                    SessionId = null;
    #if ORTCLIB
                    OrtcStatsManager.Instance.CallEnded();
    #endif
                    _peerConnection = null;

                    OnReadyToConnect?.Invoke();

                    // TODO: handle GC
                    //GC.Collect(); // Ensure all references are truly dropped.
                }
            }
        }
        private void Sctp_OnDataChannel(RTCDataChannelEvent evt)
        {
            Debug.WriteLine("Sctp OnDataChannel");

            _dataChannel            = evt.DataChannel;
            _dataChannel.OnMessage += DataChannel_OnMessage;
            _dataChannel.OnError   += DataChannel_OnError;
            _dataChannel.OnClose   += DataChannel_OnClose;

            NotifyDataChannelConnected(true);
        }
    private void createPeer()
    {
        log(LogLevel.Log, "Create RTCPeerConnection");
        var peerConfig = new RTCConfiguration {
            iceServers = iceServers
        };

        peer = new RTCPeerConnection(ref peerConfig);
        peer.OnConnectionStateChange = connectionState =>
        {
            log(LogLevel.Log, $"[OnConnectionStateChange] connectionState: {connectionState}");
        };
        peer.OnDataChannel = channel =>
        {
            dataChannel = channel;
            setupDataChannelEventHandler();
            log(LogLevel.Log, $"[OnDataChannel] label: {channel.Label}");
        };
        peer.OnIceCandidate = candidate =>
        {
            log(LogLevel.Log, $"[OnIceCandidate]");
            log(LogLevel.Log, $">>> Send \"takeCandidate\" Command (iceCandidate: '{candidate.Candidate.Substring(0, 10)} ...')");
            signaling.SendIceCandidate(streamId, candidate.Candidate, candidate.SdpMLineIndex.Value, candidate.SdpMid);
        };
        peer.OnIceGatheringStateChange = state =>
        {
            log(LogLevel.Log, $"[OnIceGatheringStateChange] iceGatheringState: {state}");
        };
        peer.OnNegotiationNeeded = () =>
        {
            log(LogLevel.Log, $"[OnNegotiationNeeded]");
        };
        peer.OnTrack = evt =>
        {
            log(LogLevel.Log, $"[OnTrack] kind: {evt.Track.Kind}");
            if (evt.Track is VideoStreamTrack track)
            {
                var texture = track.InitializeReceiver(videoWidth, videoHeight);
                playerDisplay.GetComponent <Renderer>().material.mainTexture = texture;
            }
        };

        var dcOptions = new RTCDataChannelInit();

        log(LogLevel.Log, $"CreateDataChannel label: {dataChannelLabel}");
        dataChannel = peer.CreateDataChannel(dataChannelLabel, dcOptions);
        setupDataChannelEventHandler();
        if (clientType == ClientType.Publisher)
        {
            var videoTrack = new VideoStreamTrack("VideoTrack", videoPlayer.targetTexture);
            peer.AddTrack(videoTrack);
            StartCoroutine(createDesc(RTCSdpType.Offer));
        }
    }
示例#20
0
        private void _peerConnection_OnDataChannel(IRTCDataChannelEvent e)
        {
            var asd = RTCDataChannelEvent.Cast(e);

            Debug.WriteLine("Data Channel Girdi");
            IRTCDataChannel channel = e.Channel;

            Debug.WriteLine(" Data Channel Eklendi " + channel.BinaryType);
            _rTCDataChannel            = (RTCDataChannel)channel;
            _rTCDataChannel.OnMessage += _rTCDataChannel_OnMessage;
        }
        public async void CheckDataChannelEstablishment()
        {
            logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);
            logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name);

            var aliceDataConnected = new TaskCompletionSource <bool>(TaskCreationOptions.RunContinuationsAsynchronously);
            var bobDataOpened      = new TaskCompletionSource <bool>(TaskCreationOptions.RunContinuationsAsynchronously);

            var alice = new RTCPeerConnection();
            var dc    = await alice.createDataChannel("dc1", null);

            dc.onopen += () => aliceDataConnected.TrySetResult(true);
            var aliceOffer = alice.createOffer();
            await alice.setLocalDescription(aliceOffer);

            logger.LogDebug($"alice offer: {aliceOffer.sdp}");

            var            bob     = new RTCPeerConnection();
            RTCDataChannel bobData = null;

            bob.ondatachannel += (chan) =>
            {
                bobData = chan;
                bobDataOpened.TrySetResult(true);
            };

            var setOfferResult = bob.setRemoteDescription(aliceOffer);

            Assert.Equal(SetDescriptionResultEnum.OK, setOfferResult);

            var bobAnswer = bob.createAnswer();
            await bob.setLocalDescription(bobAnswer);

            var setAnswerResult = alice.setRemoteDescription(bobAnswer);

            Assert.Equal(SetDescriptionResultEnum.OK, setAnswerResult);

            logger.LogDebug($"answer: {bobAnswer.sdp}");

            await Task.WhenAny(Task.WhenAll(aliceDataConnected.Task, bobDataOpened.Task), Task.Delay(2000));

            Assert.True(aliceDataConnected.Task.IsCompleted);
            Assert.True(aliceDataConnected.Task.Result);
            Assert.True(bobDataOpened.Task.IsCompleted);
            Assert.True(bobDataOpened.Task.Result);
            Assert.True(dc.IsOpened);
            Assert.True(bobData.IsOpened);

            bob.close();
            alice.close();
        }
示例#22
0
        private void connectPeer()
        {
            OnLogEvent.Invoke("new RTCPeerConnection", "");
            peer = new RTCPeerConnection(ref peerConfig);
            peer.OnConnectionStateChange = connectionState =>
            {
                OnLogEvent.Invoke("OnConnectionStateChange", connectionState.ToString());
            };
            peer.OnDataChannel = channel =>
            {
                dataChannel = channel;
                setupDataChannelEventHandler();
                OnLogEvent.Invoke("OnDataChannel", channel.Label);
            };
            peer.OnIceCandidate = candidate =>
            {
                OnLogEvent.Invoke("OnIceCandidate", "");
                OnLogEvent.Invoke("Send IceCandidate", "");
                signaling.SendIceCandidate(streamId, candidate.Candidate, candidate.SdpMLineIndex.Value, candidate.SdpMid);
            };
            peer.OnIceGatheringStateChange = state =>
            {
                OnLogEvent.Invoke("OnIceGatheringStateChange", state.ToString());
            };
            peer.OnNegotiationNeeded = () =>
            {
                OnLogEvent.Invoke("OnNegotiationNeeded", "");
            };
            peer.OnTrack = evt =>
            {
                OnLogEvent.Invoke("OnTrack", evt.Track.Kind.ToString());
                if (evt.Track is VideoStreamTrack track)
                {
                    var texture = track.InitializeReceiver(videoWidth, videoHeight);
                    OnVideoTrack.Invoke(texture);
                }
            };

            var dcOptions = new RTCDataChannelInit();

            OnLogEvent.Invoke("CreateDataChannel", "testDC");
            dataChannel = peer.CreateDataChannel("testDC", dcOptions);
            setupDataChannelEventHandler();
            if (clientType == ClientType.Publisher)
            {
                var videoTrack = new VideoStreamTrack("VideoTrack", renderTexture);
                peer.AddTrack(videoTrack);
                CoroutineHandler.StartStaticCoroutine(sendDesc(RTCSdpType.Offer));
            }
        }
示例#23
0
 private void OnDataChannelMessage(RTCDataChannel dc, DataChannelPayloadProtocols protocol, byte[] data)
 {
     if (protocol == DataChannelPayloadProtocols.WebRTC_Binary)
     {
         if (data != null)
         {
             _incomingQueue.BlockEnqueue(data);
         }
     }
     else
     {
         Logger.LogWarning("None binary message received on the data channel.");
     }
 }
示例#24
0
    //ピアの作成をする
    void CreatePeer()
    {
        //ローカル
        RTCConfiguration pc_config = new RTCConfiguration();
        var server = new RTCIceServer();

        server.urls          = new string[] { "stun:stun.webrtc.ecl.ntt.com:3478" };
        pc_config.iceServers = new RTCIceServer[] {
            server
        };
        localConnection = new RTCPeerConnection(ref pc_config);
        //データチャネルの作成
        RTCDataChannelInit conf = new RTCDataChannelInit(true);

        localDataChannel         = localConnection.CreateDataChannel("send", ref conf);
        localDataChannel.OnOpen  = new DelegateOnOpen(() => { Debug.Log("データチャネル:localOpen"); });
        localDataChannel.OnClose = new DelegateOnClose(() => { Debug.Log("データチャネル:localClose"); });
        //メディアストリームの追加(現在のバージョンだとメディアストリームは1つまでらしい)
        if (_rtcType == RTCTYPE.OFFER)
        {
            _videoStream = _streamCamera.CaptureStream(800, 450);
        }

        localConnection.OnDataChannel = new DelegateOnDataChannel(x => {
            Debug.Log("ondatachannel");
            remoteDataChannel           = x;
            remoteDataChannel.OnMessage = onDataChannelMessage;
        });
        localConnection.OnTrack = new DelegateOnTrack(x => {
            x.Track;
            //PlayVideo(x.Track);
        });
        localConnection.OnIceConnectionChange =
            new DelegateOnIceConnectionChange(state => { OnIceConnectionChange(localConnection, state); });

        //ICEの登録
        localConnection.OnIceCandidate = new DelegateOnIceCandidate(candidate => {
            if (!string.IsNullOrEmpty(candidate.candidate))
            {
                _localIceCandidate.Add(candidate);
                Debug.Log("アイス:add my Ice" + candidate.candidate);
            }
            else
            {
                Debug.Log("end ice candidate");
            }
        });

        Debug.Log("crete peer");
    }
        private void Sctp_OnDataChannel(RTCDataChannelEvent evt)
        {
            Debug.WriteLine("Sctp OnDataChannel");
            _dataChannel            = evt.DataChannel;
            _dataChannel.OnMessage += DataChannel_OnMessage;
            _dataChannel.OnError   += DataChannel_OnError;

            // Data channel is now open and ready for use.  This will fire on the receiver side of the call; send
            // a message back to the initiator.
            //_dataChannel.Send("Hello data channel!");

            Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
            {
                IsSendEnabled = true;
            });
        }
 /// <summary>
 ///
 /// </summary>
 /// <param name="track"></param>
 public override void SetChannel(string connectionId, RTCDataChannel channel)
 {
     if (channel == null)
     {
         Dispose();
     }
     else
     {
         sender            = new Sender();
         senderInput       = new InputRemoting(sender);
         suscriberDisposer = senderInput.Subscribe(new Observer(channel));
         channel.OnOpen   += OnOpen;
         channel.OnClose  += OnClose;
     }
     base.SetChannel(connectionId, channel);
 }
示例#27
0
    private void CloseEverything()
    {
        if (localConnection.ConnectionState != RTCPeerConnectionState.Closed)
        {
            localConnection.Close();
        }
        if (dataChannel.ReadyState != RTCDataChannelState.Closed)
        {
            dataChannel.Close();
        }

        localConnection = null;
        dataChannel     = null;

        socket = null;
    }
        public IEnumerator UnitySetUp()
        {
            MockSignaling.Reset(true);
            _test = new MonoBehaviourTest <MyMonoBehaviourTest>();

            var dependencies1 = CreateDependencies();
            var dependencies2 = CreateDependencies();

            _target1 = new RenderStreamingInternal(ref dependencies1);
            _target2 = new RenderStreamingInternal(ref dependencies2);

            bool isStarted1 = false;
            bool isStarted2 = false;

            _target1.onStart += () => { isStarted1 = true; };
            _target2.onStart += () => { isStarted2 = true; };
            yield return(new WaitUntil(() => isStarted1 && isStarted2));

            bool isCreatedConnection1 = false;
            bool isCreatedConnection2 = false;

            _target1.onCreatedConnection += _ => { isCreatedConnection1 = true; };
            _target2.onFoundConnection   += _ => { isCreatedConnection2 = true; };

            // _target1 is Receiver in private mode
            _target1.CreateConnection(connectionId);
            yield return(new WaitUntil(() => isCreatedConnection1));

            // _target2 is sender in private mode
            _target2.CreateConnection(connectionId);
            yield return(new WaitUntil(() => isCreatedConnection2));

            bool isAddChannel1 = false;
            bool isGotAnswer2  = false;

            _target1.onAddChannel += (_, channel) => { isAddChannel1 = true; _channel1 = channel; };
            _target1.onGotOffer   += (_, sdp) => { _target1.SendAnswer(connectionId); };
            _target2.onGotAnswer  += (_, sdp) => { isGotAnswer2 = true; };

            // send offer automatically after creating channel
            _channel2 = _target2.CreateChannel(connectionId, "_test");

            // send offer manually
            _target2.SendOffer(connectionId);

            yield return(new WaitUntil(() => isAddChannel1 && isGotAnswer2));
        }
        void OnDataChannel(RTCPeerConnection pc, RTCDataChannel channel)
        {
            Dictionary <int, RTCDataChannel> channels;

            if (!mapChannels.TryGetValue(pc, out channels))
            {
                channels = new Dictionary <int, RTCDataChannel>();
                mapChannels.Add(pc, channels);
            }
            channels.Add(channel.Id, channel);

            if (channel.Label == "data")
            {
                channel.OnMessage = new DelegateOnMessage(bytes => { RemoteInput.ProcessInput(bytes); });
                channel.OnClose   = new DelegateOnClose(() => { RemoteInput.Reset(); });
            }
        }
        public IEnumerator PeerConnection_SetRemoteDescription()
        {
            RTCConfiguration config = default;

            config.iceServers = new[] { new RTCIceServer {
                                            urls = new[] { "stun:stun.l.google.com:19302" }
                                        } };
            var            peer1    = new RTCPeerConnection(ref config);
            var            peer2    = new RTCPeerConnection(ref config);
            RTCDataChannel channel1 = null;

            var conf = new RTCDataChannelInit(true);

            channel1 = peer1.CreateDataChannel("data", ref conf);

            RTCOfferOptions  options1 = default;
            RTCAnswerOptions options2 = default;
            var op1 = peer1.CreateOffer(ref options1);

            yield return(op1);

            var op2 = peer1.SetLocalDescription(ref op1.desc);

            yield return(op2);

            var op3 = peer2.SetRemoteDescription(ref op1.desc);

            yield return(op3);

            var op4 = peer2.CreateAnswer(ref options2);

            yield return(op4);

            var op5 = peer2.SetLocalDescription(ref op4.desc);

            yield return(op5);

            var op6 = peer1.SetRemoteDescription(ref op4.desc);

            yield return(op6);

            channel1.Dispose();
            peer1.Dispose();
            peer2.Dispose();
        }