/// <summary> /// Attempts to place a new outgoing call. /// </summary> /// <param name="sipCallDescriptor">A call descriptor containing the information about how /// and where to place the call.</param> /// <param name="mediaSession">The media session used for this call</param> public async Task InitiateCall(SIPCallDescriptor sipCallDescriptor, IMediaSession mediaSession) { m_uac = new SIPClientUserAgent(m_transport); m_uac.CallTrying += ClientCallTryingHandler; m_uac.CallRinging += ClientCallRingingHandler; m_uac.CallAnswered += ClientCallAnsweredHandler; m_uac.CallFailed += ClientCallFailedHandler; SIPEndPoint serverEndPoint = m_uac.GetCallDestination(sipCallDescriptor); if (serverEndPoint != null) { MediaSession = mediaSession; MediaSession.SessionMediaChanged += MediaSessionOnSessionMediaChanged; var sdp = await MediaSession.CreateOffer(serverEndPoint.Address).ConfigureAwait(false); sipCallDescriptor.Content = sdp; m_uac.Call(sipCallDescriptor); } else { ClientCallFailed?.Invoke(m_uac, $"Could not resolve destination when placing call to {sipCallDescriptor.Uri}."); CallEnded(); } }
/// <summary> /// Attempts to place a new outgoing call. /// </summary> /// <param name="dst">The destination SIP URI to call.</param> /// <param name="username">Optional Username if authentication is required.</param> /// <param name="password">Optional. Password if authentication is required.</param> /// <param name="mediaSession">The RTP session for the call.</param> public async Task <bool> Call(string dst, string username, string password, IMediaSession mediaSession) { if (!SIPURI.TryParse(dst, out var dstUri)) { throw new ApplicationException("The destination was not recognised as a valid SIP URI."); } SIPCallDescriptor callDescriptor = new SIPCallDescriptor( username ?? SIPConstants.SIP_DEFAULT_USERNAME, password, dstUri.ToString(), SIPConstants.SIP_DEFAULT_FROMURI, dstUri.CanonicalAddress, null, null, null, SIPCallDirection.Out, SDP.SDP_MIME_CONTENTTYPE, null, null); await InitiateCall(callDescriptor, mediaSession); TaskCompletionSource <bool> callResult = new TaskCompletionSource <bool>(); ClientCallAnswered += (uac, resp) => Task.Run(() => callResult.SetResult(true)); ClientCallFailed += (uac, errorMessage) => Task.Run(() => callResult.SetResult(false)); return(await callResult.Task); }
/// <summary> /// Attempts to place a new outgoing call. /// </summary> /// <param name="sipCallDescriptor">A call descriptor containing the information about how /// and where to place the call.</param> /// <param name="mediaSession">The media session used for this call</param> public async Task InitiateCallAsync(SIPCallDescriptor sipCallDescriptor, IMediaSession mediaSession) { m_cts = new CancellationTokenSource(); m_uac = new SIPClientUserAgent(m_transport); m_uac.CallTrying += ClientCallTryingHandler; m_uac.CallRinging += ClientCallRingingHandler; m_uac.CallAnswered += ClientCallAnsweredHandler; m_uac.CallFailed += ClientCallFailedHandler; // Can be DNS lookups involved in getting the call destination. SIPEndPoint serverEndPoint = await Task.Run <SIPEndPoint>(() => { return(m_uac.GetCallDestination(sipCallDescriptor)); }).ConfigureAwait(false); if (serverEndPoint != null) { MediaSession = mediaSession; MediaSession.OnRtpEvent += OnRemoteRtpEvent; //MediaSession.OnRtpClosed += (reason) => Hangup(); MediaSession.OnRtpClosed += (reason) => { if (!MediaSession.IsClosed) { logger.LogWarning($"RTP channel was closed with reason {reason}."); } }; RTCOfferOptions offerOptions = new RTCOfferOptions { RemoteSignallingAddress = serverEndPoint.Address }; var sdp = await mediaSession.createOffer(offerOptions).ConfigureAwait(false); mediaSession.setLocalDescription(new RTCSessionDescription { sdp = sdp, type = RTCSdpType.offer }); if (mediaSession.localDescription == null) { ClientCallFailed?.Invoke(m_uac, $"Could not create a local SDP offer."); CallEnded(); } else { sipCallDescriptor.Content = mediaSession.localDescription.sdp.ToString(); // This initiates the call but does not wait for an answer. m_uac.Call(sipCallDescriptor); } } else { ClientCallFailed?.Invoke(m_uac, $"Could not resolve destination when placing call to {sipCallDescriptor.Uri}."); CallEnded(); } }
/// <summary> /// Incoming call handler. /// </summary> /// <param name="sender">The sender.</param> /// <param name="args">The <see cref="CollectionEventArgs{TEntity}"/> instance containing the event data.</param> private void CallsOnIncoming(ICallCollection sender, CollectionEventArgs <ICall> args) { args.AddedResources.ForEach(call => { IMediaSession mediaSession = Guid.TryParse(call.Id, out Guid callId) ? this.CreateLocalMediaSession(callId) : this.CreateLocalMediaSession(); // Answer call and start video playback call?.AnswerAsync(mediaSession).ForgetAndLogExceptionAsync( call.GraphLogger, $"Answering call {call.Id} with scenario {call.ScenarioId}."); }); }
/// <summary> /// Attempts to place a new outgoing call. /// </summary> /// <param name="sipCallDescriptor">A call descriptor containing the information about how /// and where to place the call.</param> /// <param name="mediaSession">The media session used for this call</param> public async Task InitiateCallAsync(SIPCallDescriptor sipCallDescriptor, IMediaSession mediaSession) { m_cts = new CancellationTokenSource(); m_uac = new SIPClientUserAgent(m_transport); m_uac.CallTrying += ClientCallTryingHandler; m_uac.CallRinging += ClientCallRingingHandler; m_uac.CallAnswered += ClientCallAnsweredHandler; m_uac.CallFailed += ClientCallFailedHandler; // Can be DNS lookups involved in getting the call destination. SIPEndPoint serverEndPoint = await Task.Run <SIPEndPoint>(() => { return(m_uac.GetCallDestination(sipCallDescriptor)); }).ConfigureAwait(false); if (serverEndPoint != null) { MediaSession = mediaSession; MediaSession.OnRtpEvent += OnRemoteRtpEvent; //MediaSession.OnRtpClosed += (reason) => Hangup(); MediaSession.OnRtpClosed += (reason) => { if (!MediaSession.IsClosed) { logger.LogWarning($"RTP channel was closed with reason {reason}."); } }; var sdpAnnounceAddress = NetServices.GetLocalAddressForRemote(serverEndPoint.Address); var sdp = mediaSession.CreateOffer(sdpAnnounceAddress); if (sdp == null) { ClientCallFailed?.Invoke(m_uac, $"Could not generate an offer.", null); CallEnded(); } else { sipCallDescriptor.Content = sdp.ToString(); // This initiates the call but does not wait for an answer. m_uac.Call(sipCallDescriptor); } } else { ClientCallFailed?.Invoke(m_uac, $"Could not resolve destination when placing call to {sipCallDescriptor.Uri}.", null); CallEnded(); } }
/// <summary> /// Attempts to place a new outgoing call AND waits for the call to be answered or fail. /// Use <see cref="InitiateCallAsync(SIPCallDescriptor, IMediaSession)"/> to start a call without /// waiting for it to complete and monitor <see cref="ClientCallAnsweredHandler"/> and /// <see cref="ClientCallFailedHandler"/> to detect an answer or failure. /// </summary> /// <param name="callDescriptor">The full descriptor for the call destination. Allows customising /// of additional options above the standard username, password and destination URI.</param> /// <param name="mediaSession">The RTP session for the call.</param> public async Task <bool> Call(SIPCallDescriptor callDescriptor, IMediaSession mediaSession) { TaskCompletionSource <bool> callResult = new TaskCompletionSource <bool>(TaskCreationOptions.RunContinuationsAsynchronously); await InitiateCallAsync(callDescriptor, mediaSession).ConfigureAwait(false); ClientCallAnswered += (uac, resp) => { callResult.TrySetResult(true); }; ClientCallFailed += (uac, errorMessage) => { callResult.TrySetResult(false); }; return(callResult.Task.Result); }
/// <summary> /// Answers the call request contained in the user agent server parameter. Note the /// <see cref="AcceptCall(SIPRequest)"/> method should be used to create the user agent server. /// Any existing call will be hungup. /// </summary> /// <param name="uas">The user agent server holding the pending call to answer.</param> /// <param name="mediaSession">The media session used for this call</param> /// <param name="customHeaders">Custom SIP-Headers to use in Answer.</param> public async Task Answer(SIPServerUserAgent uas, IMediaSession mediaSession, string[] customHeaders) { // This call is now taking over any existing call. if (IsCallActive) { Hangup(); } else if (uas.IsCancelled) { logger.LogDebug("The incoming call has been cancelled."); mediaSession?.Close("call cancelled"); } else { m_cts = new CancellationTokenSource(); var sipRequest = uas.ClientTransaction.TransactionRequest; MediaSession = mediaSession; MediaSession.OnRtpEvent += OnRemoteRtpEvent; //MediaSession.OnRtpClosed += (reason) => Hangup(); MediaSession.OnRtpClosed += (reason) => { if (!MediaSession.IsClosed) { logger.LogWarning($"RTP channel was closed with reason {reason}."); } }; SDP remoteSdp = SDP.ParseSDPDescription(sipRequest.Body); MediaSession.setRemoteDescription(new RTCSessionDescription { sdp = remoteSdp, type = RTCSdpType.offer });; var sdpAnswer = await MediaSession.createAnswer(null).ConfigureAwait(false); MediaSession.setLocalDescription(new RTCSessionDescription { sdp = sdpAnswer, type = RTCSdpType.answer }); await MediaSession.Start().ConfigureAwait(false); m_uas = uas; m_uas.Answer(m_sdpContentType, sdpAnswer.ToString(), null, SIPDialogueTransferModesEnum.Default, customHeaders); Dialogue.DialogueState = SIPDialogueStateEnum.Confirmed; } }
/// <summary> /// Incoming call handler. /// </summary> /// <param name="sender">The sender.</param> /// <param name="args">The <see cref="CollectionEventArgs{TEntity}"/> instance containing the event data.</param> private void CallsOnIncoming(ICallCollection sender, CollectionEventArgs <ICall> args) { args.AddedResources.ForEach(call => { // Get the compliance recording parameters. // The context associated with the incoming call. IncomingContext incomingContext = call.Resource.IncomingContext; // The RP participant. string observedParticipantId = incomingContext.ObservedParticipantId; // If the observed participant is a delegate. IdentitySet onBehalfOfIdentity = incomingContext.OnBehalfOf; // If a transfer occured, the transferor. IdentitySet transferorIdentity = incomingContext.Transferor; string countryCode = null; EndpointType?endpointType = null; // Note: this should always be true for CR calls. if (incomingContext.ObservedParticipantId == incomingContext.SourceParticipantId) { // The dynamic location of the RP. countryCode = call.Resource.Source.CountryCode; // The type of endpoint being used. endpointType = call.Resource.Source.EndpointType; } IMediaSession mediaSession = Guid.TryParse(call.Id, out Guid callId) ? this.CreateLocalMediaSession(callId) : this.CreateLocalMediaSession(); // Answer call call?.AnswerAsync(mediaSession).ForgetAndLogExceptionAsync( call.GraphLogger, $"Answering call {call.Id} with scenario {call.ScenarioId}."); }); }
/// <summary> /// Answers the call request contained in the user agent server parameter. Note the /// <see cref="AcceptCall(SIPRequest)"/> method should be used to create the user agent server. /// Any existing call will be hungup. /// </summary> /// <param name="uas">The user agent server holding the pending call to answer.</param> /// <param name="mediaSession">The media session used for this call</param> public async Task Answer(SIPServerUserAgent uas, IMediaSession mediaSession) { // This call is now taking over any existing call. if (IsCallActive) { Hangup(); } var sipRequest = uas.ClientTransaction.TransactionRequest; MediaSession = mediaSession; MediaSession.SessionMediaChanged += MediaSessionOnSessionMediaChanged; MediaSession.OnRtpClosed += (reason) => Hangup(); var sdpAnswer = await MediaSession.AnswerOffer(sipRequest.Body).ConfigureAwait(false); m_uas = uas; m_uas.Answer(m_sdpContentType, sdpAnswer, null, SIPDialogueTransferModesEnum.Default); Dialogue.DialogueState = SIPDialogueStateEnum.Confirmed; }
/// <summary> /// Attempts to place a new outgoing call AND waits for the call to be answered or fail. /// Use <see cref="InitiateCallAsync(SIPCallDescriptor, IMediaSession)"/> to start a call without /// waiting for it to complete and monitor <see cref="ClientCallAnsweredHandler"/> and /// <see cref="ClientCallFailedHandler"/> to detect an answer or failure. /// </summary> /// <param name="dst">The destination SIP URI to call.</param> /// <param name="username">Optional Username if authentication is required.</param> /// <param name="password">Optional. Password if authentication is required.</param> /// <param name="mediaSession">The RTP session for the call.</param> public Task <bool> Call(string dst, string username, string password, IMediaSession mediaSession) { if (!SIPURI.TryParse(dst, out var dstUri)) { throw new ApplicationException("The destination was not recognised as a valid SIP URI."); } SIPCallDescriptor callDescriptor = new SIPCallDescriptor( username ?? SIPConstants.SIP_DEFAULT_USERNAME, password, dstUri.ToString(), SIPConstants.SIP_DEFAULT_FROMURI, dstUri.CanonicalAddress, null, null, null, SIPCallDirection.Out, SDP.SDP_MIME_CONTENTTYPE, null, null); return(Call(callDescriptor, mediaSession)); }
/// <summary> /// Answers the call request contained in the user agent server parameter. Note the /// <see cref="AcceptCall(SIPRequest)"/> method should be used to create the user agent server. /// Any existing call will be hungup. /// </summary> /// <param name="uas">The user agent server holding the pending call to answer.</param> /// <param name="mediaSession">The media session used for this call</param> /// <param name="customHeaders">Custom SIP-Headers to use in Answer.</param> public async Task Answer(SIPServerUserAgent uas, IMediaSession mediaSession, string[] customHeaders) { // This call is now taking over any existing call. if (IsCallActive) { Hangup(); } else if (uas.IsCancelled) { logger.LogDebug("The incoming call has been cancelled."); mediaSession?.Close("call cancelled"); } else { m_cts = new CancellationTokenSource(); var sipRequest = uas.ClientTransaction.TransactionRequest; MediaSession = mediaSession; MediaSession.OnRtpEvent += OnRemoteRtpEvent; //MediaSession.OnRtpClosed += (reason) => Hangup(); MediaSession.OnRtpClosed += (reason) => { if (!MediaSession.IsClosed) { logger.LogWarning($"RTP channel was closed with reason {reason}."); } }; string sdp = null; if (!String.IsNullOrEmpty(sipRequest.Body)) { // The SDP offer was included in the INVITE request. SDP remoteSdp = SDP.ParseSDPDescription(sipRequest.Body); MediaSession.setRemoteDescription(new RTCSessionDescription { sdp = remoteSdp, type = RTCSdpType.offer }); var sdpAnswer = await MediaSession.createAnswer(null).ConfigureAwait(false); if (sdpAnswer == null) { logger.LogWarning($"Could not generate an SDP answer."); m_uas.Reject(SIPResponseStatusCodesEnum.NotAcceptable, null); return; } else { MediaSession.setLocalDescription(new RTCSessionDescription { sdp = sdpAnswer, type = RTCSdpType.answer }); sdp = sdpAnswer.ToString(); } } else { // No SDP offer was included in the INVITE request need to wait for the ACK. RTCOfferOptions offerOptions = new RTCOfferOptions { RemoteSignallingAddress = sipRequest.RemoteSIPEndPoint.GetIPEndPoint().Address }; var sdpOffer = await MediaSession.createOffer(offerOptions).ConfigureAwait(false); if (sdpOffer == null) { // This shouldn't occur unless we're unable to create an audio/video track. logger.LogWarning($"Could not generate an SDP answer."); m_uas.Reject(SIPResponseStatusCodesEnum.NotAcceptable, null); return; } else { MediaSession.setLocalDescription(new RTCSessionDescription { sdp = sdpOffer, type = RTCSdpType.offer }); sdp = sdpOffer.ToString(); } } m_uas = uas; // In cases where the initial INVITE did not contain an SDP offer the action sequence is: // - INVITE with no SDP offer received, // - Reply with OK and an SDP offer, // - Wait for ACK with SDP answer. TaskCompletionSource <SIPDialogue> dialogueCreatedTcs = new TaskCompletionSource <SIPDialogue>(TaskCreationOptions.RunContinuationsAsynchronously); m_uas.OnDialogueCreated += (dialogue) => dialogueCreatedTcs.TrySetResult(dialogue); m_uas.Answer(m_sdpContentType, sdp, null, SIPDialogueTransferModesEnum.Default, customHeaders); await Task.WhenAny(dialogueCreatedTcs.Task, Task.Delay(WAIT_DIALOG_TIMEOUT)).ConfigureAwait(false); if (Dialogue != null) { if (MediaSession.remoteDescription == null || MediaSession.remoteDescription.sdp == null) { // If the initial INVITE did not contain an offer then the remote description will not yet be set. var remoteSDP = SDP.ParseSDPDescription(Dialogue.RemoteSDP); MediaSession.setRemoteDescription(new RTCSessionDescription { sdp = remoteSDP, type = RTCSdpType.answer }); } Dialogue.DialogueState = SIPDialogueStateEnum.Confirmed; await MediaSession.Start().ConfigureAwait(false); } else { logger.LogWarning("The attempt to answer a call failed as the dialog was not created. The likely cause is the ACK not being received in time."); MediaSession.Close("dialog creation failed"); Hangup(); } } }
/// <summary> /// Answers the call request contained in the user agent server parameter. Note the /// <see cref="AcceptCall(SIPRequest)"/> method should be used to create the user agent server. /// Any existing call will be hungup. /// </summary> /// <param name="uas">The user agent server holding the pending call to answer.</param> /// <param name="mediaSession">The media session used for this call</param> public async Task Answer(SIPServerUserAgent uas, IMediaSession mediaSession) { await Answer(uas, mediaSession, null).ConfigureAwait(false); }
/// <summary> /// Answers the call request contained in the user agent server parameter. Note the /// <see cref="AcceptCall(SIPRequest)"/> method should be used to create the user agent server. /// Any existing call will be hungup. /// </summary> /// <param name="uas">The user agent server holding the pending call to answer.</param> /// <param name="mediaSession">The media session used for this call</param> /// <param name="customHeaders">Custom SIP-Headers to use in Answer.</param> /// <returns>True if the call was successfully answered or false if there was a problem /// such as incompatible codecs.</returns> public async Task <bool> Answer(SIPServerUserAgent uas, IMediaSession mediaSession, string[] customHeaders) { // This call is now taking over any existing call. if (IsCallActive) { Hangup(); } if (uas.IsCancelled) { logger.LogDebug("The incoming call has been cancelled."); mediaSession?.Close("call cancelled"); return(false); } else { m_cts = new CancellationTokenSource(); var sipRequest = uas.ClientTransaction.TransactionRequest; MediaSession = mediaSession; MediaSession.OnRtpEvent += OnRemoteRtpEvent; MediaSession.OnRtpClosed += (reason) => { if (!MediaSession.IsClosed) { logger.LogWarning($"RTP channel was closed with reason {reason}."); } }; string sdp = null; if (!String.IsNullOrEmpty(sipRequest.Body)) { // The SDP offer was included in the INVITE request. SDP remoteSdp = SDP.ParseSDPDescription(sipRequest.Body); var setRemoteResult = MediaSession.SetRemoteDescription(remoteSdp); if (setRemoteResult != SetDescriptionResultEnum.OK) { logger.LogWarning($"Error setting remote description from INVITE {setRemoteResult}."); uas.Reject(SIPResponseStatusCodesEnum.NotAcceptable, setRemoteResult.ToString()); MediaSession.Close("sdp offer not acceptable"); Hangup(); return(false); } else { var sdpAnswer = MediaSession.CreateAnswer(null); sdp = sdpAnswer.ToString(); } } else { // No SDP offer was included in the INVITE request need to wait for the ACK. var sdpAnnounceAddress = NetServices.GetLocalAddressForRemote(sipRequest.RemoteSIPEndPoint.GetIPEndPoint().Address); var sdpOffer = MediaSession.CreateOffer(sdpAnnounceAddress); sdp = sdpOffer.ToString(); } m_uas = uas; // In cases where the initial INVITE did not contain an SDP offer the action sequence is: // - INVITE with no SDP offer received, // - Reply with OK and an SDP offer, // - Wait for ACK with SDP answer. TaskCompletionSource <SIPDialogue> dialogueCreatedTcs = new TaskCompletionSource <SIPDialogue>(TaskCreationOptions.RunContinuationsAsynchronously); m_uas.OnDialogueCreated += (dialogue) => dialogueCreatedTcs.TrySetResult(dialogue); m_uas.Answer(m_sdpContentType, sdp, null, SIPDialogueTransferModesEnum.Default, customHeaders); await Task.WhenAny(dialogueCreatedTcs.Task, Task.Delay(WAIT_DIALOG_TIMEOUT)).ConfigureAwait(false); if (Dialogue != null) { if (MediaSession.RemoteDescription == null) { // If the initial INVITE did not contain an offer then the remote description will not yet be set. var remoteSDP = SDP.ParseSDPDescription(Dialogue.RemoteSDP); var setRemoteResult = MediaSession.SetRemoteDescription(remoteSDP); if (setRemoteResult != SetDescriptionResultEnum.OK) { // Failed to set the remote SDP from the ACK request. Only option is to hangup. logger.LogWarning($"Error setting remote description from ACK {setRemoteResult}."); MediaSession.Close(setRemoteResult.ToString()); Hangup(); return(false); } else { // SDP from the ACK request was accepted. Start the RTP session. Dialogue.DialogueState = SIPDialogueStateEnum.Confirmed; await MediaSession.Start().ConfigureAwait(false); return(true); } } else { Dialogue.DialogueState = SIPDialogueStateEnum.Confirmed; await MediaSession.Start().ConfigureAwait(false); return(true); } } else { logger.LogWarning("The attempt to answer a call failed as the dialog was not created. The likely cause is the ACK not being received in time."); MediaSession.Close("dialog creation failed"); Hangup(); return(false); } } }
/// <summary> /// Answers the call request contained in the user agent server parameter. Note the /// <see cref="AcceptCall(SIPRequest)"/> method should be used to create the user agent server. /// Any existing call will be hungup. /// </summary> /// <param name="uas">The user agent server holding the pending call to answer.</param> /// <param name="mediaSession">The media session used for this call</param> public Task <bool> Answer(SIPServerUserAgent uas, IMediaSession mediaSession) { return(Answer(uas, mediaSession, null)); }
/// <summary> /// Answers the call request contained in the user agent server parameter. Note the /// <see cref="AcceptCall(SIPRequest)"/> method should be used to create the user agent server. /// Any existing call will be hungup. /// </summary> /// <param name="uas">The user agent server holding the pending call to answer.</param> /// <param name="mediaSession">The media session used for this call</param> public async Task Answer(SIPServerUserAgent uas, IMediaSession mediaSession) { await Answer(uas, mediaSession, null); }