private static Task <RTCPeerConnection> CreatePeerConnection() { RTCConfiguration config = new RTCConfiguration { iceServers = new List <RTCIceServer> { new RTCIceServer { urls = STUN_URL } } }; var pc = new RTCPeerConnection(config); pc.createDataChannel("test", null); pc.onconnectionstatechange += async(state) => { logger.LogDebug($"Peer connection state change to {state}."); if (state == RTCPeerConnectionState.failed) { pc.Close("ice disconnection"); } }; // Diagnostics. pc.OnReceiveReport += (re, media, rr) => logger.LogDebug($"RTCP Receive for {media} from {re}\n{rr.GetDebugSummary()}"); pc.OnSendReport += (media, sr) => logger.LogDebug($"RTCP Send for {media}\n{sr.GetDebugSummary()}"); pc.GetRtpChannel().OnStunMessageReceived += (msg, ep, isRelay) => logger.LogDebug($"STUN {msg.Header.MessageType} received from {ep}."); pc.oniceconnectionstatechange += (state) => logger.LogDebug($"ICE connection state change to {state}."); return(Task.FromResult(pc)); }
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 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 static async Task <RTCPeerConnection> CreatePeerConnection() { RTCConfiguration config = new RTCConfiguration { X_DisableExtendedMasterSecretKey = true }; var pc = new RTCPeerConnection(config); MediaStreamTrack audioTrack = new MediaStreamTrack(SDPWellKnownMediaFormatsEnum.PCMU); pc.addTrack(audioTrack); var dc = await pc.createDataChannel("sipsocery-dc"); pc.onicecandidateerror += (candidate, error) => logger.LogWarning($"Error adding remote ICE candidate. {error} {candidate}"); 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.OnReceiveReport += (re, media, rr) => logger.LogDebug($"RTCP Receive for {media} from {re}\n{rr.GetDebugSummary()}"); pc.OnSendReport += (media, sr) => logger.LogDebug($"RTCP Send for {media}\n{sr.GetDebugSummary()}"); pc.OnRtcpBye += (reason) => logger.LogDebug($"RTCP BYE receive, reason: {(string.IsNullOrWhiteSpace(reason) ? "<none>" : reason)}."); pc.onsignalingstatechange += () => { if (pc.signalingState == RTCSignalingState.have_remote_offer) { logger.LogTrace("Remote SDP:"); logger.LogTrace(pc.remoteDescription.sdp.ToString()); } else if (pc.signalingState == RTCSignalingState.have_local_offer) { logger.LogTrace("Local SDP:"); logger.LogTrace(pc.localDescription.sdp.ToString()); } }; return(pc); }
private void InitConnection() { Logger.LogInformation("Initializing connection..."); var servers = IceServers.DistinctBy(x => x.Url).ToList(); foreach (var ice in servers) { Logger.LogInformation($"Adding ice server: '{ice}'..."); } _connection = new RTCPeerConnection(new RTCConfiguration() { iceServers = servers.Select(x => new RTCIceServer() { urls = x.Url, username = x.UserName, credential = x.Credentials, }).ToList() }); _connection.ondatachannel += OnDataChannelInitialized; _connection.onicecandidate += OnIceCandidateAvailable; _connection.onconnectionstatechange += OnConnectionStateChanged; Logger.LogInformation($"Creating data channel {ChannelName}..."); _dataChannel = _connection.createDataChannel(ChannelName).GetAwaiter().GetResult(); _dataChannel.onopen += OnDataChannelOpened; _dataChannel.onclose += OnDataChannelClosed; Logger.LogInformation("Starting connection..."); _connection.Start().GetAwaiter().GetResult(); _connectionInitialized = true; }
private RTCPeerConnection Createpc() { 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, }; var pc = new RTCPeerConnection(pcConfiguration); pc.GetRtpChannel().MdnsResolve = MdnsResolve; //pc.GetRtpChannel().OnStunMessageReceived += (msg, ep, isrelay) => logger.LogDebug($"{_peerName}: STUN message received from {ep}, message class {msg.Header.MessageClass}."); var dataChannel = pc.createDataChannel(_dataChannelLabel, null); dataChannel.onDatamessage -= DataChannel_onDatamessage; dataChannel.onDatamessage += DataChannel_onDatamessage; _dataChannels.Add(_dataChannelLabel, dataChannel); pc.onicecandidateerror += (candidate, error) => logger.LogWarning($"{_peerName}: Error adding remote ICE candidate. {error} {candidate}"); pc.oniceconnectionstatechange += (state) => logger.LogDebug($"{_peerName}: ICE connection state change to {state}."); pc.onconnectionstatechange += (state) => { logger.LogDebug($"{_peerName}: Peer connection state changed to {state}."); if (state == RTCPeerConnectionState.disconnected || state == RTCPeerConnectionState.failed) { pc.Close("remote disconnection"); } }; pc.onicecandidate += (candidate) => { if (pc.signalingState == RTCSignalingState.have_local_offer || pc.signalingState == RTCSignalingState.have_remote_offer) { OnIceCandidateAvailable?.Invoke(new RTCIceCandidateInit() { candidate = candidate.ToString(), sdpMid = candidate.sdpMid, sdpMLineIndex = candidate.sdpMLineIndex }); } }; pc.ondatachannel += (dc) => { dc.onopen += () => logger.LogDebug($"{_peerName}: Data channel now open label {dc.label}, stream ID {dc.id}."); dc.onDatamessage -= DataChannel_onDatamessage; dc.onDatamessage += DataChannel_onDatamessage; logger.LogDebug($"{_peerName}: Data channel created by remote peer, label {dc.label}, stream ID {dc.id}."); _dataChannels.Add(dc.label, dc); }; return(pc); }
private RTCPeerConnection Createpc() { List <RTCCertificate> presetCertificates = null; byte[] dummyCertBytes = Convert.FromBase64String(DUMMY_CERTIFICATE_BASE64); var dummyCert = new X509Certificate2(dummyCertBytes); presetCertificates = new List <RTCCertificate> { new RTCCertificate { Certificate = dummyCert } }; RTCConfiguration pcConfiguration = new RTCConfiguration { certificates = presetCertificates, //iceServers = new List<RTCIceServer> { new RTCIceServer { urls = "stun:stun.l.google.com:19302" } } iceServers = new List <RTCIceServer> { new RTCIceServer { urls = "stun:108.177.15.127:19302" } }, X_BindAddress = IPAddress.Any }; var pc = new RTCPeerConnection(pcConfiguration); //pc.GetRtpChannel().OnStunMessageReceived += (msg, ep, isrelay) => logger.LogDebug($"{_peerName}: STUN message received from {ep}, message class {msg.Header.MessageClass}."); var dataChannel = pc.createDataChannel(_dataChannelLabel, null); dataChannel.onDatamessage += DataChannel_onDatamessage; dataChannel.onmessage += DataChannel_onmessage; _dataChannels.Add(_dataChannelLabel, dataChannel); pc.onicecandidateerror += (candidate, error) => logger.LogWarning($"{_peerName}: Error adding remote ICE candidate. {error} {candidate}"); pc.oniceconnectionstatechange += (state) => logger.LogDebug($"{_peerName}: ICE connection state change to {state}."); pc.onconnectionstatechange += (state) => { logger.LogDebug($"{_peerName}: Peer connection state changed to {state}."); if (state == RTCPeerConnectionState.disconnected || state == RTCPeerConnectionState.failed) { pc.Close("remote disconnection"); } }; pc.onicecandidate += (candidate) => { if (pc.signalingState == RTCSignalingState.have_local_offer || pc.signalingState == RTCSignalingState.have_remote_offer) { OnIceCandidateAvailable?.Invoke(new RTCIceCandidateInit() { candidate = candidate.ToString(), sdpMid = candidate.sdpMid, sdpMLineIndex = candidate.sdpMLineIndex }); } }; pc.ondatachannel += (dc) => { dc.onopen += () => logger.LogDebug($"{_peerName}: Data channel now open label {dc.label}, stream ID {dc.id}."); dc.onDatamessage += DataChannel_onDatamessage; dc.onmessage += DataChannel_onmessage; logger.LogDebug($"{_peerName}: Data channel created by remote peer, label {dc.label}, stream ID {dc.id}."); _dataChannels.Add(dc.label, dc); }; return(pc); }
/// <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 void 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; 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; } }
private static RTCPeerConnection Createpc(WebSocketContext context, RTCIceServer stunServer) { 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, //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 class {msg.Header.MessageClass}."); 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) { context.WebSocket.Send($"candidate:{candidate}"); } } }; // 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); }
/// <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 RTCPeerClient(RTCPeerConnection peerConnection) { this.peerConnection = peerConnection; dataChannel = peerConnection.createDataChannel(Guid.NewGuid().ToString()); }
private async static Task <RTCPeerConnection> CreatePeerConnection() { RTCConfiguration config = new RTCConfiguration { iceServers = new List <RTCIceServer> { new RTCIceServer { urls = STUN_URL } } }; var pc = new RTCPeerConnection(config); pc.ondatachannel += (rdc) => { rdc.onopen += () => logger.LogDebug($"Data channel {rdc.label} opened."); rdc.onclose += () => logger.LogDebug($"Data channel {rdc.label} closed."); rdc.onmessage += (datachan, type, data) => { switch (type) { case DataChannelPayloadProtocols.WebRTC_Binary_Empty: case DataChannelPayloadProtocols.WebRTC_String_Empty: logger.LogInformation($"Data channel {datachan.label} empty message type {type}."); break; case DataChannelPayloadProtocols.WebRTC_Binary: string jsSha256 = DoJavscriptSHA256(data); logger.LogInformation($"Data channel {datachan.label} received {data.Length} bytes, js mirror sha256 {jsSha256}."); rdc.send(jsSha256); if (_loadTestCount > 0) { DoLoadTestIteration(rdc, _loadTestPayloadSize); _loadTestCount--; } break; case DataChannelPayloadProtocols.WebRTC_String: var msg = Encoding.UTF8.GetString(data); logger.LogInformation($"Data channel {datachan.label} message {type} received: {msg}."); var loadTestMatch = Regex.Match(msg, @"^\s*(?<sendSize>\d+)\s*x\s*(?<testCount>\d+)"); if (loadTestMatch.Success) { uint sendSize = uint.Parse(loadTestMatch.Result("${sendSize}")); _loadTestCount = int.Parse(loadTestMatch.Result("${testCount}")); _loadTestCount = (_loadTestCount <= 0 || _loadTestCount > MAX_LOADTEST_COUNT) ? MAX_LOADTEST_COUNT : _loadTestCount; _loadTestPayloadSize = (sendSize > pc.sctp.maxMessageSize) ? pc.sctp.maxMessageSize : sendSize; logger.LogInformation($"Starting data channel binary load test, payload size {sendSize}, test count {_loadTestCount}."); DoLoadTestIteration(rdc, _loadTestPayloadSize); _loadTestCount--; } else { // Do a string echo. rdc.send($"echo: {msg}"); } break; } }; }; var dc = await pc.createDataChannel("test", null); pc.onconnectionstatechange += (state) => { logger.LogDebug($"Peer connection state change to {state}."); if (state == RTCPeerConnectionState.failed) { pc.Close("ice disconnection"); } }; // Diagnostics. //pc.OnReceiveReport += (re, media, rr) => logger.LogDebug($"RTCP Receive for {media} from {re}\n{rr.GetDebugSummary()}"); //pc.OnSendReport += (media, sr) => logger.LogDebug($"RTCP Send for {media}\n{sr.GetDebugSummary()}"); //pc.GetRtpChannel().OnStunMessageReceived += (msg, ep, isRelay) => logger.LogDebug($"STUN {msg.Header.MessageType} received from {ep}."); pc.oniceconnectionstatechange += (state) => logger.LogDebug($"ICE connection state change to {state}."); pc.onsignalingstatechange += () => logger.LogDebug($"Signalling state changed to {pc.signalingState}."); return(pc); }
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)); }
/// <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 }