/// <summary> /// Constructor to create a new RTC peer connection instance. /// </summary> /// <param name="configuration">Optional.</param> public RTCPeerConnection(RTCConfiguration configuration) : base(true, true, true, configuration?.X_BindAddress) { if (_configuration != null && _configuration.iceTransportPolicy == RTCIceTransportPolicy.relay && _configuration.iceServers?.Count == 0) { throw new ApplicationException("RTCPeerConnection must have at least one ICE server specified for a relay only transport policy."); } _configuration = configuration; if (_configuration != null) { if (_configuration.certificates?.Count > 0) { _currentCertificate = _configuration.certificates.First(); } if (_configuration.X_UseRtpFeedbackProfile) { RTP_MEDIA_PROFILE = RTP_MEDIA_FEEDBACK_PROFILE; } } SessionID = Guid.NewGuid().ToString(); LocalSdpSessionID = Crypto.GetRandomInt(5).ToString(); // Request the underlying RTP session to create a single RTP channel that will // be used to multiplex all required media streams. addSingleTrack(); _rtpIceChannel = GetRtpChannel(SDPMediaTypesEnum.audio) as RtpIceChannel; _rtpIceChannel.OnIceCandidate += (candidate) => onicecandidate?.Invoke(candidate); _rtpIceChannel.OnIceConnectionStateChange += (state) => { if (state == RTCIceConnectionState.connected && _rtpIceChannel.NominatedEntry != null) { var connectedEP = _rtpIceChannel.NominatedEntry.RemoteCandidate.DestinationEndPoint; base.SetDestination(SDPMediaTypesEnum.audio, connectedEP, connectedEP); } iceConnectionState = state; oniceconnectionstatechange?.Invoke(iceConnectionState); if (base.IsSecureContextReady && iceConnectionState == RTCIceConnectionState.connected && connectionState != RTCPeerConnectionState.connected) { // This is the case where the ICE connection checks completed after the DTLS handshake. connectionState = RTCPeerConnectionState.connected; onconnectionstatechange?.Invoke(RTCPeerConnectionState.connected); } }; _rtpIceChannel.OnIceGatheringStateChange += (state) => onicegatheringstatechange?.Invoke(state); _rtpIceChannel.OnIceCandidateError += (candidate, error) => onicecandidateerror?.Invoke(candidate, error); OnRtpClosed += Close; OnRtcpBye += Close; onnegotiationneeded?.Invoke(); _rtpIceChannel.StartGathering(); }
/// <summary> /// Constructor to create a new RTC peer connection instance. /// </summary> /// <param name="configuration">Optional.</param> public RTCPeerConnection(RTCConfiguration configuration) : base(true, true, true, configuration?.X_BindAddress) { if (_configuration != null && _configuration.iceTransportPolicy == RTCIceTransportPolicy.relay && _configuration.iceServers?.Count == 0) { throw new ApplicationException("RTCPeerConnection must have at least one ICE server specified for a relay only transport policy."); } if (configuration != null) { _configuration = configuration; if (_configuration.certificates?.Count > 0) { // Find the first certificate that has a usable private key. RTCCertificate usableCert = null; foreach (var cert in _configuration.certificates) { // Attempting to check that a certificate has an exportable private key. // TODO: Does not seem to be a particularly reliable way of checking private key exportability. if (cert.Certificate.HasPrivateKey) { //if (cert.Certificate.PrivateKey is RSACryptoServiceProvider) //{ // var rsa = cert.Certificate.PrivateKey as RSACryptoServiceProvider; // if (!rsa.CspKeyContainerInfo.Exportable) // { // logger.LogWarning($"RTCPeerConnection was passed a certificate for {cert.Certificate.FriendlyName} with a non-exportable RSA private key."); // } // else // { // usableCert = cert; // break; // } //} //else //{ usableCert = cert; break; //} } } if (usableCert == null) { throw new ApplicationException("RTCPeerConnection was not able to find a certificate from the input configuration list with a usable private key."); } else { _currentCertificate = usableCert; } } if (_configuration.X_UseRtpFeedbackProfile) { RTP_MEDIA_PROFILE = RTP_MEDIA_FEEDBACK_PROFILE; } } else { _configuration = new RTCConfiguration(); } // No certificate was provided so create a new self signed one. if (_configuration.certificates == null || _configuration.certificates.Count == 0) { _currentCertificate = new RTCCertificate { Certificate = DtlsUtils.CreateSelfSignedCert() }; _configuration.certificates = new List <RTCCertificate> { _currentCertificate }; } SessionID = Guid.NewGuid().ToString(); LocalSdpSessionID = Crypto.GetRandomInt(5).ToString(); // Request the underlying RTP session to create a single RTP channel that will // be used to multiplex all required media streams. addSingleTrack(); _rtpIceChannel = GetRtpChannel(); _rtpIceChannel.OnIceCandidate += (candidate) => onicecandidate?.Invoke(candidate); _rtpIceChannel.OnIceConnectionStateChange += (state) => { if (state == RTCIceConnectionState.connected && _rtpIceChannel.NominatedEntry != null) { var connectedEP = _rtpIceChannel.NominatedEntry.RemoteCandidate.DestinationEndPoint; base.SetDestination(SDPMediaTypesEnum.audio, connectedEP, connectedEP); logger.LogInformation($"ICE connected to remote end point {AudioDestinationEndPoint}."); DtlsSrtpTransport dtlsHandle = new DtlsSrtpTransport( IceRole == IceRolesEnum.active ? (IDtlsSrtpPeer) new DtlsSrtpClient(_currentCertificate.Certificate) : (IDtlsSrtpPeer) new DtlsSrtpServer(_currentCertificate.Certificate)); OnDtlsPacket += (buf) => { logger.LogDebug($"DTLS transport received {buf.Length} bytes from {AudioDestinationEndPoint}."); dtlsHandle.WriteToRecvStream(buf); }; logger.LogDebug($"Starting DLS handshake with role {IceRole}."); Task.Run <bool>(() => DoDtlsHandshake(dtlsHandle)) .ContinueWith(t => { if (t.IsFaulted) { logger.LogWarning($"RTCPeerConnection DTLS handshake task completed in a faulted state. {t.Exception?.Flatten().Message}"); connectionState = RTCPeerConnectionState.failed; onconnectionstatechange?.Invoke(connectionState); } else { connectionState = (t.Result) ? RTCPeerConnectionState.connected : connectionState = RTCPeerConnectionState.failed; onconnectionstatechange?.Invoke(connectionState); } }); } iceConnectionState = state; oniceconnectionstatechange?.Invoke(iceConnectionState); }; _rtpIceChannel.OnIceGatheringStateChange += (state) => onicegatheringstatechange?.Invoke(state); _rtpIceChannel.OnIceCandidateError += (candidate, error) => onicecandidateerror?.Invoke(candidate, error); OnRtpClosed += Close; OnRtcpBye += Close; onnegotiationneeded?.Invoke(); _rtpIceChannel.StartGathering(); }