Voicemail Lesson 3

We now know how to play and record messages. Let's introduce a few more interesting techniques, including use of the file management system to check for the existence of specific files and use of application parameters to determine the voicemail box that the application is accessing.


    • Sample Files:
      • Samples\voicemail_3.py

      Specifying a Voicemail Box

      This step makes use of the application_parameters argument that contains the user-defined string originating from the Inbound Service configuration. This string is expected to contain the name we have set up to be our voicemail box. We set up a format for all voicemail filenames that locates them in a voicemailbox subfolder on the cloud.

      After answering the call the application identifies the level of access for the caller. It does this by comparing the caller Id with the voicemail box name, as determined from application_parameters.

      If the caller Id and voicemail box name match then the caller is deemed to be the owner of the voicemail box and the application calls top_level_menu() that allows the caller to listen and delete all messages. In the main function, we can do this using the file_man.exists() function, with the filename as a parameter. This will test for the existence of the file on the Cloud. We keep incrementing a counter until the filename is not found.

      If the caller Id and voicemail box name do not match, the caller is simply allowed to record a new message.

      Using the channel

      One of the first things we do is to call our get_caller function. This extacts information from the call_from property of the channel we've been given, which supplies a SIP address. Our telephony system which answers the client encapsulates the phone number information within a SIP format, which is then used by our Cloud. Our function extracts the phone number information from the SIP address.

      The application parameters are given to us as a string, accessable by the application_parameters variable. In our example, we compare the parameter against the caller number, to identify if the caller is actually the owner of our voicemail service. If they are the caller, the application jumps to the top_level_menu subroutine, otherwise to record_new_message.

      Dealing with DTMF

      The top level menu subroutine uses a function called get_menu_select. We saw in the previous example how to access DTMF tones via the keypad. This routine will play some TTS to the user, and then wait for a valid response. Once the user has entered a valid response, the keypad press will be returned. We can then simply check the return code and act appropriately.

      In play_all_messages(), we use a counter, checking if a filename derived from the counter exists, and if so, says some TTS and then the file itself. delete_all_messages() works in a similar manner, but rather than play each file, we delete the file using the file_man.delete_file call.

    • __uas_identify__ = "application"
      __uas_version__ = "1.0b1"
      
      from prosody.uas import Hangup, Error, ICanRun
      
      """
      
          An inbound application that answers the call and says
          "Welcome to the voicemail server for <name>". <name> is obtained from the
          application_parameters and must be registered on the inbound services
          page of the CWP.
      
          The application uses <name> to determine whether the caller owns the
          voicemail box. It does this by comparing <name> with the <call_from>
          property of the inbound call.
      
          If the caller owns the voicemail box, the top level options given are:
      
              1. Play all existing messages
              2. Delete all existing messages
      
          If the caller does not own the voicemail box, he can leave a new
          message. The name of the WAV file to save to will be
          "samples/voicemail3/<name>/msg<counter>.wav". The <counter> is a
          variable, initialised to 1, which increments each time a message is
          successfully saved.
      
          This application is part of the online Voice Mail tutorial.
      """
      
      class VoiceMailBox:
          def __init__(self, box_name, channel, file_manager, logger):
              self._channel = channel
              self._file_manager = file_manager
              self._logger = logger
              self._file_format = "samples/voicemail3/" + box_name + "/msg{0}.wav"
              self._recorded_file_counter = 1
              self._set_message_counter()
      
      
          def _set_message_counter(self):
              # check the message box and set the counter
              while(self._file_manager.exists(self._file_format.format(self._recorded_file_counter))):
                  self._logger.info("{0} exists".format(self._recorded_file_counter))
                  self._recorded_file_counter += 1
      
      
          def say_tts(self, message, bargein=False, beep=False):
              cause = self._channel.FilePlayer.say(message, barge_in=bargein)
              if cause == self._channel.FilePlayer.Cause.BARGEIN:
                  return
              if cause == self._channel.FilePlayer.Cause.NORMAL:
                  if beep is True:
                      cause = self._channel.DTMFPlayer.play("#");
                      if cause != self._channel.DTMFPlayer.Cause.NORMAL:
                          raise Error("Play tone failed: caused is {0}".format(cause))
              else:
                  raise Error("Say '{0}' failed: cause is {1}".format(message, cause))
      
      
          def play_file(self, filename):
              cause = self._channel.FilePlayer.play(filename)
              if cause != self._channel.FilePlayer.Cause.NORMAL:
                  raise Error("Play '{0}' failed: cause is {1}".format(filename, cause))
      
      
          def record_new_message(self, replace=False):
      
              if replace is True and self._recorded_file_counter > 1:
                  self._recorded_file_counter -= 1
      
              filename = self._file_format.format(self._recorded_file_counter)
              self._recorded_file_counter += 1
      
              self.say_tts("Please record a message after the tone. Press any digit to stop the recording.", beep=True)
      
              cause = self._channel.FileRecorder.record(filename, milliseconds_max_silence=3000,
                                                        seconds_timeout=60, barge_in=True)
              if cause != self._channel.FileRecorder.Cause.SILENCE and cause != self._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 self._file_manager.exists(filename):
                  self.say_tts("Your message could not be saved.")
              else:
                  self.say_tts("The following message has been recorded")
                  self.play_file(filename)
      
      
          def play_all_messages(self):
              counter = 1
              filename = self._file_format.format(counter)
      
              while self._file_manager.exists(filename):
                  self.say_tts("Message {0}".format(counter))
                  self.play_file(filename)
                  counter += 1
                  filename = self._file_format.format(counter)
      
              if counter == 1:
                  self.say_tts("You have no messages to play.")
              else:
                  self.say_tts("End of messages.")
      
      
          def delete_all_messages(self):
              counter = 1
              filename = self._file_format.format(counter)
      
              while self._file_manager.exists(filename):
                  self._file_manager.delete_file(filename)
                  counter += 1
                  filename = self._file_format.format(counter)
      
              if counter == 1:
                  self.say_tts("You have no messages to delete.")
              else:
                  self.say_tts("All messages deleted.")
      
              self._recorded_file_counter = 1
      
      
      def main(channel, application_instance_id, file_man, my_log, application_parameters):
          my_log.info ("Voicemail 3 started.")
          return_code = 0
          try:
      
              # The voicemail box name must have been configured at service registration
              if len(application_parameters) == 0:
                  raise Error("Argument list is empty")
      
              mailbox = VoiceMailBox(application_parameters, channel, file_man, my_log)
      
              channel.ring(2)
              channel.answer()
              caller = get_caller(channel)
      
              my_log.info ("Call from {0} to {1}'s Voicemail answered".format(caller, application_parameters))
      
              mailbox.say_tts("Welcome to the voicemail server for {0}".format(" ".join(list(application_parameters))))
      
              if caller == application_parameters:
                  top_level_menu(channel, mailbox)
              else:
                  replace = False
                  # wait on i_can_run to prevent an accidental infinite loop
                  i_can_run = ICanRun(channel)
                  while i_can_run:
                      mailbox.record_new_message(replace)
                      key_selected = get_menu_select(channel, mailbox,
                                                     "Please press 1 to keep this message or 2 to record an alternative message",
                                                     20, "12")
      
                      if key_selected == '1':
                          mailbox.say_tts("Message saved.")
                          break
                      replace = True
      
              mailbox.say_tts("Goodbye.")
      
              channel.hang_up();
      
          except Hangup as exc:
              my_log.info("Completed with Hangup: {0}".format(exc))
              return_code = -100
      
          except Error as exc:
              my_log.error("Completed with Error exception! {0}".format(exc))
              return_code = -101
      
          except Exception as exc:
              my_log.exception("Completed with exception! {0}".format(exc))
              return_code = -102
      
          finally:
              if channel.state() != channel.State.IDLE:
                  channel.hang_up()
          my_log.info("Voicemail 3 example completed")
          return return_code
      
      
      
      def get_caller(channel):
          caller = channel.Details.call_from
          # Extract the username from the SIP address
          if caller.startswith("sip:"):
              caller = caller[4:]
          return caller.split('@', 1)[0]
      
      
      def top_level_menu(channel, mailbox):
      
          key_selected = get_menu_select(channel, mailbox,
                                        "Press 1 to listen to all messages. \
                                        Press 2 to delete all messages. \
                                        Press 3 to quit.",
                                        20, "123")
          if key_selected == '1':
              mailbox.play_all_messages()
          elif key_selected == '2':
              mailbox.delete_all_messages()
      
      
      def get_menu_select(channel, mailbox, prompt, seconds_repeat_prompt, valid_keys):
          channel.DTMFDetector.clear_digits()
      
          # wait on i_can_run to prevent an accidental infinite loop
          i_can_run = ICanRun(channel)
          while i_can_run:
              mailbox.say_tts(prompt, bargein=True, beep=True)
      
              digits = channel.DTMFDetector.get_digits(end=valid_keys, seconds_predigits_timeout=seconds_repeat_prompt)
              cause = channel.DTMFDetector.cause()
              if cause == channel.DTMFDetector.Cause.END:
                  return digits[-1]
              else:
                  mailbox.say_tts("Invalid selection.")
                                  
    • Sample Files:
      • Samples\C#\Voicemail3\Voicemail3.cs

      Specifying a Voicemail Box

      This step makes use of the applicationArguments argument that contains the user-defined string originating from the Inbound Service configuration. This string is expected to contain the name we have set up to be our voicemail box. We set up a format for all voicemail filenames that locates them in a voicemailbox subfolder on the cloud.

      After answering the call the application identifies the level of access for the caller. It does this by comparing the caller Id with the voicemailBox name, as determined from applicationArguments. For SIP calls, the voicemailBox name is matched with the Username of the caller (the string before the '@' character) while for PSTN calls the caller Id is simply the calling number.

      If the caller Id and voicemailBox name match then the caller is deemed to be the owner of the voicemail box and the application calls TopLevelMenu() that allows the caller to listen and delete all messages. Otherwise the caller is simply allowed to record a new message.

      File checking

      In the Run() method we check for the existence of a file using the FileManager.FileExists() method. We continually increment the recordedFileCounter variable until we identify a free filename - this is our next filename for recording.

      Dealing with DTMF

      For menu entry via the keypad, a new method has been created called GetMenuSelection(). This reads out a prompt, passed as a parameter, a predigit timeout value, and also any valid selections that the user is able to press. A string of the single digit reply is returned. In the TopLevelMenu() method, pressing a '1' will call the PlayAllMessages() method and pressing a '2' will call DeleteAllMessage().

      Voicemail functionality

      In PlayAllMessages(), a counter is incremented from 1, testing with a derived filename is the file exists, and if so, will play the file. This continues until there are no more messages. DeleteAllMessage() does a similar job, testing for the existence of a derived filename, but rather than play it, will delete the file with the FileManager.DeleteFile() method.

      The RecordNewMessage() method behaves like the record method in the previous step, except that it now uses the GetMenuSelection method to determine user input.

    • using System;
      using System.Threading;
      using AMSClassLibrary;
      using UASAppAPI;
      
      // An inbound application that answers the call, says "Welcome to the
      // voicemail server for <name>."
      // <name> is obtained from the applicationArguments.
      // It is compared with the <callFrom> property of the inbound call.
      //
      // If <callFrom> equals <name>
      //     Say "Press 1 to hear all messages, press 2 to delete all messages".
      //     1. Play all existing messages
      //     2. Delete all existing messages
      // else
      //     Do recording as in voicemail 2.
      //
      // All options return to the above menu.
      // The app only completes when the call is hung up.
      // The name of the .wav file to save to will be "samples/voicemail3/name/msg<counter>.wav".
      // The <counter> is a variable, initialised to 1, which increments each time a
      // message is successfully saved.
      //
      // Requires:
      // [applicationParameters = voicemail box name]
      namespace Voicemail3
      {
          public class Voicemail3 : 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,
                  ArgumentEmpty = -101,
                  RecordingFailedToStart = -102
              }
      
              // This is the entry point for the application
              public override int Run(UASCallChannel channel,
                                      string applicationParameters)
              {
                  this.Trace.TraceInfo("Started");
                  ReturnCode reply = ReturnCode.Success;
      
                  // The voicemail box name must have been configured at service registration
                  voicemailBoxName = applicationParameters;
                  if (voicemailBoxName.Length == 0)
                  {
                      return (int)ReturnCode.ArgumentEmpty;
                  }
      
                  recordFilenameFormat = "samples/voicemail3/" + voicemailBoxName + "/msg{0}.wav";
      
                  // Obtain the next free counter
                  recordedFileCounter = 1;
                  while (this.FileManager.FileExists(String.Format(recordFilenameFormat, recordedFileCounter.ToString())))
                  {
                      recordedFileCounter++;
                  }
      
                  try
                  {
                      // Answer the call
                      CallState state = channel.Answer();
                      if (state == CallState.Answered)
                      {
                          string caller = GetCaller(channel);
                          this.Trace.TraceInfo("Call from {0} to {1}'s Voicemail answered",
                                                      caller, voicemailBoxName);
      
                          PlayWelcomeMessage(channel, voicemailBoxName);
      
                          if (caller == voicemailBoxName)
                          {
                              TopLevelMenu(channel);
                          }
                          else
                          {
                              RecordNewMessage(channel);
                              channel.FilePlayer.Say("Goodbye.");
                          }
                      }
                  }
                  catch (Exception e)
                  {
                      this.Trace.TraceError("Exception thrown {0}", e.Message);
                      reply = ReturnCode.ExceptionThrown;
                  }
                  finally
                  {
                      channel.HangUp();
                  }
      
                  this.Trace.TraceInfo("Completed");
                  return (int)reply;
              }
      
              private ReturnCode PlayWelcomeMessage(UASCallChannel channel, string name)
              {
                  channel.FilePlayer.Say("Welcome to the voicemail server for {0}", name);
                  return ReturnCode.Success;
              }
      
              private string GetCaller(UASCallChannel channel)
              {
                  // Extract the user part of a SIP address
                  string caller = channel.CallDetails.CallFrom;
                  if (caller.Contains("sip:"))
                  {
                      caller = caller.Substring(4);
                      int pos = caller.IndexOf('@');
                      if (pos >= 0)
                      {
                          caller = caller.Remove(pos);
                      }
                  }
                  return caller;
              }
      
              private ReturnCode TopLevelMenu(UASCallChannel channel)
              {
                  ReturnCode reply = ReturnCode.Success;
                  string prompt = "Press 1 to listen to all messages. " +
                                  "Press 2 to delete all messages. " +
                                  "Press hash to exit.";
                  char keySelected;
      
                  do
                  {
                      reply = GetMenuSelection(channel, prompt, 20, "12#", out keySelected);
                      if (reply == ReturnCode.Success)
                      {
                          switch (keySelected)
                          {
                              case '1':
                                  reply = PlayAllMessages(channel);
                                  break;
                              case '2':
                                  reply = DeleteAllMessages(channel);
                                  break;
                              case '#':
                                  return reply;
                          }
                      }
                  } while (reply == ReturnCode.Success);
      
                  return reply;
              }
      
              private ReturnCode RecordNewMessage(UASCallChannel channel)
              {
                  ReturnCode reply = ReturnCode.Success;
      
                  string filename = String.Format(recordFilenameFormat, recordedFileCounter.ToString());
                  recordedFileCounter++;
      
                  // Loop round until have recorded a message acceptable to the caller
                  // or caller has hung up
                  bool satisfiedWithMessage = 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}]", filename);
                      if (!channel.FileRecorder.Start(filename, 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(filename, true) == FilePlayerCause.HangUp)
                      {
                          this.Trace.TraceInfo("Call has been hung up");
                          reply = ReturnCode.CallHungUp;
                          break;
                      }
      
                      // Check if the user is satisfied with the recorded message
                      string prompt = "Please press 1 to keep this message or 2 to record an " +
                                      "alternative message";
                      char keySelected;
      
                      reply = GetMenuSelection(channel, prompt, 20, "12", out keySelected);
                      if (reply == ReturnCode.Success)
                      {
                          if (keySelected == '1')
                          {
                              satisfiedWithMessage = true;
                              channel.FilePlayer.Say("Message saved.");
                          }
                      }
                  } while (!satisfiedWithMessage && (reply == ReturnCode.Success));
      
                  return reply;
              }
      
              private ReturnCode PlayAllMessages(UASCallChannel channel)
              {
                  ReturnCode reply = ReturnCode.Success;
                  int counter = 1;
      
                  string filename = String.Format(recordFilenameFormat, counter.ToString());
      
                  while (this.FileManager.FileExists(filename))
                  {
                      channel.FilePlayer.Say("Message " + counter.ToString());
      
                      channel.FilePlayer.Play(filename);
                      if (channel.FilePlayer.Cause == FilePlayerCause.HangUp)
                      {
                          reply = ReturnCode.CallHungUp;
                          break;
                      }
      
                      counter++;
                      filename = String.Format(recordFilenameFormat, counter.ToString());
                  }
      
                  if (reply == ReturnCode.Success)
                  {
                      channel.FilePlayer.Say("No more messages.");
                  }
      
                  return reply;
              }
      
              private ReturnCode DeleteAllMessages(UASCallChannel channel)
              {
                  ReturnCode reply = ReturnCode.Success;
                  int counter = 1;
      
                  string filename = String.Format(recordFilenameFormat, counter.ToString());
      
                  while (this.FileManager.FileExists(filename))
                  {
                      this.FileManager.DeleteFile(filename);
      
                      counter++;
                      filename = String.Format(recordFilenameFormat, counter.ToString());
                  }
      
                  if (reply == ReturnCode.Success)
                  {
                      channel.FilePlayer.Say("All messages deleted.");
                  }
      
                  return reply;
              }
      
              // Returns: Success if a valid key was selected, CallHungUp if call hung up.
              private ReturnCode GetMenuSelection(UASCallChannel channel, string prompt, int secondsRepeatPrompt, string validKeys, out char keySelected)
              {
                  keySelected = '\0';
      
                  // Prepare to receive digits
                  channel.DtmfDetector.ClearDigits();
      
                  do
                  {
                      FilePlayerCause cause = channel.FilePlayer.Say(prompt, true);
                      if (cause == FilePlayerCause.HangUp)
                      {
                          return ReturnCode.CallHungUp;
                      }
      
                      string digits;
                      channel.DtmfDetector.GetDigits(1, out digits, secondsRepeatPrompt);
                      if (channel.DtmfDetector.Cause == DtmfDetectorCause.Count)
                      {
                          if (validKeys.Contains(digits))
                          {
                              this.Trace.TraceInfo("Got keypress {0}", digits[0]);
                              keySelected = digits[0];
                              return ReturnCode.Success;
                          }
                          else
                          {
                              channel.FilePlayer.Say("Selection not valid.");
                          }
                      }
                  } while (channel.DtmfDetector.Cause != DtmfDetectorCause.HangUp);
      
                  // returning on hangup
                  return ReturnCode.CallHungUp;
              }
      
              private string voicemailBoxName;
              private string recordFilenameFormat;
              private int recordedFileCounter;
          }
      }
                                  
    • Sample Files:
      • Samples\VB\Voicemail3\Voicemail3.vb

      Specifying a Voicemail Box

      This step makes use of the applicationArguments argument that contains the user-defined string originating from the Inbound Service configuration. This string is expected to contain the name we have set up to be our voicemail box. We set up a format for all voicemail filenames that locates them in a voicemailbox subfolder on the cloud.

      After answering the call the application identifies the level of access for the caller. It does this by comparing the caller Id with the voicemailBox name, as determined from applicationArguments. For SIP calls, the voicemailBox name is matched with the Username of the caller (the string before the '@' character) while for PSTN calls the caller Id is simply the calling number.

      If the caller Id and voicemailBox name match then the caller is deemed to be the owner of the voicemail box and the application calls TopLevelMenu() that allows the caller to listen and delete all messages. Otherwise the caller is simply allowed to record a new message.

      File checking

      In the Run() method we check for the existence of a file using the FileManager.FileExists() method. We continually increment the recordedFileCounter variable until we identify a free filename - this is our next filename for recording.

      Dealing with DTMF

      For menu entry via the keypad, a new method has been created called GetMenuSelection(). This reads out a prompt, passed as a parameter, a predigit timeout value, and also any valid selections that the user is able to press. A string of the single digit reply is returned. In the TopLevelMenu() method, pressing a '1' will call the PlayAllMessages() method and pressing a '2' will call DeleteAllMessage().

      Voicemail functionality

      In PlayAllMessages(), a counter is incremented from 1, testing with a derived filename is the file exists, and if so, will play the file. This continues until there are no more messages. DeleteAllMessage() does a similar job, testing for the existence of a derived filename, but rather than play it, will delete the file with the FileManager.DeleteFile() method.

      The RecordNewMessage() method behaves like the record method in the previous step, except that it now uses the GetMenuSelection method to determine user input.

    • Imports AMSClassLibrary
      Imports UASAppAPI
      
      ' An inbound application that answers the call, says "Welcome to the
      ' voicemail server for <name>."
      ' <name> is obtained from the applicationArguments.
      ' It is compared with the <callFrom> property of the inbound call.
      '
      ' If <callFrom> equals <name>
      '     Say "Press 1 to hear all messages, press 2 to delete all messages".
      '     1. Play all existing messages
      '     2. Delete all existing messages
      ' else
      '     Do recording as in voicemail 2.
      '
      ' All options return to the above menu.
      ' The app only completes when the call is hung up.
      ' The name of the .wav file to save to will be "samples/voicemail3/name/msg<counter>.wav".
      ' The <counter> is a variable, initialised to 1, which increments each time a
      ' message is successfully saved.
      '
      ' Requires:
      ' [applicationParameters = voicemail box name]
      Namespace Voicemail3
      
          ' 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 Voicemail3
              Inherits UASInboundApplication
      
              ' Possible return codes
              Enum ReturnCode
                  ' Success Codes:
                  Success = 0
                  RecordedAMessage = 1
                  CallHungUp = 2
      
                  ' Fail Codes:
                  ' -1 to -99 reserved
                  ExceptionThrown = -100
                  ArgumentEmpty = -101
                  RecordingFailedToStart = -102
              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
      
                  ' The voicemail box name must have been configured at service registration
                  voicemailBoxName = applicationParameters
                  If voicemailBoxName.Length = 0 Then
                      Return ReturnCode.ArgumentEmpty
                  End If
      
                  recordFilenameFormat = "samples/voicemail3/" + voicemailBoxName + "/msg{0}.wav"
      
                  ' Obtain the next free counter
                  recordedFileCounter = 1
                  While Me.FileManager.FileExists(String.Format(recordFilenameFormat, recordedFileCounter.ToString()))
                      recordedFileCounter += 1
                  End While
      
                  Try
                      ' Answer the call
                      Dim state As CallState
                      state = channel.Answer()
                      If state = CallState.Answered Then
                          Dim caller = GetCaller(channel)
                          Me.Trace.TraceInfo("Call from {0} to {1}'s Voicemail answered", _
                                                      caller, voicemailBoxName)
      
                          PlayWelcomeMessage(channel, voicemailBoxName)
      
                          If caller = voicemailBoxName Then
                              TopLevelMenu(channel)
                          Else
                              RecordNewMessage(channel)
                              channel.FilePlayer.Say("Goodbye.")
                          End If
                      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
      
              Private Function PlayWelcomeMessage(ByVal channel As UASCallChannel, ByVal name As String) As ReturnCode
                  channel.FilePlayer.Say("Welcome to the voicemail server for {0}", name)
                  Return ReturnCode.Success
              End Function
      
              Private Function GetCaller(ByVal channel As UASCallChannel) As String
                  ' Extract the user part of a SIP address
                  Dim caller = channel.CallDetails.CallFrom
                  If caller.Contains("sip:") Then
                      caller = caller.Substring(4)
                      Dim pos = caller.IndexOf("@")
                      If (pos >= 0) Then
                          caller = caller.Remove(pos)
                      End If
                  End If
                  Return caller
              End Function
      
              Private Function TopLevelMenu(ByVal channel As UASCallChannel) As ReturnCode
                  Dim reply = ReturnCode.Success
                  Dim prompt = "Press 1 to listen to all messages. " + _
                               "Press 2 to delete all messages. " + _
                               "Press hash to exit."
                  Dim keySelected As Char
      
                  Do
                      reply = GetMenuSelection(channel, prompt, 20, "12#", keySelected)
                      If reply = ReturnCode.Success Then
                          Select Case keySelected
                              Case "1"
                                  reply = PlayAllMessages(channel)
                              Case "2"
                                  reply = DeleteAllMessages(channel)
                              Case "#"
                                  Return reply
                          End Select
                      End If
                  Loop While reply = ReturnCode.Success
      
                  Return reply
              End Function
      
              Private Function RecordNewMessage(ByVal channel As UASCallChannel) As ReturnCode
                  Dim reply = ReturnCode.Success
      
                  Dim filename = String.Format(recordFilenameFormat, recordedFileCounter.ToString())
                  recordedFileCounter += 1
      
                  ' Loop round until have recorded a message acceptable to the caller
                  ' or caller has hung up
                  Dim satisfiedWithMessage = 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}]", filename)
                      If Not channel.FileRecorder.Start(filename, 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(filename, True) = FilePlayerCause.HangUp Then
                          Me.Trace.TraceInfo("Call has been hung up")
                          reply = ReturnCode.CallHungUp
                          Exit Do
                      End If
      
                      ' Check if the user is satisfied with the recorded message
                      Dim prompt = "Please press 1 to keep this message or 2 to record an " + _
                                      "alternative message"
                      Dim keySelected As Char
      
                      reply = GetMenuSelection(channel, prompt, 20, "12", keySelected)
                      If reply = ReturnCode.Success Then
                          If keySelected = "1" Then
                              satisfiedWithMessage = True
                              channel.FilePlayer.Say("Message saved.")
                          End If
                      End If
                  Loop While Not satisfiedWithMessage And (reply = ReturnCode.Success)
      
                  Return reply
              End Function
      
              Private Function PlayAllMessages(ByVal channel As UASCallChannel) As ReturnCode
                  Dim reply = ReturnCode.Success
                  Dim counter = 1
      
                  Dim filename = String.Format(recordFilenameFormat, counter.ToString())
      
                  While Me.FileManager.FileExists(filename)
                      channel.FilePlayer.Say("Message " + counter.ToString())
      
                      channel.FilePlayer.Play(filename)
                      If channel.FilePlayer.Cause = FilePlayerCause.HangUp Then
                          reply = ReturnCode.CallHungUp
                          Exit While
                      End If
      
                      counter += 1
                      filename = String.Format(recordFilenameFormat, counter.ToString())
                  End While
      
                  If reply = ReturnCode.Success Then
                      channel.FilePlayer.Say("No more messages.")
                  End If
      
                  Return reply
              End Function
      
              Private Function DeleteAllMessages(ByVal channel As UASCallChannel) As ReturnCode
                  Dim reply = ReturnCode.Success
                  Dim counter = 1
      
                  Dim filename = String.Format(recordFilenameFormat, counter.ToString())
      
                  While Me.FileManager.FileExists(filename)
                      Me.FileManager.DeleteFile(filename)
      
                      counter += 1
                      filename = String.Format(recordFilenameFormat, counter.ToString())
                  End While
      
                  If reply = ReturnCode.Success Then
                      channel.FilePlayer.Say("All messages deleted.")
                  End If
      
                  Return reply
              End Function
      
              ' Returns: Success if a valid key was selected, CallHungUp if call hung up.
              Private Function GetMenuSelection(ByVal channel As UASCallChannel, ByVal prompt As String, ByVal secondsRepeatPrompt As Integer, ByVal validKeys As String, ByRef keySelected As Char) As ReturnCode
                  keySelected = "\0"
      
                  ' Prepare to receive digits
                  channel.DtmfDetector.ClearDigits()
      
                  Do
                      Dim cause = channel.FilePlayer.Say(prompt, True)
                      If cause = FilePlayerCause.HangUp Then
                          Return ReturnCode.CallHungUp
                      End If
      
                      Dim digits As String = ""
                      channel.DtmfDetector.GetDigits(1, digits, secondsRepeatPrompt)
                      If channel.DtmfDetector.Cause = DtmfDetectorCause.Count Then
                          If (validKeys.Contains(digits)) Then
                              Me.Trace.TraceInfo("Got keypress {0}", digits(0))
                              keySelected = digits(0)
                              Return ReturnCode.Success
                          Else
                              channel.FilePlayer.Say("Invalid selection.")
                          End If
                      End If
                  Loop While channel.DtmfDetector.Cause <> DtmfDetectorCause.HangUp
      
                  ' returning on hangup
                  Return ReturnCode.CallHungUp
              End Function
      
              Private voicemailBoxName As String
              Private recordFilenameFormat As String
              Private recordedFileCounter As String
      
          End Class
      
      End Namespace
                                  

Next time...

So, we have a simple bare-bone voicemail system up and running. We could personalise the voicemail box some more though. How about allowing the owner of the voicemail box to record their own introduction message and use the file as a welcome message? Additionally we could allow the owner to select messages to keep or delete. Read on to find out how...

Lesson 4