private static async Task WebSocketMessageReceived(WebSocketContext context, RTCPeerConnection pc, string message) { try { if (pc.localDescription == null) { //logger.LogDebug("Offer SDP: " + message); logger.LogDebug("Offer SDP received."); // Add local media tracks depending on what was offered. Also add local tracks with the same media ID as // the remote tracks so that the media announcement in the SDP answer are in the same order. SDP remoteSdp = SDP.ParseSDPDescription(message); var res = pc.setRemoteDescription(new RTCSessionDescriptionInit { sdp = message, type = RTCSdpType.offer }); if (res != SetDescriptionResultEnum.OK) { // No point continuing. Something will need to change and then try again. pc.Close("failed to set remote sdp"); } else { var answer = pc.createAnswer(null); await pc.setLocalDescription(answer); context.WebSocket.Send(answer.sdp); } } else if (pc.remoteDescription == null) { logger.LogDebug("Answer SDP: " + message); var res = pc.setRemoteDescription(new RTCSessionDescriptionInit { sdp = message, type = RTCSdpType.answer }); if (res != SetDescriptionResultEnum.OK) { // No point continuing. Something will need to change and then try again. pc.Close("failed to set remote sdp"); } } else { logger.LogDebug("ICE Candidate: " + message); if (string.IsNullOrWhiteSpace(message) || message.Trim().ToLower() == SDP.END_ICE_CANDIDATES_ATTRIBUTE) { logger.LogDebug("End of candidates message received."); } else { var candInit = Newtonsoft.Json.JsonConvert.DeserializeObject <RTCIceCandidateInit>(message); pc.addIceCandidate(candInit); } } } catch (Exception excp) { logger.LogError("Exception WebSocketMessageReceived. " + excp.Message); } }
private static async void WebSocketMessageReceived(WebSocketContext context, RTCPeerConnection peerConnection, string msg) { if (peerConnection.RemoteDescription != null) { Console.WriteLine($"ICE Candidate: {msg}."); //await _peerConnections[0].addIceCandidate(new RTCIceCandidateInit { candidate = msg }); // await peerConnection.addIceCandidate(new RTCIceCandidateInit { candidate = msg }); Console.WriteLine("add ICE candidate complete."); } else { //Console.WriteLine($"websocket recv: {msg}"); //var offerSDP = SDP.ParseSDPDescription(msg); Console.WriteLine($"offer sdp: {msg}"); peerConnection.setRemoteDescription(new RTCSessionDescriptionInit { sdp = msg, type = RTCSdpType.offer }); var answerInit = peerConnection.createAnswer(null); await peerConnection.setLocalDescription(answerInit); Console.WriteLine($"answer sdp: {answerInit.sdp}"); context.WebSocket.Send(answerInit.sdp); } }
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 async void CheckPeerConnectionEstablishment() { logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); var aliceConnected = new TaskCompletionSource <bool>(TaskCreationOptions.RunContinuationsAsynchronously); var bobConnected = new TaskCompletionSource <bool>(TaskCreationOptions.RunContinuationsAsynchronously); var alice = new RTCPeerConnection(); alice.onconnectionstatechange += (state) => { if (state == RTCPeerConnectionState.connected) { logger.LogDebug("Alice connected."); aliceConnected.SetResult(true); } }; alice.addTrack(new MediaStreamTrack(SDPWellKnownMediaFormatsEnum.PCMU)); var aliceOffer = alice.createOffer(); await alice.setLocalDescription(aliceOffer); logger.LogDebug($"alice offer: {aliceOffer.sdp}"); var bob = new RTCPeerConnection(); bob.onconnectionstatechange += (state) => { if (state == RTCPeerConnectionState.connected) { logger.LogDebug("Bob connected."); bobConnected.SetResult(true); } }; bob.addTrack(new MediaStreamTrack(SDPWellKnownMediaFormatsEnum.PCMU)); 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(aliceConnected.Task, bobConnected.Task), Task.Delay(2000)); Assert.True(aliceConnected.Task.IsCompleted); Assert.True(aliceConnected.Task.Result); Assert.True(bobConnected.Task.IsCompleted); Assert.True(bobConnected.Task.Result); bob.close(); alice.close(); }
private static async Task WebSocketMessageReceived(WebSocketContext context, RTCPeerConnection pc, string message) { try { if (pc.localDescription == null) { //logger.LogDebug("Offer SDP: " + message); logger.LogDebug("Offer SDP received."); // Add local media tracks depending on what was offered. Also add local tracks with the same media ID as // the remote tracks so that the media announcement in the SDP answer are in the same order. SDP remoteSdp = SDP.ParseSDPDescription(message); foreach (var ann in remoteSdp.Media) { var capbilities = FilterCodecs(ann.Media, ann.MediaFormats); MediaStreamTrack track = new MediaStreamTrack(ann.Media, false, capbilities, MediaStreamStatusEnum.RecvOnly); pc.addTrack(track); } pc.setRemoteDescription(new RTCSessionDescriptionInit { sdp = message, type = RTCSdpType.offer }); var answer = pc.createAnswer(null); await pc.setLocalDescription(answer); Console.WriteLine(answer.sdp); context.WebSocket.Send(answer.sdp); } else if (pc.remoteDescription == null) { logger.LogDebug("Answer SDP: " + message); pc.setRemoteDescription(new RTCSessionDescriptionInit { sdp = message, type = RTCSdpType.answer }); } else { logger.LogDebug("ICE Candidate: " + message); if (string.IsNullOrWhiteSpace(message) || message.Trim().ToLower() == SDP.END_ICE_CANDIDATES_ATTRIBUTE) { logger.LogDebug("End of candidates message received."); } else { var candInit = Newtonsoft.Json.JsonConvert.DeserializeObject <RTCIceCandidateInit>(message); pc.addIceCandidate(candInit); } } } catch (Exception excp) { logger.LogError("Exception WebSocketMessageReceived. " + excp.Message); } }
private static async Task WebSocketMessageReceived(WebSocketContext context, RTCPeerConnection peerConnection, string message) { try { if (peerConnection.localDescription == null) { logger.LogDebug("Offer SDP: " + message); // Add local media tracks depending on what was offered. Also add local tracks with the same media ID as // the remote tracks so that the media announcement in the SDP answer are in the same order. SDP remoteSdp = SDP.ParseSDPDescription(message); var remoteAudioAnn = remoteSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.audio).FirstOrDefault(); var remoteVideoAnn = remoteSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.video).FirstOrDefault(); if (remoteAudioAnn != null) { MediaStreamTrack audioTrack = new MediaStreamTrack(remoteAudioAnn.MediaID, SDPMediaTypesEnum.audio, false, new List <SDPMediaFormat> { new SDPMediaFormat(SDPMediaFormatsEnum.PCMU) }, MediaStreamStatusEnum.RecvOnly); peerConnection.addTrack(audioTrack); } if (remoteVideoAnn != null) { MediaStreamTrack videoTrack = new MediaStreamTrack(remoteVideoAnn.MediaID, SDPMediaTypesEnum.video, false, new List <SDPMediaFormat> { new SDPMediaFormat(SDPMediaFormatsEnum.VP8) }, MediaStreamStatusEnum.RecvOnly); peerConnection.addTrack(videoTrack); } // After local media tracks have been added the remote description can be set. await peerConnection.setRemoteDescription(new RTCSessionDescriptionInit { sdp = message, type = RTCSdpType.offer }); var answer = await peerConnection.createAnswer(null); await peerConnection.setLocalDescription(answer); context.WebSocket.Send(answer.sdp); } else if (peerConnection.remoteDescription == null) { logger.LogDebug("Answer SDP: " + message); await peerConnection.setRemoteDescription(new RTCSessionDescriptionInit { sdp = message, type = RTCSdpType.answer }); } else { logger.LogDebug("ICE Candidate: " + message); await peerConnection.addIceCandidate(new RTCIceCandidateInit { candidate = message }); } } catch (Exception excp) { logger.LogError("Exception WebSocketMessageReceived. " + excp.Message); } }
private ResonanceActionResult <WebRTCOfferResponse> OnWebRTCOfferRequest(WebRTCOfferRequest request) { if (request.ChannelName != ChannelName) { return(null); } try { Logger.LogInformation("Offer received..."); if (!_connectionInitialized) { InitConnection(); } Logger.LogInformation("Setting remote description..."); var result = _connection.setRemoteDescription(request.Offer.ToSessionDescription()); if (result != SetDescriptionResultEnum.OK) { throw new Exception("Error setting remote description."); } if (request.Offer.InternalType == RTCSdpType.offer) { Logger.LogInformation("Creating answer..."); var answer = _connection.createAnswer(null); Logger.LogInformation("Setting local description..."); _connection.setLocalDescription(answer).GetAwaiter().GetResult(); Logger.LogInformation("Sending answer response..."); return(new ResonanceActionResult <WebRTCOfferResponse>( new WebRTCOfferResponse() { ChannelName = ChannelName, Answer = WebRTCSessionDescription.FromSessionDescription(answer) })); } else { Logger.LogError($"Invalid offer type received '{request.Offer.InternalType}'."); } throw new Exception("Invalid offer request."); } catch (Exception ex) { FailConnection(ex); throw ex; } }
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(); }
public void SendVideoRtcpFeedbackReportUnitTest() { logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); RTCConfiguration pcConfiguration = new RTCConfiguration { certificates = new List <RTCCertificate> { new RTCCertificate { X_Fingerprint = "sha-256 C6:ED:8C:9D:06:50:77:23:0A:4A:D8:42:68:29:D0:70:2F:BB:C7:72:EC:98:5C:62:07:1B:0C:5D:CB:CE:BE:CD" } }, X_UseRtpFeedbackProfile = true }; RTCPeerConnection pcSrc = new RTCPeerConnection(pcConfiguration); var videoTrackSrc = new MediaStreamTrack(SDPMediaTypesEnum.video, false, new List <SDPMediaFormat> { new SDPMediaFormat(SDPMediaFormatsEnum.VP8) }); pcSrc.addTrack(videoTrackSrc); var offer = pcSrc.createOffer(new RTCOfferOptions()); logger.LogDebug($"offer: {offer.sdp}"); RTCPeerConnection pcDst = new RTCPeerConnection(pcConfiguration); var videoTrackDst = new MediaStreamTrack(SDPMediaTypesEnum.video, false, new List <SDPMediaFormat> { new SDPMediaFormat(SDPMediaFormatsEnum.VP8) }); pcDst.addTrack(videoTrackDst); var setOfferResult = pcDst.setRemoteDescription(offer); Assert.Equal(SetDescriptionResultEnum.OK, setOfferResult); var answer = pcDst.createAnswer(null); var setAnswerResult = pcSrc.setRemoteDescription(answer); Assert.Equal(SetDescriptionResultEnum.OK, setAnswerResult); logger.LogDebug($"answer: {answer.sdp}"); RTCPFeedback pliReport = new RTCPFeedback(pcDst.VideoLocalTrack.Ssrc, pcDst.VideoRemoteTrack.Ssrc, PSFBFeedbackTypesEnum.PLI); pcDst.SendRtcpFeedback(SDPMediaTypesEnum.video, pliReport); }
public void SendVideoRtcpFeedbackReportUnitTest() { logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); RTCConfiguration pcConfiguration = new RTCConfiguration { certificates = new List <RTCCertificate> { new RTCCertificate { Certificate = DtlsUtils.CreateSelfSignedCert() } }, X_UseRtpFeedbackProfile = true }; RTCPeerConnection pcSrc = new RTCPeerConnection(pcConfiguration); var videoTrackSrc = new MediaStreamTrack(SDPMediaTypesEnum.video, false, new List <SDPMediaFormat> { new SDPMediaFormat(SDPMediaFormatsEnum.VP8) }); pcSrc.addTrack(videoTrackSrc); var offer = pcSrc.createOffer(new RTCOfferOptions()); logger.LogDebug($"offer: {offer.sdp}"); RTCPeerConnection pcDst = new RTCPeerConnection(pcConfiguration); var videoTrackDst = new MediaStreamTrack(SDPMediaTypesEnum.video, false, new List <SDPMediaFormat> { new SDPMediaFormat(SDPMediaFormatsEnum.VP8) }); pcDst.addTrack(videoTrackDst); var setOfferResult = pcDst.setRemoteDescription(offer); Assert.Equal(SetDescriptionResultEnum.OK, setOfferResult); var answer = pcDst.createAnswer(null); var setAnswerResult = pcSrc.setRemoteDescription(answer); Assert.Equal(SetDescriptionResultEnum.OK, setAnswerResult); logger.LogDebug($"answer: {answer.sdp}"); RTCPFeedback pliReport = new RTCPFeedback(pcDst.VideoLocalTrack.Ssrc, pcDst.VideoRemoteTrack.Ssrc, PSFBFeedbackTypesEnum.PLI); pcDst.SendRtcpFeedback(SDPMediaTypesEnum.video, pliReport); }
private static async Task WebSocketMessageReceived(WebSocketContext context, RTCPeerConnection peerConnection, string message) { try { if (peerConnection.localDescription == null) { //logger.LogDebug("Offer SDP: " + message); logger.LogDebug("Offer SDP received."); // Add local media tracks depending on what was offered. Also add local tracks with the same media ID as // the remote tracks so that the media announcement in the SDP answer are in the same order. SDP remoteSdp = SDP.ParseSDPDescription(message); await peerConnection.setRemoteDescription(new RTCSessionDescriptionInit { sdp = message, type = RTCSdpType.offer }); var answer = await peerConnection.createAnswer(null); await peerConnection.setLocalDescription(answer); context.WebSocket.Send(answer.sdp); } else if (peerConnection.remoteDescription == null) { logger.LogDebug("Answer SDP: " + message); await peerConnection.setRemoteDescription(new RTCSessionDescriptionInit { sdp = message, type = RTCSdpType.answer }); } else { logger.LogDebug("ICE Candidate: " + message); if (string.IsNullOrWhiteSpace(message) || message.Trim().ToLower() == SDP.END_ICE_CANDIDATES_ATTRIBUTE) { logger.LogDebug("End of candidates message received."); } else { await peerConnection.addIceCandidate(new RTCIceCandidateInit { candidate = message }); } } } catch (Exception excp) { logger.LogError("Exception WebSocketMessageReceived. " + excp.Message); } }
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; } }
/// <summary> /// This application spits out a lot of log messages. In an attempt to make command entry slightly more usable /// this method attempts to always write the current command input as the bottom line on the console output. /// </summary> private static async Task ProcessInput(CancellationTokenSource cts) { // Local function to write the current command in the process of being entered. Action <int, string> writeCommandPrompt = (lastPromptRow, cmd) => { // The cursor is already at the current row. if (Console.CursorTop == lastPromptRow) { // The command was corrected. Need to re-write the whole line. Console.SetCursorPosition(0, Console.CursorTop); Console.Write(new string(' ', Console.WindowWidth)); Console.SetCursorPosition(0, Console.CursorTop); Console.Write($"{COMMAND_PROMPT}{cmd}"); } else { // The cursor row has changed since the last input. Rewrite the prompt and command // on the current line. Console.Write($"{COMMAND_PROMPT}{cmd}"); } }; string command = null; int lastInputRow = Console.CursorTop; while (!cts.IsCancellationRequested) { var inKey = Console.ReadKey(true); if (inKey.Key == ConsoleKey.Enter) { if (command == null) { Console.WriteLine(); Console.Write(COMMAND_PROMPT); } else { // Attempt to execute the current command. switch (command.ToLower()) { case "c": // Close active peer connection. if (_peerConnection != null) { Console.WriteLine(); Console.WriteLine("Closing peer connection"); _peerConnection.Close("user initiated"); } break; case var x when x.StartsWith("cdc"): // Attempt to create a new data channel. if (_peerConnection != null) { (_, var label) = x.Split(" ", 2, StringSplitOptions.None); if (!string.IsNullOrWhiteSpace(label)) { Console.WriteLine(); Console.WriteLine($"Creating data channel for label {label}."); var dc = _peerConnection.createDataChannel(label, null); dc.onmessage += (msg) => logger.LogDebug($" data channel message received on {label}: {msg}"); } else { Console.WriteLine(); Console.WriteLine($"Send message command was in the wrong format. Needs to be: cdc <label>"); } } break; case var x when x.StartsWith("ldc"): // List data channels. if (_peerConnection != null) { if (_peerConnection.DataChannels.Count > 0) { Console.WriteLine(); foreach (var dc in _peerConnection.DataChannels) { Console.WriteLine($" data channel: label {dc.label}, stream ID {dc.id}, is open {dc.IsOpened}."); } } else { Console.WriteLine(); Console.WriteLine(" no data channels available."); } } break; case var x when x.StartsWith("sdc"): // Send data channel message. if (_peerConnection != null) { (_, var label, var msg) = x.Split(" ", 3, StringSplitOptions.None); if (!string.IsNullOrWhiteSpace(label) && !string.IsNullOrWhiteSpace(msg)) { Console.WriteLine(); Console.WriteLine($"Sending message on channel {label}: {msg}"); var dc = _peerConnection.DataChannels.FirstOrDefault(x => x.label == label && x.IsOpened); if (dc != null) { dc.send(msg); } else { Console.WriteLine($"No data channel was found for label {label}."); } } else { Console.WriteLine(); Console.WriteLine($"Send data channel message command was in the wrong format. Needs to be: sdc <label> <message>"); } } break; case "q": // Quit. Console.WriteLine(); Console.WriteLine("Quitting..."); cts.Cancel(); break; case "isalive": // Check responsiveness. Console.WriteLine(); Console.WriteLine("yep"); Console.Write(COMMAND_PROMPT); break; case var x when x.StartsWith("node"): (_, var sdpType, var myUser, string theirUser) = x.Split(" ", 4, StringSplitOptions.None); if (sdpType == "so") { _peerConnection = Createpc(null, _stunServer); var offerSdp = _peerConnection.createOffer(null); await _peerConnection.setLocalDescription(offerSdp); Console.WriteLine($"Our Offer:\n{offerSdp.sdp}"); var offerJson = JsonConvert.SerializeObject(offerSdp, new Newtonsoft.Json.Converters.StringEnumConverter()); var content = new StringContent(offerJson, Encoding.UTF8, "application/json"); var res = await _nodeDssclient.PostAsync($"{_nodeDssUri}data/{theirUser}", content); Console.WriteLine($"node-dss POST result {res.StatusCode}."); } else if (sdpType == "go") { var res = await _nodeDssclient.GetAsync($"{_nodeDssUri}data/{myUser}"); Console.WriteLine($"node-dss GET result {res.StatusCode}."); if (res.StatusCode == HttpStatusCode.OK) { var content = await res.Content.ReadAsStringAsync(); RTCSessionDescriptionInit offerInit = JsonConvert.DeserializeObject <RTCSessionDescriptionInit>(content); Console.WriteLine($"Remote offer:\n{offerInit.sdp}"); _peerConnection = Createpc(null, _stunServer); var setRes = _peerConnection.setRemoteDescription(offerInit); if (setRes != SetDescriptionResultEnum.OK) { // No point continuing. Something will need to change and then try again. _peerConnection.Close("failed to set remote sdp offer"); } else { var answer = _peerConnection.createAnswer(null); await _peerConnection.setLocalDescription(answer); Console.WriteLine($"Our answer:\n{answer.sdp}"); var answerJson = JsonConvert.SerializeObject(answer, new Newtonsoft.Json.Converters.StringEnumConverter()); var answerContent = new StringContent(answerJson, Encoding.UTF8, "application/json"); var postRes = await _nodeDssclient.PostAsync($"{_nodeDssUri}data/{theirUser}", answerContent); Console.WriteLine($"node-dss POST result {res.StatusCode}."); } } } else if (sdpType == "ga") { var res = await _nodeDssclient.GetAsync($"{_nodeDssUri}data/{myUser}"); Console.WriteLine($"node-dss GET result {res.StatusCode}."); if (res.StatusCode == HttpStatusCode.OK) { var content = await res.Content.ReadAsStringAsync(); RTCSessionDescriptionInit answerInit = JsonConvert.DeserializeObject <RTCSessionDescriptionInit>(content); Console.WriteLine($"Remote answer:\n{answerInit.sdp}"); var setRes = _peerConnection.setRemoteDescription(answerInit); if (setRes != SetDescriptionResultEnum.OK) { // No point continuing. Something will need to change and then try again. _peerConnection.Close("failed to set remote sdp answer"); } } } break; default: // Command not recognised. Console.WriteLine(); Console.WriteLine($"Unknown command: {command}"); Console.Write(COMMAND_PROMPT); break; } command = null; } } else if (inKey.Key == ConsoleKey.UpArrow) { // Convenience mechanism to get the current input prompt without // needing to change the command being entered. writeCommandPrompt(lastInputRow, command); } else if (inKey.Key == ConsoleKey.Escape) { // Escape key clears the current command. command = null; writeCommandPrompt(lastInputRow, command); } else if (inKey.Key == ConsoleKey.Backspace) { // Backspace removes the last character. command = (command?.Length > 0) ? command.Substring(0, command.Length - 1) : null; writeCommandPrompt(lastInputRow, command); } else if (!Char.IsControl(inKey.KeyChar)) { // Non-control character, append to current command. command += inKey.KeyChar; if (Console.CursorTop == lastInputRow) { Console.Write(inKey.KeyChar); } else { writeCommandPrompt(lastInputRow, command); } } lastInputRow = Console.CursorTop; } }
public async Task <RTCSessionDescriptionInit> GotOffer(RTCSessionDescriptionInit offer) { logger.LogTrace($"SDP offer received."); logger.LogTrace(offer.sdp); var pc = new RTCPeerConnection(); if (_presetIceAddresses != null) { foreach (var addr in _presetIceAddresses) { var rtpPort = pc.GetRtpChannel().RTPPort; var publicIPv4Candidate = new RTCIceCandidate(RTCIceProtocol.udp, addr, (ushort)rtpPort, RTCIceCandidateType.host); pc.addLocalIceCandidate(publicIPv4Candidate); } } MediaStreamTrack audioTrack = new MediaStreamTrack(SDPWellKnownMediaFormatsEnum.PCMU); pc.addTrack(audioTrack); MediaStreamTrack videoTrack = new MediaStreamTrack(new VideoFormat(VideoCodecsEnum.VP8, VP8_PAYLOAD_ID)); pc.addTrack(videoTrack); pc.OnRtpPacketReceived += (IPEndPoint rep, SDPMediaTypesEnum media, RTPPacket rtpPkt) => { pc.SendRtpRaw(media, rtpPkt.Payload, rtpPkt.Header.Timestamp, rtpPkt.Header.MarkerBit, rtpPkt.Header.PayloadType); }; pc.OnTimeout += (mediaType) => logger.LogWarning($"Timeout for {mediaType}."); pc.oniceconnectionstatechange += (state) => logger.LogInformation($"ICE connection state changed to {state}."); pc.onsignalingstatechange += () => logger.LogInformation($"Signaling state changed to {pc.signalingState}."); pc.onconnectionstatechange += (state) => { logger.LogInformation($"Peer connection state changed to {state}."); if (state == RTCPeerConnectionState.failed) { pc.Close("ice failure"); } }; var setResult = pc.setRemoteDescription(offer); if (setResult == SetDescriptionResultEnum.OK) { var offerSdp = pc.createOffer(null); await pc.setLocalDescription(offerSdp); var answer = pc.createAnswer(null); logger.LogTrace($"SDP answer created."); logger.LogTrace(answer.sdp); return(answer); } else { logger.LogWarning($"Failed to set remote description {setResult}."); return(null); } }
private static async void WebSocketMessageReceived(WebSocketContext context, RTCPeerConnection peerConnection, string msg) { if (peerConnection.RemoteDescription != null) { Console.WriteLine($"ICE Candidate: {msg}."); //await _peerConnections[0].addIceCandidate(new RTCIceCandidateInit { candidate = msg }); // await peerConnection.addIceCandidate(new RTCIceCandidateInit { candidate = msg }); Console.WriteLine("add ICE candidate complete."); } else { //Console.WriteLine($"websocket recv: {msg}"); //var offerSDP = SDP.ParseSDPDescription(msg); Console.WriteLine($"offer sdp: {msg}"); var dtls = new DtlsHandshake(DTLS_CERTIFICATE_PATH, DTLS_KEY_PATH); await peerConnection.setRemoteDescription(new RTCSessionDescriptionInit { sdp = msg, type = RTCSdpType.offer }); peerConnection.OnReceiveReport += RtpSession_OnReceiveReport; peerConnection.OnSendReport += RtpSession_OnSendReport; peerConnection.onconnectionstatechange += (state) => { if (state == RTCPeerConnectionState.closed) { Console.WriteLine($"webrtc session closed."); //_peerConnections.Remove(peerConnection); } }; peerConnection.oniceconnectionstatechange += (state) => { if (state == RTCIceConnectionState.connected) { Console.WriteLine("Starting DTLS handshake task."); bool dtlsResult = false; Task.Run(async() => dtlsResult = await DoDtlsHandshake(peerConnection, dtls)) .ContinueWith((t) => { Console.WriteLine($"dtls handshake result {dtlsResult}."); if (dtlsResult) { //peerConnection.SetDestination(SDPMediaTypesEnum.audio, peerConnection.IceSession.ConnectedRemoteEndPoint, peerConnection.IceSession.ConnectedRemoteEndPoint); //_peerConnections.Add(peerConnection); peerConnection.OnRtpPacketReceived += RtpSession_OnRtpPacketReceived; } else { dtls.Shutdown(); peerConnection.Close("dtls handshake failed."); } }); } }; var answerInit = await peerConnection.createAnswer(null); await peerConnection.setLocalDescription(answerInit); Console.WriteLine($"answer sdp: {answerInit.sdp}"); context.WebSocket.Send(answerInit.sdp); } }
public RTCSessionDescriptionInit GetSdpAnswer(SdpAnswerRequestArgs args) { ConnectionId = args.ConnectionId; RTCConfiguration config = new RTCConfiguration { X_UseRtpFeedbackProfile = true }; var pc = new RTCPeerConnection(config); // Add local receive only tracks. This ensures that the SDP answer includes only the codecs we support. if (!_noAudio) { MediaStreamTrack audioTrack = new MediaStreamTrack(SDPMediaTypesEnum.audio, false, new List <SDPAudioVideoMediaFormat> { new SDPAudioVideoMediaFormat(SDPWellKnownMediaFormatsEnum.PCMU) }, MediaStreamStatusEnum.RecvOnly); pc.addTrack(audioTrack); } // MediaStreamTrack videoTrack = new MediaStreamTrack(new VideoFormat(96, "VP8", 90000, "x-google-max-bitrate=5000000"), MediaStreamStatusEnum.RecvOnly); // pc.OnVideoFrameReceived += _videoSink.GotVideoFrame; // pc.OnVideoFormatsNegotiated += (formats) => _videoSink.SetVideoSinkFormat(formats.First()); pc.OnVideoFrameReceived += (endpoint, rtpTimestampMs, frame, format) => { int frameLength = frame.Length; string rtpTimestamp = new DateTime(rtpTimestampMs * 100).ToString("dd.MM.yyyy HH:mm:ss,ffff"); string timestamp = "none"; long timestampMs = 0; if (ExtractTimestampFromFrame) { frameLength -= 8; var span = new Span <byte>(frame, frameLength, 8); span.Reverse(); timestampMs = BitConverter.ToInt64(span); if (timestampMs > 0 && timestampMs < 4398046511104) { timestamp = DateTime.UnixEpoch.AddMilliseconds(timestampMs).ToString("dd.MM.yyyy HH:mm:ss,ffff"); } } Console.WriteLine($"On frame received: byte[{frame.Length}], rtpTs: {rtpTimestamp} extractedTs: {timestamp})"); _listener.OnFrameReceived(this, new Memory <byte>(frame, 0, frameLength), rtpTimestampMs, timestampMs); }; pc.onicecandidate += (iceCandidate) => { Console.WriteLine("On ice candidate"); _listener.OnIceCandidate(this, iceCandidate); }; pc.onconnectionstatechange += (state) => { Console.WriteLine($"Peer connection state change to {state}."); if (state == RTCPeerConnectionState.failed) { pc.Close("ice disconnection"); } _listener.OnConnectionStateChanged(this, state); }; pc.OnSendReport += (media, sr) => Console.WriteLine($"RTCP Send for {media}\n{sr.GetDebugSummary()}"); pc.oniceconnectionstatechange += (state) => Console.WriteLine($"ICE connection state change to {state}."); var sdpOffer = SDP.ParseSDPDescription(args.SdpOffer); //sdpOffer.Media.FirstOrDefault()?. var videoMedia = sdpOffer.Media.FirstOrDefault(m => m.Media == SDPMediaTypesEnum.video); var h264VideoFormat = videoMedia.MediaFormats.Values.First(m => m.Rtpmap == "H264/90000").ToVideoFormat(); var videoTrack = new MediaStreamTrack(h264VideoFormat, MediaStreamStatusEnum.RecvOnly); pc.addTrack(videoTrack); //var track = new MediaStreamTrack(SDPMediaTypesEnum.video, false, new List<SDPAudioVideoMediaFormat> { new SDPAudioVideoMediaFormat(SDPMediaTypesEnum.video, 102, "H264", 90000) }); //pc.addTrack(track); var setRemoteDescriptionResult = pc.setRemoteDescription(new RTCSessionDescriptionInit { type = RTCSdpType.offer, sdp = args.SdpOffer }); //MediaStreamTrack videoTrack = new MediaStreamTrack(new VideoFormat(VideoCodecsEnum.H264, 102), MediaStreamStatusEnum.RecvOnly); //pc.addTrack(videoTrack); var answer = pc.createAnswer(); var setLocalDescriptionResult = pc.setLocalDescription(answer); Console.WriteLine("SDP Offer:\n" + args.SdpOffer); Console.WriteLine("SDP Answer 2:\n" + answer.sdp); _peerConnection = pc; return(answer); }
public async Task <RTCSessionDescriptionInit> GotOffer(RTCSessionDescriptionInit offer) { _logger.LogDebug($"SDP offer received."); _logger.LogTrace($"Offer SDP:\n{offer.sdp}"); var pc = new RTCPeerConnection(); if (_publicIPv4 != null) { var rtpPort = pc.GetRtpChannel().RTPPort; var publicIPv4Candidate = new RTCIceCandidate(RTCIceProtocol.udp, _publicIPv4, (ushort)rtpPort, RTCIceCandidateType.host); pc.addLocalIceCandidate(publicIPv4Candidate); } if (_publicIPv6 != null) { var rtpPort = pc.GetRtpChannel().RTPPort; var publicIPv6Candidate = new RTCIceCandidate(RTCIceProtocol.udp, _publicIPv6, (ushort)rtpPort, RTCIceCandidateType.host); pc.addLocalIceCandidate(publicIPv6Candidate); } MediaStreamTrack audioTrack = new MediaStreamTrack(SDPMediaTypesEnum.audio, false, new List <SDPAudioVideoMediaFormat> { new SDPAudioVideoMediaFormat(SDPWellKnownMediaFormatsEnum.PCMU) }, MediaStreamStatusEnum.SendRecv); pc.addTrack(audioTrack); MediaStreamTrack videoTrack = new MediaStreamTrack(new VideoFormat(VideoCodecsEnum.VP8, VP8_PAYLOAD_ID), MediaStreamStatusEnum.SendRecv); pc.addTrack(videoTrack); pc.OnRtpPacketReceived += (IPEndPoint rep, SDPMediaTypesEnum media, RTPPacket rtpPkt) => { pc.SendRtpRaw(media, rtpPkt.Payload, rtpPkt.Header.Timestamp, rtpPkt.Header.MarkerBit, rtpPkt.Header.PayloadType); //_logger.LogDebug($"RTP {media} pkt received, SSRC {rtpPkt.Header.SyncSource}, SeqNum {rtpPkt.Header.SequenceNumber}."); }; //peerConnection.OnReceiveReport += RtpSession_OnReceiveReport; //peerConnection.OnSendReport += RtpSession_OnSendReport; pc.OnTimeout += (mediaType) => _logger.LogWarning($"Timeout for {mediaType}."); pc.onconnectionstatechange += (state) => { _logger.LogDebug($"Peer connection state changed to {state}."); if (state == RTCPeerConnectionState.failed) { pc.Close("ice failure"); } }; var setResult = pc.setRemoteDescription(offer); if (setResult == SetDescriptionResultEnum.OK) { var offerSdp = pc.createOffer(null); await pc.setLocalDescription(offerSdp); var answer = pc.createAnswer(null); await pc.setLocalDescription(answer); _logger.LogTrace($"Answer SDP:\n{answer.sdp}"); return(answer); } else { return(null); } }
/// <summary> /// This is a javascript application. /// </summary> /// <param name="page">HTML document rendered by the web server which can now be enhanced.</param> public Application(IApp page) { // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection // https://github.com/cjb/serverless-webrtc // https://github.com/XSockets/WebRTC // jsc when was the last time we tried p2p? // var peer = new PeerConnection(iceServers, optional); where iceServers = null this is working without internet // http://stackoverflow.com/questions/19675165/whether-stun-server-is-needed-within-lan-for-webrtc //var peer = new PeerConnection(iceServers, optional); // https://www.webrtc-experiment.com/docs/WebRTC-PeerConnection.html // http://stackoverflow.com/questions/12848013/what-is-the-replacement-for-the-deprecated-peerconnection-api // http://docs.webplatform.org/wiki/apis/webrtc/RTCPeerConnection // http://w3schools.invisionzone.com/index.php?showtopic=46661 // http://www.html5rocks.com/en/tutorials/webrtc/basics/#toc-rtcpeerconnection // IDL dictionary looks like C# PrimaryCnstructor concept does it not ////var d = new RTCSessionDescription( //// new ////{ ////} ////); // 02000002 TestPeerConnection.Application // script: error JSC1000: You tried to instance a class which seems to be marked as native. // script: error JSC1000: type has no callable constructor: [ScriptCoreLib.JavaScript.DOM.RTCPeerConnection] //Void.ctor() // Uncaught ReferenceError: RTCPeerConnection is not defined // wtf? // {{ RTCPeerConnection = undefined }} //new IHTMLPre { new { w.RTCPeerConnection } }.AttachToDocument(); // {{ webkitRTCPeerConnection = function RTCPeerConnection() { [native code] } }} //new IHTMLPre { new { w.webkitRTCPeerConnection } }.AttachToDocument(); // wtf chrome? stop prefixing var w = Native.window as dynamic; Console.WriteLine(new { w.RTCPeerConnection }); w.RTCPeerConnection = w.webkitRTCPeerConnection; // Uncaught TypeError: Failed to construct 'RTCPeerConnection': 1 argument required, but only 0 present. // http://stackoverflow.com/questions/22470291/rtcdatachannels-readystate-is-not-open // after Chrome 31, you can use SCTP based data channels. // http://stackoverflow.com/questions/21585681/send-image-data-over-rtc-data-channel // https://code.google.com/p/chromium/issues/detail?id=295771 // https://gist.github.com/shacharz/9661930 // http://chimera.labs.oreilly.com/books/1230000000545/ch18.html#_tracking_ice_gathering_and_connectivity_status var peer = new RTCPeerConnection( new { iceServers = new object[0] }, null // https://groups.google.com/forum/#!topic/discuss-webrtc/y2A97iCByTU //constraints: new { // optional = new[] // { // new { RtpDataChannels = true } // } //} ); // how the hell cann I connect two p2p? // i see we need to do data //peer.setLocalDescription // https://groups.google.com/forum/#!topic/discuss-webrtc/zK_5yUqiqsE // X:\jsc.svn\examples\javascript\xml\VBDisplayServerDebuggerPresence\VBDisplayServerDebuggerPresence\ApplicationWebService.vb // https://code.google.com/p/webrtc/source/browse/trunk/samples/js/base/adapter.js // http://www.webrtc.org/faq-recent-topics // http://stackoverflow.com/questions/14134090/how-is-a-webrtc-peer-connection-established peer.onicecandidate = new Action <RTCPeerConnectionIceEvent>( (RTCPeerConnectionIceEvent e) => { if (e.candidate != null) { new IHTMLPre { "onicecandidate: " + new { e.candidate.candidate } }.AttachToDocument(); peer.addIceCandidate(e.candidate, new Action( delegate { new IHTMLPre { "addIceCandidate" }.AttachToDocument(); } )); } } ); // http://stackoverflow.com/questions/15484729/why-doesnt-onicecandidate-work // http://www.skylinetechnologies.com/Blog/Article/48/Peer-to-Peer-Media-Streaming-with-WebRTC-and-SignalR.aspx peer.createOffer( new Action <RTCSessionDescription>( (RTCSessionDescription x) => { new IHTMLPre { "after createOffer " + new { x.sdp } }.AttachToDocument(); peer.setLocalDescription(x, new Action( delegate { // // send the offer to a server that can negotiate with a remote client new IHTMLPre { "after setLocalDescription " }.AttachToDocument(); } ) ); peer.setRemoteDescription(x, new Action( delegate { // // send the offer to a server that can negotiate with a remote client new IHTMLPre { "after setRemoteDescription " }.AttachToDocument(); } ) ); } ) ); peer.createAnswer( new Action <RTCSessionDescription>( (RTCSessionDescription x) => { new IHTMLPre { "after createAnswer " + new { x.sdp } }.AttachToDocument(); } )); // https://groups.google.com/forum/#!topic/discuss-webrtc/wbcgYMrIii4 // https://groups.google.com/forum/#!msg/discuss-webrtc/wbcgYMrIii4/aZ12cENVTxEJ // http://blog.printf.net/articles/2013/05/17/webrtc-without-a-signaling-server/ //peer.onconn // https://github.com/cjb/serverless-webrtc/blob/master/js/serverless-webrtc.js peer.ondatachannel = new Action <RTCDataChannelEvent>( (RTCDataChannelEvent e) => { //Console.WriteLine("ondatachannel"); new IHTMLPre { "ondatachannel" }.AttachToDocument(); var c = e.channel; c.onmessage = new Action <MessageEvent>( (MessageEvent ee) => { new IHTMLPre { new { ee.data } }.AttachToDocument(); } ); } ); // jsc cant the idl generator understand optinal? RTCDataChannel dc = peer.createDataChannel("label1", null); // {{ id = 65535, label = label1, readyState = connecting }} new IHTMLPre { new { dc.id, dc.label, dc.readyState } }.AttachToDocument(); new IHTMLButton { "awaiting to open..." }.AttachToDocument().With( button => { // !!! can our IDL compiler generate events and async at the same time? dc.onopen = new Action <IEvent>( async ee => { button.innerText = "send"; while (true) { await button.async.onclick; new IHTMLPre { "send" }.AttachToDocument(); // Failed to execute 'send' on 'RTCDataChannel': RTCDataChannel.readyState is not 'open' dc.send("data to send"); } } ); } ); //connection.createOffer }