public async Task BlindTransferCancelUnitTest() { logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); SIPTransport transport = new SIPTransport(); transport.AddSIPChannel(new MockSIPChannel(new System.Net.IPEndPoint(IPAddress.Any, 0))); SIPUserAgent userAgent = new SIPUserAgent(transport, null); string inviteReqStr = "INVITE sip:192.168.11.50:5060 SIP/2.0" + m_CRLF + "Via: SIP/2.0/UDP 192.168.11.50:60163;rport;branch=z9hG4bKPj869f70960bdd4204b1352eaf242a3691" + m_CRLF + "To: <sip:[email protected]>;tag=ZUJSXRRGXQ" + m_CRLF + "From: <sip:[email protected]>;tag=4a60ce364b774258873ff199e5e39938" + m_CRLF + "Call-ID: 17324d6df8744d978008c8997bfd208d" + m_CRLF + "CSeq: 3532 INVITE" + m_CRLF + "Contact: <sip:[email protected]:60163;ob>" + m_CRLF + "Max-Forwards: 70" + m_CRLF + "User-Agent: MicroSIP/3.19.22" + m_CRLF + "Allow: PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS" + m_CRLF + "Supported: replaces, 100rel, timer, norefersub" + m_CRLF + "Content-Length: 343" + m_CRLF + "Content-Type: application/sdp" + m_CRLF + "Session-Expires: 1800" + m_CRLF + "Min-SE: 90" + m_CRLF + "" + m_CRLF + "v=0" + m_CRLF + "o=- 3785527268 3785527269 IN IP4 192.168.11.50" + m_CRLF + "s=pjmedia" + m_CRLF + "t=0 0" + m_CRLF + "m=audio 4032 RTP/AVP 0 101" + m_CRLF + "c=IN IP4 192.168.11.50" + m_CRLF + "a=rtpmap:0 PCMU/8000" + m_CRLF + "a=rtpmap:101 telephone-event/8000" + m_CRLF + "a=fmtp:101 0-16" + m_CRLF + "a=sendrecv"; SIPEndPoint dummySipEndPoint = new SIPEndPoint(new IPEndPoint(IPAddress.Any, 0)); SIPMessageBuffer sipMessageBuffer = SIPMessageBuffer.ParseSIPMessage(inviteReqStr, dummySipEndPoint, dummySipEndPoint); SIPRequest inviteReq = SIPRequest.ParseSIPRequest(sipMessageBuffer); UASInviteTransaction uasTx = new UASInviteTransaction(transport, inviteReq, null); SIPServerUserAgent mockUas = new SIPServerUserAgent(transport, null, null, null, SIPCallDirection.In, null, null, null, uasTx); await userAgent.Answer(mockUas, CreateMediaSession()); CancellationTokenSource cts = new CancellationTokenSource(); var blindTransferTask = userAgent.BlindTransfer(SIPURI.ParseSIPURIRelaxed("127.0.0.1"), TimeSpan.FromSeconds(2), cts.Token); cts.Cancel(); Assert.False(await blindTransferTask); //await Assert.ThrowsAnyAsync<TaskCanceledException>(async () => { bool result = ; }); }
/// <summary> /// Sends a request to the remote call party to initiate a blind transfer to the /// supplied destination. /// </summary> /// <param name="destination">The SIP URI of the blind transfer destination.</param> /// <returns>True if the transfer was accepted or false if not.</returns> public Task <bool> BlindTransfer(string destination) { if (SIPURI.TryParse(destination, out var uri)) { return(m_userAgent.BlindTransfer(uri, TimeSpan.FromSeconds(TRANSFER_RESPONSE_TIMEOUT_SECONDS), _cts.Token)); } else { StatusMessage(this, $"The transfer destination was not a valid SIP URI."); return(Task.FromResult(false)); } }
static void Main() { Console.WriteLine("SIPSorcery Call Hold and Blind Transfer example."); Console.WriteLine("Press 'c' to initiate a call to the default destination."); Console.WriteLine("Press 'h' to place an established call on and off hold."); Console.WriteLine("Press 'H' to hangup an established call."); Console.WriteLine("Press 't' to request a blind transfer on an established call."); Console.WriteLine("Press 'q' or ctrl-c to exit."); // Plumbing code to facilitate a graceful exit. CancellationTokenSource exitCts = new CancellationTokenSource(); // Cancellation token to stop the SIP transport and RTP stream. Log = AddConsoleLogger(); // Set up a default SIP transport. var sipTransport = new SIPTransport(); sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(IPAddress.Any, SIP_LISTEN_PORT))); Console.WriteLine($"Listening for incoming calls on: {sipTransport.GetSIPChannels().First().ListeningEndPoint}."); EnableTraceLogs(sipTransport); var winAudio = new WindowsAudioEndPoint(new AudioEncoder()); winAudio.RestrictCodecs(new List <AudioCodecsEnum> { AudioCodecsEnum.PCMU }); // Create a client/server user agent to place a call to a remote SIP server along with event handlers for the different stages of the call. var userAgent = new SIPUserAgent(sipTransport, null, true); userAgent.RemotePutOnHold += () => Log.LogInformation("Remote call party has placed us on hold."); userAgent.RemoteTookOffHold += () => Log.LogInformation("Remote call party took us off hold."); userAgent.OnIncomingCall += async(ua, req) => { Log.LogInformation($"Incoming call from {req.Header.From.FriendlyDescription()} at {req.RemoteSIPEndPoint}."); var uas = userAgent.AcceptCall(req); if (userAgent?.IsCallActive == true) { // If we are already on a call return a busy response. Log.LogWarning($"Busy response returned for incoming call request."); uas.Reject(SIPResponseStatusCodesEnum.BusyHere, null); } else { var voipSession = new VoIPMediaSession(winAudio.ToMediaEndPoints()); voipSession.AcceptRtpFromAny = true; var answerResult = await userAgent.Answer(uas, voipSession); } }; // At this point the call has been initiated and everything will be handled in an event handler. Task.Run(async() => { try { while (!exitCts.Token.WaitHandle.WaitOne(0)) { var keyProps = Console.ReadKey(); if (keyProps.KeyChar == 'c') { if (!userAgent.IsCallActive) { var voipSession = new VoIPMediaSession(winAudio.ToMediaEndPoints()); voipSession.AcceptRtpFromAny = true; bool callResult = await userAgent.Call(DEFAULT_DESTINATION_SIP_URI, SIP_USERNAME, SIP_PASSWORD, voipSession); Log.LogInformation($"Call attempt {((callResult) ? "successfull" : "failed")}."); } else { Log.LogWarning("There is already an active call."); } } else if (keyProps.KeyChar == 'h') { // Place call on/off hold. if (userAgent.IsCallActive) { if (userAgent.IsOnLocalHold) { Log.LogInformation("Taking the remote call party off hold."); (userAgent.MediaSession as VoIPMediaSession).TakeOffHold(); userAgent.TakeOffHold(); } else { Log.LogInformation("Placing the remote call party on hold."); await(userAgent.MediaSession as VoIPMediaSession).PutOnHold(); userAgent.PutOnHold(); } } else { Log.LogWarning("There is no active call to put on hold."); } } else if (keyProps.KeyChar == 'H') { if (userAgent.IsCallActive) { Log.LogInformation("Hanging up call."); userAgent.Hangup(); } } else if (keyProps.KeyChar == 't') { // Initiate a blind transfer to the remote call party. if (userAgent.IsCallActive) { var transferURI = SIPURI.ParseSIPURI(TRANSFER_DESTINATION_SIP_URI); bool result = await userAgent.BlindTransfer(transferURI, TimeSpan.FromSeconds(TRANSFER_TIMEOUT_SECONDS), exitCts.Token); if (result) { // If the transfer was accepted the original call will already have been hungup. // Wait a second for the transfer NOTIFY request to arrive. await Task.Delay(1000); exitCts.Cancel(); } else { Log.LogWarning($"Transfer to {TRANSFER_DESTINATION_SIP_URI} failed."); } } else { Log.LogWarning("There is no active call to transfer."); } } else if (keyProps.KeyChar == 'q') { // Quit application. exitCts.Cancel(); } } } catch (Exception excp) { Log.LogError($"Exception Key Press listener. {excp.Message}."); } }); // Ctrl-c will gracefully exit the call at any point. Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) { e.Cancel = true; exitCts.Cancel(); }; // Wait for a signal saying the call failed, was cancelled with ctrl-c or completed. exitCts.Token.WaitHandle.WaitOne(); #region Cleanup. Log.LogInformation("Exiting..."); if (userAgent != null) { if (userAgent.IsCallActive) { Log.LogInformation($"Hanging up call to {userAgent?.CallDescriptor?.To}."); userAgent.Hangup(); } // Give the BYE or CANCEL request time to be transmitted. Log.LogInformation("Waiting 1s for call to clean up..."); Task.Delay(1000).Wait(); } if (sipTransport != null) { Log.LogInformation("Shutting down SIP transport..."); sipTransport.Shutdown(); } #endregion }
static void Main() { Console.WriteLine("SIPSorcery Call Hold and Blind Transfer example."); Console.WriteLine("Press 'c' to initiate a call to the default destination."); Console.WriteLine("Press 'h' to place an established call on and off hold."); Console.WriteLine("Press 'H' to hangup an established call."); Console.WriteLine("Press 't' to request a blind transfer on an established call."); Console.WriteLine("Press 'q' or ctrl-c to exit."); // Plumbing code to facilitate a graceful exit. CancellationTokenSource exitCts = new CancellationTokenSource(); // Cancellation token to stop the SIP transport and RTP stream. AddConsoleLogger(); // Set up a default SIP transport. var sipTransport = new SIPTransport(); sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(IPAddress.Any, SIP_LISTEN_PORT))); Console.WriteLine($"Listening for incoming calls on: {sipTransport.GetSIPChannels().First().ListeningEndPoint}."); EnableTraceLogs(sipTransport); _currentDir = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); RtpAVSession rtpAVSession = null; // Create a client/server user agent to place a call to a remote SIP server along with event handlers for the different stages of the call. var userAgent = new SIPUserAgent(sipTransport, null); userAgent.RemotePutOnHold += () => Log.LogInformation("Remote call party has placed us on hold."); userAgent.RemoteTookOffHold += () => Log.LogInformation("Remote call party took us off hold."); sipTransport.SIPTransportRequestReceived += async(localEndPoint, remoteEndPoint, sipRequest) => { if (sipRequest.Header.From != null && sipRequest.Header.From.FromTag != null && sipRequest.Header.To != null && sipRequest.Header.To.ToTag != null) { // This is an in-dialog request that will be handled directly by a user agent instance. } else if (sipRequest.Method == SIPMethodsEnum.INVITE) { if (userAgent?.IsCallActive == true) { Log.LogWarning($"Busy response returned for incoming call request from {remoteEndPoint}: {sipRequest.StatusLine}."); // If we are already on a call return a busy response. UASInviteTransaction uasTransaction = new UASInviteTransaction(sipTransport, sipRequest, null); SIPResponse busyResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.BusyHere, null); uasTransaction.SendFinalResponse(busyResponse); } else { Log.LogInformation($"Incoming call request from {remoteEndPoint}: {sipRequest.StatusLine}."); var incomingCall = userAgent.AcceptCall(sipRequest); rtpAVSession = new RtpAVSession(new AudioOptions { AudioSource = AudioSourcesEnum.CaptureDevice }, null); await userAgent.Answer(incomingCall, rtpAVSession); Log.LogInformation($"Answered incoming call from {sipRequest.Header.From.FriendlyDescription()} at {remoteEndPoint}."); } } else { Log.LogDebug($"SIP {sipRequest.Method} request received but no processing has been set up for it, rejecting."); SIPResponse notAllowedResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.MethodNotAllowed, null); await sipTransport.SendResponseAsync(notAllowedResponse); } }; // At this point the call has been initiated and everything will be handled in an event handler. Task.Run(async() => { try { while (!exitCts.Token.WaitHandle.WaitOne(0)) { var keyProps = Console.ReadKey(); if (keyProps.KeyChar == 'c') { if (!userAgent.IsCallActive) { rtpAVSession = new RtpAVSession(new AudioOptions { AudioSource = AudioSourcesEnum.CaptureDevice }, null); bool callResult = await userAgent.Call(DEFAULT_DESTINATION_SIP_URI, SIP_USERNAME, SIP_PASSWORD, rtpAVSession); Log.LogInformation($"Call attempt {((callResult) ? "successfull" : "failed")}."); } else { Log.LogWarning("There is already an active call."); } } else if (keyProps.KeyChar == 'h') { // Place call on/off hold. if (userAgent.IsCallActive) { if (userAgent.IsOnLocalHold) { Log.LogInformation("Taking the remote call party off hold."); userAgent.TakeOffHold(); await(userAgent.MediaSession as RtpAVSession).SetSources(new AudioOptions { AudioSource = AudioSourcesEnum.CaptureDevice }, null); } else { Log.LogInformation("Placing the remote call party on hold."); userAgent.PutOnHold(); await(userAgent.MediaSession as RtpAVSession).SetSources(new AudioOptions { AudioSource = AudioSourcesEnum.Music, SourceFiles = new Dictionary <SDPMediaFormatsEnum, string> { { SDPMediaFormatsEnum.PCMU, _currentDir + "/" + AUDIO_FILE_PCMU } } }, null); } } else { Log.LogWarning("There is no active call to put on hold."); } } else if (keyProps.KeyChar == 'H') { if (userAgent.IsCallActive) { Log.LogInformation("Hanging up call."); userAgent.Hangup(); } } else if (keyProps.KeyChar == 't') { // Initiate a blind transfer to the remote call party. if (userAgent.IsCallActive) { var transferURI = SIPURI.ParseSIPURI(TRANSFER_DESTINATION_SIP_URI); bool result = await userAgent.BlindTransfer(transferURI, TimeSpan.FromSeconds(TRANSFER_TIMEOUT_SECONDS), exitCts.Token); if (result) { // If the transfer was accepted the original call will already have been hungup. // Wait a second for the transfer NOTIFY request to arrive. await Task.Delay(1000); exitCts.Cancel(); } else { Log.LogWarning($"Transfer to {TRANSFER_DESTINATION_SIP_URI} failed."); } } else { Log.LogWarning("There is no active call to transfer."); } } else if (keyProps.KeyChar == 'q') { // Quit application. exitCts.Cancel(); } } } catch (Exception excp) { SIPSorcery.Sys.Log.Logger.LogError($"Exception Key Press listener. {excp.Message}."); } }); // Ctrl-c will gracefully exit the call at any point. Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) { e.Cancel = true; exitCts.Cancel(); }; // Wait for a signal saying the call failed, was cancelled with ctrl-c or completed. exitCts.Token.WaitHandle.WaitOne(); #region Cleanup. Log.LogInformation("Exiting..."); rtpAVSession?.Close("app exit"); if (userAgent != null) { if (userAgent.IsCallActive) { Log.LogInformation($"Hanging up call to {userAgent?.CallDescriptor?.To}."); userAgent.Hangup(); } // Give the BYE or CANCEL request time to be transmitted. Log.LogInformation("Waiting 1s for call to clean up..."); Task.Delay(1000).Wait(); } SIPSorcery.Net.DNSManager.Stop(); if (sipTransport != null) { Log.LogInformation("Shutting down SIP transport..."); sipTransport.Shutdown(); } #endregion }
static void Main() { Console.WriteLine("SIPSorcery call hold example."); Console.WriteLine("Press ctrl-c to exit."); // Plumbing code to facilitate a graceful exit. CancellationTokenSource exitCts = new CancellationTokenSource(); // Cancellation token to stop the SIP transport and RTP stream. bool isCallHungup = false; bool hasCallFailed = false; AddConsoleLogger(); // Set up a default SIP transport. var sipTransport = new SIPTransport(); sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(IPAddress.Any, SIP_LISTEN_PORT))); //EnableTraceLogs(sipTransport); // Get the default speaker. var(audioOutEvent, audioOutProvider) = GetAudioOutputDevice(); WaveInEvent waveInEvent = GetAudioInputDevice(); RTPMediaSession RtpMediaSession = null; // Create a client/server user agent to place a call to a remote SIP server along with event handlers for the different stages of the call. var userAgent = new SIPUserAgent(sipTransport, null); userAgent.ClientCallTrying += (uac, resp) => Log.LogInformation($"{uac.CallDescriptor.To} Trying: {resp.StatusCode} {resp.ReasonPhrase}."); userAgent.ClientCallRinging += (uac, resp) => Log.LogInformation($"{uac.CallDescriptor.To} Ringing: {resp.StatusCode} {resp.ReasonPhrase}."); userAgent.ClientCallFailed += (uac, err) => { Log.LogWarning($"{uac.CallDescriptor.To} Failed: {err}"); hasCallFailed = true; exitCts.Cancel(); }; userAgent.ClientCallAnswered += (uac, resp) => { if (resp.Status == SIPResponseStatusCodesEnum.Ok) { Log.LogInformation($"{uac.CallDescriptor.To} Answered: {resp.StatusCode} {resp.ReasonPhrase}."); PlayRemoteMedia(RtpMediaSession, audioOutProvider); } else { Log.LogWarning($"{uac.CallDescriptor.To} Answered: {resp.StatusCode} {resp.ReasonPhrase}."); hasCallFailed = true; exitCts.Cancel(); } }; userAgent.OnCallHungup += () => { Log.LogInformation($"Call hungup by remote party."); exitCts.Cancel(); }; userAgent.ServerCallCancelled += (uas) => Log.LogInformation("Incoming call cancelled by caller."); sipTransport.SIPTransportRequestReceived += async(localEndPoint, remoteEndPoint, sipRequest) => { if (sipRequest.Header.From != null && sipRequest.Header.From.FromTag != null && sipRequest.Header.To != null && sipRequest.Header.To.ToTag != null) { // This is an in-dialog request that will be handled directly by a user agent instance. } else if (sipRequest.Method == SIPMethodsEnum.INVITE) { if (userAgent?.IsCallActive == true) { Log.LogWarning($"Busy response returned for incoming call request from {remoteEndPoint}: {sipRequest.StatusLine}."); // If we are already on a call return a busy response. UASInviteTransaction uasTransaction = new UASInviteTransaction(sipTransport, sipRequest, null); SIPResponse busyResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.BusyHere, null); uasTransaction.SendFinalResponse(busyResponse); } else { Log.LogInformation($"Incoming call request from {remoteEndPoint}: {sipRequest.StatusLine}."); var incomingCall = userAgent.AcceptCall(sipRequest); RtpMediaSession = new RTPMediaSession(SDPMediaTypesEnum.audio, (int)SDPMediaFormatsEnum.PCMU, AddressFamily.InterNetwork); RtpMediaSession.RemotePutOnHold += () => Log.LogInformation("Remote call party has placed us on hold."); RtpMediaSession.RemoteTookOffHold += () => Log.LogInformation("Remote call party took us off hold."); await userAgent.Answer(incomingCall, RtpMediaSession); PlayRemoteMedia(RtpMediaSession, audioOutProvider); waveInEvent.StartRecording(); Log.LogInformation($"Answered incoming call from {sipRequest.Header.From.FriendlyDescription()} at {remoteEndPoint}."); } } else { Log.LogDebug($"SIP {sipRequest.Method} request received but no processing has been set up for it, rejecting."); SIPResponse notAllowedResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.MethodNotAllowed, null); await sipTransport.SendResponseAsync(notAllowedResponse); } }; // Wire up the RTP send session to the audio output device. uint rtpSendTimestamp = 0; waveInEvent.DataAvailable += (object sender, WaveInEventArgs args) => { byte[] sample = new byte[args.Buffer.Length / 2]; int sampleIndex = 0; for (int index = 0; index < args.BytesRecorded; index += 2) { var ulawByte = NAudio.Codecs.MuLawEncoder.LinearToMuLawSample(BitConverter.ToInt16(args.Buffer, index)); sample[sampleIndex++] = ulawByte; } if (RtpMediaSession != null) { RtpMediaSession.SendAudioFrame(rtpSendTimestamp, sample); rtpSendTimestamp += (uint)(8000 / waveInEvent.BufferMilliseconds); } }; // At this point the call has been initiated and everything will be handled in an event handler. Task.Run(async() => { try { while (!exitCts.Token.WaitHandle.WaitOne(0)) { var keyProps = Console.ReadKey(); if (keyProps.KeyChar == 'c') { if (!userAgent.IsCallActive) { RtpMediaSession = new RTPMediaSession(SDPMediaTypesEnum.audio, (int)SDPMediaFormatsEnum.PCMU, AddressFamily.InterNetwork); RtpMediaSession.RemotePutOnHold += () => Log.LogInformation("Remote call party has placed us on hold."); RtpMediaSession.RemoteTookOffHold += () => Log.LogInformation("Remote call party took us off hold."); var callDescriptor = GetCallDescriptor(DEFAULT_DESTINATION_SIP_URI); await userAgent.InitiateCall(callDescriptor, RtpMediaSession); } else { Log.LogWarning("There is already an active call."); } } else if (keyProps.KeyChar == 'h') { // Place call on/off hold. if (userAgent.IsCallActive) { if (RtpMediaSession.LocalOnHold) { Log.LogInformation("Taking the remote call party off hold."); RtpMediaSession.TakeOffHold(); } else { Log.LogInformation("Placing the remote call party on hold."); RtpMediaSession.PutOnHold(); } } else { Log.LogWarning("There is no active call to put on hold."); } } else if (keyProps.KeyChar == 't') { if (userAgent.IsCallActive) { var transferURI = SIPURI.ParseSIPURI(TRANSFER_DESTINATION_SIP_URI); bool result = await userAgent.BlindTransfer(transferURI, TimeSpan.FromSeconds(TRANSFER_TIMEOUT_SECONDS), exitCts.Token); if (result) { // If the transfer was accepted the original call will already have been hungup. // Wait a second for the transfer NOTIFY request to arrive. await Task.Delay(1000); exitCts.Cancel(); } else { Log.LogWarning($"Transfer to {TRANSFER_DESTINATION_SIP_URI} failed."); } } else { Log.LogWarning("There is no active call to transfer."); } } else if (keyProps.KeyChar == 'q') { // Quit application. exitCts.Cancel(); } } } catch (Exception excp) { SIPSorcery.Sys.Log.Logger.LogError($"Exception Key Press listener. {excp.Message}."); } }); // Ctrl-c will gracefully exit the call at any point. Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) { e.Cancel = true; exitCts.Cancel(); }; // Wait for a signal saying the call failed, was cancelled with ctrl-c or completed. exitCts.Token.WaitHandle.WaitOne(); #region Cleanup. Log.LogInformation("Exiting..."); RtpMediaSession?.Close(); waveInEvent?.StopRecording(); audioOutEvent?.Stop(); if (!isCallHungup && userAgent != null) { if (userAgent.IsCallActive) { Log.LogInformation($"Hanging up call to {userAgent?.CallDescriptor?.To}."); userAgent.Hangup(); } else if (!hasCallFailed) { Log.LogInformation($"Cancelling call to {userAgent?.CallDescriptor?.To}."); userAgent.Cancel(); } // Give the BYE or CANCEL request time to be transmitted. Log.LogInformation("Waiting 1s for call to clean up..."); Task.Delay(1000).Wait(); } SIPSorcery.Net.DNSManager.Stop(); if (sipTransport != null) { Log.LogInformation("Shutting down SIP transport..."); sipTransport.Shutdown(); } #endregion }
/// <summary> /// Process user key presses. /// </summary> /// <param name="exit">The cancellation token to set if the user requests to quit the application.</param> private static void OnKeyPress(CancellationTokenSource exitCts) { try { while (!exitCts.IsCancellationRequested) { var keyProps = Console.ReadKey(); if (keyProps.KeyChar == 'c') { if (_transfereeCall == null || !_transfereeCall.IsCallActive) { _transfereeCall = new SIPUserAgent(_sipTransport, null); // Place an outgoing call. _transfereeCall.ClientCallTrying += (uac, resp) => Log.LogInformation($"{uac.CallDescriptor.To} Trying: {resp.StatusCode} {resp.ReasonPhrase}."); _transfereeCall.ClientCallRinging += (uac, resp) => Log.LogInformation($"{uac.CallDescriptor.To} Ringing: {resp.StatusCode} {resp.ReasonPhrase}."); _transfereeCall.ClientCallFailed += (uac, err, resp) => Log.LogWarning($"{uac.CallDescriptor.To} Failed: {err}, Status code: {resp?.StatusCode}"); _transfereeCall.ClientCallAnswered += (uac, resp) => Log.LogInformation($"{uac.CallDescriptor.To} Answered: {resp.StatusCode} {resp.ReasonPhrase}."); _transfereeCall.OnDtmfTone += (key, duration) => Log.LogInformation($"Received DTMF tone {key}."); //ua.OnRtpEvent += (evt, hdr) => Log.LogDebug($"transferee rtp event {evt.EventID}, ssrc {hdr.SyncSource}, duration {evt.Duration}, end of event {evt.EndOfEvent}, timestamp {hdr.Timestamp}, marker {hdr.MarkerBit}."); _transfereeCall.OnCallHungup += (dialog) => Log.LogDebug("Call hungup by remote party."); Task.Run(async() => { var rtpSession = CreateRtpSession(); var callResult = await _transfereeCall.Call(TRANSFEREE_DST, null, null, rtpSession); if (!callResult) { Log.LogWarning($"Call to {TRANSFEREE_DST} failed."); } else { Log.LogInformation($"Call to {TRANSFEREE_DST} was successful."); } }); } else { Log.LogWarning("Cannot place a new call, the transferee user agent is busy."); } } else if (keyProps.KeyChar == 'h') { if (_transfereeCall != null && _transfereeCall.IsCallActive) { Log.LogDebug("Hanging up transferee call."); _transfereeCall.Hangup(); } else { Log.LogDebug("No active transferee call."); } _transfereeCall = null; } else if (keyProps.KeyChar == 't') { if (_transfereeCall == null) { Log.LogWarning("The call to the transferee is not established."); } else { Task.Run(async() => { Log.LogInformation("Initiating transfer to the transferee..."); bool transferResult = await _transfereeCall.BlindTransfer(SIPURI.ParseSIPURI(TARGET_DST), TimeSpan.FromSeconds(2), exitCts.Token); Log.LogDebug($"Transfer result {transferResult}."); await Task.Delay(2000); Log.LogDebug($"Transferee is call active (should be false if transfer succeeded) {_transfereeCall?.IsCallActive}."); }); } } else if (keyProps.KeyChar == 'a') { Log.LogDebug($"Yes I am alive, transferee is call active ? {_transfereeCall?.IsCallActive}!"); } else if (keyProps.KeyChar == 'q') { // Quit application. Log.LogInformation("Quitting"); exitCts.Cancel(); break; } } } catch (Exception excp) { Log.LogError($"Exception OnKeyPress. {excp.Message}."); } }