Listening to a Saved Recording Using Tropo and ASP.NET MVC

In my last post I discussed how to save a recording from Tropo using ASP.NET MVC and VoiceModel. I also discussed one of the issues I had to deal with was a difference in how VoiceXML and Tropo handle recordings.  A nice feature in VoiceXML is that you can listen to the recording on the IVR before you save it. With Tropo's Web API there is no mechanism to do this.  The goal of VoiceModel is to develop your voice application once and you can run it on either a VoiceXML or Tropo platform,  therefore I had to make both behave in the same manner.  The solution was to save the recording immediately on completion and to let the user listen to the recording on the application server that VoiceModel is deployed on.

In order to grab the correct recording from the application server I needed a method to map a recording to the current session. Fortunately both VoiceXML and Tropo send a unique session ID with each request so I used the session ID as the file name for the audio file that is stored on the application server.  This necessitated a few changes to VoiceModel and the Recording Example application.  Here is the code for the  Recording Example.

public override CallFlow BuildCallFlow()
    CallFlow flow = new CallFlow();
    flow.AddState(ViewStateBuilder.Build("getRecording", "playback",
        new Record("getRecording", "Please record your information after the beep.  Press the pound key when you are finished.")),true);
    Prompt playbackPrompt = new Prompt("You recorded ");
    playbackPrompt.audios.Add(new Audio(new ResourceLocation(new Var(flow, "recordingUri")),
        new Var(flow, "recordingName"), "Error finding recording"));
    State playbackState = ViewStateBuilder.Build("playback", "goodbye", new Say("playback", playbackPrompt));
    playbackState.AddOnEntryAction(delegate(CallFlow cf, State state, Event e)
        cf["recordingUri"] = cf.RecordedAudioUri;
        cf["recordingName"] = cf.SessionId + ".wav";
    flow.AddState(ViewStateBuilder.Build("goodbye", new Exit("goodbye", "Your message has been saved. Goodbye.")));
    return flow;

VoiceModel maintains a separate instance of a Call Flow for each session to maintain state information that is unique to that session.   I introduced a new concept to VoiceModel which allows the application developer to maintain variable information with this state information. A new VoiceModel object was introduced called a Var, for variable.  A Var just has two properties a Name and a Value, and it is stored in the context of an instance of a Call Flow. What also changed in VoiceModel is that a ResourceLocation and an Audio object now accept a Var to store the URI for the location and file name of the audio respectively.  Now we can dynamically set the resource location and audio file name.  This is useful for getting the recording as well as handling multi-lingual applications where the location of the audio can indicate which language to use, allowing switching of languages at run-time.

To set the audio location and file name at run-time an OnEntryAction is used on the state called playbackState.  As the name implies these actions are performed immediately on transitioning to that state. If you look at  the definition of playbackState.AddOnEntryAction you can see how the Var's for audio location and file name are set before we playback the recording using a Say object.

The other challenge was how to stream the recorded audio back to the IVR for playback. To do this I added an action to VoiceController called Recording.

public ActionResult Recording(string id)
    //TODO: Need to return a 404 error if the file does not exist.
    _log.Debug("Requested recording " + id);
    string filename = id;
    string path = Path.Combine(Server.MapPath(recordingPath), filename);
    return File(path, "audio/wav", Server.UrlEncode(filename));

As you can see from the comments we need to add some logic to check that the file exists and if it does not return a 404 error to the IVR.  Currently it returns a 500 error which is not very clean.  To request the recording we just indicate to use the URI in the format http://server/application/controllerName/myRecording.wav which is what we are building when we set the variables in the OnEntryAction.

So making recordings and playing them back is working consistently in Tropo and VoiceXML now. This completes the last major functionality that needed to be integrated in VoiceModel to allow it to work on both Tropo and VoiceXML IVR platforms.  All that is left is some minor cleanup and testing and it will be ready for release. You can get the latest source code to play with this new feature.

We always welcome feedback on the VoiceModel features and issues. I have been getting some requests to allow VoiceModel to work with Twilio also.  If you would like to see Twilio support incorporated into VoiceModel you can vote on it here.   And we are always looking for interested developers to help with this project.  We could especially use developers familiar with Twilio if we move forward with this feature request.


Popular posts from this blog

Customizing Claims for Authorization in ASP.NET Core 2.0

Using Claims in ASP.NET Identity

Adding Email Confirmation to ASP.NET Identity in MVC 5