Voicemail Lesson 4
We can now leave multiple messages for the voicemail box owner. The application can differientiate between the owner and anyone else and play a different menu accordingly. The owner can obtain access to all previous recorded messages, whereas anyone else may only record a message. So, what next?
To make the voicemail system more personal, we will add an option to record a voicemail prompt for the owner, to be played as a welcome message. Additionally, our voicemail box owner may want to selectively delete messages.
Let's get started!
-
-
Sample Files:
- Samples\voicemail_4.py
We've expanded our welcome message logic by checking for the existence of a pre-recorded file. If it exists, we play the file, rather than using TTS for a welcome message.
Menu expansion
The
top_level_menu()
code has been expanded to include a third option for the owner of the voicemail box. The owner may record a welcome message, used by the welcome logic on first answering the call.Optional delete
The
play_all_messages()
subroutine has been extended. After playing the message, the user is given the option of either keeping the message, or deleting it. We can delete a file using thefile_man.delete_file()
function. -
__uas_identify__ = "application" __uas_version__ = "1.0b1" from prosody.uas import Hangup, Error, ICanRun """ An inbound application that answers the call and says a welcome message. The welcome message defaults to "Welcome to the voicemail server for <name>" but can be changed. <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 with the option to delete each one individually 2. Delete all existing messages 3. Record a new welcome message 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/voicemail4/<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. For this sample it is important that you also read the online documentation about working with media files, which describes some of the effects of Eventual Consistency. """ class VoiceMailBox: def __init__(self, box_name, channel, file_manager, logger, welcome_message): self._channel = channel self._file_manager = file_manager self._logger = logger self._file_format = "samples/voicemail4/" + box_name + "/msg{0}.wav" self._recorded_file_counter = 1 self.welcome_message_filename = "samples/voicemail4/" + box_name + '/' + welcome_message def _move_files_down(self, counter): next_file = self._file_format.format(counter + 1) while self._file_manager.exists(next_file): this_file = self._file_format.format(counter) if self._file_manager.rename(next_file, this_file) is True: counter += 1 next_file = self._file_format.format(counter + 1) else: break def record_welcome_message(self): # wait on i_can_run to prevent an accidental infinite loop i_can_run = ICanRun(self._channel) while i_can_run: msg = "Please record a welcome message after the tone. Press any digit to stop the recording." self.record_new_message(msg=msg, filename=self.welcome_message_filename) key_selected = get_menu_select(self._channel, self, "Please press 1 to keep this message or 2 to record an alternative message.", 20, "12") if key_selected == '1': self.say_tts("Message saved.") break def reset_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_welcome(self): if self._file_manager.exists(self.welcome_message_filename): self.play_file(self.welcome_message_filename) return True return False 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, msg="Please record a message after the tone. Press any digit to stop the recording.", filename=None, replace=False): if filename is None: 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(msg, 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): file_counter = 1 say_counter = 1 filename = self._file_format.format(file_counter) while self._file_manager.exists(filename): self.say_tts("Message {0}".format(say_counter)) self.play_file(filename) key_selected = get_menu_select(self._channel, self, "Please press 1 to keep this message. \ Press 2 to delete this message. \ Press 3 to quit.", 20, "123") if key_selected == '2': if self._file_manager.delete_file(filename) is True: self.say_tts("Message deleted") self._move_files_down(file_counter) file_counter -= 1 if key_selected == '3': break file_counter += 1 say_counter += 1 filename = self._file_format.format(file_counter) self._recorded_file_counter = file_counter if file_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): return_code = 0 my_log.info("Started {0} :".format(application_parameters)) 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, "welcome.wav") mailbox.reset_message_counter() my_log.info("Ring and answer") channel.ring(2) channel.answer() caller = get_caller(channel) my_log.info("Call from {0} to {1}'s Voicemail answered".format(caller, application_parameters)) if mailbox.play_welcome() is False: 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=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") 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 4 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 record a welcome message. \ Press 4 to quit.", 20, "1234") if key_selected == '1': mailbox.play_all_messages() elif key_selected == '2': mailbox.delete_all_messages() elif key_selected == '3': mailbox.record_welcome_message() 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#\Voicemail4\Voicemail4.cs
In this step the
PlayWelcomeMessage()
method has been expanded. If the required pre-recorded file exists it plays it. Otherwise, a welcome message is played using TTS. Additionally the message names are now stored in a database (REQUIRES DATABASE SETUP: see Database Tutorial for instructions).Menu Expansion
The
TopLevelMenu()
method now has a third option: the ability to leave a message prompt.RecordNewMessage
has been altered so that we can specify a destination record filename.Optional Delete
In
PlayAllMessage()
, after playing the message, we use some TTS and then theGetMenuSelection()
method to prompt the user to keep or delete the message just played. Depending on the selection, the message is deleted using theFileManager.DeleteFile()
method.Database
The names of the message files have been abstracted and stored in a local database. As the cloud file system stores the messages files in a reliable and distributed manner, there can be some delays before files are available to read. Using the database ensures the list of files the sample manipulates is accurate and up to date.
-
using System; using System.Threading; using System.Collections.Generic; using AMSClassLibrary; using UASAppAPI; using System.Data.SqlClient; // An inbound application that answers the call, and says a welcome message // that defaults to "Welcome to the voicemail server for <name>." but can // be changed. <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 listen to all messages. // Press 2 to delete all messages. // Press 3 to record a welcome message". // 1. Play all existing messages - with option of deleting each one individually. // 2. Delete all existing messages // 3. Record a welcome message // 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/voicemail4/name/msg_<guid>.wav" // where guid is a unique identifier. // // This application uses an SQLEXPRESS database to manage the recorded message files. // Please see the Database Tutorial documentation for instructions on how to set up // the required database 'test'. // // Requires: // [applicationParameters = voicemail box name] namespace Voicemail4 { public class Voicemail4 : 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 } // This is the entry point for the application public override int Run(UASCallChannel channel, string applicationParameters) { this.Trace.TraceInfo("Started with application Parameters: {0}", applicationParameters); ReturnCode reply = ReturnCode.Success; // The voicemail box name must have been configured in the // applicationParameters at service registration _voicemailBoxName = applicationParameters; if (_voicemailBoxName.Length == 0) { return (int)ReturnCode.ArgumentEmpty; } try { // Set up the location for the recorded message files _messageFolder = "samples/voicemail4/" + _voicemailBoxName + "/"; // Setup the location and format for the recorded filenames and welcome message _welcomeMessageFileName = "welcome.wav"; _messageFileNameFormat = "msg_{0}.wav"; // Answer the call CallState state = channel.Answer(); if (state == CallState.Answered) { // Create Database Connection Object _voicemailBoxDatabase = new VoicemailBoxDatabase(applicationParameters); // Open the database _voicemailBoxDatabase.Open(); string caller = GetCaller(channel); this.Trace.TraceInfo("Call from {0} to {1}'s Voicemail answered", caller, _voicemailBoxName); PlayWelcomeMessage(channel); // If the caller is the owner of the voicemailbox let them manage their voicemailbox // Else record a message 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(); _voicemailBoxDatabase.Close(); } this.Trace.TraceInfo("Completed"); return (int)reply; } // Play the recorded welcome message or a standard welcome message if it // doesn't exist. private ReturnCode PlayWelcomeMessage(UASCallChannel channel) { if (this.FileManager.FileExists(_welcomeMessageFileName)) { channel.FilePlayer.Play(_messageFolder + _welcomeMessageFileName); } else { string welcomeMessage = "Welcome to the voicemail server for " + _voicemailBoxName; channel.FilePlayer.Say(welcomeMessage); } if (channel.FilePlayer.Cause == FilePlayerCause.HangUp) { return ReturnCode.CallHungUp; } return ReturnCode.Success; } // Obtain the caller id 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; } // Get a new message filename private string GetNextRecordFileName() { return String.Format(_messageFileNameFormat, Guid.NewGuid().ToString()); } // Run the top level menu for the voicemailbox owner. 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 3 to record a welcome message. " + "Press hash to exit."; char keySelected; do { reply = GetMenuSelection(channel, prompt, 20, "123#", out keySelected); if (reply == ReturnCode.Success) { switch (keySelected) { case '1': reply = PlayAllMessages(channel); break; case '2': DeleteAllMessages(channel); break; case '3': reply = RecordNewMessage(channel, true); break; case '#': return reply; } } } while (reply == ReturnCode.Success); return reply; } private ReturnCode RecordNewMessage(UASCallChannel channel) { return RecordNewMessage(channel, false); } // Record a new message, allowing the caller to save or retry the recording private ReturnCode RecordNewMessage(UASCallChannel channel, bool recordingWelcomeMessage) { ReturnCode reply = ReturnCode.Success; string prompt; string fileName; if (recordingWelcomeMessage) { fileName = _welcomeMessageFileName; prompt = "Please record a welcome message after the tone. " + "Press any digit to stop the recording."; } else { fileName = GetNextRecordFileName(); prompt = "Please record a message after the tone. " + "Press any digit to stop the recording."; } string fullFileName = _messageFolder + fileName; // Loop round until we have recorded a message acceptable to the caller // or caller has hung up bool satisfiedWithMessage = false; do { // Play the prompt channel.FilePlayer.Say(prompt); // 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 to file: [{0}]", fullFileName); if (!channel.FileRecorder.Start(fullFileName, 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(fullFileName, 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 acknowledgePrompt = "Please press 1 to keep this message or 2 to record an " + "alternative message"; char keySelected; reply = GetMenuSelection(channel, acknowledgePrompt, 20, "12", out keySelected); if (reply == ReturnCode.Success) { if (keySelected == '1') { satisfiedWithMessage = true; if (!recordingWelcomeMessage) { _voicemailBoxDatabase.SaveMessage(fileName); } channel.FilePlayer.Say("Message saved."); } } } while (!satisfiedWithMessage && (reply == ReturnCode.Success)); return reply; } // Play all the recorded messages, allowing the caller to delete any as required private ReturnCode PlayAllMessages(UASCallChannel channel) { ReturnCode reply = ReturnCode.Success; List<string> fileNames = _voicemailBoxDatabase.GetMessageFileNames(); foreach (string fileName in fileNames) { string fullFileName = _messageFolder + fileName; if (!this.FileManager.FileExists(fullFileName)) { channel.FilePlayer.Say("The next message is currently inaccessable"); Thread.Sleep(1000); continue; } channel.FilePlayer.Say("Next message"); channel.FilePlayer.Play(fullFileName); if (channel.FilePlayer.Cause == FilePlayerCause.HangUp) { reply = ReturnCode.CallHungUp; break; } // Allow the caller to delete individual messages string prompt = "Press 1 to keep this message. Press 2 to delete this message"; char keySelected; reply = GetMenuSelection(channel, prompt, 20, "12", out keySelected); if (reply == ReturnCode.Success) { if (keySelected == '2') { // delete the entry in the db _voicemailBoxDatabase.DeleteMessage(fileName); // and delete the message file on the cloud this.FileManager.DeleteFile(fullFileName); channel.FilePlayer.Say("Message deleted."); } } } if (reply == ReturnCode.Success) { channel.FilePlayer.Say("No more messages."); } return reply; } // Delete all recorded messages, both in the database and the files themselves private void DeleteAllMessages(UASCallChannel channel) { // Get the names of all existing files List<string> fileNames = _voicemailBoxDatabase.GetMessageFileNames(); // Delete them from the db _voicemailBoxDatabase.DeleteAllMessages(); // Delete all the message files on the cloud if (fileNames.Count > 0) { foreach (string fileName in fileNames) { this.FileManager.DeleteFile(_messageFolder + fileName); } channel.FilePlayer.Say("All messages have been deleted."); } else { channel.FilePlayer.Say("There are no messages to delete."); } } // Find out what the caller wants to do. // 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 _messageFolder; private string _welcomeMessageFileName; private string _messageFileNameFormat; private VoicemailBoxDatabase _voicemailBoxDatabase; private string _voicemailBoxName; } /// <summary> /// A class that represents a database of voicemail messages. /// </summary> class VoicemailBoxDatabase { public VoicemailBoxDatabase(string tableName) { // add a non-numeric prefix to the table name _tableName = "vmb" + tableName; _mySqlConnection = new SqlConnection("server=.\\SQLEXPRESS;" + "Trusted_Connection=yes;" + "database=test;" + "connection timeout=10"); } public void Open() { _mySqlConnection.Open(); // Create the table if it doesn't already exist. CreateTable(); } public void Close() { _mySqlConnection.Close(); } public void SaveMessage(string fileName) { // Save an entry in the database string command = "insert into " + _tableName; command += " values (getdate(), '" + fileName + "')"; ExecuteSqlNonQueryCommand(command); } public void DeleteMessage(string fileName) { // Delete the entry in the database string command = "delete from " + _tableName; command += " where filename='" + fileName + "'"; ExecuteSqlNonQueryCommand(command); } public void DeleteAllMessages() { // Delete all the entries in the database string command = "delete from " + _tableName; ExecuteSqlNonQueryCommand(command); } public List<string> GetMessageFileNames() { List<string> fileNames = new List<string>(); string command = "select * from " + _tableName; SqlCommand myCommand = new SqlCommand(command, _mySqlConnection); SqlDataReader reader = myCommand.ExecuteReader(); if (reader.HasRows) { // Read the row data while (reader.Read()) { // Get rid of any extraneous characters string fileName = reader["filename"].ToString().TrimEnd(_endChars); fileNames.Add(fileName); } } reader.Close(); return fileNames; } // Create the table if necessary private void CreateTable() { string command = "if object_id('" + _tableName + "', 'U') is null begin " + "create table " + _tableName + " (timestamp datetime, filename char(64)) " + "end"; ExecuteSqlNonQueryCommand(command); } private void ExecuteSqlNonQueryCommand(string command) { SqlCommand myCommand = new SqlCommand(command, _mySqlConnection); myCommand.ExecuteNonQuery(); } private string _tableName; private SqlConnection _mySqlConnection; private readonly char[] _endChars = { ' ', '\n', '\r' }; } }
-
-
-
Sample Files:
- Samples\VB\Voicemail4\Voicemail4.vb
In this step the
PlayWelcomeMessage()
method has been expanded. If the required pre-recorded file exists it plays it. Otherwise, a welcome message is played using TTS.Menu Expansion
The
TopLevelMenu()
method now has a third option: the ability to leave a message prompt.RecordNewMessage
has been altered so that we can specify a destination record filename.Optional Delete
In
PlayAllMessage()
, after playing the message, we use some TTS and then theGetMenuSelection()
method to prompt the user to keep or delete the message just played. Depending on the selection, the message is deleted using theFileManager.DeleteFile()
method.Database
The names of the message files have been abstracted and stored in a local database. As the cloud file system stores the messages files in a reliable and distributed manner, there can be some delays before files are available to read. Using the database ensures the list of files the sample manipulates is accurate and up to date.
-
Imports System.Collections.Generic Imports System.Data.SqlClient Imports AMSClassLibrary Imports UASAppAPI ' An inbound application that answers the call, and says a welcome message ' that defaults to "Welcome to the voicemail server for <name>." but can ' be changed. <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 listen to all messages. ' Press 2 to delete all messages. ' Press 3 to record a welcome message". ' 1. Play all existing messages - with option of deleting each one individually. ' 2. Delete all existing messages ' 3. Record a welcome message ' 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/voicemail4/name/msg<guid>.wav". ' where guid is a unique identifier. ' ' This application uses an SQLEXPRESS database to manage the recorded message files. ' ' Requires: ' [applicationParameters = voicemail box name] Namespace Voicemail4 ' 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 Voicemail4 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 in the ' applicationParameters at service registration voicemailBoxName = applicationParameters If voicemailBoxName.Length = 0 Then Return ReturnCode.ArgumentEmpty End If Try ' Set up the location for the recorded message files messageFolder = "samples/voicemail4/" + voicemailBoxName + "/" ' Setup the location and format for the recorded filenames and welcome message welcomeMessageFilename = "welcome.wav" messageFileNameFormat = "msg_{0}.wav" ' Answer the call Dim state As CallState state = channel.Answer() If state = CallState.Answered Then ' Create Database Connection Object voicemailBoxDatabase = New VoicemailBoxDatabase(applicationParameters) ' Open the database voicemailBoxDatabase.Open() Dim caller = GetCaller(channel) Me.Trace.TraceInfo("Call from {0} to {1}'s Voicemail answered", _ caller, voicemailBoxName) PlayWelcomeMessage(channel) ' If the caller is the owner of the voicemailbox let them manage their voicemailbox ' Else record a message 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 ' Play the recorded welcome message or a standard welcome message if it ' doesn't exist. Private Function PlayWelcomeMessage(ByVal channel As UASCallChannel) As ReturnCode If Me.FileManager.FileExists(welcomeMessageFilename) Then channel.FilePlayer.Play(messageFolder + welcomeMessageFilename) Else Dim welcomeMessage = "Welcome to the voicemail server for " + voicemailBoxName channel.FilePlayer.Say(welcomeMessage) End If If channel.FilePlayer.Cause = FilePlayerCause.HangUp Then Return ReturnCode.CallHungUp End If 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 ' Get a new message filename Private Function GetNextRecordFileName() As String Return String.Format(messageFileNameFormat, Guid.NewGuid().ToString()) End Function ' Run the top level menu for the voicemailbox owner. 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 3 to record a welcome message. " + _ "Press hash to exit." Dim keySelected As Char Do reply = GetMenuSelection(channel, prompt, 20, "123#", keySelected) If reply = ReturnCode.Success Then Select Case keySelected Case "1" reply = PlayAllMessages(channel) Case "2" DeleteAllMessages(channel) Case "3" reply = RecordNewMessage(channel, True) Case "#" Return reply End Select End If Loop While reply = ReturnCode.Success Return reply End Function 'Dim filename = String.Format(recordFilenameFormat, recordedFileCounter.ToString()) ' recordedFileCounter += 1 'Dim prompt = "Please record a message after the tone. " + _ ' "Press any digit to stop the recording." Private Function RecordNewMessage(ByVal channel As UASCallChannel) As ReturnCode Return RecordNewMessage(channel, False) End Function Private Function RecordNewMessage(ByVal channel As UASCallChannel, ByVal recordingWelcomeMessage As Boolean) As ReturnCode Dim reply = ReturnCode.Success Dim prompt As String Dim fileName As String If recordingWelcomeMessage Then fileName = welcomeMessageFilename prompt = "Please record a welcome message after the tone. " + _ "Press any digit to stop the recording." Else fileName = GetNextRecordFileName() prompt = "Please record a message after the tone. " + _ "Press any digit to stop the recording." End If Dim fullFileName = messageFolder + fileName ' Loop round until have recorded a message acceptable to the caller ' or caller has hung up Dim satisfiedWithMessage = False Do ' Play the prompt channel.FilePlayer.Say(prompt) ' 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}]", fullFileName) If Not channel.FileRecorder.Start(fullFileName, 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(fullFileName, 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 acknowledgePrompt = "Please press 1 to keep this message or 2 to record an " + _ "alternative message" Dim keySelected As Char reply = GetMenuSelection(channel, acknowledgePrompt, 20, "12", keySelected) If reply = ReturnCode.Success Then If keySelected = "1" Then satisfiedWithMessage = True If Not recordingWelcomeMessage Then voicemailBoxDatabase.SaveMessage(fileName) End If 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 fileNames = voicemailBoxDatabase.GetMessageFileNames() For Each fileName In fileNames Dim fullFileName = messageFolder + fileName If Not Me.FileManager.FileExists(fullFileName) Then channel.FilePlayer.Say("The next message is currently inaccessable") Thread.Sleep(1000) Continue For End If channel.FilePlayer.Say("Next Message") channel.FilePlayer.Play(fullFileName) If channel.FilePlayer.Cause = FilePlayerCause.HangUp Then reply = ReturnCode.CallHungUp Exit For End If ' Allow the caller to delete individual messages Dim prompt = "Press 1 to keep this message. Press 2 to delete this message" Dim keySelected As Char reply = GetMenuSelection(channel, prompt, 20, "12", keySelected) If reply = ReturnCode.Success Then If keySelected = "2" Then ' delete the entry in the db voicemailBoxDatabase.DeleteMessage(fileName) Me.FileManager.DeleteFile(fullFileName) channel.FilePlayer.Say("Message deleted.") End If End If Next If reply = ReturnCode.Success Then channel.FilePlayer.Say("No more messages.") End If Return reply End Function ' Delete all recorded messages, both in the database and the files themselves Private Sub DeleteAllMessages(ByVal channel As UASCallChannel) ' Get the names of all existing files Dim fileNames = voicemailBoxDatabase.GetMessageFileNames() ' Delete them from the db voicemailBoxDatabase.DeleteAllMessages() ' Delete all the message files on the cloud If (fileNames.Count > 0) Then For Each fileName In fileNames Me.FileManager.DeleteFile(messageFolder + fileName) Next channel.FilePlayer.Say("All messages have been deleted.") Else channel.FilePlayer.Say("There are no messages to delete.") End If End Sub ' 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 voicemailBoxDatabase As VoicemailBoxDatabase Private voicemailBoxName As String Private messageFolder As String Private messageFileNameFormat As String Private welcomeMessageFilename As String Private recordFilenameFormat As String Private recordedFileCounter As String End Class ' A class that represents a database of voicemail messages. Public Class VoicemailBoxDatabase Public Sub New(ByVal tableName As String) ' add a non-numeric prefix to the table name Me.tableName = "vmb" + tableName mySqlConnection = New SqlConnection("server=.\SQLEXPRESS;" + _ "Trusted_Connection=yes;" + _ "database=test;" + _ "connection timeout=10") End Sub Public Sub Open() mySqlConnection.Open() ' Create the table if it doesn't already exist. CreateTable() End Sub Public Sub Close() mySqlConnection.Close() End Sub Public Sub SaveMessage(ByVal fileName As String) ' Save an entry in the database Dim command = "insert into " + tableName command += " values (getdate(), '" + fileName + "')" ExecuteSqlNonQueryCommand(command) End Sub Public Sub DeleteMessage(ByVal fileName As String) ' Delete the entry in the database Dim command = "delete from " + tableName command += " where filename='" + fileName + "'" ExecuteSqlNonQueryCommand(command) End Sub Public Sub DeleteAllMessages() ' Delete all the entries in the database Dim command = "delete from " + tableName ExecuteSqlNonQueryCommand(command) End Sub Public Function GetMessageFileNames() As List(Of String) Dim fileNames = New List(Of String) Dim command = "select * from " + tableName Dim myCommand = New SqlCommand(command, mySqlConnection) Dim reader = myCommand.ExecuteReader() If (reader.HasRows) Then ' Read the row data While (reader.Read()) ' Get rid of any extraneous characters Dim fileName = reader("filename").ToString().TrimEnd(" "c, vbLf, vbCr) fileNames.Add(fileName) End While End If reader.Close() Return fileNames End Function ' Create the table if necessary Private Sub CreateTable() Dim command = _ "if object_id('" + tableName + "', 'U') is null begin " + _ "create table " + tableName + " (timestamp datetime, filename char(64)) " + _ "end" ExecuteSqlNonQueryCommand(Command) End Sub Private Sub ExecuteSqlNonQueryCommand(ByVal command As String) Dim myCommand = New SqlCommand(command, mySqlConnection) myCommand.ExecuteNonQuery() End Sub Private tableName As String Private mySqlConnection As SqlConnection End Class End Namespace
-
Take it away...
If you have understood everything in these tutorials, you should be some way to understanding how easy it is to create a multi-purpose voicemail system. Take our code and experiment!