예제 #1
0
        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));
        }
예제 #2
0
        public async Task Connect()
        {
            TaskCompletionSource <bool> dcAOpened = new TaskCompletionSource <bool>(TaskCreationOptions.RunContinuationsAsynchronously);

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

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

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

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

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

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

            await Task.WhenAll(dcAOpened.Task);
        }
        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();
        }
예제 #4
0
        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);
        }
예제 #5
0
        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;
        }
예제 #6
0
        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);
        }
예제 #7
0
        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);
        }
예제 #8
0
        /// <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;
            }
        }
예제 #9
0
        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);
        }
예제 #10
0
        /// <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;
            }
        }
예제 #11
0
 public RTCPeerClient(RTCPeerConnection peerConnection)
 {
     this.peerConnection = peerConnection;
     dataChannel         = peerConnection.createDataChannel(Guid.NewGuid().ToString());
 }
예제 #12
0
        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);
        }
예제 #13
0
        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));
        }
예제 #14
0
        /// <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
        }