How to build an IVR with Ozeki VoIP SIP SDK
How to develop an IVR system written in C# by using XML code
Download: | ivr-using-xml-basic.zip | |
Download: | ivr-using-xml-multi-level.zip |
In the previous example we created a Multi-level IVR code example written in C#, which is able to read a greeting message to the caller, and give him/her some menu options to choose. The caller could listen more information about Ozeki Ltd., or a sample mp3 song by pressing button one or two, and he/she could enter a sub menu by pressing button three. In this lower menu level the caller was able to use the blind transfer functionality of the IVR system by pressing button one, or he/she could return to the main menu level by pressing button two. If you click on the following link, you can reach the full description of the Multi-level IVR example code.
In this tutorial example we will learn the followings:
- How to create basic and multi-level IVR systems by using XML codes
Video Tutorial:How to develop IVR by using Ozeki VoIP SDK - Tutorial 4 |
|
Video Tutorial:How to develop IVR by using Ozeki VoIP SDK - Tutorial 5 |
This example program allows you to listen welcome messages, important information, sample mp3 songs in your IVR system.
For the source code of the basic IVR using XML example, please click here!
ICommand.cs
Program.cs
Menu.cs
MultipleCommandHandler.cs
Softphone.cs
SpeakCommand.cs
PlayCommand.cs
For the source code of the Multi-level IVR using XML example, please click here!
ICommand.cs
Program.cs
Menu.cs
MultipleCommandHandler.cs
Softphone.cs
SpeakCommand.cs
PlayCommand.cs
We are using seven classes in these examples:
-
Program.cs
Our class with the Main() method, this class controls the console window, interacts with the user, by using softphone and callhandler objects. -
Softphone.cs
The softphone's implementation goes here, all of it's events, methods, functions, variables. -
Menu.cs
This class serves to managing and handling the menu sections of the IVR system, no matter it is the main menu or a sub-menu. -
MultipleCommandHandler.cs
If more than one commands belong to the key what the user pressed, the IVR system needs to start all of the commands with the same key. -
SpeakCommand.cs
With the speak command class the IVR system is able to read an optional text message to the caller. -
PlayCommand.cs
The play command class is useful when we would like to play an mp3 file to the caller. It could be a song or a pre recorded speech. -
ICommand.cs
This interface is intended to execute any subsequent command (play, speak, new menu level).
The implementation of the IVR using XML
We are going to create a program, which is intended to create a functional one or multi-level IVR system by using XML code to add the structure of the system. Start the coding with the creation of the Softphone class.
Softphone.cs
This class is used to introduce how to declare, define and initialize a softphone,
how to handle some of the Ozeki VoIP SIP SDK's events and how to use some of that's
functions. In other words, we would like to create a "telephone software", which has the
same functions (or much more), as an ordinary mobile (or any other) phone.
If you would like to know more about the specification of the softphone (and the creation of it), then visit our How to build a softphone
page, where you can find discriptions about the most important functions, methods and events of this topic.
ICommand.cs
We use the ICommand interface to give a general form to the commands belong to the current menu level. So if the command is a text to speech, an mp3 player or perhaps
a new menu level command, the system will be able to handle all of them.
This interface implements two methods (Start() and Cancel()) and one event (Completed). So if we implement all of these components in a class, we can start or cancel
the commands and we can check whether the commands are completed or not.
interface ICommand { void Start(ICall call); void Cancel(); event EventHandler Completed; }
Program.cs
The Program class is for getting the SIP account values from the user to create a softphone, managing the incoming calls and processing all the components of the XML code. If we want to modify the structure of the IVR system, we don't need to change the source code, we only need to modify the XML part.
The Main() method
In the Main method of the Program class first we need to call the ShowHelp() method to give some information to the user about the behavior of the program. After this, create a softphone object from the Softphone class and call the sipAccountInitialization() method with softphone parameter. With this, the program gets the necessary SIP account informations from the user to create the softphone and the phone line. Finally, we need to subscribe on the IncomingCall event of the softphone to check if the system has an incoming call.
static void Main(string[] args) { ShowHelp(); Softphone softphone = new Softphone(); sipAccountInitialization(softphone); softphone.IncomigCall += softphone_IncomigCall; Console.ReadLine(); }
The softphone_IncomingCall() method
This method should be called when the IncomingCall event occurs. In this method create a menu variable which will be equals with return value of the ReadXML() method. If it is not null, then we call the Start() method of this menu, with the call parameter. If the menu object is null, then we need to reject the incoming call.
static void softphone_IncomigCall(object sender, Ozeki.VoIP.VoIPEventArgs<Ozeki.VoIP.IPhoneCall> e) { var menu = ReadXML(); if (menu != null) menu.Start(e.Item); else e.Item.Reject(); }
The ReadXML() method
In my example, the Menu return type ReadXML() method will contain the XML code of the IVR, but reading it from a file is also a good option,
because if we would like to modify something in the xml code we can do it without shutting down the running of the program.
It starts with a menu tag so it will be the main menu. If the system answeres the call, then the caller will hear a greeting message.
This is what the init section is for. We will use two types of commands in this example: the speak command and the play command.
With the speak command the IVR system is able to read an optional text message to the caller. The play command is useful when we would
like to play an mp3 file to the caller. It could be a song or a pre recorded speech. If the caller press button one then he or she
can hear more information about ozeki company, and a sample mp3 song. Button two has no specific function.
After the creation of the XML code, make a new Menu object, and call the MenuLevel() method with the ivrXML parameter which contains
the xml code in string format, then return with the menu.
private static Menu ReadXML() { string ivrXml = @"<ivr> <menu> <init> <speak> Introduce you the Interactive Voice Mail aka IVR example code written with Ozeki VoIP SDK. To get more information about Ozeki Ltd. and hear a sample mp3 song, please press button one. By pressing button two, you can listen an inform message </speak> <play>../../test.mp3</play> </init> <keys> <key pressed='1'> <speak> Ozeki Informatics Ltd. is a leading mobile messaging software vendor and Ozeki VoIP SIP SDK is an excellent software development kit that allows you to establish VoIP calls from your application easily and quickly. You do not need to have expert programming skills, with the most basic programming knowledge you will be able to create extraordinary VoIP solutions with this tool. </speak> <play>../../test.mp3</play> </key> <key pressed='2'> <speak> You pressed button two. You did nothing. </speak> </key> </keys> </menu> </ivr>"; var menu = new Menu(); MenuLevel(ivrXml, menu); return menu; }
The MenuLevel() method
The MenuLevel() method has two parameters: a string type ivrXml which contains the current menu section of the XML code,
and a Menu type menu object. The whole process is in a try-catch block to get the unexpected exceptions. If the system catches one,
then we need to write the error message on the screen and stop running.
First use the Parse() method with the ivrXml parameter to load an XElement from our XML string. Then select the "menu" element
of the code. If the menuElement variable is null (so there is no menu tag in the XML code), then the XML code is inadequate,
stop the running. If there was no problem, then select the init element of the menuElement. We need to get all of the commands in
the init, so iterate in it with foreach statement. In my example a command can be "play" or "speak". If the current command is play,
then call the AddInitCommand() method of the menu class with PlayCommand() parameter to add this command to the command list, and do the same if command is speak,
but use the SpeakCommand() parameter.
If we got all the commands from the init section, select the "keys" element of the menu. This will contain the keys, which can be pressed by the caller. If there is no pressedKeyAttribute
the XML code is invalid. Then get the pressed key, store it in the pressedKey integer variable, and add all of the commands which belong to the current key.
private static void MenuLevel(string ivrXml, Menu menu) { try { var xelement = XElement.Parse(ivrXml); var menuElement = xelement.Element("menu"); if (menuElement == null) { Console.WriteLine("Wrong XML code!"); return; } var menuInit = menuElement.Element("init"); foreach (var initElements in menuInit.Elements()) { switch (initElements.Name.ToString()) { case "play": menu.AddInitCommand(new PlayCommand(initElements.Value.ToString())); break; case "speak": menu.AddInitCommand(new SpeakCommand(initElements.Value.ToString())); break; } } var menuKeys = menuElement.Element("keys"); foreach (var key in menuKeys.Elements("key")) { var pressedKeyAttribute = key.Attribute("pressed"); if (pressedKeyAttribute == null) { Console.WriteLine("Invalid ivr xml, keypress has no value!"); return; } int pressedKey; if (!Int32.TryParse(pressedKeyAttribute.Value, out pressedKey)) { Console.WriteLine("You did not add any number!"); } foreach (var element in key.Elements()) { switch (element.Name.ToString()) { case "play": menu.AddKeypressCommand(pressedKey, new PlayCommand(element.Value.ToString())); break; case "speak": menu.AddKeypressCommand(pressedKey, new SpeakCommand(element.Value.ToString())); break; default: return; } } } } catch (Exception ex) { Console.WriteLine(ex.Message); Console.WriteLine("Invalid ivr xml"); } }
Menu.cs
This class serves to managing and handling the menu sections of the IVR system, no matter it is the main menu or a sub-menu. So this class will manage the init and keys parts of the code which belong to the same menu level. We need to implemet all the methods and the event of the ICommand interface and create some objects at the beginning of the class.
Objects
Create the following objects:
- Dictionary<int, MultipleCommandHandler> keys to store the commands with the keys
- MultipleCommandHandler init to handle the process of the init section
- ICall call for managing the call
- Timer greetingMessageTimer for repeating the greeting message
- MultipleCommandHandler handler to handle the actual commands
Dictionary<int, MultipleCommandHandler> keys; MultipleCommandHandler init; ICall call; Timer greetingMessageTimer; MultipleCommandHandler handler;
The constructor
If we create a Menu object we need to set the keys, init and greetingMessageTimer instances, and set the greetingMessageTimer to auto reset. So the greeting message will repeat automatically.
public Menu() { keys = new Dictionary<int, MultipleCommandHandler>(); init = new MultipleCommandHandler(); greetingMessageTimer = new Timer(); greetingMessageTimer.AutoReset = true; }
The Start() method
If we start the menu, then we need to set the local call object with the parameter, subscribe on the necessary events, like the CallStateChanged and DtmfReceived events of the call and the Elapsed event of the greetingMessageTimer. Set the Interval of the greetingMessageTimer, accept the call, then start the greetingMessageTimer and call the start method of the init to start the included commands.
public void Start(ICall call) { this.call = call; Onsubscribe(); greetingMessageTimer.Interval = 20000; call.Answer(); greetingMessageTimer.Start(); init.Start(call); }
The call_DtmfReceived() method
We need to listen the DTMF signals to know the caller pressed a button and he/she would like start a menu option. This is what the call_DtmfReceived() method is for. If the caller pressed a button, it should be checked whether there is a command list with the key or not. If it is true, then we need to unsubscribe from all the events, subscribe on the the completed event of the handler and call the start method of it with the call parameter.
void call_DtmfReceived(object sender, VoIPEventArgs<DtmfInfo> e) { if (keys.TryGetValue(e.Item.Signal.Signal, out handler)) { Unsubscribe(); handler.Completed += handler_Completed; handler.Start(call); } }
The Onsubscribe(), Unsubscribe() and handler_Completed() methods
In the Unsubscribe() method we need to cancel the init and stop the greetingMessageTimer,
because there are commands for the pressed key. If the handler is not null, then call
the Cancel() method of it and we need to unsubscribe from all of the events.
The Onsubscribe() method is for subscribing back on all of the events and start
the greetingMessageTimer.
When the current handler is completed, then call the Onsubscribe method and start
again the greeting message of the menu.
public void Unsubscribe() { init.Cancel(); greetingMessageTimer.Stop(); if (handler != null) handler.Cancel(); call.CallStateChanged -= call_CallStateChanged; call.DtmfReceived -= call_DtmfReceived; greetingMessageTimer.Elapsed -= greetingMessageTimer_Elapsed; } private void Onsubscribe() { Unsubscribe(); call.CallStateChanged += call_CallStateChanged; call.DtmfReceived += call_DtmfReceived; greetingMessageTimer.Elapsed += greetingMessageTimer_Elapsed; greetingMessageTimer.Start(); } void handler_Completed(object sender, EventArgs e) { Onsubscribe(); init.Start(call); }
The AddInitCommand and AddKeyPressCommand
These methods are for passing the commands (from the Program class) to the MultipleCommandHandler class wheter be an init command or a keypress command.
public void AddInitCommand(ICommand command) { init.AddCommand(command); } public void AddKeypressCommand(int digit, ICommand command) { if (!keys.ContainsKey(digit)) keys[digit] = new MultipleCommandHandler(); keys[digit].AddCommand(command); }
MultipleCommandHandler.cs
If more than one commands belong to the key what the user pressed, the IVR system needs to start all of the commands with the same key. We will reach this functionality by using the methods and events of the MultipleCommandHandler class.
Objects and the constructor
We need to create some important objects:
- Queue<ICommand> commandQueue to get the commands in sequence
- List<ICommand> commandList; to store the commands in it
- ICall call managing the call
- ICommand currentCommand the handle the current command in the list
In the constructor of the class we need to set the commandList instance.
Queue<ICommand> commandQueue; List<ICommand> commandList; ICall call; ICommand currentCommand; public MultipleCommandHandler() { commandList = new List<ICommand>(); }
The Start() method
If we call the Start() method of this class we need the set the call with the call parameter, set the commandQueue object with ICommand elements, and we need to call the StartNextCommand() method.
public void Start(ICall call) { this.call = call; commandQueue = new Queue<ICommand>(commandList); StartNextCommand(); }
The StartNextCommand() method
In these methods we managing the starting of all the commands in the command list.
In the StartNextCommand() method check the commandQueue.Count is bigger than 0.
If it is true, then get the first command from the commandQueue, subscribe on the completed
event of the currentCommand, cancel it then call the Start() method of it with the call parameter.
In the OnCompleted() method invoke the Completed event, and in the currentCommand_Complete method
start the next command if the current command finished.
void StartNextCommand() { if (commandQueue.Count > 0) { currentCommand = commandQueue.Dequeue(); currentCommand.Completed += currentCommand_Complete; currentCommand.Cancel(); currentCommand.Start(call); } else OnCompleted(); } private void OnCompleted() { var Handler = Completed; if (Handler != null) Handler(this, EventArgs.Empty); } void currentCommand_Complete(object sender, EventArgs e) { StartNextCommand(); }
The Cancel() method
If we call the Cancel method of this class we need to unsubscribe from the completed event of the currentCommand and cancel it.
public void Cancel() { if (currentCommand != null) { currentCommand.Completed -= currentCommand_Complete; currentCommand.Cancel(); } }
SpeakCommand.cs
I mentioned before that in my example two specific command will be implemented. With the speak command class the IVR system is able to read an optional text message to the caller. The play command class is useful when we would like to play an mp3 file to the caller. It could be a song or a pre recorded speech.
Objects and the constructor
Implement the following objects to manage the speak commands in the IVR system:
- ICall call to manage the calls
- PhoneCallAudioSender phoneCallAudioSender to send audio to the caller through the phone line
- AudioHandler audioHandler to handle audio devices
- string text this will be read by the system to the caller
If we create an instance from this class we need to set the local text variable with the text parameter. That is what you can see in the constructor.
ICall call; PhoneCallAudioSender phoneCallAudioSender; MediaConnector mediaConnector; string text; bool isStarted; public SpeakCommand(string text) { this.text = text; }
The Start() method
In the Start() method of the SpeakCommand class need to set the objects and call the TextToSpeech() method with the text parameter to form the text into speech.
public void Start(Ozeki.VoIP.ICall call) { isStarted = true; this.call = call; phoneCallAudioSender = new PhoneCallAudioSender(); phoneCallAudioSender.AttachToCall(call); mediaConnector = new MediaConnector(); TextToSpeech(text); }
The TextToSpeech() method
We would like to turn the added text into speech. We will make it happen in the TextToSpeech() method of this class. Create a TextToSpeech object, then subscribe on the Stopped event of the tts, connect to the PhoneCallAudioHandler and finally call the AddAndStartText of the tts with text parameter.
private void TextToSpeech(string speach) { tts = new TextToSpeech(); tts.Stopped += tts_Stopped; mediaConnector.Connect(tts, phoneCallAudioSender); tts.AddAndStartText(speach); }
The tts_Stopped() and Cancel() methods
In the tts_Stopped() method we need to invoke the Completed event of the this class to know when the command stopped. If we would like to cancel the current Speak command we need to call the Cancel method of this class. Check whether the tts is null or not. If it is not null, then disconnect the tts from the phoneCallAudioSender, and dispose the mediaConnector with the Dispose() method.
void tts_Stopped(object sender, EventArgs e) { if (!isStarted) return; isStarted = false; var handler = Completed; if (handler != null) handler(this, EventArgs.Empty); } public void Cancel() { if (tts != null) { mediaConnector.Disconnect(tts, phoneCallAudioSender); mediaConnector.Dispose(); } }
PlayCommand.cs
The PlayCommand class is almost the same as the SpeakCommand.cs, the only difference is we need to create a MP3ToSpeaker() method instead of the TextToSpeech() method.
The MP3ToSpeaker() method
We need to create an MP3StreamPlayback object with the path parameter, which is the path of the file we want to play. Subscribe on the Stopped event of the mp3Player, connect the to the phoneCallAudioSender and finally call the Start() method for streaming.
private void MP3ToSpeaker(string path) { DisposeMediaConnection(); mediaConnector = new MediaConnector(); mp3Player = new MP3StreamPlayback(path); mp3Player.Stopped += mp3Player_Stopped; mediaConnector.Connect(mp3Player, phoneCallAudioSender); mp3Player.Start(); }
Now we finished the creation our IVR system using by XML code. This is a one-level menu system, and it can happen we need to manage a multi-level IVR system. The good news is we can turn this system to a multi-level IVR very easily. Only need to make some modifications in the Program class.
Multi-Level IVR using XML
Right now we own a one-level IVR system with one-level XML code. First modify the XML code to be multi-level:
The only thing we left to do is making some modifications in the MenuLevel() method. At the section where we check which commands belong to pressed key we need to add a new case to the switch statement, because the command can be a new menu as well. When it happens, create a new Menu object, add the menu command to the command list with the AddKeypressCommand (parameters are pressed key and the new Menu object), and recursively call this MenuLevel() method with the innerMenu object and that section of the XML code which belongs to the pressed key.
foreach (var element in key.Elements()) { switch (element.Name.ToString()) { case "play": menu.AddKeypressCommand(pressedKey, new PlayCommand(element.Value.ToString())); break; case "speak": menu.AddKeypressCommand(pressedKey, new SpeakCommand(element.Value.ToString())); break; case "menu": Menu innerMenu = new Menu(); menu.AddKeypressCommand(pressedKey, innerMenu); MenuLevel(key.ToString(), innerMenu); break; default: return; } }
After these modifications we have a multi-level IVR system, and if we would like to add more sub-menu levels, we do not need modify the source code, only need to write some new menu level into the XML code.
Conclusion
By using this example codes you are able to create and manage your own multi-level IVR system with the help of the components of the Ozeki VoIP SIP SDK, and you can specify more commands to customize and modernize your IVR.
If you have any questions or need assistance, please contact us at info@voip-sip-sdk.com
Related Pages
More information
- How to develop a basic IVR in C#
- How to create blind transfer and voice controlling functionality in your IVR
- How to develop a multi-level IVR in C#
- How the create an IVR by using XML code
- IVR database integration
- IVR with DTMF authentication
- C# IVR Video Tutorial Part 1 - C# Basic IVR
- C# IVR Video Tutorial Part 2 - Blind transfer, voice control
- C# IVR Video Tutorial Part 3 - Multi-level IVR
- C# IVR Video Tutorial Part 4 - IVR using XML code
- C# IVR Video Tutorial Part 5 - IVR using XML code