right  Talk To Us!

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 the DTMFDetector.get_digits() function, specifying we want a single digit return with count=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 the FilePlayer.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 the digits 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 the FilePlayer.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 the digits 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