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); }
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)); }
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); } }
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."); }
/// <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); }
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); }
/// <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); }
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); }
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); }
private void OnAddChannel(string connectionId, RTCDataChannel channel) { var data = new SignalingEventData(EventSystem.current) { connectionId = connectionId, channel = channel }; ExecuteEventToAllTargets(data, ExecuteSignalingEvents.addChannelHandler); }
/// <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)); } }
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(); }
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)); } }
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."); } }
//ピアの作成をする 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); }
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(); }