[asterisk-dev] Asterisk 14 - Remote URI Playback
BJ Weschke
bweschke at btwtech.com
Wed Nov 5 01:45:15 CST 2014
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.
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).
>> 2) What kind of locking is in place on the design to prevent HTTP Media
>> Cache from trying to update an expired resource that's already in the middle
>> of being streamed to a channel?
> Items in the cache are reference counted, so if something is using an
> item in the cache while the cache is being purged, that is safely
> handled. The buckets API (which is based on sorcery) assumes a 'if
> you're using it, you can hold it safely while something else swaps it
> out' model of management - so it is safe to update the entry in the
> cache with something new while something else uses the old cached
> entry. The 'local file name' associated with the URI would be created
> with mkstemp, so the risk of collision with local file names is low.
>
> In the same fashion, a local file that is currently open and being
> streamed has a reference associated with it in the OS. Calling unlink
> on it will not cause the file to be disposed of until it is released.
I had to do a little bit of reading up on the Bucket File API, but
yes, that definitely resolves the concern I had, and that's pretty cool.
:-)
>> 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.
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
More information about the asterisk-dev
mailing list