Voicemail Lesson 1
In this tutorial, we are going to create a simple voicemail service. To do this, we will build up the application step by step, successively adding more complex code as we go. This step provides a basic foundation for the rest of the tutorial.
We're going to write an application that can answer the phone, say some text, ask the user to leave a message and then hang up the phone. The data is saved onto the Cloud, so that you can download the recorded wave file, and play it on your local machine. You can find any media files saved by the Cloud in the Media Files menu on cloud.aculab.com.
-
-
Sample Files:
- Samples\voicemail_1.py
Application Execution
The application is called when an inbound call is detected by the UAS. For Python, the
main
function is called first. The UAS expects a return error code, which we must set before program termination. Any values at zero or above are classified as success codes, while anything negative is classed as a failure. The UAS system reserves error codes -1 to -99, but we are free to use anything less than these values for our own reference.Exceptions
At any time, a call to the Cloud may result in an exception being fired. For example, if we play some Text-To-Speech (TTS) to a call, and then the caller hangs up, we will get a HangUp exception. Therefore, whenever we write an application for the UAS, we need to write code in a try/catch block.
Parameters
Our
main
function receives a number of parameters:channel
, which offers us a channel to the Cloud andapplication_instance_id
, which tells us our unique application identification code.file_man
is a file management class, which allows us to alter files already situated on the Cloud, including moving and deleting.my_log
gives access to the logging facilities of the UAS, andapplication_parameters
are application parameters which are configured on the Inbound Services page. -
""" An inbound application that answers the call; prompts the caller to record a message (and to press any key to stop recording); plays a beep to indicate that it is recording and then records for a maximum of 60 seconds. The recorded filename is samples/voicemail1/recordedMessageFrom<callFrom>.wav The application will check that the recorded message file exists before exiting. Actions: - check the channel state - ring and answer - use TTS to play a prompt - record a file - check that the file exists - hang up This application is part of the online Voice Mail tutorial. """ from prosody.uas import Hangup, Error __uas_version__ = "0.0.1" __uas_identify__ = "application" def main (channel, application_instance_id, file_man, my_log, application_parameters): return_code = 0 try: # check the incoming channel state and answer the call state = channel.state() if state == channel.State.CALL_INCOMING: state = channel.ring() # this can raise a Hangup exception if state == channel.State.RING_INCOMING: state = channel.answer() # this can raise a Hangup exception else: raise Hangup('No inbound call, state is {0}'.format(state)) if state != channel.State.ANSWERED: raise Hangup('Failed to answer inbound call, state is {0}'.format(state)) my_log.info("Answered an inbound call") # use text-to-speech to pass instructions to the caller channel.FilePlayer.say("Please record a message after the tone. Press any digit to stop the recording.") # use DTMF to play the tone channel.DTMFPlayer.play ('#') # use the call_from information from the call details to identify this recording msg_file_name = "samples/voicemail1/recordedMessageFrom{0}.wav".format(channel.Details.call_from) # the recording will stop after three seconds of silence, or after one minute # (we don't want people speaking for ages), or if the caller presses a digit (barge_in). channel.FileRecorder.record(msg_file_name, milliseconds_max_silence=3000, seconds_timeout=60, barge_in=True) # check whether the file has been saved correctly, does it exist? if file_man.exists(msg_file_name): channel.FilePlayer.say("Your message has been saved. Good bye.") else: channel.FilePlayer.say("Your message could not be saved. Good bye.") # catch any Hangup exceptions, this is still regarded as a success but the 100 code # identifies that the caller hung up before the application completed except Hangup as exc: my_log.info ("Hangup exception: {0}.".format(exc)) return_code = 100 # catch any Error exceptions, these are often caused by application errors which can be fixed except Error as exc: my_log.error("Error exception caught: {0}".format(exc)) return_code = -101 # catch any other exceptions, these are almost certainly caused by application errors which should be fixed except Exception as exc: my_log.exception("Unexpected exception caught: {0}".format(exc)) return_code = -102 # now, if the caller has not already hung up, we do finally: if channel.state() != channel.State.IDLE: channel.hang_up() my_log.info("Example completed") return return_code
-
-
-
Sample Files:
- Samples\C#\Voicemail1\Voicemail1.cs
Application Execution
The application is run when specific inbound calls are directed to the UAS. In response, the
Run()
method is called . The UAS expects a return code that indicates success or failure of the application (as defined by the application author). Zero or above are classified as success codes, while negative values are classed as a failure. The UAS system reserves failure codes -1 to -99. Anything from -100 onwards are available to be defined by the user.Exceptions
Unhandled exceptions that occur within an application will be caught by the UAS and will not interfere with the UAS operation or other applications running in the UAS. However, it is good practice to encapsulate application code within try/catch blocks. This allows for error logging and conversion of exceptions into return error codes that can help with application fault diagnosis.
Call Hang Up
At any time, the call may be hung up by the caller. This is indicated by the State of the call going to Idle. Many channel methods return either this state or a cause value that indicates that a feature (e.g. recording) was stopped as the call was hung up.
Parameters
The
Run()
method for an inbound application takes two arguments:channel
which gives us information and control of a single telephone call on the Cloud andapplicationParameters
which is a user-defined string that originates from the Inbound Service entry on the Cloud.Using the
channel
class, we can instruct the UAS to answer the incoming call, remembering that our inbound application is only ever executed when an inbound call has been made. If the call is answered successfully we use theSay
method on theFilePlayer
object (a property of the channel) to perform TTS and prompt the caller to leave a message. This instructs the Cloud to convert our text into speech data, which is then played to the caller.Recording audio from the caller will be saved to a file on the Cloud, which is accessable via the Manage - Media Files menu option, or via a WebServices API. We must therefore create a filename for the recorded voice message located on the Cloud. The root folder will be unique for our cloud account and the path relative to this can contain subfolders using the '/' character. In this case, we have including in the filename the
CallDetails.CallFrom
property of channel, that typically identifies the caller.We start recording voice data using the
FileRecorder.Start()
method, passing it our recording filename. This method does not block and allows the application to continue executing lines after the recording has started. We signal to the caller that recording has commenced by playing a single DTMF tone using theDtmfPlayer.Play()
method. We could alternatively have used TTS to signal the start of the recording.The
FileRecorder.WaitUntilFinished()
method instructs the application to wait until the recording has finished. Here we have specified a maximum of 60 seconds to wait. The recording can finish for several different reasons, including call hang up, timeout or max file size reached. In this case we aren't interested in the reason, but simply check whether a file exists on the cloud of the specified name. To do this we used theFileManager
object that is available as a property on the base class ofUASInboundApplication
. -
using System; using System.Threading; using AMSClassLibrary; using UASAppAPI; // An inbound application that answers the call, prompts the caller to // record a message (and to press any key to stop recording), plays a // beep to indicate it is recording and then waits for a max of 60 seconds. // The recorded filename includes the callee address (callFrom) // i.e. samples/voicemail1/recordedMessageFrom<>.wav. // It checks that a recorded message file exists. namespace Voicemail1 { public class Voicemail1 : UASInboundApplication { // Possible return codes enum ReturnCode { // Success Codes: Success = 0, // ... any positive integer RecordedAMessage = 1, // Fail Codes: // -1 to -99 reserved ExceptionThrown = -100, RecordingFailed = -101 } // This is the entry point for the appliction public override int Run(UASCallChannel channel, string applicationParameters) { this.Trace.TraceInfo("Started"); ReturnCode reply = ReturnCode.Success; try { // Answer the call CallState state = channel.Answer(); if (state == CallState.Answered) { this.Trace.TraceInfo("Call answered"); string recordFileName = "samples/voicemail1/recordedMessageFrom" + channel.CallDetails.CallFrom + ".wav"; // Prompt to leave a message channel.FilePlayer.Say("Please record a message after the tone. " + "Press any digit to stop the recording."); // Delay before starting the recording and playing the tone (for effect) Thread.Sleep(1000); // Start recording this.Trace.TraceInfo("Recording message to file: [{0}]", recordFileName); if (channel.FileRecorder.Start(recordFileName, 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 channel.FileRecorder.WaitUntilFinished(60); // Check there's a file there if (this.FileManager.FileExists(recordFileName)) { this.Trace.TraceInfo("Recorded message to file: [{0}]", recordFileName); // Return a specific pass code reply = ReturnCode.RecordedAMessage; } } else { // This invocation failed this.Trace.TraceError("Recording failed to start cause = {0}", channel.FileRecorder.Cause); reply = ReturnCode.RecordingFailed; } } } catch (Exception e) { this.Trace.TraceError("Exception thrown {0}", e.Message); reply = ReturnCode.ExceptionThrown; } finally { // Ensure call is hung up channel.HangUp(); } this.Trace.TraceInfo("Completed"); return (int)reply; } } }
-
-
-
Sample Files:
- Samples\VB\Voicemail1\Voicemail1.vb
Application Execution
The application is run when specific inbound calls are directed to the UAS. In response, the
Run()
method is called . The UAS expects a return code that indicates success or failure of the application (as defined by the application author). Zero or above are classified as success codes, while negative values are classed as a failure. The UAS system reserves failure codes -1 to -99. Anything from -100 onwards are available to be defined by the user.Exceptions
Unhandled exceptions that occur within an application will be caught by the UAS and will not interfere with the UAS operation or other applications running in the UAS. However, it is good practice to encapsulate application code within try/catch blocks. This allows for error logging and conversion of exceptions into return error codes that can help with application fault diagnosis.
Call Hang Up
At any time, the call may be hung up by the caller. This is indicated by the State of the call going to Idle. Many channel methods return either this state or a cause value that indicates that a feature (e.g. recording) was stopped as the call was hung up.
Parameters
The
Run()
method for an inbound application takes two arguments:channel
which gives us information and control of a single telephone call on the Cloud andapplicationParameters
which is a user-defined string that originates from the Inbound Service entry on the Cloud.Using the
channel
class, we can instruct the UAS to answer the incoming call, remembering that our inbound application is only ever executed when an inbound call has been made. If the call is answered successfully we use theSay
method on theFilePlayer
object (a property of the channel) to perform TTS and prompt the caller to leave a message. This instructs the Cloud to convert our text into speech data, which is then played to the caller.Recording audio from the caller will be saved to a file on the Cloud, which is accessable via the Manage - Media Files menu option, or via a WebServices API. We must therefore create a filename for the recorded voice message located on the Cloud. The root folder will be unique for our cloud account and the path relative to this can contain subfolders using the '/' character. In this case, we have including in the filename the
CallDetails.CallFrom
property of channel, that typically identifies the caller.We start recording voice data using the
FileRecorder.Start()
method, passing it our recording filename. This method does not block and allows the application to continue executing lines after the recording has started. We signal to the caller that recording has commenced by playing a single DTMF tone using theDtmfPlayer.Play()
method. We could alternatively have used TTS to signal the start of the recording.The
FileRecorder.WaitUntilFinished()
method instructs the application to wait until the recording has finished. Here we have specified a maximum of 60 seconds to wait. The recording can finish for several different reasons, including call hang up, timeout or max file size reached. In this case we aren't interested in the reason, but simply check whether a file exists on the cloud of the specified name. To do this we used theFileManager
object that is available as a property on the base class ofUASInboundApplication
. -
Imports AMSClassLibrary Imports UASAppAPI ' An inbound application that answers the call, prompts the caller to ' record a message (and to press any key to stop recording), plays a ' beep to indicate it is recording and then waits for a max of 60 seconds. ' The recorded filename includes the callee address (callFrom) ' i.e. samples/voicemail1/recordedMessageFrom<callFrom>.wav. ' It checks that a recorded message file exists. Namespace Voicemail1 ' 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 Voicemail1 Inherits UASInboundApplication ' Possible return codes Enum ReturnCode ' Success Codes: Success = 0 RecordedAMessage = 1 ' Fail Codes: ' -1 to -99 reserved ExceptionThrown = -100 RecordingFailed = -101 End Enum ' This is the entry point for the application Overrides Function Run(ByVal channel As UASCallChannel, _ ByVal applicationParameters As String) _ As Integer Me.Trace.TraceInfo("Started") Dim reply As ReturnCode = ReturnCode.Success Try ' Answer the call Dim state As CallState state = channel.Answer() If state = CallState.Answered Then Me.Trace.TraceInfo("Call answered") Dim recordFileName = "samples/voicemail1/recordedMessageFrom" + channel.CallDetails.CallFrom + ".wav" ' Prompt to leave a message channel.FilePlayer.Say("Please record a message after the tone. " + _ "Press any digit to stop the recording.") ' Delay before starting the recording and playing the tone (for effect) Thread.Sleep(1000) ' Start recording Me.Trace.TraceInfo("Recording message to file: [{0}]", recordFileName) If channel.FileRecorder.Start(recordFileName, True) Then ' 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 channel.FileRecorder.WaitUntilFinished(60) ' Check there's a file there If Me.FileManager.FileExists(recordFileName) Then Me.Trace.TraceInfo("Recorded message to file: [{0}]", recordFileName) ' Return a specific pass code reply = ReturnCode.RecordedAMessage End If Else ' This invocation failed Me.Trace.TraceError("Recording failed to start cause = {0}", _ channel.FileRecorder.Cause) reply = ReturnCode.RecordingFailed End If End If Catch ex As Exception Me.Trace.TraceError("Exception thrown {0}", ex.Message) reply = ReturnCode.ExceptionThrown Finally ' Ensure call is hung up channel.HangUp() End Try Me.Trace.TraceInfo("Completed") Return reply End Function End Class End Namespace
-
Next time...
Well done! So, by now you're probably wondering what we're going to do with this recorded file. In our next step, we're going to allow the user to listen to the recorded message and then ask if they want to keep the message or not. We're one step closer to having a voicemail system running!
Lesson 2