shell

Build a FlexML Advanced Play Application

In this guide, you will learn how to build a FlexML application using the Play verb. This advanced application will incorporate all of the optional supported attributes available except for digits, which is an alternative to the verb Dtmf. These attributes allow you to do things like fast-forward, rewind, loop, and pause. We are building our sample application server and routing with Python, but you may use any language you are comfortable with.

Advanced Play Application

In this section, we will build a simple FlexML application, using Python to serve our route. We use the following folder structure. app.py will hold our route, and instruct.xml will hold our FlexML. instruct.xml is inside of the static directory so that it can be accessible to the route we'll write.

flexml_play
        app.py
        static
            instruct.xml


Next, we are going to build a Flask server and create our route. We use Flask because it allows us to quickly run a server on our local machine. Start by importing Flask and the Flask module request. Request allows our app to process data that was sent to the browser.

Create one route, which serves and prints the FlexML code. Ensure that you add both the methods GET and POST.

from flask import Flask, request
app = Flask(__name__, static_url_path='')

@app.route("/", methods=["GET", "POST"])
def home():
    return app.send_static_file('instruct.xml')

if __name__ == '__main__':
    app.run(debug=True)


After we create our route, we will write our FlexML in the instruct.xml document.

We nest all of our FlexML inside a Response tag and Play verb. Inside the Play tags, add a file of your choosing. In this case, we added a podcast episode downloaded from Bullhorn that we have stored in a CarrierX container. This should be a long file so that you can test fast-forward and reverse commands. Feel free to borrow any of the links included in this tutorial in your sample application.

In the Play start-tag, add the following supported attributes:

In addition to the controls to fast-forward and rewind, you can add alternative commands that allow you to fast-forward and rewind in smaller increments.
  • minorControlSkip: the amount of seconds skipped for minorForwardOnKey and minorRewindOnKey.
  • minorForwardOnKey: the DTMF key that listeners enter to fast-forward the playback by the number of seconds set in minorControlSkip.
  • minorRewindOnKey: the DMTF key that listeners enter to rewind the playback by the number of seconds set in minorControlSkip.

In the following example, you are able to fast-forward and rewind the recording by 15 second increments. To fast-forward, press 4; to pause, press 2; to rewind, press 6. Finally, strictForwardControl is set to true, meaning that the file cannot be forwarded past the end. If you try to fast-forward past the end a file, the forward command will not work.

<Response>
    <Play 
        controlSkip="15" 
        forwardOnKey="4" 
        pauseOnKey="2" 
        rewindOnKey="6" 
        strictForwardControl="true">
        https://storage.carrierx.com/c/d8b637fa-efa2-4d4d-817a-6b5f7f33e268/no_sleep_podcast.mp3
    </Play>
</Response>


Now let's test the first lines of our application! You can run your local Flask server through Terminal using the following command.

FLASK_APP=app.py flask run


We will also need to expose the route publicly over the Internet. To do this, we can use the free tool ngrok. If you choose to use ngrok, you will need to follow the instructions on their website to download it. Alternatively, you may expose the URL any other way you choose. Once your Flask server is running, your Terminal will list the port it's running on. Expose this port by running the following command through Terminal. Make sure to swap out 5000 with the port that your Flask server is running on.

./ngrok http 5000


ngrok will open a new Terminal tab that will show you the http and https URLs that make the application publicly available over the Internet. Add the https ngrok link to either a FlexML endpoint or a DID associated with a FlexML endpoint. Refer to the FlexML Endpoint Quick Start Guide to learn how.

Ngrok Terminal Window

Now that we have seen how to run and test this application, let's build onto it. In the code following, we're going to add some controls to restart and stop a file.

To your preexisting code, add the following attributes:

In the following example, you will be able to press the 1 key to restart the file. You will also be able to press 3 or * to stop the playback.

<Response>
    <Play 
        restartOnKey="1"
        stopOnKey="3*"
        controlSkip="15" 
        forwardOnKey="4" 
        pauseOnKey="2" 
        rewindOnKey="6" 
        strictForwardControl="true">
        https://storage.carrierx.com/c/d8b637fa-efa2-4d4d-817a-6b5f7f33e268/no_sleep_podcast.mp3
    </Play>
</Response>


In this next portion of code, we will enable listeners to enter a DTMF key to obtain information about the controls available when interacting with the file.

Add three new attributes to your code:

In the following example, you can press the 9 key to play the informationAudioUrl audio file. pauseAudioUrl is the file that will play when the main audio file is paused.

<Response>
    <Say>
        Once the playback starts, press 9 to hear the options menu. 
    </Say>
    <Play 
        informationOnKey="9"
        informationAudioUrl="https://storage.carrierx.com/c/4e33f4a8-4c04-4da0-9896-9550164ed296/informationAudioUrl.mp3"
        pauseAudioUrl="https://storage.carrierx.com/c/4e33f4a8-4c04-4da0-9896-9550164ed296/pauseAudioUrl.mp3"
        restartOnKey="1"
        stopOnKey="3*"
        controlSkip="15" 
        forwardOnKey="4" 
        pauseOnKey="2" 
        rewindOnKey="6" 
        strictForwardControl="true">
        https://storage.carrierx.com/c/d8b637fa-efa2-4d4d-817a-6b5f7f33e268/no_sleep_podcast.mp3
    </Play>
</Response>


Next, we will add functionality to be able to control looping of the file. Add the following to your code:

In the following, the file will play twice because loop is set to 2. 5 seconds will elapse between each loop.

<Response>
    <Play
        loop="2"
        loopPause="5">
        https://storage.carrierx.com/c/d8b637fa-efa2-4d4d-817a-6b5f7f33e268/piano.wav
    </Play>
    <Say>
        Once the playback starts, press 9 to hear the options menu. 
    </Say>
    <Play 
        informationOnKey="9"
        informationAudioUrl="https://storage.carrierx.com/c/4e33f4a8-4c04-4da0-9896-9550164ed296/informationAudioUrl.mp3"
        pauseAudioUrl="https://storage.carrierx.com/c/4e33f4a8-4c04-4da0-9896-9550164ed296/pauseAudioUrl.mp3"
        restartOnKey="1"
        stopOnKey="3*"
        controlSkip="15" 
        forwardOnKey="4" 
        pauseOnKey="2" 
        rewindOnKey="6" 
        strictForwardControl="true">
        https://storage.carrierx.com/c/d8b637fa-efa2-4d4d-817a-6b5f7f33e268/no_sleep_podcast.mp3
    </Play>
</Response>


Now let's make the URL audio file play in the background. Add the following supported attribute:

In the following, we have enabled an audio file to play in the background.

<Response>
    <Play
        background="true">
        https://storage.carrierx.com/c/d8b637fa-efa2-4d4d-817a-6b5f7f33e268/spooky_background.mp3
    </Play>
    <Play
        loop="2"
        loopPause="5">
        https://storage.carrierx.com/c/d8b637fa-efa2-4d4d-817a-6b5f7f33e268/piano.wav
    </Play>
    <Say>
        Once the playback starts, press 9 to hear the options menu. 
    </Say>
    <Play 
        informationOnKey="9"
        informationAudioUrl="https://storage.carrierx.com/c/4e33f4a8-4c04-4da0-9896-9550164ed296/informationAudioUrl.mp3"
        pauseAudioUrl="https://storage.carrierx.com/c/4e33f4a8-4c04-4da0-9896-9550164ed296/pauseAudioUrl.mp3"
        restartOnKey="1"
        stopOnKey="3*"
        controlSkip="15" 
        forwardOnKey="4" 
        pauseOnKey="2" 
        rewindOnKey="6" 
        strictForwardControl="true">
        https://storage.carrierx.com/c/d8b637fa-efa2-4d4d-817a-6b5f7f33e268/no_sleep_podcast.mp3
    </Play>
</Response>


Let's go over streaming for files that are not prerecorded. We will also set a 20 second time limit for the recording while streaming. Add the following two supported attributes:

In the following, rather than playing a prerecorded file, we are streaming. The stream will play for 10 seconds before the code moves onto the next verb.

<Response>
    <Play
        streaming="true"
        timeLimit="10">
        http://us4.internet-radio.com:8193
    </Play>
    <Play
        background="true">
        https://storage.carrierx.com/c/d8b637fa-efa2-4d4d-817a-6b5f7f33e268/spooky_background.mp3
    </Play>
    <Play
        loop="2"
        loopPause="5">
        https://storage.carrierx.com/c/d8b637fa-efa2-4d4d-817a-6b5f7f33e268/piano.wav
    </Play>
    <Say>
        Once the playback starts, press 9 to hear the options menu. 
    </Say>
    <Play 
        informationOnKey="9"
        informationAudioUrl="https://storage.carrierx.com/c/4e33f4a8-4c04-4da0-9896-9550164ed296/informationAudioUrl.mp3"
        pauseAudioUrl="https://storage.carrierx.com/c/4e33f4a8-4c04-4da0-9896-9550164ed296/pauseAudioUrl.mp3"
        restartOnKey="1"
        stopOnKey="3*"
        controlSkip="15" 
        forwardOnKey="4" 
        pauseOnKey="2" 
        rewindOnKey="6" 
        strictForwardControl="true">
        https://storage.carrierx.com/c/d8b637fa-efa2-4d4d-817a-6b5f7f33e268/no_sleep_podcast.mp3
    </Play>
</Response>


Let's add some buffer to account for the playback loading. Additionally, we'll add a skip value, which will start a playback after that given number of seconds. Add the following attributes to your code:

In the following, the playback will start once 5 seconds have been downloaded. And the file itself will start playing 2 seconds in.

<Response>
    <Play
        streaming="true"
        timeLimit="10">
        http://us4.internet-radio.com:8193
    </Play>
    <Play
        background="true">
        https://storage.carrierx.com/c/d8b637fa-efa2-4d4d-817a-6b5f7f33e268/spooky_background.mp3
    </Play>
    <Play
        loop="2"
        loopPause="5">
        https://storage.carrierx.com/c/d8b637fa-efa2-4d4d-817a-6b5f7f33e268/piano.wav
    </Play>
    <Say>
        Once the playback starts, press 9 to hear the options menu. 
    </Say>
    <Play 
        preBuffer="5"
        skip="2"
        informationOnKey="9"
        informationAudioUrl="https://storage.carrierx.com/c/4e33f4a8-4c04-4da0-9896-9550164ed296/informationAudioUrl.mp3"
        pauseAudioUrl="https://storage.carrierx.com/c/4e33f4a8-4c04-4da0-9896-9550164ed296/pauseAudioUrl.mp3"
        restartOnKey="1"
        stopOnKey="3*"
        controlSkip="15" 
        forwardOnKey="4" 
        pauseOnKey="2" 
        rewindOnKey="6" 
        strictForwardControl="true">
        https://storage.carrierx.com/c/d8b637fa-efa2-4d4d-817a-6b5f7f33e268/no_sleep_podcast.mp3
    </Play>
</Response>


Next, we're going to add some values that will account for slow downloads. Let's add the following attributes:

In the following, waitInitialUrl will start playing if the initial playback cannot start and it has been 5 seconds. If 5 seconds has expired and waitInitialUrl has not started playing, waitRepeatUrl will start. waitRepeatUrl will repeat every 15 seconds until Play starts.

<Response>
    <Play
        streaming="true"
        timeLimit="10">
        http://us4.internet-radio.com:8193
    </Play>
    <Play
        background="true">
        https://storage.carrierx.com/c/d8b637fa-efa2-4d4d-817a-6b5f7f33e268/spooky_background.mp3
    </Play>
    <Play
        loop="2"
        loopPause="5">
        https://storage.carrierx.com/c/d8b637fa-efa2-4d4d-817a-6b5f7f33e268/piano.wav
    </Play>
    <Say>
        Once the playback starts, press 9 to hear the options menu. 
    </Say>
    <Play 
        waitInitial="5"
        waitInitialUrl="https://storage.carrierx.com/c/4e33f4a8-4c04-4da0-9896-9550164ed296/waitInitialUrl.mp3"
        waitRepeat="15"
        waitRepeatUrl="https://storage.carrierx.com/c/4e33f4a8-4c04-4da0-9896-9550164ed296/waitRepeatUrl.mp3"
        preBuffer="5"
        skip="2"
        informationOnKey="9"
        informationAudioUrl="https://storage.carrierx.com/c/4e33f4a8-4c04-4da0-9896-9550164ed296/informationAudioUrl.mp3"
        pauseAudioUrl="https://storage.carrierx.com/c/4e33f4a8-4c04-4da0-9896-9550164ed296/pauseAudioUrl.mp3"
        restartOnKey="1"
        stopOnKey="3*"
        controlSkip="15" 
        forwardOnKey="4" 
        pauseOnKey="2" 
        rewindOnKey="6" 
        strictForwardControl="true">
        https://storage.carrierx.com/c/d8b637fa-efa2-4d4d-817a-6b5f7f33e268/no_sleep_podcast.mp3
    </Play>
</Response>


Finally, we are going to add a link that enables our app to access more FlexML instructions. We're also going to add error handling. First, let's set up a second route for our Flask server. We called this route action and it will return the FlexML instructions kept in our file action.xml.

from flask import Flask, request
app = Flask(__name__, static_url_path='')

@app.route("/", methods=["GET", "POST"])
def home():
    return app.send_static_file('instruct.xml')

@app.route("/action", methods=["GET", "POST"])
def action():
    return app.send_static_file('action.xml')

if __name__ == '__main__':
    app.run(debug=True)


The JSON received during requests will include the following fields that provide you with useful information during testing.

{
  "AccountSid": "",
  "OriginalTo": "19093189029",
  "Direction": "inbound",
  "OriginalFrom": "+19093189030",
  "ApiVersion": "2.0",
  "CallSid": "4f0d84c72ff90967f58351cbe8364a77",
  "To": "19093189029",
  "RequestUrl": "http://9d583764.ngrok.io",
  "CallerName": "",
  "From": "19093189030",
  "PlayOffset": 6,
  "PlaybackUrl": "https://storage.carrierx.com/c/d8b637fa-efa2-4d4d-817a-6b5f7f33e268/no_sleep_podcast.mp3",
  "Digits": None 
}


The following is further information about the Digits, PlaybackUrl, and PlayOffset fields included in the JSON response above.

Attribute Description
Digits If the playback was stopped due to stopOnKey, this is the key that triggered the end of the playback. If the playback ended without being stopped by a DTMF key, this field will be None.
PlaybackUrl The URL that was being played.
PlayOffset The amount of time in seconds that the URL file played through until it was stopped. Alternatively, if the file played through, the data in this field will be the number of seconds of the entire file.

Now let's head back to our instruct.xml file to add some more instructions. Include the attributes below to your app:

In the following, the code will make a POST request to the action URL after the Play file ends. In our example, a request will be made to the /action route that we created.

If the Play file fails to play entirely, a POST request will be made to errorAction.

<Response>
    <Play
        streaming="true"
        timeLimit="10">
        http://us4.internet-radio.com:8193
    </Play>
    <Play
        background="true">
        https://storage.carrierx.com/c/d8b637fa-efa2-4d4d-817a-6b5f7f33e268/spooky_background.mp3
    </Play>
    <Play
        loop="2"
        loopPause="5">
        https://storage.carrierx.com/c/d8b637fa-efa2-4d4d-817a-6b5f7f33e268/piano.wav
    </Play>
    <Say>
        Once the playback starts, press 9 to hear the options menu. 
    </Say>
    <Play 
        action="/action"
        method="POST"
        errorAction="/errorAction"
        errorMethod="POST"
        waitInitial="5"
        waitInitialUrl="https://storage.carrierx.com/c/4e33f4a8-4c04-4da0-9896-9550164ed296/waitInitialUrl.mp3"
        waitRepeat="15"
        waitRepeatUrl="https://storage.carrierx.com/c/4e33f4a8-4c04-4da0-9896-9550164ed296/waitRepeatUrl.mp3"
        preBuffer="5"
        skip="2"
        informationOnKey="9"
        informationAudioUrl="https://storage.carrierx.com/c/4e33f4a8-4c04-4da0-9896-9550164ed296/informationAudioUrl.mp3"
        pauseAudioUrl="https://storage.carrierx.com/c/4e33f4a8-4c04-4da0-9896-9550164ed296/pauseAudioUrl.mp3"
        restartOnKey="1"
        stopOnKey="3*"
        controlSkip="15" 
        forwardOnKey="4" 
        pauseOnKey="2" 
        rewindOnKey="6" 
        strictForwardControl="true">
        https://storage.carrierx.com/c/d8b637fa-efa2-4d4d-817a-6b5f7f33e268/no_sleep_podcast.mp3
    </Play>
</Response>


Now, test your application by calling your rented phone number and using the commands we created!

Next Steps

You built an advanced Play application using all available attributes for the Play verb! For more information about the FlexML Play attributes, refer to the FlexML Documentation.

Once you have finished your advanced Play application, feel free to add more abilities by using other verbs. See the FlexML Verbs section of the FleXML documentation for a comprehensive list of verbs.