[asterisk-dev] Asterisk 14 - Remote URI Playback

Matthew Jordan mjordan at digium.com
Thu Nov 6 15:04:05 CST 2014


On Wed, Nov 5, 2014 at 1:45 AM, BJ Weschke <bweschke at btwtech.com> wrote:
> On 11/4/14, 3:40 PM, Matthew Jordan wrote:
>>
>> On Tue, Nov 4, 2014 at 12:57 PM, BJ Weschke <bweschke at btwtech.com> wrote:
>>>
>>>   Matt -
>>>
>>>   This is a pretty neat idea, indeed, but I've got some
>>> questions/thoughts on
>>> implementation. :-)   Apologies if all of this was already
>>> considered/accounted for already..
>>>
>>>   1) Does the entire file need to be downloaded and in place on the HTTP
>>> Media Cache before you can call an ast_openstream on it? This could cause
>>> some problems with larger files not sitting on a fat pipe local to the
>>> Asterisk instance.
>>
>> It does need to be completely on the local file system, which would be
>> a problem for extremely large files and/or slow network connections.
>>
>> The ability to do an 'asynchronous' version of this is not really
>> present. The filestream code in the core of Asterisk doesn't have
>> anything present that would allow it to buffer the file partially
>> before playing back with some expected max size. If we went down that
>> road, it'd almost be a completely separate filestream concept from
>> what we have today, which is pretty non-trivial.
>>
>> I don't think I have a good solution for really large files just yet.
>> There's some ways to do this using cURL (where we get back a chunk of
>> binary data, buffer it, and immediately start turning it into frames
>> for a channel) - but that feels like it would need a lot of work,
>> since we'd be essentially creating a new remote filestream type.
>
>  I know there's going to be a large population of Asterisk users that will
> want the simplicity of just specifying a URI for playback and expecting
> "sorcery" to happen. A decent number of them may even be OK with what may be
> a sub-second awkward silence to the caller on the line while things like the
> servicing thread synchronously queues the URI resource into the local HTTP
> media cache before playback. That's probably going to be an acceptable
> experience for a decent number of functional use cases. However, I think one
> somewhat common use case where this wouldn't go so well would be a list of
> URI resources that weren't already in HTTP media cache since they'd be
> fetched serially in-line at the time where playback really should be
> starting and block the channel with silence until the resource is set in
> media cache.

I think this is the way to do this in the long run. There's some major
technical hurdles to overcome with this, however:

(1) A remote file stream doesn't have the ability to do a 'parallel
fetch' the way it is proposed on the wiki page. For video streams,
that means Asterisk would (finally) have to understand media container
formats, which is a *very* large project.
(2) There are the obvious technical issues dealing with buffering,
overrun of the network download, and other off nominal kinds of
things. I think those are reasonably well understood, or at least can
be thought through.
(3) The largest concern I have is that I can't envision the API for
this. Currently, Asterisk channels support two file streams - one for
audio, one for video. Those streams are opaque objects managed by the
file API (file.h). If this fits behind that API, then there are some
serious things to work through with how that API works. If it is a
separate type of stream and a different object, then we can't take
advantage of the remote stream in all of the existing applications,
which would be unfortunate.

I think - for the purposes of this project only - I'd go with the
following philosophy: Make the media cache/HTTP playback blocking for
now, but put the hooks in for an asynchronous access to the retrieved
bytes for future expansion. That way the larger questions above can be
dealt with separately, but the information is available for what that
occurs.

I do think that means punting on using func_curl for this however, as
it just wasn't designed for this feature. That's not a big deal, since
it's pretty easy to use libcurl directly and - in the callback
function that receives the bytes from the remote HTTP server - make
the ast_channel object available for 'usage' in the future.

(And by the way: if someone was very interested in this part of the
project, I'd be thrilled if they wanted to go take a look at it. I
just think defining all of those APIs is outside the scope of getting
remote playback working first.)

>  eg -
> Playback(http://myserver.com/monkeys.wav&http://myserver.com/can.wav&http://myserver.com/act.wav&http://myserver.com/like.wav&http://myserver.com/weasels.wav)
> <--- On an empty HTTP Media cache, the previous app invocation would
> probably sound pretty bad to the first caller going through this workflow.
> :-)
>
>  Also, I think the inability to use & in a URI for playback really limits
> the usefulness of this change. I totally understand why the typical URI
> decode doesn't work, but perhaps a combination of a URI encoded & with an
> HTML entity representation is a suitable alternative?  eg - (%26amp; == & in
> a URI in Playback and do that pattern replacement in the URI before any
> other URI decoding/encoding operations. Ya, I know, it's a hack, but not
> allowing multiple parameters in a loaded queryString URL is way too
> restricting IMHO).

So I just replied to this part on Johan's e-mail - do you think we can
skip supporting an '&' in a resource? (How many media files are going
to be named tt-monkeys&weasels anyway?)

<snip>

>>>   3) I think you need to also introduce a PUT method on HTTP Media Cache
>>> because I can think of a bunch of scenarios where having a write
>>> operation
>>> on func_curl may be lacking in the file needing to be retrieved (eg -
>>> trying
>>> to pull ACL'd media from an S3 volume where you need custom HTTP request
>>> headers, etc). We shouldn't try to architect/design for all of these
>>> scenarios in Asterisk via a write operation on func_curl and a PUT to
>>> HTTP
>>> Media Cache seems like a reasonable approach to handle that.
>>>
>> I had thought about this, but didn't have a strong use case for it -
>> thanks for providing one!
>>
>> How about something like:
>>
>> GET /media_cache - retrieve a List of [Sound] in the cache
>> PUT /media_cache (note: would need to have parameters passed in the body)
>>      uri=URI to retrieve the media from
>>      headers=JSON list of key/value pairs to pass with the uri
>> DELETE /media_cache?uri
>>      uri=URI to remove from the cache
>>
>> Sounds data model would be updated with something like the following:
>>    "uri": {
>>         "required": false,
>>         "description": "If retrieved from a remote source, the
>> originating URI of the sound",
>>         "type": "string"
>>     },
>>     "local_timestamp": {
>>         "required": false,
>>         "description": "Creation timestamp of the sound on the local
>> system",
>>         "type": "datetime"
>>     },
>>     "remote_timestamp": {
>>          "required": false,
>>          "description": "Creation timestamp of the sound as known by
>> the remote system (if remote)",
>>          "type": "datetime"
>>     }
>>
>>
>  Well, kind of. I think you're still envisioning using CURL behind the
> scenes using the input provided in the JSON body of the PUT to /media_cache
> to go and grab the resource from the remote server. If you go that way, I
> think not only should we handle custom headers, but it's probably also not
> unreasonable to provide a way to do basic/digest authentication for the GET
> call as well. However, instead of that, I had envisioned being able to do a
> PUT to /media_cache as a multipart MIME request where one part is the JSON
> descriptor and the second part is the binary resource itself you're looking
> to place into HTTP Media cache. The advantage of doing things this way is
> that if you're running call control via some sort of API, that API will know
> for certain when files/resources are ready to be played back and you don't
> run the risk of the awkward blocking silence scenario that you have above.
> However, when you do it this way, the URI description/parameter itself
> doesn't make too much sense because it's not really where the resource came
> from. I guess there's also a question as to whether or not we follow the
> true REST practice with using POST for a brand new resource and PUT for
> updates to existing resources.

I'd prefer that approach actually. Pushing media to the server is
preferable to having Asterisk attempt to pull it.

To do this correctly, I think we'll need a sorcery wizard that accepts
push configuration/objects. We had previously talked about this with
respect to a push configuration model for PJSIP, but this actually
takes it one step further with a push wizard for buckets. Since
buckets sits on top of sorcery, it feels like this is theoretically
possible... but I'm not sure yet how it would play out completely.
Josh may want to comment on this part.

>  As for the timestamps for deciding whether the local cache is dirty, I
> don't think we should try to reinvent the wheel here. We should stick what's
> already well established for stuff like this and use the entity tag (Etag)
> response header stored and then use the "If-None-Match" request header
> approach. Google does a much better job of explaining it than I can here:
> https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching
>

Agreed, as commented on Johan's e-mail. E-Tags for the win!

I'll update the wiki from the conversation here shortly.

-- 
Matthew Jordan
Digium, Inc. | Engineering Manager
445 Jan Davis Drive NW - Huntsville, AL 35806 - USA
Check us out at: http://digium.com & http://asterisk.org



More information about the asterisk-dev mailing list