private async Task <string> OnMessage(string jsonStr, RTCPeerConnection pc) { if (RTCIceCandidateInit.TryParse(jsonStr, out var iceCandidateInit)) { logger.LogDebug("Got remote ICE candidate."); pc.addIceCandidate(iceCandidateInit); } else if (RTCSessionDescriptionInit.TryParse(jsonStr, out var descriptionInit)) { logger.LogDebug($"Got remote SDP, type {descriptionInit.type}."); var result = pc.setRemoteDescription(descriptionInit); if (result != SetDescriptionResultEnum.OK) { logger.LogWarning($"Failed to set remote description, {result}."); pc.Close("failed to set remote description"); } if (descriptionInit.type == RTCSdpType.offer) { var answerSdp = pc.createAnswer(null); await pc.setLocalDescription(answerSdp); return(answerSdp.toJSON()); } } else { logger.LogWarning($"node-dss could not parse JSON message. {jsonStr}"); } return(null); }
protected override void OnMessage(MessageEventArgs e) { logger.LogDebug($"OnMessage: {e.Data}"); if (RTCIceCandidateInit.TryParse(e.Data, out var iceCandidateInit)) { logger.LogDebug("Got remote ICE candidate."); _pc.addIceCandidate(iceCandidateInit); } else if (RTCSessionDescriptionInit.TryParse(e.Data, out var descriptionInit)) { logger.LogDebug($"Got remote SDP, type {descriptionInit.type}."); var result = _pc.setRemoteDescription(descriptionInit); if (result != SetDescriptionResultEnum.OK) { logger.LogWarning($"Failed to set remote description, {result}."); _pc.Close("failed to set remote description"); this.Close(); } } else { logger.LogWarning($"websocket-server could not parse JSON message. {e.Data}"); } }
/// <summary> /// This set remote description overload is a convenience method for SIP/VoIP callers /// instead of WebRTC callers. The method signature better matches what the SIP /// user agent is expecting. /// TODO: Using two very similar overloads could cause confusion. Possibly /// consolidate. /// </summary> /// <param name="sdpType">Whether the remote SDP is an offer or answer.</param> /// <param name="sessionDescription">The SDP from the remote party.</param> /// <returns>The result of attempting to set the remote description.</returns> public override SetDescriptionResultEnum SetRemoteDescription(SdpType sdpType, SDP sessionDescription) { RTCSessionDescriptionInit init = new RTCSessionDescriptionInit { sdp = sessionDescription.ToString(), type = (sdpType == SdpType.answer) ? RTCSdpType.answer : RTCSdpType.offer }; return(setRemoteDescription(init)); }
protected override async void OnMessage(MessageEventArgs e) { //logger.LogDebug($"OnMessage: {e.Data}"); if (RTCIceCandidateInit.TryParse(e.Data, out var iceCandidateInit)) { logger.LogDebug("Got remote ICE candidate."); bool useCandidate = true; if (FilterRemoteICECandidates != null && !string.IsNullOrWhiteSpace(iceCandidateInit.candidate)) { useCandidate = FilterRemoteICECandidates(iceCandidateInit); } if (!useCandidate) { logger.LogDebug($"WebRTCWebSocketPeer excluding ICE candidate due to filter: {iceCandidateInit.candidate}"); } else { _pc.addIceCandidate(iceCandidateInit); } } else if (RTCSessionDescriptionInit.TryParse(e.Data, out var descriptionInit)) { logger.LogDebug($"Got remote SDP, type {descriptionInit.type}."); var result = _pc.setRemoteDescription(descriptionInit); if (result != SetDescriptionResultEnum.OK) { logger.LogWarning($"Failed to set remote description, {result}."); _pc.Close("failed to set remote description"); this.Close(); } else { if (_pc.signalingState == RTCSignalingState.have_remote_offer) { var answerSdp = _pc.createAnswer(AnswerOptions); await _pc.setLocalDescription(answerSdp).ConfigureAwait(false); logger.LogDebug($"Sending SDP answer to client {Context.UserEndPoint}."); //logger.LogDebug(answerSdp.sdp); Context.WebSocket.Send(answerSdp.toJSON()); } } } else { logger.LogWarning($"websocket-server could not parse JSON message. {e.Data}"); } }
private async Task <string> OnMessage(string signal, RTCPeerConnection pc) { string sdpAnswer = null; if (RTCIceCandidateInit.TryParse(signal, out var iceCandidateInit)) { logger.LogDebug($"Got remote ICE candidate, {iceCandidateInit.candidate}"); bool useCandidate = true; if (FilterRemoteICECandidates != null && !string.IsNullOrWhiteSpace(iceCandidateInit.candidate)) { useCandidate = FilterRemoteICECandidates(iceCandidateInit); } if (!useCandidate) { logger.LogDebug($"WebRTCRestPeer excluding ICE candidate due to filter: {iceCandidateInit.candidate}"); } else { _pc.addIceCandidate(iceCandidateInit); } } else if (RTCSessionDescriptionInit.TryParse(signal, out var descriptionInit)) { logger.LogDebug($"Got remote SDP, type {descriptionInit.type}."); //logger.LogDebug(descriptionInit.sdp); var result = pc.setRemoteDescription(descriptionInit); if (result != SetDescriptionResultEnum.OK) { logger.LogWarning($"Failed to set remote description, {result}."); pc.Close("failed to set remote description"); } else if (descriptionInit.type == RTCSdpType.offer) { var answerSdp = pc.createAnswer(AnswerOptions); await pc.setLocalDescription(answerSdp).ConfigureAwait(false); sdpAnswer = answerSdp.toJSON(); } } else { logger.LogWarning($"webrtc-rest could not parse JSON message. {signal}"); } return(sdpAnswer); }
protected override void OnMessage(MessageEventArgs e) { logger.LogDebug($"OnMessage: {e.Data}"); if (RTCIceCandidateInit.TryParse(e.Data, out var iceCandidateInit)) { logger.LogDebug("Got remote ICE candidate."); bool useCandidate = true; if (FilterRemoteICECandidates != null && !string.IsNullOrWhiteSpace(iceCandidateInit.candidate)) { useCandidate = FilterRemoteICECandidates(iceCandidateInit); } if (!useCandidate) { logger.LogDebug( $"WebRTCWebSocketPeer excluding ICE candidate due to filter: {iceCandidateInit.candidate}"); } else { _pc.addIceCandidate(iceCandidateInit); } } else if (RTCSessionDescriptionInit.TryParse(e.Data, out var descriptionInit)) { logger.LogDebug($"Got remote SDP, type {descriptionInit.type}."); var result = _pc.setRemoteDescription(descriptionInit); if (result != SetDescriptionResultEnum.OK) { logger.LogWarning($"Failed to set remote description, {result}."); _pc.Close("failed to set remote description"); this.Close(); } } else { logger.LogWarning($"websocket-server could not parse JSON message. {e.Data}"); } }
/// <summary> /// Sets the local SDP. /// </summary> /// <remarks> /// As specified in https://www.w3.org/TR/webrtc/#dom-peerconnection-setlocaldescription. /// </remarks> /// <param name="description">Optional. The session description to set as /// local description. If not supplied then an offer or answer will be created as required. /// </param> public Task setLocalDescription(RTCSessionDescriptionInit init) { RTCSessionDescription description = new RTCSessionDescription { type = init.type, sdp = SDP.ParseSDPDescription(init.sdp) }; localDescription = description; if (init.type == RTCSdpType.offer) { _rtpIceChannel.IsController = true; } // This is the point the ICE session potentially starts contacting STUN and TURN servers. //IceSession.StartGathering(); signalingState = RTCSignalingState.have_local_offer; onsignalingstatechange?.Invoke(); return(Task.CompletedTask); }
protected override void OnMessage(MessageEventArgs e) { logger.LogDebug($"OnMessage: {e.Data}"); if (Regex.Match(e.Data, @"^.*?candidate.*?,").Success) { logger.LogDebug("Got remote ICE candidate."); var iceCandidateInit = JsonConvert.DeserializeObject <RTCIceCandidateInit>(e.Data); _pc.addIceCandidate(iceCandidateInit); } else { RTCSessionDescriptionInit descriptionInit = JsonConvert.DeserializeObject <RTCSessionDescriptionInit>(e.Data); var result = _pc.setRemoteDescription(descriptionInit); if (result != SetDescriptionResultEnum.OK) { logger.LogWarning($"Failed to set remote description, {result}."); _pc.Close("failed to set remote description"); this.Close(); } } }
/// <summary> /// Creates an answer to an SDP offer from a remote peer. /// </summary> /// <remarks> /// As specified in https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-createanswer and /// https://tools.ietf.org/html/rfc3264#section-6.1. /// </remarks> /// <param name="options">Optional. If supplied the options will be used to apply additional /// controls over the generated answer SDP.</param> public RTCSessionDescriptionInit createAnswer(RTCAnswerOptions options) { if (remoteDescription == null) { throw new ApplicationException("The remote SDP must be set before an SDP answer can be created."); } else { var audioCapabilities = (AudioLocalTrack != null && AudioRemoteTrack != null) ? SDPMediaFormat.GetCompatibleFormats(AudioLocalTrack.Capabilities, AudioRemoteTrack.Capabilities) : null; var videoCapabilities = (VideoLocalTrack != null && VideoRemoteTrack != null) ? SDPMediaFormat.GetCompatibleFormats(VideoLocalTrack.Capabilities, VideoRemoteTrack.Capabilities) : null; List <MediaStreamTrack> localTracks = GetLocalTracks(); var answerSdp = createBaseSdp(localTracks, audioCapabilities, videoCapabilities); if (answerSdp.Media.Any(x => x.Media == SDPMediaTypesEnum.audio)) { var audioAnnouncement = answerSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.audio).Single(); audioAnnouncement.AddExtra($"{ICE_SETUP_ATTRIBUTE}{IceRole}"); } if (answerSdp.Media.Any(x => x.Media == SDPMediaTypesEnum.video)) { var videoAnnouncement = answerSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.video).Single(); videoAnnouncement.AddExtra($"{ICE_SETUP_ATTRIBUTE}{IceRole}"); } RTCSessionDescriptionInit initDescription = new RTCSessionDescriptionInit { type = RTCSdpType.answer, sdp = answerSdp.ToString() }; return(initDescription); } }
/// <summary> /// Generates the SDP for an offer that can be made to a remote peer. /// </summary> /// <remarks> /// As specified in https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-createoffer. /// </remarks> /// <param name="options">Optional. If supplied the options will be sued to apply additional /// controls over the generated offer SDP.</param> public RTCSessionDescriptionInit createOffer(RTCOfferOptions options) { try { var audioCapabilities = AudioLocalTrack?.Capabilities; var videoCapabilities = VideoLocalTrack?.Capabilities; List <MediaStreamTrack> localTracks = GetLocalTracks(); var offerSdp = createBaseSdp(localTracks, audioCapabilities, videoCapabilities); if (offerSdp.Media.Any(x => x.Media == SDPMediaTypesEnum.audio)) { var audioAnnouncement = offerSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.audio).Single(); audioAnnouncement.AddExtra($"{ICE_SETUP_ATTRIBUTE}{IceRole}"); } if (offerSdp.Media.Any(x => x.Media == SDPMediaTypesEnum.video)) { var videoAnnouncement = offerSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.video).Single(); videoAnnouncement.AddExtra($"{ICE_SETUP_ATTRIBUTE}{IceRole}"); } RTCSessionDescriptionInit initDescription = new RTCSessionDescriptionInit { type = RTCSdpType.offer, sdp = offerSdp.ToString() }; return(initDescription); } catch (Exception excp) { logger.LogError("Exception createOffer. " + excp); throw; } }
/// <summary> /// Updates the session after receiving the remote SDP. /// At this point check that the codecs match. We currently only support: /// - Audio: PCMU, /// - Video: VP8. /// If they are not available there's no point carrying on. /// </summary> /// <param name="sessionDescription">The answer/offer SDP from the remote party.</param> public SetDescriptionResultEnum setRemoteDescription(RTCSessionDescriptionInit init) { RTCSessionDescription description = new RTCSessionDescription { type = init.type, sdp = SDP.ParseSDPDescription(init.sdp) }; remoteDescription = description; SDP remoteSdp = SDP.ParseSDPDescription(init.sdp); SdpType sdpType = (init.type == RTCSdpType.offer) ? SdpType.offer : SdpType.answer; var setResult = base.SetRemoteDescription(sdpType, remoteSdp); if (setResult == SetDescriptionResultEnum.OK) { string remoteIceUser = remoteSdp.IceUfrag; string remoteIcePassword = remoteSdp.IcePwd; string dtlsFingerprint = remoteSdp.DtlsFingerprint; var audioAnnounce = remoteSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.audio).FirstOrDefault(); if (audioAnnounce != null) { remoteIceUser = remoteIceUser ?? audioAnnounce.IceUfrag; remoteIcePassword = remoteIcePassword ?? audioAnnounce.IcePwd; dtlsFingerprint = dtlsFingerprint ?? audioAnnounce.DtlsFingerprint; } var videoAnnounce = remoteSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.video).FirstOrDefault(); if (videoAnnounce != null) { if (remoteIceUser == null || remoteIcePassword == null || dtlsFingerprint == null) { remoteIceUser = remoteIceUser ?? videoAnnounce.IceUfrag; remoteIcePassword = remoteIcePassword ?? videoAnnounce.IcePwd; dtlsFingerprint = dtlsFingerprint ?? videoAnnounce.DtlsFingerprint; } } SdpSessionID = remoteSdp.SessionId; if (init.type == RTCSdpType.answer) { _rtpIceChannel.IsController = true; // Set DTLS role to be server. IceRole = IceRolesEnum.passive; } else { // Set DTLS role to be server. // As of 20 Jun 2020 the DTLS handshake logic is based on OpenSSL and the mechanism // used hands over the socket handle to the C++ class. This logic works a lot better // when acting as the server in the handshake. IceRole = IceRolesEnum.passive; } if (remoteIceUser != null && remoteIcePassword != null) { _rtpIceChannel.SetRemoteCredentials(remoteIceUser, remoteIcePassword); } if (!string.IsNullOrWhiteSpace(dtlsFingerprint)) { dtlsFingerprint = dtlsFingerprint.Trim().ToLower(); if (dtlsFingerprint.Length < DTLS_FINGERPRINT_DIGEST.Length + 1) { logger.LogWarning($"The DTLS fingerprint was too short."); return(SetDescriptionResultEnum.DtlsFingerprintDigestNotSupported); } else if (!dtlsFingerprint.StartsWith(DTLS_FINGERPRINT_DIGEST)) { logger.LogWarning($"The DTLS fingerprint was supplied with an unsupported digest function (supported one is {DTLS_FINGERPRINT_DIGEST}): {dtlsFingerprint}."); return(SetDescriptionResultEnum.DtlsFingerprintDigestNotSupported); } else { dtlsFingerprint = dtlsFingerprint.Substring(DTLS_FINGERPRINT_DIGEST.Length + 1).Trim().Replace(":", "").Replace(" ", ""); RemotePeerDtlsFingerprint = ByteBufferInfo.ParseHexStr(dtlsFingerprint); logger.LogDebug($"The DTLS fingerprint for the remote peer's SDP {ByteBufferInfo.HexStr(RemotePeerDtlsFingerprint)}."); } } else { logger.LogWarning("The DTLS fingerprint was missing from the remote party's session description."); return(SetDescriptionResultEnum.DtlsFingerprintMissing); } // All browsers seem to have gone to trickling ICE candidates now but just // in case one or more are given we can start the STUN dance immediately. if (remoteSdp.IceCandidates != null) { foreach (var iceCandidate in remoteSdp.IceCandidates) { addIceCandidate(new RTCIceCandidateInit { candidate = iceCandidate }); } } foreach (var media in remoteSdp.Media) { if (media.IceCandidates != null) { foreach (var iceCandidate in media.IceCandidates) { addIceCandidate(new RTCIceCandidateInit { candidate = iceCandidate }); } } } signalingState = RTCSignalingState.have_remote_offer; onsignalingstatechange?.Invoke(); } return(setResult); }
/// <summary> /// Updates the session after receiving the remote SDP. /// At this point check that the codecs match. We currently only support: /// - Audio: PCMU, /// - Video: VP8. /// If they are not available there's no point carrying on. /// </summary> /// <param name="sessionDescription">The answer/offer SDP from the remote party.</param> public SetDescriptionResultEnum setRemoteDescription(RTCSessionDescriptionInit init) { RTCSessionDescription description = new RTCSessionDescription { type = init.type, sdp = SDP.ParseSDPDescription(init.sdp) }; remoteDescription = description; SDP remoteSdp = SDP.ParseSDPDescription(init.sdp); SdpType sdpType = (init.type == RTCSdpType.offer) ? SdpType.offer : SdpType.answer; var setResult = base.SetRemoteDescription(sdpType, remoteSdp); if (setResult == SetDescriptionResultEnum.OK) { string remoteIceUser = remoteSdp.IceUfrag; string remoteIcePassword = remoteSdp.IcePwd; string dtlsFingerprint = remoteSdp.DtlsFingerprint; var audioAnnounce = remoteSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.audio).FirstOrDefault(); if (audioAnnounce != null) { remoteIceUser = remoteIceUser ?? audioAnnounce.IceUfrag; remoteIcePassword = remoteIcePassword ?? audioAnnounce.IcePwd; dtlsFingerprint = dtlsFingerprint ?? audioAnnounce.DtlsFingerprint; } var videoAnnounce = remoteSdp.Media.Where(x => x.Media == SDPMediaTypesEnum.video).FirstOrDefault(); if (videoAnnounce != null) { if (remoteIceUser == null || remoteIcePassword == null || dtlsFingerprint == null) { remoteIceUser = remoteIceUser ?? videoAnnounce.IceUfrag; remoteIcePassword = remoteIcePassword ?? videoAnnounce.IcePwd; dtlsFingerprint = dtlsFingerprint ?? videoAnnounce.DtlsFingerprint; } } SdpSessionID = remoteSdp.SessionId; if (init.type == RTCSdpType.answer) { _rtpIceChannel.IsController = true; // Set DTLS role to be server. IceRole = IceRolesEnum.passive; } else { // Set DTLS role as client. IceRole = IceRolesEnum.active; } if (remoteIceUser != null && remoteIcePassword != null) { _rtpIceChannel.SetRemoteCredentials(remoteIceUser, remoteIcePassword); } if (!string.IsNullOrWhiteSpace(dtlsFingerprint)) { dtlsFingerprint = dtlsFingerprint.Trim().ToLower(); if (RTCDtlsFingerprint.TryParse(dtlsFingerprint, out var remoteFingerprint)) { RemotePeerDtlsFingerprint = remoteFingerprint; } else { logger.LogWarning($"The DTLS fingerprint was invalid or not supported."); return(SetDescriptionResultEnum.DtlsFingerprintDigestNotSupported); } } else { logger.LogWarning("The DTLS fingerprint was missing from the remote party's session description."); return(SetDescriptionResultEnum.DtlsFingerprintMissing); } // All browsers seem to have gone to trickling ICE candidates now but just // in case one or more are given we can start the STUN dance immediately. if (remoteSdp.IceCandidates != null) { foreach (var iceCandidate in remoteSdp.IceCandidates) { addIceCandidate(new RTCIceCandidateInit { candidate = iceCandidate }); } } foreach (var media in remoteSdp.Media) { if (media.IceCandidates != null) { foreach (var iceCandidate in media.IceCandidates) { addIceCandidate(new RTCIceCandidateInit { candidate = iceCandidate }); } } } signalingState = RTCSignalingState.have_remote_offer; onsignalingstatechange?.Invoke(); } return(setResult); }