/// <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); } } }