Video
Video is particularly difficult, as it typically requires multiple files (one video, one audio) to be useful. Container formats are beyond the scope of this project. To support video, remote playback of URIs should support a parallel form of extraction using the pipe |
character:
same => n,Playback(http://myserver.com/monkeys.h264|http://myserver.com/monkeys.wav)
The behaviour of the files retrieved in such a fashion are as follows:
- Playback does not begin until all files are retrieved.
- Files are treated individually in the cache.
- While files with different names can be played back, the first file in the parallel list will be the actual "core sound file" played back. That is, assuming
monkeys.h264
andmonkeys.wav
already existed on the server, thePlayback
operation would operate on sound filemonkeys
, which would play both the audio and video files.
Persistence
The cached entries and their metadata should be stored in the AstDB
. When Asterisk starts, the entries in the AstDB
should be verified to still be accurate (that is, that the local files still exist), and if so, the buckets for them re-created.
Playback of a URI
Playback of a URI can be done via any of the supported playback operations, through any API. This functionality should be a part of the Asterisk core, and available through any of the media playback operations.
Dialplan
same => n,Playback(http://myserver.com/monkeys.wav)
Note that this can be combined with multiple URIs or sounds to form a playlist:
same => n,Playback(http://myserver.com/monkeys.wav&http://myserver.com/weasels.wav)
Since an &
is valid for a URI but is also used as a separator in dialplan, ampersands in a resource cannot be supported. If an ampersand is used in a URI (say, as part of a query), then the entire URI must be URI encoded.
AGI
CONTROL STREAM FILE http://myserver.com/monkeys.wav "" 3000
ARI
Playback can be done on either a channel or a bridge. Since the resource being requested is a URI, it should be presented in the body of the request encoded using text/uri-list
(see RFC 2483). Note that we still need to provide a media query parameter that specifies the resource type to play - the actual 'entity' being played back is simply specified in the body of the request.
http://localhost:8088/ari/channels/12345/play/p1?media=uri:list Content-Type: text/uri-list http://myserver.com/monkeys.wav
This format works nicely with simple playlists, as it can specify multiple files to retrieve. Note that these files should be played back sequentially as a playlist (which is not yet supported, but will need to be by the time we get here!)
http://localhost:8088/ari/channels/12345/play/p2?media=uri:list Content-Type: text/uri-list # Audio File One (I'm a comment and should be ignored) http://myserver.com/monkeys.wav # Comment comment comment http://myserver.com/awesome-sound.wave
Note that when the Content-Type
is text/uri-list
, the resource specified by the uri
media scheme is simply tossed away, as we can only have a single list of URIs. Note that this approach is somewhat limiting in that supporting multiples
Another option is to provide the URIs in JSON. This would allow parallel playback of files for video support. Note that the JSON must follow the following schema:
http://localhost:8088/ari/channels/12345/play/p3 { "media": "playlist", "playlist": [ { "media_group": [ { "scheme": "uri", "resource": "http://myserver.com/monkeys.wav" }, { "scheme": "uri", "resource": "http://myserver.com/monkeys.h264" } ] } { "media_group": [ { "scheme": "uri", "resource": "http://myserver.com/awesome-sound.wav", } ] } ] }
There's some obvious differences here:
- The
schema
type must beplaylist
. We can't use auri
schema type, as doing so would prevent combiningURI
resources with other media resource types for playlists. - The playlist must be strongly typed. Otherwise, swagger code generation turns into a nightmare. As such, that means a new strongly named body parameter must be used.
Configuration
No configuration should be needed.
Design
Core - media_cache
This new file in the Asterisk core will provide the following:
- A universal API that the core and rest of Asterisk can do to query for something in the media cache.
- Caching of the bucket information in the
AstDB
- Informing the bucket implementation that a new bucket has been created during startup (or otherwise creating the buckets itself)
- CLI commands
Implementations of a cache should implement the bucket
API for a particular scheme.
API
CLI Commands
core show media-cache
Just for fun! It'd be good to see what is in the cache, the timestamps, etc.
*CLI>core show media-cache URI Last update Local file http://myserver.com/awesome.wav 2014-10-30 00:52:25 UTC /var/spool/asterisk/media_cache/ahsd98d1.wav http://myserver.com/monkeys.wav 2014-09-14 10:10:00 UTC /var/spool/asterisk/media_cache/77asdf7a.wav http://myserver.com/monkeys.h264 2014-09-14 10:10:00 UTC /var/spool/asterisk/media_cache/77asdf7a.h264 3 items found.
Note that the last two files would have been created using a preferred file prefix. This allow the file
and app
core to "find" both the audio and the video file when opening up the stream returned by the file_path
by the media_cache
.
core clear media-cache
This is really a handy way for a system administrator to force files to be pulled down.
*CLI>core clear media-cache 3 items purged. *CLI>core show media-cache URI Last update Local file 0 items found.
res_http_media_cache
File Retrieval
A module should be implemented that does the actual work of using libcurl
to retrieve a media file from the system subject to caching constraints, if they exist. It should:
- Implement the HTTP caching, noted previously
- cURL files down to a local file, create a bucket, and store the bucket along with the appropriate metadata
Core - Usage of ast_openstream
Prior to call ast_openstream
, users who want to support URI playback should first:
- Determine if the provided "file" is actually a URI. A simple 'strncmp' is sufficient for this.
- If so, ask the cache for the actual file. Use that for subsequent calls to
ast_openstream
andast_openvstream
. - If not, move on as normal.
Core - file.c::ast_streamfile
This covers most functionality, as most dialplan applications (and other things) end up calling ast_streamfile
.
AGI - res_agi.c::handle_streamfile | handle_getoption
AGI implementations of basic sound playback, which emulate their dialplan counterparts. These will need to be updated in the same way as ast_streamfile
.
Core - HTTP server
Support for a new Content-Type, text/uri-list
, needs to be added to the HTTP server. Since we typically parse the body as JSON using ast_http_get_json
, this should be a new public function ast_http_get_uri_list
that pulls a body as a text/uri-list
. Since we now have core support for URIs, a bit of basic support for a list of URIs should be added to uri.h
and used by the HTTP server.
http.h
uri.h
ARI
Swagger Schema
Providing a 'type' for the media
parameter is a bit tricky. We have the following situation:
- If the
media
parameter does not specify a resource type ofuri
, treat it as a string. - If the
media
parameter does specify a resource type ofuri
, look to the body as a URI list. - If the
media
parameter is in the body, however, it must be of typestring
. Hence, we cannot specify the URIs directly in themedia
parameter. Thus, to specify URIs, a body parameter ofplaylist
must be provided and the URIs provided in aPlaylist
model object.
Note that the following for channels.json
would be repeated for bridges.json
:
Mustache Templates
The Mustache templates generated will need to be modified to check for text/uri-list
as a possible body type:
- The existing
body_parsing.mustache
should be renamed to note that it parses JSON. The caller of thebody_parsing
routines should be made to only look at the results if the function returns non-NULL.- If it does return NULL, it should also check for a
text/uri-list
using the new function inhttp.h
. - The body parsers should be updated for a playback operation to return the structured Playback object.
- If it does return NULL, it should also check for a
A new
text_uri_body_parser
should be added that parses a body into astruct ast_uri_list
. This should be hard-typed to convert the URI list into a structured Playback object.
Test Plan
Unit Tests
Category | Test | Description |
---|---|---|
/main/media_cache/exists | nominal | Nominal test that verifies a valid item in the cache can be found |
/main/media_cache/exists | non_existent | Test that verifies that an item in the cache that doesn't exist isn't located |
/main/media_cache/exists | bad_scheme | Verify that we reject a URI with an unknown or unsupported scheme |
/main/media_cache/exists | bad_params | Pass in bad parameters, make sure we get back a "huh?" |
/main/media_cache/retrieve | nominal | Retrieve a file from the Asterisk HTTP server (using magic!) and make sure it is in the cache. Retrieve it again and make sure we didn't do an HTTP request twice. |
/main/media_cache/retrieve | non_existent | Ask for a file that doesn't exist. Get an error back. |
/main/media_cache/retrieve | bad_scheme | Ask for something that has a bad URI scheme. |
/main/media_cache/retrieve | bad_params | Ask for something with no URI and no file_path. Make sure we reject it. |
/main/media_cache/retrieve | new_file | Retrieve a media file, cache it. Update the file, ask for the file again; make sure it gets the new copy. |
/main/media_cache/retrieve | preferred_file_name | Ask for a file with a preferred file name; verify that we retrieve the file and set the file name accordingly (with the right extension). |
/main/media_cache/delete | nominal | Put something in the cache. Call delete; verify the cache is purged. |
/main/media_cache/empty | nominal | Call delete on an empty cache; make sure everything is cool. |
/main/media_cache/update | create | Verify that a new item in the cache can be created |
/main/media_cache/update | update | Verify that an existing item in the cache can be updated |
/main/uri | to_string | Test that a constructed URI can be converted back to a string |
/main/uri | list_ctor | Verify that we can make a URI list |
/main/uri | list_append_nominal | Test appending URIs to a list |
/main/uri | list_append_off_nominal | Test appending things that aren't a URI, and make sure we fail appropriately |
/main/uri | iterator_ctor | Test nominal creation of an iterator |
/main/uri | iterator_ctor_off_nominal | Test off-nominal creation of an iterator with a bad list |
/main/uri | iterator_iteration | Test nominal iteration over lists. Include empty lists. |
Asterisk Test Suite
Test | Level | Description |
---|---|---|
| Asterisk Test Suite | A regression test that verifies that the current read functionality of |
| Asterisk Test Suite | Verify that we can cURL a file down and store it |
funcs/func_curl/curl_opt/timestamp | Asterisk Test Suite | Verify that we can retrieve the timestamp from a resource |
tests/apps/playback/uri | Asterisk Test Suite | Verify that the Playback dialplan application can playback a remote URI |
tests/apps/control_playback/uri | Asterisk Test Suite | Verify that the ControlPlayback dialplan application can playback a remote URI |
tests/fastagi/stream-file-uri | Asterisk Test Suite | Verify that the AGI Stream File command can play a URI |
tests/fastagi/control-stream-file-uri | Asterisk Test Suite | Verify that the AGI Control Stream File command can play a URI |
tests/rest_api/channels/playback/playlist | Asterisk Test Suite | Verify that a Playback resource that contains a playlist can be played back to a channel |
tests/rest_api/bridges/playback/playlist | Asterisk Test Suite | Verify that a Playback resource that contains a playlist can be played back to a bridge |
tests/rest_api/channels/playback/uri | Asterisk Test Suite | Verify that a Playback resource can be created from a URI for a Channel |
tests/rest_api/bridges/playback/uri | Asterisk Test Suite | Verify that a Playback resource can be created from a URI for a Bridge |
Project Planning
The following are rough tasks that need to be done in order to complete this feature. These are meant to be guidelines, and should not necessarily be followed verbatim. Note that many of these are actually independent of each other, and can be worked out simultaneously. If you're interested in helping out with any of these tasks, please speak up on the asterisk-dev
mailing list!
The various phases are meant to be implemented as separately as possible to ease the process of peer review.
Phase One - Core Media Cache
See peer reviews:
Task | Description | Status |
---|---|---|
Implement the basic API | Mask callbacks into the bucket API based on the URI scheme being requested. Add handling for manipulating the created bucket's local file if the predefined filename is provided. | Done |
Integrate with the AstDB | When items are created via a bucket When Asterisk is started, re-create buckets based on the entries currently in the AstDB. | Done |
Add CLI commands | Both for showing elements in the media cache as well as for purging the cache. If the cache is purged, remove entries from the AstDB. | Done |
Phase Two - res_http_media_cache
Task | Description | Status |
---|---|---|
Create http_media_cache . Add bucket wizards for schema types http and https .
| Generally, get the basic structure of the the thing defined, implement unit tests, and make sure the unit tests fail. TDD. | Done |
Implement retrieval. Use the underlying CURL function to retrieve a provided URI and store as a temporary file (or use the predetermined filename). | A few observations:
| Done |
Implement 'dirtying' of cache entry based on HTTP caching rules. | Implement handling of E-Tags as well as the Cache-Control header. | Done |
Phase Three - Core/dialplan/AGI implementations
Task | Description | Status |
---|---|---|
Update the file core to use the http_media_cache | Update the core file users of ast_openstream to first look for the resource in the http_media_cache . If found, use the returned file. | Done |
Update the dialplan users | Same as the core, except for dialplan functions. | Done |
Add tests for Playback and ControlPlayback | Done | |
Update the AGI users | Same as the core, except for res_agi functions. | Done |
Add tests for stream file and control stream file | Done |
Phase Four - ARI Playlists
Task | Description | Status |
---|---|---|
Update the JSON schema with JSON playlists | This should require updates to the Playbacks model, as well as the play operations for channels and bridges . The mustache templates may need to be updated to properly extract this complex of a body type. | Not Done (see Note below) |
Re-generate stubs; add connecting logic | Re-generating the stubs will create a new body handler and a more complex 'playlist' object that can be optionally present. This will be passed to the resource_channels and resource_bridges operations. | Not Done (see Note below) |
Add a 'playlist' media resource type |
| Not Done (see Note below) |
Update res_stasis_playback | The various function calls boil down to play_on_channel in res_stasis_playback . This is passed the actual Playback resource object, which now can contain a Playlist . The function should be updated to parse out the various items in the playlist and pass them to ast_control_streamfile_lang . | Not Done (see Note below) |
Add rest_api tests for playlists. | Not Done (see Note below) |
Phase Five - HTTP Server Updates
Task | Description | Status |
---|---|---|
Update uri.h to support URI lists and URI iterators. | Add unit tests! | Not Done |
Update http.h to support body types of text/uri-list . Generate a ast_uri_list as a result of said body type. | Not Done |
Phase Six - ARI text/uri-list
support/URI playbacks
Task | Description | Status |
---|---|---|
Update mustache templates to understand a body type of text/uri-list . Re-generate appropriate stubs. | Body parsing should be made to handle both JSON as well as the text/uri-list body type. A text/uri-list body parser can be made to explicitly return Playback objects suitable for consumption in the existing playlist mechanisms. | Not Done |
Wire generated code to resource_channels , resource_bridges . Update API callbacks as needed. | Generally, this should "just work" (or nearly) at this point. We have:
Most of this should be just gluing the pieces together as needed. | Not Done |
Add URI playback tests to Asterisk Test Suite. | Not Done |
JIRA Issues
ASTERISK-25654
-
Playback: Add the ability to play remote URIs
Closed
Contributors
Name | E-mail Address |
---|---|
Reference Information
http%3A%2F%2Fmyserver.com%2Fmedia%3Fsound%3Dmonkeys%26format%3Dwav
12 Comments
Ben Langfeld
Instead of including the URL for playback in the query string in ARI, can we not support POSTing some document for rendering? In the simplest case, this could be a URI list (see https://www.ietf.org/rfc/rfc2483.txt), and in the future with TTS it could be an SSML document or similar. This provides continuity, simplicity in listing multiple URLs, and no concerns about URL encoding.
Matt Jordan
Yup, that's a lot better. I'll update the document - thanks!
Ben Langfeld
So, I don't have a solution to propose yet, but I just wanted to register an objection while I think through the solution.
It seems to me like parallel playback as the default is very surprising. If I were to submit a list of audio files, my expectation would be that they would be played back sequentially. This is also consistent with SSML, although SSML does not provide a mechanism for parallel playback aside from multiple concurrent resources.
Matt Jordan
Yeah, I can't say I like the idea of a parallel playback, but I can see some use for it for video. Since Asterisk doesn't have support for media containers (which would be great, but is a large and separate project), you have to pull back the video file and the audio file as separate actions. However, you don't want to start playback until you have both, which means there has to be some nomenclature for retrieving two resources as a single operation, while also supporting a sequential list of URIs to operate on.
Structured data lends itself well to this problem, so another option would be to use a JSON body. Something like:
Ben Langfeld
I would prefer for parallel playback to be the special case, and therefore be supported by some custom (to Asterisk) format such as that you suggest, while the standardised format (text/uri-list) retains the simpler and least surprising behaviour of sequential playback.
VoiceXML itself does not contain any functionality for parallel playback, but Dialogic's extensions do: http://www.dialogic.com/webhelp/Vision/Release5.0/64-0402-01/par.html. As far as I can tell, MRCP does not contain any specific support for parallel synthesis, nor does Rayo. I'm struggling to find further precedence, though I understand the proposed use case.
Matt Jordan
I had the same thought on the drive in to work. Thinking about it some more, the only time parallel retrieval should be used is for video (since Asterisk doesn't support multiple audio streams). Really, this feature is of limited use for video: any reasonable sized video file is going to be quite large and would incur a pretty negative penalty for retrieval. I'm not saying it won't be used, just that it doesn't seem as useful as audio file.
I'll go back through the document and update it accordingly.
Seán C. McCord
Is there a compelling reason for supporting text/uri-list? Sure, it's a trivial markup and a MIME-type, but with the rest of the API being JSON-based, do we really save something by using an alternate body scheme.... particularly since it is insufficient to handle the general case? Certainly it doesn't hurt anything by having it, but I just don't see a compelling reason for it. If you are interacting with ARI, you are capable of constructing JSON.
Ben Langfeld
Because this is a lowest common denominator standard for TTS engines. If Asterisk can support it natively also for file playback as well as supporting passing it (or SSML) through to a TTS engine for rendering, it makes consumers of the API more consistent.
Seán C. McCord
Ah, that's reasonable, then
Matt Jordan
I agree that it's reasonable, although for now, I'm kind of holding off working on it.
The existing implementation, while a bit clunky, will accept an HTTP/HTTPS URI to playback:
media=sound:http://localhost/foo.wav
That doesn't solve the case of video, where you need to pull back both the video media as well as the audio media and start the playback of both simultaneously, but in the case of Asterisk, that's kind of an edge case. (Arguably, this feature - which doesn't yet support streaming media - isn't really suitable for video in the first place.)
Seán C. McCord
Pardon my ignorance if so, but what is the purpose of the additional `media=` prefix? We will already be inside a media context, and so far as I have seen (I only know ARI), the media URIs are simply of the form `<type>:<value>` (e.g. `sound:tt-monkeys` or `tone:!900/500,!0/500`.
Seán C. McCord
Idiot me; you're just including the key for the URI parameter. Never mind.