コード例 #1
0
        public void Close(string reason)
        {
            IsClosed           = true;
            IceConnectionState = IceConnectionStatesEnum.Closed;
            m_stunChecksTimer.Dispose();
            _rtpSession.CloseSession(reason);

            OnClose?.Invoke(reason);
        }
コード例 #2
0
        /// <summary>
        /// Close the session including the underlying RTP session and channels.
        /// </summary>
        /// <param name="reason">An optional descriptive reason for the closure.</param>
        public void Close(string reason)
        {
            if (!IsClosed)
            {
                IceConnectionState = IceConnectionStatesEnum.Closed;
                m_stunChecksTimer.Dispose();
                CloseSession(reason);

                OnClose?.Invoke(reason);
            }
        }
コード例 #3
0
        private void SetIceConnectionState(IceConnectionStatesEnum iceConnectionState)
        {
            try
            {
                IceConnectionState = iceConnectionState;

                OnIceStateChange?.Invoke(iceConnectionState);
            }
            catch (Exception excp)
            {
                logger.Error("Exception SetIceConnectionState. " + excp);
            }
        }
コード例 #4
0
ファイル: WebRtcPeer.cs プロジェクト: pookokok/sipsorcery
        private void SetIceConnectionState(IceConnectionStatesEnum iceConnectionState)
        {
            try
            {
                IceConnectionState = iceConnectionState;

                if (OnIceStateChange != null)
                {
                    OnIceStateChange(iceConnectionState);
                }
            }
            catch (Exception excp)
            {
                logger.LogError("Exception SetIceConnectionState. " + excp);
            }
        }
コード例 #5
0
        private void ProcessStunMessage(STUNv2Message stunMessage, IPEndPoint remoteEndPoint)
        {
            //logger.LogDebug("STUN message received from remote " + remoteEndPoint + " " + stunMessage.Header.MessageType + ".");

            if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingRequest)
            {
                STUNv2Message stunResponse = new STUNv2Message(STUNv2MessageTypesEnum.BindingSuccessResponse);
                stunResponse.Header.TransactionId = stunMessage.Header.TransactionId;
                stunResponse.AddXORMappedAddressAttribute(remoteEndPoint.Address, remoteEndPoint.Port);

                // ToDo: Check authentication.

                string localIcePassword = LocalIcePassword;
                byte[] stunRespBytes    = stunResponse.ToByteBufferStringKey(localIcePassword, true);
                //iceCandidate.LocalRtpSocket.SendTo(stunRespBytes, remoteEndPoint);
                _rtpChannel.SendAsync(RTPChannelSocketsEnum.RTP, remoteEndPoint, stunRespBytes);

                //iceCandidate.LastStunRequestReceivedAt = DateTime.Now;
                //iceCandidate.IsStunRemoteExchangeComplete = true;

                if (RemoteEndPoint == null)
                {
                    RemoteEndPoint = remoteEndPoint;
                    _rtpSession.DestinationEndPoint = RemoteEndPoint;
                    _rtpSession.RtcpSession.ControlDestinationEndPoint = RemoteEndPoint;
                    //OnIceConnected?.Invoke(iceCandidate, remoteEndPoint);
                    IceConnectionState = IceConnectionStatesEnum.Connected;
                }

                if (_remoteIceCandidates != null && !_remoteIceCandidates.Any(x =>
                                                                              (x.NetworkAddress == remoteEndPoint.Address.ToString() || x.RemoteAddress == remoteEndPoint.Address.ToString()) &&
                                                                              (x.Port == remoteEndPoint.Port || x.RemotePort == remoteEndPoint.Port)))
                {
                    // This STUN request has come from a socket not in the remote ICE candidates list. Add it so we can send our STUN binding request to it.
                    IceCandidate remoteIceCandidate = new IceCandidate("udp", remoteEndPoint.Address, remoteEndPoint.Port, IceCandidateTypesEnum.host);
                    logger.LogDebug("Adding missing remote ICE candidate for " + remoteEndPoint + ".");
                    _remoteIceCandidates.Add(remoteIceCandidate);

                    // Some browsers require a STUN binding request from our end before the DTLS handshake will be initiated.
                    // The STUN connectivity checks are already scheduled but we can speed things up by sending a binding
                    // request immediately.
                    SendStunConnectivityChecks(null);
                }
            }
            else if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingSuccessResponse)
            {
                // TODO: What needs to be done here?

                //if (_turnServerEndPoint != null && remoteEndPoint.ToString() == _turnServerEndPoint.ToString())
                //{
                //    if (iceCandidate.IsGatheringComplete == false)
                //    {
                //        var reflexAddressAttribute = stunMessage.Attributes.FirstOrDefault(y => y.AttributeType == STUNv2AttributeTypesEnum.XORMappedAddress) as STUNv2XORAddressAttribute;

                //        if (reflexAddressAttribute != null)
                //        {
                //            iceCandidate.StunRflxIPEndPoint = new IPEndPoint(reflexAddressAttribute.Address, reflexAddressAttribute.Port);
                //            iceCandidate.IsGatheringComplete = true;

                //            logger.LogDebug("ICE gathering complete for local socket " + iceCandidate.RtpChannel.RTPLocalEndPoint + ", rflx address " + iceCandidate.StunRflxIPEndPoint + ".");
                //        }
                //        else
                //        {
                //            iceCandidate.IsGatheringComplete = true;

                //            logger.LogDebug("The STUN binding response received on " + iceCandidate.RtpChannel.RTPLocalEndPoint + " from " + remoteEndPoint + " did not have an XORMappedAddress attribute, rlfx address can not be determined.");
                //        }
                //    }
                //}
                //else
                //{
                //    iceCandidate.LastStunResponseReceivedAt = DateTime.Now;

                //    if (iceCandidate.IsStunLocalExchangeComplete == false)
                //    {
                //        iceCandidate.IsStunLocalExchangeComplete = true;
                //        logger.LogDebug("WebRTC client STUN exchange complete for call " + CallID + ", candidate local socket " + iceCandidate.RtpChannel.RTPLocalEndPoint + ", remote socket " + remoteEndPoint + ".");

                //        SetIceConnectionState(IceConnectionStatesEnum.Connected);
                //    }
                //}
            }
            else if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingErrorResponse)
            {
                logger.LogWarning($"A STUN binding error response was received from {remoteEndPoint}.");
            }
            else
            {
                logger.LogWarning($"An unrecognised STUN request was received from {remoteEndPoint}.");
            }
        }
コード例 #6
0
        /// <summary>
        /// Initialises the WebRTC session by carrying out the ICE connectivity steps and when complete
        /// handing the RTP socket off for the DTLS handshake. Once the handshake is complete the session
        /// is ready for to exchange encrypted RTP and RTCP packets.
        /// </summary>
        /// <param name="turnServerEndPoint">An optional parameter that can be used include a TURN
        /// server in this session's ICE candidate gathering.</param>
        public async Task Initialise(DoDtlsHandshakeDelegate doDtlsHandshake, IPEndPoint turnServerEndPoint)
        {
            try
            {
                _doDtlsHandshake    = doDtlsHandshake;
                _turnServerEndPoint = turnServerEndPoint;

                DateTime startGatheringTime = DateTime.Now;

                IceConnectionState = IceConnectionStatesEnum.Gathering;

                await GetIceCandidatesAsync();

                logger.LogDebug($"ICE gathering completed for in {DateTime.Now.Subtract(startGatheringTime).TotalMilliseconds:#}ms, candidate count {LocalIceCandidates.Count}.");

                IceConnectionState = IceConnectionStatesEnum.GatheringComplete;

                if (LocalIceCandidates.Count == 0)
                {
                    logger.LogWarning("No local socket candidates were found for WebRTC call closing.");
                    Close("No local ICE candidates available.");
                }
                else
                {
                    string localIceCandidateString = null;

                    foreach (var iceCandidate in LocalIceCandidates)
                    {
                        localIceCandidateString += iceCandidate.ToString();
                    }

                    LocalIceUser     = LocalIceUser ?? Crypto.GetRandomString(20);
                    LocalIcePassword = LocalIcePassword ?? Crypto.GetRandomString(20) + Crypto.GetRandomString(20);
                    //var localIceCandidate = GetIceCandidatesForMediaType(RtpMediaTypesEnum.None).First();

                    var offerHeader = String.Format(_sdpOfferTemplate, Crypto.GetRandomInt(10).ToString());

                    string dtlsAttribute         = String.Format(_dtlsFingerprint, _dtlsCertificateFingerprint);
                    string rtpSecurityDescriptor = RTP_MEDIA_SECURE_DESCRIPTOR;

                    var audioOffer = String.Format(_sdpAudioPcmOfferTemplate,
                                                   _rtpChannel.RTPPort,
                                                   rtpSecurityDescriptor,
                                                   IPAddress.Loopback,
                                                   localIceCandidateString.TrimEnd(),
                                                   LocalIceUser,
                                                   LocalIcePassword,
                                                   dtlsAttribute);

                    var videoOffer = String.Format(_sdpVideoOfferTemplate,
                                                   rtpSecurityDescriptor,
                                                   IPAddress.Loopback,
                                                   LocalIceUser,
                                                   LocalIcePassword,
                                                   dtlsAttribute);

                    string offer = offerHeader + audioOffer + videoOffer;

                    //logger.LogDebug("WebRTC Offer SDP: " + offer);

                    SDP = offer;

                    OnSdpOfferReady?.Invoke(offer);
                }

                // We may have received some remote candidates from the remote part SDP so perform an immediate STUN check.
                // If there are no remote candidates this call will end up being a NOP.
                SendStunConnectivityChecks(null);

                if (_doDtlsHandshake != null)
                {
                    _ = Task.Run(() =>
                    {
                        int result = _doDtlsHandshake(this, _rtpChannel.m_rtpSocket, out _rtpSession.SrtpProtect, out _rtpSession.RtcpSession.SrtcpProtect);
                        IsDtlsNegotiationComplete = (result == 0);
                    });
                }
            }
            catch (Exception excp)
            {
                logger.LogError("Exception WebRtcPeer.Initialise. " + excp);
                Close(excp.Message);
            }
        }
コード例 #7
0
        /// <summary>
        /// Generates the base SDP for an offer or answer. The SDP will then be tailored depending
        /// on whether it's being used in an offer or an answer.
        /// </summary>
        private async Task <SDP> createBaseSdp()
        {
            DateTime startGatheringTime = DateTime.Now;

            IceConnectionState = IceConnectionStatesEnum.Gathering;

            await GetIceCandidatesAsync().ConfigureAwait(false);

            logger.LogDebug($"ICE gathering completed for in {DateTime.Now.Subtract(startGatheringTime).TotalMilliseconds:#}ms, candidate count {LocalIceCandidates.Count}.");

            IceConnectionState = IceConnectionStatesEnum.GatheringComplete;

            if (LocalIceCandidates.Count == 0)
            {
                //logger.LogWarning("No local socket candidates were found for WebRTC call closing.");
                //Close("No local ICE candidates available.");
                throw new ApplicationException("No local ICE candidates available.");
            }
            else
            {
                SDP offerSdp = new SDP(IPAddress.Loopback);
                offerSdp.SessionId = LocalSdpSessionID;

                bool   haveIceCandidatesBeenAdded = false;
                string localIceCandidateString    = null;

                foreach (var iceCandidate in LocalIceCandidates)
                {
                    localIceCandidateString += iceCandidate.ToString();
                }

                // Add a bundle attribute. Indicates that audio and video sessions will be multiplexed
                // on a single RTP socket.
                if (AudioLocalTrack != null && VideoLocalTrack != null)
                {
                    offerSdp.Group = MEDIA_GROUPING;
                }

                // The media is being multiplexed so the audio and video RTP channel is the same.
                var rtpChannel = GetRtpChannel(SDPMediaTypesEnum.audio);

                // --- Audio announcement ---
                if (AudioLocalTrack != null)
                {
                    SDPMediaAnnouncement audioAnnouncement = new SDPMediaAnnouncement(
                        SDPMediaTypesEnum.audio,
                        rtpChannel.RTPPort,
                        AudioLocalTrack.Capabilties);

                    audioAnnouncement.Transport = RTP_MEDIA_PROFILE;

                    if (!haveIceCandidatesBeenAdded)
                    {
                        audioAnnouncement.IceCandidates = LocalIceCandidates;
                        haveIceCandidatesBeenAdded      = true;
                    }

                    audioAnnouncement.Connection      = new SDPConnectionInformation(IPAddress.Any);
                    audioAnnouncement.IceUfrag        = LocalIceUser;
                    audioAnnouncement.IcePwd          = LocalIcePassword;
                    audioAnnouncement.DtlsFingerprint = _dtlsCertificateFingerprint;
                    audioAnnouncement.AddExtra(RTCP_MUX_ATTRIBUTE);
                    audioAnnouncement.MediaStreamStatus = AudioLocalTrack.Transceiver.Direction;
                    audioAnnouncement.MediaID           = AudioLocalTrack.Transceiver.MID;

                    offerSdp.Media.Add(audioAnnouncement);
                }

                // --- Video announcement ---
                if (VideoLocalTrack != null)
                {
                    SDPMediaAnnouncement videoAnnouncement = new SDPMediaAnnouncement(
                        SDPMediaTypesEnum.video,
                        rtpChannel.RTPPort,
                        VideoLocalTrack.Capabilties);

                    videoAnnouncement.Transport = RTP_MEDIA_PROFILE;

                    if (!haveIceCandidatesBeenAdded)
                    {
                        videoAnnouncement.IceCandidates = LocalIceCandidates;
                        haveIceCandidatesBeenAdded      = true;
                    }

                    videoAnnouncement.Connection      = new SDPConnectionInformation(IPAddress.Any);
                    videoAnnouncement.IceUfrag        = LocalIceUser;
                    videoAnnouncement.IcePwd          = LocalIcePassword;
                    videoAnnouncement.DtlsFingerprint = _dtlsCertificateFingerprint;
                    videoAnnouncement.AddExtra(RTCP_MUX_ATTRIBUTE);
                    videoAnnouncement.MediaStreamStatus = VideoLocalTrack.Transceiver.Direction;
                    videoAnnouncement.MediaID           = VideoLocalTrack.Transceiver.MID;

                    offerSdp.Media.Add(videoAnnouncement);
                }

                return(offerSdp);
            }
        }
コード例 #8
0
ファイル: WebRtcSession.cs プロジェクト: bleissem/sipsorcery
        /// <summary>
        /// Generates the SDP for an offer or answer.
        /// </summary>
        private async Task <SDP> getSdp(string setupAttribute)
        {
            try
            {
                DateTime startGatheringTime = DateTime.Now;

                IceConnectionState = IceConnectionStatesEnum.Gathering;

                await GetIceCandidatesAsync();

                logger.LogDebug($"ICE gathering completed for in {DateTime.Now.Subtract(startGatheringTime).TotalMilliseconds:#}ms, candidate count {LocalIceCandidates.Count}.");

                IceConnectionState = IceConnectionStatesEnum.GatheringComplete;

                if (LocalIceCandidates.Count == 0)
                {
                    //logger.LogWarning("No local socket candidates were found for WebRTC call closing.");
                    //Close("No local ICE candidates available.");
                    throw new ApplicationException("No local ICE candidates available.");
                }
                else
                {
                    bool haveIceCandidatesBeenAdded = false;

                    string localIceCandidateString = null;

                    foreach (var iceCandidate in LocalIceCandidates)
                    {
                        localIceCandidateString += iceCandidate.ToString();
                    }

                    LocalIceUser     = LocalIceUser ?? Crypto.GetRandomString(20);
                    LocalIcePassword = LocalIcePassword ?? Crypto.GetRandomString(20) + Crypto.GetRandomString(20);

                    SDP offerSdp = new SDP(IPAddress.Loopback);
                    offerSdp.SessionId = Crypto.GetRandomInt(5).ToString();

                    // Add a bundle attribute. Indicates that audio and video sessions will be multiplexed
                    // on a single RTP socket.
                    if (RtpSession.AudioTrack != null && RtpSession.VideoTrack != null)
                    {
                        offerSdp.Group = MEDIA_GROUPING;
                    }

                    // --- Audio announcement ---
                    if (RtpSession.AudioTrack != null)
                    {
                        SDPMediaAnnouncement audioAnnouncement = new SDPMediaAnnouncement(
                            SDPMediaTypesEnum.audio,
                            _rtpChannel.RTPPort,
                            RtpSession.AudioTrack.Capabilties);

                        audioAnnouncement.Transport = RTP_MEDIA_PROFILE;
                        if (!haveIceCandidatesBeenAdded)
                        {
                            audioAnnouncement.IceCandidates = LocalIceCandidates;
                            haveIceCandidatesBeenAdded      = true;
                        }

                        audioAnnouncement.Connection      = new SDPConnectionInformation(IPAddress.Any);
                        audioAnnouncement.IceUfrag        = LocalIceUser;
                        audioAnnouncement.IcePwd          = LocalIcePassword;
                        audioAnnouncement.DtlsFingerprint = _dtlsCertificateFingerprint;
                        audioAnnouncement.AddExtra(RTCP_MUX_ATTRIBUTE);
                        audioAnnouncement.AddExtra(setupAttribute);
                        audioAnnouncement.MediaStreamStatus = RtpSession.AudioTrack.Transceiver.Direction;
                        audioAnnouncement.MediaID           = RtpSession.AudioTrack.Transceiver.MID;

                        offerSdp.Media.Add(audioAnnouncement);
                    }

                    // --- Video announcement ---
                    if (RtpSession.VideoTrack != null)
                    {
                        SDPMediaAnnouncement videoAnnouncement = new SDPMediaAnnouncement(
                            SDPMediaTypesEnum.video,
                            _rtpChannel.RTPPort,
                            RtpSession.VideoTrack.Capabilties);

                        videoAnnouncement.Transport = RTP_MEDIA_PROFILE;
                        if (!haveIceCandidatesBeenAdded)
                        {
                            videoAnnouncement.IceCandidates = LocalIceCandidates;
                            haveIceCandidatesBeenAdded      = true;
                        }

                        videoAnnouncement.Connection      = new SDPConnectionInformation(IPAddress.Any);
                        videoAnnouncement.IceUfrag        = LocalIceUser;
                        videoAnnouncement.IcePwd          = LocalIcePassword;
                        videoAnnouncement.DtlsFingerprint = _dtlsCertificateFingerprint;
                        videoAnnouncement.AddExtra(RTCP_MUX_ATTRIBUTE);
                        videoAnnouncement.AddExtra(setupAttribute);
                        videoAnnouncement.MediaStreamStatus = RtpSession.VideoTrack.Transceiver.Direction;
                        videoAnnouncement.MediaID           = RtpSession.VideoTrack.Transceiver.MID;;

                        offerSdp.Media.Add(videoAnnouncement);
                    }

                    OnSdpOfferReady?.Invoke(offerSdp);

                    return(offerSdp);
                }

                // We may have received some remote candidates from the remote part SDP so perform an immediate STUN check.
                // If there are no remote candidates this call will end up being a NOP.
                //SendStunConnectivityChecks(null);

                //if (_doDtlsHandshake != null)
                //{
                //    _ = Task.Run(() =>
                //    {
                //        int result = _doDtlsHandshake(this);
                //        IsDtlsNegotiationComplete = (result == 0);
                //    });
                //}
            }
            catch (Exception excp)
            {
                logger.LogError("Exception getSdp. " + excp);
                Close(excp.Message);

                throw;
            }
        }
コード例 #9
0
ファイル: WebRtcPeer.cs プロジェクト: sipsorcery/sipsorcery
        private void SetIceConnectionState(IceConnectionStatesEnum iceConnectionState)
        {
            try
            {
                IceConnectionState = iceConnectionState;

                if (OnIceStateChange != null)
                {
                    OnIceStateChange(iceConnectionState);
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception SetIceConnectionState. " + excp);
            }
        }
コード例 #10
0
ファイル: WebRtcSession.cs プロジェクト: spFly/sip
        /// <summary>
        /// Initialises the WebRTC session by carrying out the ICE connectivity steps and when complete
        /// handing the RTP socket off for the DTLS handshake. Once the handshake is complete the session
        /// is ready for to exchange encrypted RTP and RTCP packets.
        /// </summary>
        /// <param name="turnServerEndPoint">An optional parameter that can be used include a TURN
        /// server in this session's ICE candidate gathering.</param>
        public async Task Initialise(DoDtlsHandshakeDelegate doDtlsHandshake, IPEndPoint turnServerEndPoint)
        {
            try
            {
                _doDtlsHandshake    = doDtlsHandshake;
                _turnServerEndPoint = turnServerEndPoint;

                DateTime startGatheringTime = DateTime.Now;

                IceConnectionState = IceConnectionStatesEnum.Gathering;

                await GetIceCandidatesAsync();

                logger.LogDebug($"ICE gathering completed for in {DateTime.Now.Subtract(startGatheringTime).TotalMilliseconds:#}ms, candidate count {LocalIceCandidates.Count}.");

                IceConnectionState = IceConnectionStatesEnum.GatheringComplete;

                if (LocalIceCandidates.Count == 0)
                {
                    logger.LogWarning("No local socket candidates were found for WebRTC call closing.");
                    Close("No local ICE candidates available.");
                }
                else
                {
                    bool includeAudioOffer          = _supportedAudioFormats?.Count() > 0;
                    bool includeVideoOffer          = _supportedVideoFormats?.Count() > 0;
                    bool haveIceCandidatesBeenAdded = false;
                    bool isMediaBundle = includeAudioOffer && includeVideoOffer;    // Is this SDP offer bundling audio and video on the same RTP connection.

                    string localIceCandidateString = null;

                    foreach (var iceCandidate in LocalIceCandidates)
                    {
                        localIceCandidateString += iceCandidate.ToString();
                    }

                    LocalIceUser     = LocalIceUser ?? Crypto.GetRandomString(20);
                    LocalIcePassword = LocalIcePassword ?? Crypto.GetRandomString(20) + Crypto.GetRandomString(20);

                    SDP offerSdp = new SDP(IPAddress.Loopback);
                    offerSdp.SessionId = Crypto.GetRandomInt(5).ToString();

                    // Add a bundle attribute. Indicates that audio and video sessions will be multiplexed
                    // on a single RTP socket.
                    if (isMediaBundle)
                    {
                        offerSdp.Group = MEDIA_GROUPING;
                    }

                    if (includeAudioOffer)
                    {
                        SDPMediaAnnouncement audioAnnouncement = new SDPMediaAnnouncement(
                            SDPMediaTypesEnum.audio,
                            _rtpChannel.RTPPort,
                            _supportedAudioFormats);

                        audioAnnouncement.Transport = RTP_MEDIA_PROFILE;
                        if (!haveIceCandidatesBeenAdded)
                        {
                            audioAnnouncement.IceCandidates = LocalIceCandidates;
                            haveIceCandidatesBeenAdded      = true;
                        }

                        audioAnnouncement.Connection      = new SDPConnectionInformation(IPAddress.Any);
                        audioAnnouncement.IceUfrag        = LocalIceUser;
                        audioAnnouncement.IcePwd          = LocalIcePassword;
                        audioAnnouncement.DtlsFingerprint = _dtlsCertificateFingerprint;
                        audioAnnouncement.AddExtra(RTCP_MUX_ATTRIBUTE);
                        audioAnnouncement.AddExtra(SETUP_ATTRIBUTE);
                        audioAnnouncement.MediaStreamStatus = AudioStreamStatus;

                        if (isMediaBundle)
                        {
                            audioAnnouncement.MediaID = AUDIO_MEDIA_ID;
                        }

                        offerSdp.Media.Add(audioAnnouncement);
                    }

                    if (includeVideoOffer)
                    {
                        SDPMediaAnnouncement videoAnnouncement = new SDPMediaAnnouncement(
                            SDPMediaTypesEnum.video,
                            _rtpChannel.RTPPort,
                            _supportedVideoFormats);

                        videoAnnouncement.Transport = RTP_MEDIA_PROFILE;
                        if (!haveIceCandidatesBeenAdded)
                        {
                            videoAnnouncement.IceCandidates = LocalIceCandidates;
                            haveIceCandidatesBeenAdded      = true;
                        }

                        videoAnnouncement.Connection      = new SDPConnectionInformation(IPAddress.Any);
                        videoAnnouncement.IceUfrag        = LocalIceUser;
                        videoAnnouncement.IcePwd          = LocalIcePassword;
                        videoAnnouncement.DtlsFingerprint = _dtlsCertificateFingerprint;
                        videoAnnouncement.AddExtra(RTCP_MUX_ATTRIBUTE);
                        videoAnnouncement.AddExtra(SETUP_ATTRIBUTE);
                        videoAnnouncement.MediaStreamStatus = VideoStreamStatus;

                        if (isMediaBundle)
                        {
                            videoAnnouncement.MediaID = VIDEO_MEDIA_ID;
                        }

                        offerSdp.Media.Add(videoAnnouncement);
                    }

                    SDP = offerSdp;

                    OnSdpOfferReady?.Invoke(SDP);
                }

                // We may have received some remote candidates from the remote part SDP so perform an immediate STUN check.
                // If there are no remote candidates this call will end up being a NOP.
                SendStunConnectivityChecks(null);

                if (_doDtlsHandshake != null)
                {
                    _ = Task.Run(() =>
                    {
                        int result = _doDtlsHandshake(this);
                        IsDtlsNegotiationComplete = (result == 0);
                    });
                }
            }
            catch (Exception excp)
            {
                logger.LogError("Exception WebRtcPeer.Initialise. " + excp);
                Close(excp.Message);
            }
        }