right  Talk To Us!

UAS API - Outbound Connect Sample Application

  • Filename:

    samples/outbound_connect.py

    Description:

    An outbound application that makes calls to two destination addresses and connects them together. The destination addresses are supplied in the outbound_parameters variable, using the semi-colon (;) as a delimiter. This application also requires one extra channel.

    First the two call destination addresses are extracted from outbound_parameters using Python's split() function. Then the extra channel is retrieved from the channel.ExtraChannel list. Extra channels are set on the Inbound and Outbound Services pages.

    The application places two calls and the calling code has been put in a function called _place_call, which takes a channel, destination address and call_from as parameters. The call_from variable identifies the calling party and its value is taken from application_parameters which is configured on the Outbound Services page.

    The first outbound call is placed and, if successful, TTS is used to ask the user to speak a message after the beep. To create a beep, a DTMF tone is played using the channel.DTMFPlayer.play() function. The message is recorded and then the caller is asked to hold while the system calls the second destination..

    The extra channel is used to call the second destination and the recorded message from the first call is played using the channel.FilePlayer.start() function which does not block. This allows the application to use TTS to tell the first caller that his message is being played.

    Once the message has been played, the application joins the two calls together using the channel.connect() function, this allows the two calls to communicate with each other. The application then waits for either party to hang up, using the channel.state() function on each channel.

    Code:

    """
        An outbound application that makes calls to two destination addresses 
        (passed in through outbound_parameters using the semi-colon as a 
        delimiter). 
    
        It prompts the first call to record a message and plays this to the 
        second call before connecting the two calls together. It then waits for 
        the calls to be hung up. 
    
        This application requires that call_from is supplied in 
        application_parameters and it requires one extra channel (this is 
        configured on the outbound service page of the CWP). 
    
        For information on placing outbound calls please read the online 
        web services documentation.
        
        For additional information regarding outbound PSTN calls, please see the 
        online documentation.
        
        Also have a look at the invoke_outbound_service sample code in the 
        samples_ws directory.
    
        Actions:
            - place an outbound call
            - record a message
            - place another outbound call
            - play the message to the outbound call
            - connect the two calls together
            - wait for someone to hang up
    
    """
    
    from prosody.uas import Hangup, Error, ICanRun
    import time
    
    __uas_version__  = "0.0.1"
    __uas_identify__ = "application"
    
    
    def _place_call(channel, destination, call_from):
            # Place an outbound call. If the destination is BUSY,
            # wait ten seconds and try again; try for one minute.
            endTime = time.time() + 60
    
            while channel.call(destination, call_from=call_from) != channel.State.ANSWERED:
                cause = channel.cause()
                if cause != channel.Cause.BUSY:
                    raise Error("Call destination returned cause {0}".format(cause))
                if time.time() > endTime:
                    raise Hangup("Call destination is busy.")
                time.sleep(10)
                continue
    
            
    def main(channel, application_instance_id, file_man, my_log, application_parameters, outbound_parameters):
        try:
            return_code = 0
            out_channel_2 = None
            # read the outbound args, we're expecting two destination_address with a ; as delimiter
            my_log.info("Out Args: {0}".format(outbound_parameters))
            
            out_arg_splits = outbound_parameters.split(';')
            try:
                outbound_destination_1 = out_arg_splits[0]
                outbound_destination_2 = out_arg_splits[1]
                # we expect the call_from details to be in application_parameters
                # this is used in CLI validation for PSTN calls
                call_from = application_parameters
            except:
                raise Error("Could not get outbound call destinations.")
            
            # get the extra channel
            try:
                out_channel_2 = channel.ExtraChannel[0]
            except:
                raise Error("You need to register an extra channel for this application")
    
            #place the first call
            _place_call(channel, outbound_destination_1, call_from)
            
            my_log.info("Placed first outbound call")
    
            # prompt the party to record a message, the prompt is played using TTS
            cause = channel.FilePlayer.say("<acu-engine name='Polly'><voice name='Brian'>Hello, please record a message after the beep.</voice></acu-engine>")
            if cause != channel.FilePlayer.Cause.NORMAL:
                raise Error("Say hello failed: cause is {0}".format(cause)) # log at error level
    
            # play a DTMF digit for the tone
            cause = channel.DTMFPlayer.play('#')
            if cause != channel.DTMFPlayer.Cause.NORMAL:
                raise Error("Play # failed: cause is {0}".format(cause))
            
            # record the message, record until we've had three seconds of silence
            cause = channel.FileRecorder.record('recorded_message_{0}.wav'.format(application_instance_id), milliseconds_max_silence=3000)
            if cause != channel.FileRecorder.Cause.SILENCE:
                raise Error("Record message failed: cause is {0}".format(cause)) # log at error level        
    
            # thanks for the recording
            cause = channel.FilePlayer.say("Thank you, please wait while I phone a friend.")
            if cause != channel.FilePlayer.Cause.NORMAL:
                raise Error("Say thank you failed: cause is {0}".format(cause)) # log at error level
            
            # place next call
            _place_call(out_channel_2, outbound_destination_2, call_from)
                
            # We have an outbound call, start playing the recording to the outbound call
            if out_channel_2.FilePlayer.start('recorded_message_{0}.wav'.format(application_instance_id)) is False:
                raise Error("Failed to start playing recording to outbound channel")
            
            # tell the inbound channel what we're doing
            cause = channel.FilePlayer.say("I am playing your message to your friend.")
            if cause != channel.FilePlayer.Cause.NORMAL:
                raise Error("Say playing to friend failed: cause is {0}".format(cause))
            
            # wait for message play to complete
            cause = out_channel_2.FilePlayer.wait_until_finished()
            if cause != out_channel_2.FilePlayer.Cause.NORMAL:
                raise Error("Wait for finished failed: cause is {0}".format(cause))
    
            # indicate to the far end that the message has finished playing
            cause = out_channel_2.DTMFPlayer.play('#')
            if cause != out_channel_2.DTMFPlayer.Cause.NORMAL:
                raise Error("DTMF play returned {0}: expected {1}".format(cause, out_channel_2.DTMFPlayer.Cause.NORMAL))
                
            # let the parties talk to each other
            if channel.connect(out_channel_2) is True:
                # wait until either call hangs up
                # wait on i_can_run to prevent an accidental infinite loop
                i_can_run = ICanRun(channel)
                while i_can_run:
                    # throw hangup exception on IDLE
                    channel.state(check_for_hangup=True)
                    out_channel_2.state(check_for_hangup=True)
                    time.sleep(1)
            else:
                raise Error('Connect channels failed')
                
        except Hangup as exc:
            my_log.info("Hangup exception reports: {0}".format(exc))
             # in this app a hangup is not an error, return a positive value
            return_code = 100
            
        except Error as exc:
            # for error conditions return a negative value
            my_log.error("Error exception reports: {0}".format(exc))
            return_code = -101
            
        except Exception as exc:
            # an unexpected exception, return a negative value
            my_log.exception("Unexpected exception reports: {0}".format(exc))
            return_code = -102
            
        finally:
            if channel is not None:
                if channel.state() != channel.State.IDLE:
                    channel.hang_up()
            if out_channel_2 is not None:
                if out_channel_2.state() != out_channel_2.State.IDLE:
                    out_channel_2.hang_up()
        return return_code
    
  • Filename:

    Samples\C#\OutboundConnect\OutboundConnect.cs

    Description:

    In this application we extract two call destination addresses from the outboundParameters argument using the String.Split() method and then ensure we have at least one extra channel available. This will allow us to make one outbound call on the primary channel (the channel argument) and one outbound call on an extra channel.

    If either of the destination addresses are PSTN numbers then the source address (caller Id) that is passed into the application via the applicationParameters argument must be set to a PSTN number that has been validated from the Caller Id page on cloud.aculab.com.

    We call the first destination address on the primary channel using the channel.Call() method and when answered, we use TTS to ask the callee to record a message that will ultimately be played to the second callee. We can record a message using the channel.FileRecorder.Start() method. This requires a filename as a parameter to identify where to store the recorded data. To play a message prompt, we use the channel.DTmfPlayer.Play() method. We wait for silence from the other end for a couple of seconds before ending the recording.

    We then call the second destination address on one of the extra channels and when answered, we play the previously recorded message to the second callee. Then we use TTS to notify both callees that they are being connected and use the Connect() method to connect both calls together bidirectionally. To wait for either caller to hangup, we use the channel.WaitForIdle() method.

    Code:

    using System;
    using System.Threading;
    using AMSClassLibrary;
    using UASAppAPI;
    
    // An outbound application that makes calls to two destination addresses (passed in).
    // It prompts the first call to record a message and plays this to the second call 
    // before tromboning the two calls together. It then waits for the calls to be hung up.
    //
    // The Caller Id (callFrom) that is to be used when calling out should be supplied in
    // the applicationsParameters field. For outgoing SIP calls this can be empty, but for
    // outgoing PSTN calls see the Outbound Calls documentation for your platform.
    //
    // Requires:
    // [outboundParameters = two destination addresses for the two outgoing calls, semicolon delimited]
    // [applicationParameters = an optional validated Caller Id]
    // [1 extra channels]
    //
    // e.g.
    // outboundParameters = "sip:bob@bobscompany.com;sip:brian@brianscompany.com"
    // applicationParameters = ""
    // or
    // outboundParameters = "441908081876;441908678180"
    // applicationParameters = "441908876543"
    //
    namespace OutboundConnect
    {
        // The application class.
        // This must have the same name as the assembly and must inherit from either 
        // UASInboundApplication or UASOutboundApplication.
        // It must override the Run method.
        public class OutboundConnect : UASOutboundApplication
        {
            // Possible return codes
            enum ReturnCode
            {
                // Success Codes:
                Success = 0,
                // ... any positive integer
    
                // Fail Codes:
                // -1 to -99 reserved
                ExceptionThrown = -100,
                ArgumentError = -101,
                NoCallerIdError = -102,
                NotSufficientExtraChannels = -103
            }
    
            // This is the entry point for the application
            public override int Run(UASCallChannel channel,
                                    string applicationParameters, // Caller Id (may be required for outgoing PSTN, optional for outgoing SIP)
                                    string outboundParameters)    // 2 x destinationAddresses, semicolon delimited...
            {
                this.Trace.TraceInfo("Start - appParms [{0}] outParms [{1}]",
                                   applicationParameters,
                                   outboundParameters);
                ReturnCode reply = ReturnCode.Success;
    
                UASCallChannel otherChannel = null;
                string recFileName = "recordings/introMessage";
    
                try
                {
                    // Typically the outboundParameters field is simply the destination address.
                    // However, in this case we require 2 destination addresses to be specified.
                    string[] destinationAddress = outboundParameters.Split(';');
                    if (destinationAddress.Length < 2)
                    {
                        reply = ReturnCode.ArgumentError;
                        this.Trace.TraceError("This app requires 2 destination addresses in the outboundParameters field");
                    }
    
                    // Check we've got an extra Channel
                    if (reply == ReturnCode.Success)
                    {
                        if (channel.ExtraChannels.Length < 1)
                        {
                            this.Trace.TraceError("This app requires at least 1 extra channel to be configured");
                            reply = ReturnCode.NotSufficientExtraChannels;
                        }
                    }
    
                    // Make the first call on the primary channel
                    if (reply == ReturnCode.Success)
                    {
                        string callFrom = applicationParameters;
                        CallState state = channel.Call(destinationAddress[0], callFrom);
                        if (state == CallState.Answered)
                        {
                            // Record an introduction message
                            channel.FilePlayer.Say("Hello." +
                                                   "Please record an introduction message for " +
                                                   destinationAddress[1] +
                                                   "after the tone." +
                                                   "Press any digit to stop the recording.");
    
                            Thread.Sleep(1000);
    
                            // Start recording, enabling barge in
                            channel.FileRecorder.Start(recFileName, true);
    
                            // Play a tone to signal that recording has started
                            channel.DtmfPlayer.Play("0");
    
                            // Wait for a period for recording to complete (10 seconds of silence) 
                            // or the call to have been hung up
                            FileRecorderCause recCause = channel.FileRecorder.WaitUntilFinished(60);
                            if (recCause == FileRecorderCause.Timeout)
                            {
                                channel.FileRecorder.Stop();
                            }
    
                            // And play it back
                            if (channel.State != CallState.Idle)
                            {
                                // Replay the recorded message
                                channel.FilePlayer.Say("The following message has been recorded");
                                channel.FilePlayer.Play(recFileName);
                            }
                        }
                    }
    
                    // Make the second call on the extra channel
                    if (reply == ReturnCode.Success)
                    {
                        channel.FilePlayer.Say("Please wait while phoning contact.");
    
                        string callFrom = applicationParameters;
    
                        otherChannel = channel.ExtraChannels[0];
                        if (otherChannel.Call(destinationAddress[1], callFrom) == CallState.Answered)
                        {
                            // Play the introductory message to other call
                            otherChannel.FilePlayer.Play(recFileName);
    
                            // Connect the calls together
                            channel.FilePlayer.Say("Connecting call");
                            otherChannel.FilePlayer.Say("Connecting call");
                            channel.Connect(otherChannel);
                        }
    
                        // Wait for hang up
                        channel.WaitForIdle(Timeout.Infinite);
                    }
                }
                catch (Exception e)
                {
                    this.Trace.TraceError("Exception thrown {0}", e.Message);
                    reply = ReturnCode.ExceptionThrown;
                }
                finally
                {
                    channel.HangUp();
                    if (otherChannel != null)
                    {
                        otherChannel.HangUp();
                    }
                }
    
                this.Trace.TraceInfo("Completed");
                return (int)reply;
            }
        }
    }
  • Filename:

    Samples\VB\OutboundConnect\OutboundConnect.vb

    Description:

    In this application we extract two call destination addresses from the outboundParameters argument using the String.Split() method and then ensure we have at least one extra channel available. This will allow us to make one outbound call on the primary channel (the channel argument) and one outbound call on an extra channel.

    If either of the destination addresses are PSTN numbers then the source address (caller Id) that is passed into the application via the applicationParameters argument must be set to a PSTN number that has been validated from the Caller Id page on cloud.aculab.com.

    We call the first destination address on the primary channel using the channel.Call() method and when answered, we use TTS to ask the callee to record a message that will ultimately be played to the second callee. We can record a message using the channel.FileRecorder.Start() method. This requires a filename as a parameter to identify where to store the recorded data. To play a message prompt, we use the channel.DTmfPlayer.Play() method. We wait for silence from the other end for a couple of seconds before ending the recording.

    We then call the second destination address on one of the extra channels and when answered, we play the previously recorded message to the second callee. Then we use TTS to notify both callees that they are being connected and use the Connect() method to connect both calls together bidirectionally. To wait for either caller to hangup, we use the channel.WaitForIdle() method.

    Code:

    Imports AMSClassLibrary
    Imports UASAppAPI
    
    ' An outbound application that makes calls to two destination addresses (passed in).
    ' It prompts the first call to record a message and plays this to the second call 
    ' before tromboning the two calls together. It then waits for the calls to be hung up.
    '
    ' The Caller Id (callFrom) that is to be used when calling out should be supplied in
    ' the applicationsParameters field. For outgoing SIP calls this can be empty, but for
    ' outgoing PSTN calls see the Outbound Calls documentation for your platform.
    '
    ' Requires:
    ' [outboundParameters = two destination addresses for the two outgoing calls, semicolon delimited]
    ' [applicationParameters = a validated Caller Id]
    ' [1 extra channels]
    '
    ' e.g.
    ' outboundParameters = "sip:bob@bobscompany.com;sip:brian@brianscompany.com"
    ' applicationParameters = ""
    ' or
    ' outboundParameters = "441908081876;441908678180"
    ' applicationParameters = "441908876543"
    '
    Namespace OutboundConnect
    
        ' The application class.
        ' This must have the same name as the assembly and must inherit from either 
        ' UASInboundApplication or UASOutboundApplication.
        ' It must override the Run method.
        Public Class OutboundConnect
            Inherits UASOutboundApplication
    
            ' Possible return codes
            Enum ReturnCode
                ' Success Codes:
                Success = 0
                ' ... any positive integer
    
                ' Fail Codes:
                ' -1 to -99 reserved
                ExceptionThrown = -100
                ArgumentError = -101
                NoCallerIdError = -102
                NotSufficientExtraChannels = -103
            End Enum
    
            ' This is the entry point for the application
            ' applicationParameters - Caller Id (required for outgoing PSTN, optional for outgoing SIP)
            ' outboundParameters -    2 x destinationAddresses, semicolon delimited
            Overrides Function Run(ByVal channel As UASCallChannel, _
                                   ByVal applicationParameters As String, _
                                   ByVal outboundParameters As String) _
                                   As Integer
    
                Me.Trace.TraceInfo("Start - appParms [{0}] outParms [{1}]", _
                                   applicationParameters, _
                                   outboundParameters)
                Dim reply As ReturnCode = ReturnCode.Success
    
                Dim otherChannel As UASCallChannel = Nothing
                Dim recFileName = "recordings/introMessage"
    
                Try
                    ' Typically the outboundParameters field is simply the destination address.
                    ' However, in this case we require 2 destination addresses to be specified.
                    Dim destinationAddress = outboundParameters.Split(";")
                    If destinationAddress.Length < 2 Then
                        reply = ReturnCode.ArgumentError
                        Me.Trace.TraceError("This app requires 2 destination addresses in the outboundParameters field")
                    End If
    
                    ' Check we've got an extra Channel
                    If reply = ReturnCode.Success Then
                        If channel.ExtraChannels.Length < 1 Then
                            Me.Trace.TraceError("This app requires at least 1 extra channel to be configured")
                            reply = ReturnCode.NotSufficientExtraChannels
                        End If
                    End If
    
                    ' Check we've got a Caller Id supplied if either of these calls are PSTN
                    If reply = ReturnCode.Success Then
                        If Not (destinationAddress(0).Contains("@") And destinationAddress(1).Contains("@")) Then
                            ' At least one address is a PSTN number
                            If String.IsNullOrEmpty(applicationParameters) Then
                                Me.Trace.TraceError("This app requires a validated CallerId in the applicationParameters" + _
                                                    "if any PSTN calls are to be made")
                                reply = ReturnCode.NoCallerIdError
                            End If
                        End If
                    End If
    
                    ' Make the first call on the primary channel
                    If reply = ReturnCode.Success Then
                        Dim callFrom = applicationParameters
                        Dim state = channel.Call(destinationAddress(0), callFrom)
                        If state = CallState.Answered Then
                            ' Record an introduction message
                            channel.FilePlayer.Say("Hello." + _
                                                   "Please record an introduction message for " + _
                                                   destinationAddress(1) + _
                                                   "after the tone." + _
                                                   "Press any digit to stop the recording.")
    
                            Thread.Sleep(1000)
    
                            ' Start recording, enabling barge in
                            channel.FileRecorder.Start(recFileName, True)
    
                            ' Play a tone to signal that recording has started
                            channel.DtmfPlayer.Play("0")
    
                            ' Wait for a period for recording to complete or 
                            ' the call to have been hung up
                            Dim recCause = channel.FileRecorder.WaitUntilFinished(30)
                            If recCause = FileRecorderCause.Timeout Then
                                channel.FileRecorder.Stop()
                            End If
    
                            ' And play it back
                            If channel.State <> CallState.Idle Then
                                ' Replay the recorded message
                                channel.FilePlayer.Say("The following message has been recorded")
                                channel.FilePlayer.Play(recFileName)
                            End If
                        End If
                    End If
    
                    ' Make the second call on the extra channel
                    If reply = ReturnCode.Success Then
                        channel.FilePlayer.Say("Please wait while phoning contact.")
    
                        Dim callFrom = applicationParameters
    
                        otherChannel = channel.ExtraChannels(0)
                        If otherChannel.Call(destinationAddress(1), callFrom) = CallState.Answered Then
                            ' Play the introductory message to other call
                            otherChannel.FilePlayer.Play(recFileName)
    
                            ' Connect the calls together
                            channel.FilePlayer.Say("Connecting call")
                            otherChannel.FilePlayer.Say("Connecting call")
                            channel.Connect(otherChannel)
                        End If
    
                        ' Wait for hang up
                        channel.WaitForIdle(Timeout.Infinite)
                    End If
    
                Catch ex As Exception
                    Me.Trace.TraceError("Exception thrown {0}", ex.Message)
                    reply = ReturnCode.ExceptionThrown
                Finally
                    channel.HangUp()
                End Try
    
                Me.Trace.TraceInfo("Completed")
                Return reply
    
            End Function
    
        End Class
    
    End Namespace
  • Filename:

    Samples\F#\OutboundConnect\OutboundConnect.fs

    Description:

    In this application we first ensure we have at least one extra channel available. Then we extract two call destination addresses from the outboundParameters argument using the String.Split() method. This will allow us to make one outbound call on the primary channel (the channel argument) and one outbound call on an extra channel.

    If either of the destination addresses are PSTN numbers then the source address (caller Id) that is passed into the application via the applicationParameters argument must be set to a PSTN number that has been validated from the Caller Id page on cloud.aculab.com. You can set this in the Application Parameters field of the Outbound Service that runs this application. In the application this is checked using the recursive member function ContainsPSTNAddress().

    We call the first destination address on the primary channel using the channel.Call() method and when answered, we use TTS to ask the callee to record a message that will ultimately be played to the second callee. We can record a message using the channel.FileRecorder.Start() method. This requires a filename as a parameter to identify where to store the recorded data. To play a message prompt, we use the channel.DTmfPlayer.Play() method. We wait for silence from the other end for a couple of seconds before ending the recording.

    We then call the second destination address on one of the extra channels and when answered, we play the previously recorded message to the second callee. Then we use TTS to notify both callees that they are being connected and use the Connect() method to connect both calls together bidirectionally. To wait for either caller to hangup, we use the channel.WaitForIdle() method.

    Code:

    // An outbound application that makes calls to two destination addresses (passed in).
    // It prompts the first call to record a message and plays this to the second call 
    // before tromboning the two calls together. It then waits for the calls to be hung up.
    //
    // The Caller Id (callFrom) that is to be used when calling out should be supplied in
    // the applicationsParameters field. For outgoing SIP calls this can be empty, but for
    // outgoing PSTN calls see the Outbound Calls documentation for your platform.
    //
    // Requires:
    // [outboundParameters = two destination addresses for the two outgoing calls, semicolon delimited]
    // [applicationParameters = a validated Caller Id]
    // [1 extra channels]
    namespace OutboundConnect
    
        open System
        open System.Threading
        open AMSClassLibrary
        open UASAppAPI
    
        // Possible return codes
        type ReturnCode =
            // Success Codes:
            | Success = 0
            // ... any positive integer
    
            // Fail Codes:
            // -1 to -99 reserved
            | ExceptionThrown = -100
            | ArgumentError = -101
            | NoCallerIdError = -102
            | NotSufficientExtraChannels = -103
    
        // The application class.
        // This must have the same name as the assembly and must inherit from either 
        // UASInboundApplication or UASOutboundApplication.
        // It must override the Run method.
        type OutboundConnect() = 
            inherit UASOutboundApplication()
    
            // This is the entry point for the application
            override obj.Run(channel:UASCallChannel, applicationParameters:string, outboundParameters:string) : int = 
                
                obj.Trace.TraceInfo("Started")
                let mutable reply = ReturnCode.Success
    
                // Check we've got an extra Channel
                if channel.ExtraChannels.Length < 1 then
                    obj.Trace.TraceError("This app requires at least 1 extra channel to be configured")
                    int ReturnCode.NotSufficientExtraChannels
                else 
                    let otherChannel : UASCallChannel = channel.ExtraChannels.[0]
                    let recFileName = "recordings/introMessage"
    
                    try
                        try
                            // Typically the outboundParameters field is simply the destination address.
                            // However, in this case we require 2 destination addresses to be specified.
                            let destinationAddresses = outboundParameters.Split(';')
                            if destinationAddresses.Length < 2 then
                                reply <- ReturnCode.ArgumentError
                                obj.Trace.TraceError("This app requires 2 destination addresses in the outboundParameters field")
    
                            // Check we've got a Caller Id supplied if either of these calls are PSTN
                            if reply = ReturnCode.Success then
                                if obj.ContainsPSTNAddress(destinationAddresses) then
                                    // At least one address is a PSTN number
                                    if String.IsNullOrEmpty(applicationParameters) then
                                        obj.Trace.TraceError("This app requires a validated CallerId in the applicationParameters" +
                                                              "if any PSTN calls are to be made")
                                        reply <- ReturnCode.NoCallerIdError
                    
                            // Make the first call on the primary channel
                            if reply = ReturnCode.Success then
                                let callFrom = applicationParameters
    
                                let state = channel.Call(destinationAddresses.[0], callFrom)
                                if state = CallState.Answered then
                                    // Record an introduction message
                                    channel.FilePlayer.Say("Hello." +
                                                           "Please record an introduction message for " +
                                                           destinationAddresses.[1] +
                                                           "after the tone." +
                                                           "Press any digit to stop the recording.") |> ignore
    
                                    Thread.Sleep(1000)
    
                                    // Start recording, enabling barge in
                                    channel.FileRecorder.Start(recFileName, true) |> ignore
    
                                    // Play a tone to signal that recording has started
                                    channel.DtmfPlayer.Play("0") |> ignore
    
                                    // Wait for a period for recording to complete or 
                                    // the call to have been hung up
                                    let recCause = channel.FileRecorder.WaitUntilFinished(30)
                                    if recCause = FileRecorderCause.Timeout then
                                        channel.FileRecorder.Stop() |> ignore
    
                                    // And play it back
                                    if channel.State <> CallState.Idle then
                                        // Replay the recorded message
                                        channel.FilePlayer.Say("The following message has been recorded") |> ignore
                                        channel.FilePlayer.Play(recFileName) |> ignore
    
                            // Make the second call on the extra channel
                            if reply = ReturnCode.Success then
                                channel.FilePlayer.Say("Please wait while phoning contact.") |> ignore
    
                                let callFrom = applicationParameters
    
                                if otherChannel.Call(destinationAddresses.[1], callFrom) = CallState.Answered then
    
                                    // Play the introductory message to other call
                                    otherChannel.FilePlayer.Play(recFileName) |> ignore
    
                                    // Connect the calls together
                                    channel.FilePlayer.Say("Connecting call") |> ignore
                                    otherChannel.FilePlayer.Say("Connecting call") |> ignore
                                    channel.Connect(otherChannel) |> ignore
    
                                // Wait for hang up
                                channel.WaitForIdle(Timeout.Infinite) |> ignore
                        with
                            | _ as e->
                                obj.Trace.TraceError("Exception thrown {0}", e.Message)
                                reply <- ReturnCode.ExceptionThrown
                    finally
                        channel.HangUp() |> ignore
                        if otherChannel <> null then
                            otherChannel.HangUp() |> ignore
                    
                    obj.Trace.TraceInfo("Completed")
                    int reply
    
            // Check whether an array of addresses contains a PSTN address
            member obj.ContainsPSTNAddress (addresses : string[]) =
                let rec IsPSTNAddress(addresses : string[], index : int) : bool =
                    if index < addresses.Length then
                        false 
                    elif addresses.[index].Contains("@") then
                        true
                    else
                        IsPSTNAddress(addresses, index + 1)
                IsPSTNAddress(addresses, 0)