Voicemail Lesson 2
We are going to build on top of the previous example by adding some more code to make a functional voicemail service. At the moment we can record a message that is saved to the Cloud. However, a practical voicemail service also needs to be able to play messages back to the caller. We will also need some input from the user via the keypad (DTMF), to allow the caller to navigate through our menus and select options when appropriate.
-
-
Sample Files:
- Samples\voicemail_2.py
We answer the call, use TTS to ask the user for a message, and then use DTMF as a prompt beep. We record a segment of speech using the
FileRecorder.record()
function, taking the filename as a parameter, along with allowing barge in with a DTMF tone to indicate the end of speech, or a couple of seconds of silence. We check the result, and raise an error otherwise.We use more TTS to indicate recording was successful, and then use the
FilePlayer.play()
function to play the file we just saved. We now want to ask the user if they wish to keep this message or not, and allow the user to record another message if they are not happy with it.DTMF Overview
We first clear any possible DTMF digits already stored in the buffer by using
DTMFDetector.clear_digits()
. We can then use theDTMFDetector.get_digits()
function, specifying we want a single digit return withcount=1
.seconds_perdigits_tineout
is the amount of time in seconds to wait for without any keypad presses before returning.seconds_interdigit_timeout
is the timeout value to wait between digits once the first digit has been pressed.We can determine the cause of the DTMF detector function return by inspecting the
Cause
variable. If we receive a '1', we break from the loop, which ends the application. The message the user recorded is left alone, and they may access it at any time. Pressing '2' continues the loop, meaning the user is prompted for a message, and another is recorded again. -
""" This inbound application answers a call and then loops. The loop prompts the caller to record a message, then plays it back to them and asks whether he is satisfied with it. The recorded filename is samples/voicemail2/recordedMessageFrom<callFrom>.wav. The actions are: - check the channel state - ring and answer - record a file - play the file - save or loop on DTMF - hang up This application is part of the online Voice Mail tutorial. """ from prosody.uas import Hangup, Error, ICanRun __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 and answer the incoming call state = channel.state() if state == channel.State.CALL_INCOMING: state = channel.ring() if state == channel.State.RING_INCOMING: state = channel.answer() 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") my_log.debug("Call from: {0}".format(channel.Details.call_from)) # The message recording is now done in a loop # wait on i_can_run to prevent an accidental infinite loop i_can_run = ICanRun(channel) while i_can_run: channel.FilePlayer.say("Please record a message after the tone. Press any digit to stop the recording.") channel.DTMFPlayer.play('#') msg_file_name = "samples/voicemail2/recordedMessageFrom{0}.wav".format(channel.Details.call_from) cause = channel.FileRecorder.record(msg_file_name, milliseconds_max_silence=3000, barge_in=True) if cause != channel.FileRecorder.Cause.SILENCE and cause != channel.FileRecorder.Cause.BARGEIN: raise Error("Record message failed: cause is {0}".format(cause)) # check whether the file has been saved correctly, does it exist? if not file_man.exists(msg_file_name): channel.FilePlayer.say("Your message could not be saved. Try again.") continue channel.FilePlayer.say("The following message has been recorded.") channel.FilePlayer.play(msg_file_name) # clear the DTMF buffer channel.DTMFDetector.clear_digits() digits = '' while i_can_run: # ask the caller to indicate whether he is happy with the message, press 1 or 2 channel.FilePlayer.say("Please press 1 to keep this message or 2 to record an alternative message") # wait for a 1 or a 2 to be pressed (detection will end on a 1 or 2) digits = channel.DTMFDetector.get_digits(end='12', seconds_predigits_timeout=30) # an END cause means that either a 1 or 2 was pressed if channel.DTMFDetector.cause() == channel.DTMFDetector.Cause.END: break # check the last digit recognised; 1 breaks the loop, 2 asks for another message if digits[-1] == '1': break elif digits[-1] == '2': continue channel.FilePlayer.say("Your message has been saved. Good bye.") except Hangup as exc: my_log.info("Hangup exception: {0}.".format(exc)) return_code = 100 except Error as exc: my_log.error("Error exception: {0}".format(exc)) return_code = -101 except Exception as exc: my_log.exception("Unexpected exception: {0}".format(exc)) return_code = -102 finally: if channel.state() != channel.State.IDLE: channel.hang_up() my_log.info("Example completed") return return_code
-
-
-
Sample Files:
- Samples\C#\Voicemail1\Voicemail2.cs
We answer the call and use TTS to prompt the user for a message. Again, we record the message using
FileRecorder.Start()
. Once finished, however, we now use theFilePlayer.Play()
method to play the recorded file (on the cloud) back to the caller. We then allow the caller to select whether they want to keep this message or record another one. We are going to use DTMF again here, but this time wait for a response and analyse the keypad button pressed.DTMF Overview
The
DtmfDetector.GetDigits()
method has a number of overloads. In our example, we specify that we want to receive a single DTMF digit, with the digit returned in thedigits
variable. Our call will also wait a total of 15 seconds for a tone, before timing out.We check the detected
digits
variable by inspecting the first character and determining the required action: option '1' will keep the saved message and quit the application; option '2' will repeat the loop and record another message to the same filename as before. -
using System; using System.Threading; using AMSClassLibrary; using UASAppAPI; // An inbound application that answers the call, loops round prompting // the caller to record a message and playing it back to them until they are // satisfied with it. // The recorded filename is samples/voicemail2/recordedMessageFrom<callFrom>.wav. namespace Voicemail2 { public class Voicemail2 : UASInboundApplication { // Possible return codes enum ReturnCode { // Success Codes: Success = 0, // ... any positive integer RecordedAMessage = 1, CallHungUp = 2, // Fail Codes: // -1 to -99 reserved ExceptionThrown = -100, RecordingFailedToStart = -101 } // This is the entry point for the application 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/voicemail2/recordedMessageFrom" + channel.CallDetails.CallFrom + ".wav"; // Loop round until have recorded a message acceptable to the caller // or caller has hung up bool satisfiedOrHungup = false; do { // 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); // Clear any received digits channel.DtmfDetector.ClearDigits(); // Start recording, enabling barge in this.Trace.TraceInfo("Recording message to file: [{0}]", recordFileName); if (!channel.FileRecorder.Start(recordFileName, true)) { this.Trace.TraceError("Recording failed to start cause = {0}", channel.FileRecorder.Cause); reply = ReturnCode.RecordingFailedToStart; break; } // 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 FileRecorderCause recCause = channel.FileRecorder.WaitUntilFinished(60); if (recCause == FileRecorderCause.HangUp) { this.Trace.TraceInfo("Call has been hung up"); reply = ReturnCode.CallHungUp; break; } if (recCause == FileRecorderCause.Timeout) { this.Trace.TraceInfo("Recording has stopped after 60 seconds"); channel.FileRecorder.Stop(); } // Replay the recorded message channel.FilePlayer.Say("The following message has been recorded"); if (channel.FilePlayer.Play(recordFileName, true) == FilePlayerCause.HangUp) { this.Trace.TraceInfo("Call has been hung up"); reply = ReturnCode.CallHungUp; break; } bool gotValidResponse = false; do { // Prompt to accept or repeat message channel.FilePlayer.Say( "Please press 1 to keep this message or 2 to record an" + "alternative message"); // Wait for 15 seconds for a single digit, including any digits // received during recording string digits; DtmfDetectorCause detCause = channel.DtmfDetector.GetDigits(1, null, out digits, true, 15); if (detCause == DtmfDetectorCause.Count) { if (digits[0] == '1') { gotValidResponse = true; satisfiedOrHungup = true; channel.FilePlayer.Say("Message saved. Goodbye."); } else if (digits[0] == '2') { gotValidResponse = true; } } else if (detCause == DtmfDetectorCause.HangUp) { reply = ReturnCode.CallHungUp; satisfiedOrHungup = true; break; } } while (!gotValidResponse); } while (!satisfiedOrHungup); // Ensure call is hung up channel.HangUp(); } } catch (Exception e) { this.Trace.TraceError("Exception thrown {0}", e.Message); reply = ReturnCode.ExceptionThrown; } this.Trace.TraceInfo("Completed"); return (int)reply; } } }
-
-
-
Sample Files:
- Samples\VB\Voicemail1\Voicemail2.vb
We answer the call and use TTS to prompt the user for a message. Again, we record the message using
FileRecorder.Start()
. Once finished, however, we now use theFilePlayer.Play()
method to play the recorded file (on the cloud) back to the caller. We then allow the caller to select whether they want to keep this message or record another one. We are going to use DTMF again here, but this time wait for a response and analyse the keypad button pressed.DTMF Overview
The
DtmfDetector.GetDigits()
method has a number of overloads. In our example, we specify that we want to receive a single DTMF digit, with the digit returned in thedigits
variable. Our call will also wait a total of 15 seconds for a tone, before timing out.We check the detected
digits
variable by inspecting the first character and determining the required action: option '1' will keep the saved message and quit the application; option '2' will repeat the loop and record another message to the same filename as before. -
Imports AMSClassLibrary Imports UASAppAPI ' An inbound application that answers the call, loops round prompting ' the caller to record a message and playing it back to them until they are ' satisfied with it. ' The recorded filename is samples/voicemail1/recordedMessageFrom<callFrom>.wav. Namespace Voicemail2 ' 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 Voicemail2 Inherits UASInboundApplication ' Possible return codes Enum ReturnCode ' Success Codes: Success = 0 RecordedAMessage = 1 CallHungUp = 2 ' Fail Codes: ' -1 to -99 reserved ExceptionThrown = -100 RecordingFailedToStart = -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 Dim recordFileName = "samples/voicemail2/recordedMessageFrom" + _ channel.CallDetails.CallFrom + ".wav" ' Loop round until have recorded a message acceptable to the caller ' or caller has hung up Dim satisfiedOrHungup = False Do ' 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) ' Clear any received digits channel.DtmfDetector.ClearDigits() ' Start recording, enabling barge in Me.Trace.TraceInfo("Recording message to file: [{0}]", recordFileName) If Not channel.FileRecorder.Start(recordFileName, True) Then Me.Trace.TraceError("Recording failed to start cause = {0}", _ channel.FileRecorder.Cause) reply = ReturnCode.RecordingFailedToStart Exit Do End If ' 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(60) If recCause = FileRecorderCause.HangUp Then Me.Trace.TraceInfo("Call has been hung up") reply = ReturnCode.CallHungUp Exit Do End If If recCause = FileRecorderCause.Timeout Then Me.Trace.TraceInfo("Recording has stopped after 60 seconds") channel.FileRecorder.Stop() End If ' Replay the recorded message channel.FilePlayer.Say("The following message has been recorded") If channel.FilePlayer.Play(recordFileName, True) = FilePlayerCause.HangUp Then Me.Trace.TraceInfo("Call has been hung up") reply = ReturnCode.CallHungUp Exit Do End If Dim gotValidResponse = False Do ' Prompt to accept or repeat message channel.FilePlayer.Say( _ "Please press 1 to keep this message or 2 to record an " + _ "alternative message") ' Wait for 15 seconds for a single digit, including any digits ' received during recording Dim digits As String = "" Dim detCause = channel.DtmfDetector.GetDigits(1, Nothing, digits, True, 15) If detCause = DtmfDetectorCause.Count Then If digits(0) = "1" Then gotValidResponse = True satisfiedOrHungup = True channel.FilePlayer.Say("Message saved. Goodbye.") ElseIf (digits(0) = "2") Then gotValidResponse = True End If ElseIf detCause = DtmfDetectorCause.HangUp Then reply = ReturnCode.CallHungUp satisfiedOrHungup = True Exit Do End If Loop While Not gotValidResponse Loop While Not satisfiedOrHungup ' Ensure call is hung up channel.HangUp() End If Catch ex As Exception Me.Trace.TraceError("Exception thrown {0}", ex.Message) reply = ReturnCode.ExceptionThrown End Try Me.Trace.TraceInfo("Completed") Return reply End Function End Class End Namespace
-
Next time...
OK. So we can record a message, play it back and use the keypad to determine caller feedback. Wow! Not bad for a few minutes work!
One of the typical uses for DTMF input is to create menu systems that the caller can control. As we shall see in the next step, we also need some way of recording multiple messages from the caller.
Lesson 3