private void OnWSMessage(object sender, MessageEventArgs args) { var message = JsonUtility.FromJson <SignalingMessage>(args.Data); var data = message.data; switch (message.type) { case "message": Debug.Log($"Receive a message from Node: {data.message}"); break; case "offer": Debug.Log("Receive an offer from Node"); var offerDesc = new RTCSessionDescription { type = RTCSdpType.Offer, sdp = data.sdp }; onOfferCallBack = state => { StartCoroutine(OnOffer(offerDesc)); }; mainThreadSyncCtx.Post(onOfferCallBack, null); break; case "candidate": var candidateInfo = new RTCIceCandidateInit { candidate = data.candidate, sdpMid = data.sdpMid, sdpMLineIndex = data.sdpMLineIndex }; pc.AddIceCandidate(new RTCIceCandidate(candidateInfo)); break; default: Debug.Log($"Receive unknown message type: {message.type}"); break; } }
public static Webrtc.IceCandidate ToNative(this RTCIceCandidateInit iceCandidateInit) => new Webrtc.IceCandidate(iceCandidateInit.SdpMid, (int)iceCandidateInit.SdpMLineIndex, iceCandidateInit.Candidate) { //AdapterType = Webrtc.PeerConnection.AdapterType.AdapterTypeAny, ////ServerUrl = ??? };
private void AddActionsToSignalingWebSocket() { signaling.OnMessage += (sender, message) => { logger.LogDebug($"Received message: {message.Data}"); if (message.Data == "{\"data\":{\"getRemoteMedia\":true}}") { Negotiate(); return; } if (message.Data == "{\"data\":{\"candidate\":null}}") { return; } string correct_message = ConvertString(message.Data); logger.LogDebug($"After nesting:{correct_message}"); if (RTCIceCandidateInit.TryParse(correct_message, out var IceCandidate)) { logger.LogDebug($"Got remote candidate: {correct_message}"); pc.addIceCandidate(IceCandidate); } else if (RTCSessionDescriptionInit.TryParse(correct_message, out var SDP)) { logger.LogDebug($"Setting SDP: {correct_message}"); var result = pc.setRemoteDescription(SDP); if (result != SetDescriptionResultEnum.OK) { logger.LogWarning($"Failed to set remote description, {result}."); pc.Close("failed to set remote description"); } } }; }
public void ToJsonUnitTest() { logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); var candidate = RTCIceCandidate.Parse("1390596646 1 udp 1880747346 192.168.11.50 61680 typ host generation 0"); Assert.NotNull(candidate); Assert.Equal(RTCIceCandidateType.host, candidate.type); Assert.Equal(RTCIceProtocol.udp, candidate.protocol); logger.LogDebug(candidate.toJSON()); bool parseResult = RTCIceCandidateInit.TryParse(candidate.toJSON(), out var init); Assert.True(parseResult); Assert.Equal(0, init.sdpMLineIndex); Assert.Equal("0", init.sdpMid); var initCandidate = RTCIceCandidate.Parse(init.candidate); Assert.Equal(RTCIceCandidateType.host, initCandidate.type); Assert.Equal(RTCIceProtocol.udp, initCandidate.protocol); }
public IActionResult AddIceCandidate(string id, [FromBody] RTCIceCandidateInit iceCandidate) { _logger.LogDebug($"SetIceCandidate {id} {iceCandidate?.candidate}."); if (string.IsNullOrWhiteSpace(id)) { return(BadRequest("The id cannot be empty in AddIceCandidate.")); } else if (string.IsNullOrWhiteSpace(iceCandidate?.candidate)) { return(BadRequest("The candidate field cannot be empty in AddIceCandidate.")); } // TODO: Handle .local addresses. if (iceCandidate.candidate.Contains(".local")) { _logger.LogWarning("ICE candidates with .local addresses are currently not supported, coming soon."); } else { _webRTCServer.AddIceCandidate(id, iceCandidate); } return(Ok()); }
public void NonEquivalentCandidateFoundationUnitTest() { logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); RTCIceCandidateInit initA = new RTCIceCandidateInit { usernameFragment = "abcd" }; var candidateA = new RTCIceCandidate(initA); candidateA.SetAddressProperties(RTCIceProtocol.udp, IPAddress.Loopback, 1024, RTCIceCandidateType.host, null, 0); RTCIceCandidateInit initB = new RTCIceCandidateInit { usernameFragment = "efgh" }; var candidateB = new RTCIceCandidate(initB); candidateB.SetAddressProperties(RTCIceProtocol.udp, IPAddress.IPv6Loopback, 1024, RTCIceCandidateType.host, null, 0); Assert.NotNull(candidateA); Assert.NotNull(candidateB); Assert.NotEqual(candidateA.foundation, candidateB.foundation); logger.LogDebug(candidateA.ToString()); logger.LogDebug(candidateB.ToString()); }
public void ConstructWithOption() { var option = new RTCIceCandidateInit { sdpMid = "0", sdpMLineIndex = 0, candidate = "candidate:102362043 1 udp 2122262783 240b:10:2fe0:4900:3cbd:7306:63c4:a8e1 50241 typ host generation 0 ufrag DEIG network-id 5" }; var candidate = new RTCIceCandidate(option); Assert.IsNotEmpty(candidate.Candidate); Assert.AreEqual(candidate.Candidate, option.candidate); Assert.AreEqual(candidate.SdpMLineIndex, option.sdpMLineIndex); Assert.AreEqual(candidate.SdpMid, option.sdpMid); Assert.AreEqual(RTCIceComponent.Rtp, candidate.Component); Assert.IsNotEmpty(candidate.Foundation); Assert.NotNull(candidate.Port); Assert.NotNull(candidate.Priority); Assert.IsNotEmpty(candidate.Address); Assert.NotNull(candidate.Protocol); Assert.IsNotEmpty(candidate.RelatedAddress); Assert.NotNull(candidate.RelatedPort); Assert.IsNotEmpty(candidate.SdpMid); Assert.NotNull(candidate.SdpMLineIndex); Assert.NotNull(candidate.Type); Assert.Null(candidate.TcpType); Assert.IsNotEmpty(candidate.UserNameFragment); }
public Task AddIceCandidate(RTCIceCandidateInit candidate) { var x = candidate.ToNative(); ((Webrtc.RTCPeerConnection)NativeObject).AddIceCandidate(x /*candidate.ToNative()*/); return(Task.CompletedTask); }
public Task AddIceCandidate(RTCIceCandidateInit candidate) { var x = candidate.ToNative(); System.Diagnostics.Debug.WriteLine($"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ SET ICE: {x.AdapterType} {x.Sdp} {x.SdpMid} {x.SdpMLineIndex} {x.ServerUrl}"); ((Webrtc.PeerConnection)NativeObject).AddIceCandidate(x); return(Task.CompletedTask); }
public async Task <ActionResult> Ice(string id, [FromBody] RTCIceCandidateInit candidate) { _logger.LogDebug($"Echo controller posting ice candidate to {_echoTestRestUrl}/ice/{id}."); _logger.LogDebug($"Candidate={candidate.candidate}"); HttpClient client = new HttpClient(); var postResp = await client.PostAsync($"{_echoTestRestUrl}/ice/{id}", new StringContent(candidate.toJSON(), Encoding.UTF8, REST_CONTENT_TYPE)); _logger.LogDebug($"Echo controller post ice response {postResp.StatusCode}:{postResp.ReasonPhrase}."); return(postResp.IsSuccessStatusCode ? Ok() : BadRequest(postResp.ReasonPhrase)); }
public async Task AddIceCandidate(string id, RTCIceCandidateInit iceCandidate) { if (!_peerConnections.TryGetValue(id, out var pc)) { throw new ApplicationException("No peer connection is available for the specified id."); } else { _logger.LogDebug("ICE Candidate: " + iceCandidate.candidate); await pc.addIceCandidate(iceCandidate); } }
public RTCIceCandidate toCand() { var candidateInfo = new RTCIceCandidateInit { candidate = candidate, sdpMid = sdpMid, sdpMLineIndex = sdpMLineIndex }; var cand = new RTCIceCandidate(candidateInfo); return(cand); }
void OnIceCandidate(ISignaling signaling, CandidateData e) { if (!m_mapConnectionIdAndPeer.TryGetValue(e.connectionId, out var pc)) { return; } RTCIceCandidateInit option = new RTCIceCandidateInit { candidate = e.candidate, sdpMLineIndex = e.sdpMLineIndex, sdpMid = e.sdpMid }; pc.AddIceCandidate(new RTCIceCandidate(option)); }
public IActionResult AddIceCandidate(string id, [FromBody] RTCIceCandidateInit iceCandidate) { _logger.LogDebug($"SetIceCandidate {id} {iceCandidate?.candidate}."); if (string.IsNullOrWhiteSpace(id)) { return(BadRequest("The id cannot be empty in AddIceCandidate.")); } else if (string.IsNullOrWhiteSpace(iceCandidate?.candidate)) { return(BadRequest("The candidate field cannot be empty in AddIceCandidate.")); } _webRTCServer.AddIceCandidate(id, iceCandidate); return(Ok()); }
void OnIceCandidate(ISignaling signaling, CandidateData e) { if (!_mapConnectionIdAndPeer.TryGetValue(e.connectionId, out var pc)) { return; } RTCIceCandidateInit option = new RTCIceCandidateInit { candidate = e.candidate, sdpMLineIndex = e.sdpMLineIndex, sdpMid = e.sdpMid }; if (!pc.peer.AddIceCandidate(new RTCIceCandidate(option)) && !pc.ignoreOffer) { Debug.LogWarning($"{pc} this candidate can't accept current signaling state {pc.peer.SignalingState}."); } }
public static bool TryParse(string json, out RTCIceCandidateInit init) { //init = JsonSerializer.Deserialize< RTCIceCandidateInit>(json); init = null; if (string.IsNullOrWhiteSpace(json)) { return(false); } else { init = TinyJson.JSONParser.FromJson <RTCIceCandidateInit>(json); // To qualify as parsed all required fields must be set. return(init != null && init.candidate != null && init.sdpMid != null); } }
private void DoProcessMessage(Message message) { switch (message.type) { case "Offer": // var offer = JsonUtility.FromJson<RTCSessionDescriptionInit>(message.args); if (RTCSessionDescriptionInit.TryParse(message.args, out RTCSessionDescriptionInit offer)) { Debug.Log($"Got remote SDP, type {offer.type}"); var result = rtcPeerConnection.setRemoteDescription(offer); if (result != SetDescriptionResultEnum.OK) { Debug.Log($"Failed to set remote description, {result}."); rtcPeerConnection.Close("Failed to set remote description"); } else { if (rtcPeerConnection.signalingState == RTCSignalingState.have_remote_offer) { var answerSdp = rtcPeerConnection.createAnswer(); rtcPeerConnection.setLocalDescription(answerSdp); Debug.Log($"Sending SDP answer"); Send("Offer", answerSdp.toJSON()); } } } break; case "IceCandidate": if (RTCIceCandidateInit.TryParse(message.args, out RTCIceCandidateInit candidate)) { Debug.Log($"Got remote Ice Candidate, uri {candidate.candidate}"); rtcPeerConnection.addIceCandidate(candidate); } break; } }
public async Task <IActionResult> PutIce(string from, string to, [FromBody] RTCIceCandidateInit ice) { if (string.IsNullOrEmpty(to) || string.IsNullOrEmpty(from) || ice == null || ice.candidate == null) { _logger.LogWarning($"WebRTC signal controller PUT ice candidate request had invalid parameters."); return(BadRequest()); } WebRTCSignal iceSignal = new WebRTCSignal { ID = Guid.NewGuid().ToString(), To = to, From = from, SignalType = WebRTCSignalTypesEnum.ice.ToString(), Signal = ice.toJSON(), Inserted = DateTime.UtcNow.ToString("o") }; _context.WebRTCSignals.Add(iceSignal); await _context.SaveChangesAsync(); return(Ok()); }
public async Task SignalIceAsync(string name, Guid peerId, RTCIceCandidateInit e, Guid roomId) { await _hub.SendAsync("SignalIceCandidate", e.candidate, peerId, name, roomId); }
public static Webrtc.RTCIceCandidate ToNative(this RTCIceCandidateInit iceCandidateInit) => new Webrtc.RTCIceCandidate(iceCandidateInit.Candidate, (int)iceCandidateInit.SdpMLineIndex, iceCandidateInit.SdpMid) { ///ServerUrl = ??? };
private static RTCPeerConnection Createpc(WebSocketContext context, RTCIceServer stunServer, bool relayOnly) { if (_peerConnection != null) { _peerConnection.Close("normal"); } List <RTCCertificate> presetCertificates = null; if (File.Exists(LOCALHOST_CERTIFICATE_PATH)) { var localhostCert = new X509Certificate2(LOCALHOST_CERTIFICATE_PATH, (string)null, X509KeyStorageFlags.Exportable); presetCertificates = new List <RTCCertificate> { new RTCCertificate { Certificate = localhostCert } }; } RTCConfiguration pcConfiguration = new RTCConfiguration { certificates = presetCertificates, //X_RemoteSignallingAddress = (context != null) ? context.UserEndPoint.Address : null, iceServers = stunServer != null ? new List <RTCIceServer> { stunServer } : null, //iceTransportPolicy = RTCIceTransportPolicy.all, iceTransportPolicy = relayOnly ? RTCIceTransportPolicy.relay : RTCIceTransportPolicy.all, //X_BindAddress = IPAddress.Any, // NOTE: Not reqd. Using this to filter out IPv6 addresses so can test with Pion. }; _peerConnection = new RTCPeerConnection(pcConfiguration); //_peerConnection.GetRtpChannel().MdnsResolve = (hostname) => Task.FromResult(NetServices.InternetDefaultAddress); _peerConnection.GetRtpChannel().MdnsResolve = MdnsResolve; //_peerConnection.GetRtpChannel().OnStunMessageReceived += (msg, ep, isrelay) => logger.LogDebug($"STUN message received from {ep}, message type {msg.Header.MessageType}."); var dc = _peerConnection.createDataChannel(DATA_CHANNEL_LABEL, null); dc.onmessage += (msg) => logger.LogDebug($"data channel receive ({dc.label}-{dc.id}): {msg}"); // Add inactive audio and video tracks. //MediaStreamTrack audioTrack = new MediaStreamTrack(SDPMediaTypesEnum.audio, false, new List<SDPMediaFormat> { new SDPMediaFormat(SDPMediaFormatsEnum.PCMU) }, MediaStreamStatusEnum.RecvOnly); //pc.addTrack(audioTrack); //MediaStreamTrack videoTrack = new MediaStreamTrack(SDPMediaTypesEnum.video, false, new List<SDPMediaFormat> { new SDPMediaFormat(SDPMediaFormatsEnum.VP8) }, MediaStreamStatusEnum.Inactive); //pc.addTrack(videoTrack); _peerConnection.onicecandidateerror += (candidate, error) => logger.LogWarning($"Error adding remote ICE candidate. {error} {candidate}"); _peerConnection.onconnectionstatechange += (state) => { logger.LogDebug($"Peer connection state changed to {state}."); if (state == RTCPeerConnectionState.disconnected || state == RTCPeerConnectionState.failed) { _peerConnection.Close("remote disconnection"); } }; _peerConnection.OnReceiveReport += (ep, type, rtcp) => logger.LogDebug($"RTCP {type} report received."); _peerConnection.OnRtcpBye += (reason) => logger.LogDebug($"RTCP BYE receive, reason: {(string.IsNullOrWhiteSpace(reason) ? "<none>" : reason)}."); _peerConnection.onicecandidate += (candidate) => { if (_peerConnection.signalingState == RTCSignalingState.have_local_offer || _peerConnection.signalingState == RTCSignalingState.have_remote_offer) { if (context != null && (_iceTypes.Count == 0 || _iceTypes.Any(x => x == candidate.type))) { var candidateInit = new RTCIceCandidateInit { sdpMid = candidate.sdpMid, sdpMLineIndex = candidate.sdpMLineIndex, usernameFragment = candidate.usernameFragment, candidate = "candidate:" + candidate.ToString() }; context.WebSocket.Send(JsonConvert.SerializeObject(candidateInit)); } } }; // Peer ICE connection state changes are for ICE events such as the STUN checks completing. _peerConnection.oniceconnectionstatechange += (state) => logger.LogDebug($"ICE connection state change to {state}."); _peerConnection.ondatachannel += (dc) => { logger.LogDebug($"Data channel opened by remote peer, label {dc.label}, stream ID {dc.id}."); dc.onmessage += (msg) => { logger.LogDebug($"data channel ({dc.label}:{dc.id}): {msg}."); }; }; return(_peerConnection); }
private async void Start() { websocket = new WebSocket("ws://localhost:8000/socket"); Debug.Log("GetSelectedSdpSemantics"); var configuration = GetSelectedSdpSemantics(); 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(); dataChannel = pc2.CreateDataChannel("data", conf); dataChannel.OnOpen = onDataChannelOpen; //dataChannel.Send("3TEST"); pc2OnIceConnectionChange = state => { OnIceConnectionChange(pc2, state); }; pc2OnIceCandidate = candidate => { OnIceCandidate(pc2, candidate); }; textReceive.text += "0TEST"; onDataChannel = channel => { Debug.Log("Data Channel works!"); textReceive.text += "2TEST"; dataChannel = channel; dataChannel.OnMessage = onDataChannelMessage; }; onDataChannelMessage = bytes => { textReceive.text = System.Text.Encoding.UTF8.GetString(bytes); // var epoint = new Uint8Array(event.data, 92); // if (epoint.byteLength > 93) { //var dpoint = MessagePack.decode(epoint); // console.log("Decoded pointarray:", dpoint); //var pointbuff = new Int8Array(dpoint); // colate(pointbuff); }; onDataChannelOpen = () => { textReceive.text += "1TEST"; }; onDataChannelClose = () => { sendButton.interactable = false; }; websocket.OnOpen += () => { Debug.Log("Connection open!"); }; websocket.OnError += (e) => { Debug.Log("Error! " + e); }; websocket.OnClose += (e) => { Debug.Log("Connection closed!"); }; websocket.OnMessage += (bytes) => { Debug.Log("Message: " + System.Text.Encoding.UTF8.GetString(bytes)); Message message = JsonConvert.DeserializeObject <Message>(System.Text.Encoding.UTF8.GetString(bytes)); Debug.Log("New message: " + message.type); switch (message.type) { case "offer": var remoteDesc = new RTCSessionDescription(); remoteDesc.type = 0; remoteDesc.sdp = message.sdp; pc2.SetRemoteDescription(ref remoteDesc); var answer = pc2.CreateAnswer(ref AnswerOptions); Debug.Log("Answer: " + answer.Desc.sdp); Debug.Log("Answer Desc: " + answer.Desc.type); var localDesc = new RTCSessionDescription(); localDesc.type = answer.Desc.type; localDesc.sdp = message.sdp; pc2.SetLocalDescription(ref localDesc); Message newMessage = new Message(); newMessage.type = "answer"; newMessage.sdp = answer.Desc.sdp; string output = JsonConvert.SerializeObject(newMessage); websocket.SendText(output); break; case "candidate": RTCIceCandidateInit candidateMessage = new RTCIceCandidateInit(); candidateMessage.candidate = message.sdp; candidateMessage.sdpMLineIndex = 0; candidateMessage.sdpMid = ""; RTCIceCandidate candidate = new RTCIceCandidate(candidateMessage); pc2.AddIceCandidate(candidate); break; default: Debug.Log("P2: We got something from the signaling server but we don't know what it is!"); Debug.Log("Take a look for yourself: " + message.type); Debug.Log("Take a look for yourself: " + message.sdp); break; } }; await websocket.Connect(); }
public Task AddIceCandidate(RTCIceCandidateInit candidate) => JsRuntime.CallJsMethodVoidAsync(NativeObject, "addIceCandidate", candidate /*.NativeObject*/).AsTask();
public void MessageFromPeerTaskRun(int peerId, string content) { PeerId = peerId; Task.Run(async() => { Debug.Assert(_peerId == PeerId || _peerId == -1); Debug.Assert(content.Length > 0); if (_peerId != PeerId && _peerId != -1) { Debug.WriteLine("Received a message from unknown peer " + "while already in a conversation with a different peer."); return; } if (!JsonObject.TryParse(content, out JsonObject jMessage)) { Debug.WriteLine($"Received unknown message: {content}"); return; } string type = jMessage.ContainsKey(NegotiationAtributes.Type) ? jMessage.GetNamedString(NegotiationAtributes.Type) : null; if (PeerConnection == null) { if (!string.IsNullOrEmpty(type)) { // Create the peer connection only when call is // about to get initiated. Otherwise ignore the // message from peers which could be result // of old (but not yet fully closed) connections. if (type == "offer" || type == "answer" || type == "json") { Debug.Assert(_peerId == -1); _peerId = PeerId; if (!CreatePeerConnection()) { Debug.WriteLine("Failed to initialize our PeerConnection instance"); OnSignedOut.Invoke(this, null); return; } else if (_peerId != PeerId) { Debug.WriteLine("Received a message from unknown peer while already " + "in a conversation with a different peer."); return; } } } else { Debug.WriteLine("[Warn] Received an untyped message after closing peer connection."); return; } } if (PeerConnection != null && !string.IsNullOrEmpty(type)) { if (type == "offer-loopback") { // Loopback not supported Debug.Assert(false); } string sdp = null; sdp = jMessage.ContainsKey(NegotiationAtributes.Sdp) ? jMessage.GetNamedString(NegotiationAtributes.Sdp) : null; if (string.IsNullOrEmpty(sdp)) { Debug.WriteLine("[Error] Can't parse received session description message."); return; } Debug.WriteLine($"Received session description:\n{content}"); RTCSdpType messageType = RTCSdpType.Offer; switch (type) { case "offer": messageType = RTCSdpType.Offer; break; case "answer": messageType = RTCSdpType.Answer; break; case "pranswer": messageType = RTCSdpType.Pranswer; break; default: Debug.Assert(false, type); break; } var sdpInit = new RTCSessionDescriptionInit(); sdpInit.Sdp = sdp; sdpInit.Type = messageType; var description = new RTCSessionDescription(sdpInit); await PeerConnection.SetRemoteDescription(description); if (messageType == RTCSdpType.Offer) { var answerOptions = new RTCAnswerOptions(); IRTCSessionDescription answer = await PeerConnection.CreateAnswer(answerOptions); await PeerConnection.SetLocalDescription(answer); string jsonString = SdpToJsonString(answer); // Send answer OnSendMessageToRemotePeer.Invoke(this, jsonString); } } else { RTCIceCandidate candidate = null; string sdpMid = jMessage.ContainsKey(NegotiationAtributes.SdpMid) ? jMessage.GetNamedString(NegotiationAtributes.SdpMid) : null; double sdpMLineIndex = jMessage.ContainsKey(NegotiationAtributes.SdpMLineIndex) ? jMessage.GetNamedNumber(NegotiationAtributes.SdpMLineIndex) : -1; string sdpCandidate = jMessage.ContainsKey(NegotiationAtributes.Candidate) ? jMessage.GetNamedString(NegotiationAtributes.Candidate) : null; if (string.IsNullOrEmpty(sdpMid) || sdpMLineIndex == -1 || string.IsNullOrEmpty(sdpCandidate)) { Debug.WriteLine($"[Error] Can't parse received message.\n{content}"); return; } var candidateInit = new RTCIceCandidateInit(); candidateInit.Candidate = sdpCandidate; candidateInit.SdpMid = sdpMid; candidateInit.SdpMLineIndex = (ushort)sdpMLineIndex; candidate = new RTCIceCandidate(candidateInit); await PeerConnection.AddIceCandidate(candidate); Debug.WriteLine($"Receiving ice candidate:\n{content}"); } }).Wait(); }
async Task CreateOrDeletePeerConnectionAsync(Guid peerId, string peerName, bool isInitiator, bool isDelete = false) { try { PeerContext peerContext = null; IRTCPeerConnection peerConnection = null; IMediaStream mediaStream = null; IRTCDataChannel dataChannel = null; if (isDelete) { peerContext = _connectionContext.PeerContexts.Single(context => context.Id.Equals(peerId)); peerConnection = peerContext.PeerConnection; peerConnection.OnConnectionStateChanged -= OnConnectionStateChanged; peerConnection.OnDataChannel -= OnDataChannel; peerConnection.OnIceCandidate -= OnIceCandidate; peerConnection.OnIceConnectionStateChange -= OnIceConnectionStateChange; peerConnection.OnIceGatheringStateChange -= OnIceGatheringStateChange; peerConnection.OnNegotiationNeeded -= OnNegotiationNeeded; peerConnection.OnSignallingStateChange -= OnSignallingStateChange; peerConnection.OnTrack -= OnTrack; // Remove local tracks and close. var senders = peerConnection.GetSenders(); foreach (var sender in senders) { peerConnection.RemoveTrack(sender); } peerConnection.Close(); _connectionContext.PeerContexts.Remove(peerContext); } else { mediaStream = _webRtc.Window(_jsRuntime).MediaStream(); RTCIceServer[] iceServers = _connectionContext.IceServers; if (iceServers is null) { var result = await _signalingServerApi.GetIceServersAsync(); if (!result.IsOk) { throw new Exception($"{result.ErrorMessage}"); } iceServers = result.Value; _connectionContext.IceServers = iceServers; } var configuration = new RTCConfiguration { IceServers = iceServers, //PeerIdentity = peerName }; _logger.LogInformation($"################ LIST OF ICE SERVERS ################"); foreach (var iceServer in configuration.IceServers) { foreach (var url in iceServer.Urls) { _logger.LogInformation($"\t - {url}"); } } _logger.LogInformation($"#####################################################"); peerConnection = _webRtc.Window(_jsRuntime).RTCPeerConnection(configuration); peerContext = new PeerContext { Id = peerId, Name = peerName, PeerConnection = peerConnection, IsInitiator = isInitiator, }; _connectionContext.PeerContexts.Add(peerContext); peerConnection.OnConnectionStateChanged += OnConnectionStateChanged; peerConnection.OnDataChannel += OnDataChannel; peerConnection.OnIceCandidate += OnIceCandidate; peerConnection.OnIceConnectionStateChange += OnIceConnectionStateChange; peerConnection.OnIceGatheringStateChange += OnIceGatheringStateChange; peerConnection.OnNegotiationNeeded += OnNegotiationNeeded; peerConnection.OnSignallingStateChange += OnSignallingStateChange; peerConnection.OnTrack += OnTrack; if (_connectionContext.UserContext.DataChannelName is not null && isInitiator) { dataChannel = peerConnection.CreateDataChannel( _connectionContext.UserContext.DataChannelName, new RTCDataChannelInit { Negotiated = false, }); } if (_connectionContext.UserContext.LocalStream is not null) { var videoTrack = _connectionContext.UserContext.LocalStream.GetVideoTracks().FirstOrDefault(); var audioTrack = _connectionContext.UserContext.LocalStream.GetAudioTracks().FirstOrDefault(); if (videoTrack is not null) { peerConnection.AddTrack(videoTrack, _connectionContext.UserContext.LocalStream); } if (audioTrack is not null) { peerConnection.AddTrack(audioTrack, _connectionContext.UserContext.LocalStream); } } } void OnConnectionStateChanged(object s, EventArgs e) { _logger.LogInformation( $"######## OnConnectionStateChanged - room:{_connectionContext.UserContext.Room} " + $"user:{_connectionContext.UserContext.Name} " + $"peerUser:{peerName} " + $"connectionState:{peerConnection.ConnectionState}"); if (peerConnection.ConnectionState == RTCPeerConnectionState.Connected) { _connectionContext.Observer.OnNext(new PeerResponse { Type = PeerResponseType.PeerJoined, Id = peerId, Name = peerName, MediaStream = mediaStream, DataChannel = isInitiator ? dataChannel : null }); } //// WILL BE HANDLED BY PEER LEFT //else if (peerConnection.ConnectionState == RTCPeerConnectionState.Disconnected) //ConnectionResponseSubject.OnCompleted(); } void OnDataChannel(object s, IRTCDataChannelEvent e) { _logger.LogInformation( $"######## OnDataChannel - room:{_connectionContext.UserContext.Room} " + $"user:{_connectionContext.UserContext.Name} " + $"peerUser:{peerName} " + $"state:{e.Channel.ReadyState}"); dataChannel?.Close(); dataChannel?.Dispose(); dataChannel = e.Channel; _connectionContext.Observer.OnNext(new PeerResponse { Type = PeerResponseType.PeerJoined, Name = peerName, MediaStream = null, DataChannel = dataChannel }); } async void OnIceCandidate(object s, IRTCPeerConnectionIceEvent e) { //_logger.LogInformation( // $"######## OnIceCandidate - room:{roomName} " + // $"user:{connectionContext.ConnectionRequestParameters.ConnectionParameters.UserName} " + // $"peerUser:{peerName}"); // 'null' is valid and indicates end of ICE gathering process. if (e.Candidate is not null) { var iceCandidate = new RTCIceCandidateInit { Candidate = e.Candidate.Candidate, SdpMid = e.Candidate.SdpMid, SdpMLineIndex = e.Candidate.SdpMLineIndex, //UsernameFragment = ??? }; var ice = JsonSerializer.Serialize(iceCandidate, JsonHelper.WebRtcJsonSerializerOptions); _logger.LogInformation( $"--------> Sending ICE Candidate - room:{_connectionContext.UserContext.Room} " + $"user:{_connectionContext.UserContext.Name} " + $"peerUser:{peerName} " + $"ice:{ice}"); var result = await _signalingServerApi.IceAsync(peerId, ice); if (!result.IsOk) { throw new Exception($"{result.ErrorMessage}"); } } } void OnIceConnectionStateChange(object s, EventArgs e) { _logger.LogInformation( $"######## OnIceConnectionStateChange - room:{_connectionContext.UserContext.Room} " + $"user:{_connectionContext.UserContext.Name} " + $"peerUser:{peerName} " + $"iceConnectionState:{peerConnection.IceConnectionState}"); } void OnIceGatheringStateChange(object s, EventArgs e) { _logger.LogInformation( $"######## OnIceGatheringStateChange - room:{_connectionContext.UserContext.Room} " + $"user:{_connectionContext.UserContext.Name} " + $"peerUser:{peerName} " + $"iceGatheringState: {peerConnection.IceGatheringState}"); } void OnNegotiationNeeded(object s, EventArgs e) { _logger.LogInformation( $"######## OnNegotiationNeeded - room:{_connectionContext.UserContext.Room} " + $"user:{_connectionContext.UserContext.Name} " + $"peerUser:{peerName}"); // TODO: WHAT IF Not initiator adds track (which trigggers this event)??? } void OnSignallingStateChange(object s, EventArgs e) { _logger.LogInformation( $"######## OnSignallingStateChange - room:{_connectionContext.UserContext.Room} " + $"user:{_connectionContext.UserContext.Name} " + $"peerUser:{peerName}, " + $"signallingState:{ peerConnection.SignalingState }"); //RoomEventSubject.OnNext(new RoomEvent //{ // Code = RoomEventCode.PeerJoined, // RoomName = roomName, // PeerUserName = peerName, // MediaStream = mediaStream //}); } void OnTrack(object s, IRTCTrackEvent e) { _logger.LogInformation( $"######## OnTrack - room:{_connectionContext.UserContext.Room} " + $"user:{_connectionContext.UserContext.Name} " + $"peerUser:{peerName} " + $"trackType:{e.Track.Kind}"); mediaStream.AddTrack(e.Track); } } catch (Exception ex) { _connectionContext?.Observer.OnNext(new PeerResponse { Type = PeerResponseType.PeerError, Id = peerId, Name = peerName, ErrorMessage = ex.Message }); } }
private static Task <RTCPeerConnection> Createpc(WebSocketContext context, RTCIceServer stunServer, bool relayOnly) { if (_peerConnection != null) { _peerConnection.Close("normal"); } List <RTCCertificate> presetCertificates = null; if (File.Exists(LOCALHOST_CERTIFICATE_PATH)) { var localhostCert = new X509Certificate2(LOCALHOST_CERTIFICATE_PATH, (string)null, X509KeyStorageFlags.Exportable); presetCertificates = new List <RTCCertificate> { new RTCCertificate { Certificate = localhostCert } }; } RTCConfiguration pcConfiguration = new RTCConfiguration { certificates = presetCertificates, //X_RemoteSignallingAddress = (context != null) ? context.UserEndPoint.Address : null, iceServers = stunServer != null ? new List <RTCIceServer> { stunServer } : null, //iceTransportPolicy = RTCIceTransportPolicy.all, iceTransportPolicy = relayOnly ? RTCIceTransportPolicy.relay : RTCIceTransportPolicy.all, //X_BindAddress = IPAddress.Any, // NOTE: Not reqd. Using this to filter out IPv6 addresses so can test with Pion. }; _peerConnection = new RTCPeerConnection(pcConfiguration); //_peerConnection.GetRtpChannel().MdnsResolve = (hostname) => Task.FromResult(NetServices.InternetDefaultAddress); _peerConnection.GetRtpChannel().MdnsResolve = MdnsResolve; //_peerConnection.GetRtpChannel().OnStunMessageReceived += (msg, ep, isrelay) => logger.LogDebug($"STUN message received from {ep}, message type {msg.Header.MessageType}."); var dc = _peerConnection.createDataChannel(DATA_CHANNEL_LABEL, null); dc.onmessage += (msg) => logger.LogDebug($"data channel receive ({dc.label}-{dc.id}): {msg}"); // Add a send-only audio track (this doesn't require any native libraries for encoding so is good for x-platform testing). AudioExtrasSource audioSource = new AudioExtrasSource(new AudioEncoder(), new AudioSourceOptions { AudioSource = AudioSourcesEnum.SineWave }); audioSource.OnAudioSourceEncodedSample += _peerConnection.SendAudio; MediaStreamTrack audioTrack = new MediaStreamTrack(audioSource.GetAudioSourceFormats(), MediaStreamStatusEnum.SendOnly); _peerConnection.addTrack(audioTrack); _peerConnection.OnAudioFormatsNegotiated += (sdpFormat) => audioSource.SetAudioSourceFormat(SDPMediaFormatInfo.GetAudioCodecForSdpFormat(sdpFormat.First().FormatCodec)); _peerConnection.onicecandidateerror += (candidate, error) => logger.LogWarning($"Error adding remote ICE candidate. {error} {candidate}"); _peerConnection.onconnectionstatechange += async(state) => { logger.LogDebug($"Peer connection state changed to {state}."); if (state == RTCPeerConnectionState.disconnected || state == RTCPeerConnectionState.failed) { _peerConnection.Close("remote disconnection"); } if (state == RTCPeerConnectionState.connected) { await audioSource.StartAudio(); } else if (state == RTCPeerConnectionState.closed) { await audioSource.CloseAudio(); } }; _peerConnection.OnReceiveReport += (ep, type, rtcp) => logger.LogDebug($"RTCP {type} report received."); _peerConnection.OnRtcpBye += (reason) => logger.LogDebug($"RTCP BYE receive, reason: {(string.IsNullOrWhiteSpace(reason) ? "<none>" : reason)}."); _peerConnection.onicecandidate += (candidate) => { if (_peerConnection.signalingState == RTCSignalingState.have_local_offer || _peerConnection.signalingState == RTCSignalingState.have_remote_offer) { if (context != null && (_iceTypes.Count == 0 || _iceTypes.Any(x => x == candidate.type))) { var candidateInit = new RTCIceCandidateInit { sdpMid = candidate.sdpMid, sdpMLineIndex = candidate.sdpMLineIndex, usernameFragment = candidate.usernameFragment, candidate = "candidate:" + candidate.ToString() }; context.WebSocket.Send(JsonConvert.SerializeObject(candidateInit)); } } }; // Peer ICE connection state changes are for ICE events such as the STUN checks completing. _peerConnection.oniceconnectionstatechange += (state) => logger.LogDebug($"ICE connection state change to {state}."); _peerConnection.ondatachannel += (dc) => { logger.LogDebug($"Data channel opened by remote peer, label {dc.label}, stream ID {dc.id}."); dc.onmessage += (msg) => { logger.LogDebug($"data channel ({dc.label}:{dc.id}): {msg}."); }; }; return(Task.FromResult(_peerConnection)); }