private SIPDialogue Dial( ForkCall call, string data, int ringTimeout, int answeredCallLimit, SIPRequest clientRequest, List <string> customHeaders) { SIPDialogue answeredDialogue = null; ManualResetEvent waitForCallCompleted = new ManualResetEvent(false); //call.CallProgress += (s, r, h, t, b) => { Log("Progress response of " + s + " received on CallBack Dial" + "."); }; call.CallProgress += CallProgress; call.CallFailed += (s, r, h) => { waitForCallCompleted.Set(); }; call.CallAnswered += (s, r, toTag, h, t, b, d, transferMode) => { answeredDialogue = d; waitForCallCompleted.Set(); }; try { Queue <List <SIPCallDescriptor> > callsQueue = m_dialStringParser.ParseDialString(DialPlanContextsEnum.Script, clientRequest, data, customHeaders, null, null, null, null, null, null, null, CustomerServiceLevels.None); call.Start(callsQueue); // Wait for an answer. ringTimeout = (ringTimeout > MAXCALLBACK_RINGTIME_SECONDS || ringTimeout <= 0) ? MAXCALLBACK_RINGTIME_SECONDS : ringTimeout; logger.Debug("Set callback cancel timeout to " + ringTimeout + " seconds."); if (!waitForCallCompleted.WaitOne(ringTimeout * 1000, false)) { call.CancelNotRequiredCallLegs(CallCancelCause.TimedOut); } logger.Debug("Callback dial returning has dialogue ? " + (answeredDialogue == null) + "."); return(answeredDialogue); } catch (Exception excp) { logger.Error("Exception CallbackApp Dial. " + excp); return(null); } }
/// <summary> /// /// </summary> /// <param name="data"></param> /// <param name="ringTimeout"></param> /// <param name="answeredCallLimit"></param> /// <param name="redirectMode"></param> /// <param name="clientTransaction"></param> /// <param name="keepScriptAlive">If false will let the dial plan engine know the script has finished and the call is answered. For applications /// like Callback which need to have two calls answered it will be true.</param> /// <returns></returns> private DialPlanAppResult Dial( string data, int ringTimeout, int answeredCallLimit, SIPRequest clientRequest, CRMHeaders contact) { if (m_dialPlanContext.IsAnswered) { Log("The call has already been answered the Dial command was not processed."); return DialPlanAppResult.AlreadyAnswered; } else if (data.IsNullOrBlank()) { Log("The dial string cannot be empty when calling Dial."); return DialPlanAppResult.Error; } else if (m_callInitialisationCount > MAX_CALLS_ALLOWED) { Log("You have exceeded the maximum allowed calls for a dialplan execution."); return DialPlanAppResult.Error; } else { Log("Commencing Dial with: " + data + "."); DialPlanAppResult result = DialPlanAppResult.Unknown; m_waitForCallCompleted = new ManualResetEvent(false); SIPResponseStatusCodesEnum answeredStatus = SIPResponseStatusCodesEnum.None; string answeredReason = null; string answeredContentType = null; string answeredBody = null; SIPDialogue answeredDialogue = null; SIPDialogueTransferModesEnum uasTransferMode = SIPDialogueTransferModesEnum.Default; int numberLegs = 0; QueueNewCallDelegate queueNewCall = (m_callManager != null) ? m_callManager.QueueNewCall : (QueueNewCallDelegate)null; m_currentCall = new ForkCall(m_sipTransport, FireProxyLogEvent, queueNewCall, m_dialStringParser, Username, m_adminMemberId, m_outboundProxySocket, m_callManager, m_dialPlanContext, out LastDialled); m_currentCall.CallProgress += m_dialPlanContext.CallProgress; m_currentCall.CallFailed += (status, reason, headers) => { LastFailureStatus = status; LastFailureReason = reason; result = DialPlanAppResult.Failed; m_waitForCallCompleted.Set(); }; m_currentCall.CallAnswered += (status, reason, toTag, headers, contentType, body, dialogue, transferMode) => { answeredStatus = status; answeredReason = reason; answeredContentType = contentType; answeredBody = body; answeredDialogue = dialogue; uasTransferMode = transferMode; result = DialPlanAppResult.Answered; m_waitForCallCompleted.Set(); }; try { Queue<List<SIPCallDescriptor>> callsQueue = m_dialStringParser.ParseDialString( DialPlanContextsEnum.Script, clientRequest, data, m_customSIPHeaders, m_customContentType, m_customContent, m_dialPlanContext.CallersNetworkId, m_customFromName, m_customFromUser, m_customFromHost, contact, ServiceLevel); List<SIPCallDescriptor>[] callListArray = callsQueue.ToArray(); callsQueue.ToList().ForEach((list) => numberLegs += list.Count); if (numberLegs == 0) { Log("The dial string did not result in any call legs."); return DialPlanAppResult.Error; } else { m_callInitialisationCount += numberLegs; if (m_callInitialisationCount > MAX_CALLS_ALLOWED) { Log("You have exceeded the maximum allowed calls for a dialplan execution."); return DialPlanAppResult.Error; } } m_currentCall.Start(callsQueue); // Wait for an answer. if (ringTimeout <= 0 || ringTimeout * 1000 > m_maxRingTime) { ringTimeout = m_maxRingTime; } else { ringTimeout = ringTimeout * 1000; } ExtendScriptTimeout(ringTimeout / 1000 + DEFAULT_CREATECALL_RINGTIME); DateTime startTime = DateTime.Now; if (m_waitForCallCompleted.WaitOne(ringTimeout, false)) { if (!m_clientCallCancelled) { if (result == DialPlanAppResult.Answered) { // The call limit duration is only used if there hasn't already been a per leg duration set on the call. if (answeredCallLimit > 0 && answeredDialogue.CallDurationLimit == 0) { answeredDialogue.CallDurationLimit = answeredCallLimit; } m_dialPlanContext.CallAnswered(answeredStatus, answeredReason, null, null, answeredContentType, answeredBody, answeredDialogue, uasTransferMode); // Dial plan script stops once there is an answered call to bridge to or the client call is cancelled. Log("Dial command was successfully answered in " + DateTime.Now.Subtract(startTime).TotalSeconds.ToString("0.00") + "s."); // Do some Google Analytics call tracking. if (answeredDialogue.RemoteUserField != null) { SendGoogleAnalyticsEvent("Call", "Answered", answeredDialogue.RemoteUserField.URI.Host, 1); } m_executingScript.StopExecution(); } else if (result == DialPlanAppResult.Failed) { // Check whether any of the responses were redirects. if (LastDialled != null && LastDialled.Count > 0) { var redirect = (from trans in LastDialled where trans.TransactionFinalResponse != null && trans.TransactionFinalResponse.StatusCode >= 300 && trans.TransactionFinalResponse.StatusCode <= 399 && trans.TransactionFinalResponse.Header.Contact != null && trans.TransactionFinalResponse.Header.Contact.Count > 0 select trans.TransactionFinalResponse).FirstOrDefault(); if (redirect != null) { m_redirectResponse = redirect; m_redirectURI = RedirectResponse.Header.Contact[0].ContactURI; result = DialPlanAppResult.Redirect; } } } } } else { if (!m_clientCallCancelled) { // Call timed out. m_currentCall.CancelNotRequiredCallLegs(CallCancelCause.TimedOut); result = DialPlanAppResult.TimedOut; } } if (m_clientCallCancelled) { Log("Dial command was halted by cancellation of client call after " + DateTime.Now.Subtract(startTime).TotalSeconds.ToString("#.00") + "s."); m_executingScript.StopExecution(); } return result; } catch (ThreadAbortException) { return DialPlanAppResult.Unknown; } catch (Exception excp) { logger.Error("Exception DialPlanScriptFacade Dial. " + excp.Message); return DialPlanAppResult.Error; } } }
/// <summary> /// Establishes a new call with the client end tied to the proxy. Since the proxy will not be sending any audio the idea is that once /// the call is up it should be re-INVITED off somewhere else pronto to avoid the callee sitting their listening to dead air. /// </summary> /// <param name="dest1">The dial string of the first call to place.</param> /// <param name="dest2">The dial string of the second call to place.</param> /// <param name="delaySeconds">Delay in seconds before placing the first call. Gives the user a chance to hangup their phone if they are calling themselves back.</param> /// <param name="ringTimeoutLeg1">The ring timeout for the first call leg, If 0 the max timeout will be used.</param> /// <param name="ringTimeoutLeg1">The ring timeout for the second call leg, If 0 the max timeout will be used.</param> /// <param name="customHeadersCallLeg1">A | delimited string that contains a list of custom SIP headers to add to the INVITE request sent for the first call leg.</param> /// /// <param name="customHeadersCallLeg2">A | delimited string that contains a list of custom SIP headers to add to the INVITE request sent for the second call leg.</param> /// <returns>The result of the call.</returns> public void Callback(string dest1, string dest2, int delaySeconds, int ringTimeoutLeg1, int ringTimeoutLeg2, string customHeadersCallLeg1, string customHeadersCallLeg2) { var ts = new CancellationTokenSource(); CancellationToken ct = ts.Token; try { if (delaySeconds > 0) { delaySeconds = (delaySeconds > MAXCALLBACK_DELAY_SECONDS) ? MAXCALLBACK_DELAY_SECONDS : delaySeconds; Log("Callback app delaying by " + delaySeconds + "s."); Thread.Sleep(delaySeconds * 1000); } Log("Callback app commencing first leg to " + dest1 + "."); SIPEndPoint defaultUDPEP = m_sipTransport.GetDefaultSIPEndPoint(SIPProtocolsEnum.udp); SIPRequest firstLegDummyInviteRequest = GetCallbackInviteRequest(defaultUDPEP.GetIPEndPoint(), null); ForkCall firstLegCall = new ForkCall(m_sipTransport, Log_External, m_callManager.QueueNewCall, null, m_username, m_adminMemberId, m_outboundProxy, m_callManager, null); m_firstLegDialogue = Dial(firstLegCall, dest1, ringTimeoutLeg1, 0, firstLegDummyInviteRequest, SIPCallDescriptor.ParseCustomHeaders(customHeadersCallLeg1)); if (m_firstLegDialogue == null) { Log("The first call leg to " + dest1 + " was unsuccessful."); return; } // Persist the dialogue to the database so any hangup can be detected. m_sipDialoguePersistor.Add(new SIPDialogueAsset(m_firstLegDialogue)); SDP firstLegSDP = SDP.ParseSDPDescription(m_firstLegDialogue.RemoteSDP); string call1SDPIPAddress = firstLegSDP.Connection.ConnectionAddress; int call1SDPPort = firstLegSDP.Media[0].Port; Log("The first call leg to " + dest1 + " was successful, audio socket=" + call1SDPIPAddress + ":" + call1SDPPort + "."); Log("Callback app commencing second leg to " + dest2 + "."); SIPRequest secondLegDummyInviteRequest = GetCallbackInviteRequest(defaultUDPEP.GetIPEndPoint(), m_firstLegDialogue.RemoteSDP); ForkCall secondLegCall = new ForkCall(m_sipTransport, Log_External, m_callManager.QueueNewCall, null, m_username, m_adminMemberId, m_outboundProxy, m_callManager, null); Task.Factory.StartNew(() => { while (true) { Thread.Sleep(CHECK_FIRST_LEG_FOR_HANGUP_PERIOD); Console.WriteLine("Checking if first call leg is still up..."); if (ct.IsCancellationRequested) { Console.WriteLine("Checking first call leg task was cancelled."); break; } else { // Check that the first call leg hasn't been hung up. var dialog = m_sipDialoguePersistor.Get(m_firstLegDialogue.Id); if (dialog == null) { Console.WriteLine("First call leg has been hungup."); // The first call leg has been hungup while waiting for the second call. Log("The first call leg was hungup while the second call leg was waiting for an answer."); secondLegCall.CancelNotRequiredCallLegs(CallCancelCause.ClientCancelled); break; } } } Console.WriteLine("Checking first call leg task finished..."); }, ct); SIPDialogue secondLegDialogue = Dial(secondLegCall, dest2, ringTimeoutLeg2, 0, secondLegDummyInviteRequest, SIPCallDescriptor.ParseCustomHeaders(customHeadersCallLeg2)); ts.Cancel(); if (secondLegDialogue == null) { Log("The second call leg to " + dest2 + " was unsuccessful."); m_firstLegDialogue.Hangup(m_sipTransport, m_outboundProxy); return; } // Check that the first call leg hasn't been hung up. var firstLegDialog = m_sipDialoguePersistor.Get(m_firstLegDialogue.Id); if (firstLegDialog == null) { // The first call leg has been hungup while waiting for the second call. Log("The first call leg was hungup while waiting for the second call leg."); secondLegDialogue.Hangup(m_sipTransport, m_outboundProxy); return; } SDP secondLegSDP = SDP.ParseSDPDescription(secondLegDialogue.RemoteSDP); string call2SDPIPAddress = secondLegSDP.Connection.ConnectionAddress; int call2SDPPort = secondLegSDP.Media[0].Port; Log("The second call leg to " + dest2 + " was successful, audio socket=" + call2SDPIPAddress + ":" + call2SDPPort + "."); // Persist the second leg dialogue and update the bridge ID on the first call leg. Guid bridgeId = Guid.NewGuid(); secondLegDialogue.BridgeId = bridgeId; m_sipDialoguePersistor.Add(new SIPDialogueAsset(secondLegDialogue)); m_sipDialoguePersistor.UpdateProperty(firstLegDialog.Id, "BridgeID", bridgeId.ToString()); //m_callManager.CreateDialogueBridge(m_firstLegDialogue, secondLegDialogue, m_username); Log("Re-inviting Callback dialogues to each other."); m_callManager.ReInvite(m_firstLegDialogue, secondLegDialogue); //m_callManager.ReInvite(secondLegDialogue, m_firstLegDialogue.RemoteSDP); SendRTPPacket(call2SDPIPAddress + ":" + call2SDPPort, call1SDPIPAddress + ":" + call1SDPPort); SendRTPPacket(call1SDPIPAddress + ":" + call1SDPPort, call2SDPIPAddress + ":" + call2SDPPort); } catch (Exception excp) { logger.Error("Exception CallbackApp. " + excp); Log("Exception in Callback. " + excp); } finally { if (!ts.IsCancellationRequested) { ts.Cancel(); } } }
private SIPDialogue Dial( string data, int ringTimeout, int answeredCallLimit, SIPRequest clientRequest, List<string> customHeaders) { SIPDialogue answeredDialogue = null; ManualResetEvent waitForCallCompleted = new ManualResetEvent(false); ForkCall call = new ForkCall(m_sipTransport, Log_External, m_callManager.QueueNewCall, null, m_username, m_adminMemberId, m_outboundProxy, m_callManager, null); //call.CallProgress += (s, r, h, t, b) => { Log("Progress response of " + s + " received on CallBack Dial" + "."); }; call.CallProgress += CallProgress; call.CallFailed += (s, r, h) => { waitForCallCompleted.Set(); }; call.CallAnswered += (s, r, toTag, h, t, b, d, transferMode) => { answeredDialogue = d; waitForCallCompleted.Set(); }; try { Queue<List<SIPCallDescriptor>> callsQueue = m_dialStringParser.ParseDialString(DialPlanContextsEnum.Script, clientRequest, data, customHeaders, null, null, null, null, null, null, null, CustomerServiceLevels.None); call.Start(callsQueue); // Wait for an answer. ringTimeout = (ringTimeout > m_maxRingTime) ? m_maxRingTime : ringTimeout; if (waitForCallCompleted.WaitOne(ringTimeout * 1000, false)) { // Call timed out. call.CancelNotRequiredCallLegs(CallCancelCause.TimedOut); } return answeredDialogue; } catch (Exception excp) { logger.Error("Exception CallbackApp Dial. " + excp); return null; } }
private SIPDialogue Dial( ForkCall call, string data, int ringTimeout, int answeredCallLimit, SIPRequest clientRequest, List<string> customHeaders) { SIPDialogue answeredDialogue = null; ManualResetEvent waitForCallCompleted = new ManualResetEvent(false); //call.CallProgress += (s, r, h, t, b) => { Log("Progress response of " + s + " received on CallBack Dial" + "."); }; call.CallProgress += CallProgress; call.CallFailed += (s, r, h) => { waitForCallCompleted.Set(); }; call.CallAnswered += (s, r, toTag, h, t, b, d, transferMode) => { answeredDialogue = d; waitForCallCompleted.Set(); }; try { Queue<List<SIPCallDescriptor>> callsQueue = m_dialStringParser.ParseDialString(DialPlanContextsEnum.Script, clientRequest, data, customHeaders, null, null, null, null, null, null, null, CustomerServiceLevels.None); call.Start(callsQueue); // Wait for an answer. ringTimeout = (ringTimeout > MAXCALLBACK_RINGTIME_SECONDS || ringTimeout <= 0) ? MAXCALLBACK_RINGTIME_SECONDS : ringTimeout; logger.Debug("Set callback cancel timeout to " + ringTimeout + " seconds."); if (!waitForCallCompleted.WaitOne(ringTimeout * 1000, false)) { call.CancelNotRequiredCallLegs(CallCancelCause.TimedOut); } logger.Debug("Callback dial returning has dialogue ? " + (answeredDialogue == null) + "."); return answeredDialogue; } catch (Exception excp) { logger.Error("Exception CallbackApp Dial. " + excp); return null; } }