Serving up Video or Audio files to Safari and Mobile Safari iOS

Some time ago my web app that serves up video snippets recorded from security cams stopped working on Safari. I have been having to tell people to use an alternative browser if they needed this to work. I finally figured out why Xojo is unable to serve a video file to Safari via a webFile! Safari absolutely requires the support of the Range header. The Xojo webFile happily ignores the Range header and just serves up the whole file. This doesn’t seem to bother chrome or firefox but Safari has a snit and shuts down the connection refusing to load the file.

I’ve created a webFile replacement class that more or less properly handles the Range headers and sends back the appropriate sections, headers and status code. Safari and iOS Safari will now happily play my video files! I know that mostly you don’t want to serve up huge video files through the app but to place them in a regular web server directory, but thats not always feasible.

It is very easy to incorporate into your own code. Import the class and make a single call to a shared class method in the handleSpecialURL event. I offer up the class free, as in beer, to anyone else who can benefit from that or who wants to improve the support:

https://github.com/PlanetaryGear/mediaFile

The demo project includes 2 movie players, one using the webFile showing how it doesn’t work, and the other showing the mediaFile driven one that does.

There are 2 places I deviated from the draft of the spec. Firstly Xojo always appends a content-length message when you return from the handleSpecialURL. It’s my impression that the content-length response from a partial result should still contain the length of the entire file and not just the length of the snippet you’re sending. Xojo won’t let me replace that with my own number, it always sends the length of the sent data. Safari doesn’t seem to be bothered by that, nor do any of the other browsers I’ve tested on. The other is that if you ask for a range outside the size of the file I adjust the end of the range to be the end of the file rather than send a range not available error. That also doesn’t seem to cause it any problems but could be improved if it does cause anyone any trouble.

You really shouldn’t be using WebFiles to serve movies (or any large files for that matter) anyway. The amount of processing required will seriously degrade performance for all connected users.

You’d be better off putting movies on a CDN somewhere and providing temporary links.

That said… The Xojo Web HTTP Server is only HTTP/1.0 compliant and Range requests are part of the HTTP/1.1 compliance requirements.

Looking at your class, you’ve forgotten some very important things…

  1. There can be more than one range of bytes in a Range header. i.e. Range: bytes=0-100, 500-700, 800-1200
  2. Negative values are allowed to count from the end of the file, so Range: bytes=0, -10 is perfectly valid.
  3. Ranges without values are also allowed: Range: bytes=-500 or Range: bytes=9500- (both of which mean "the last 500 bytes of a 10000 byte file)

It’s also worth noting that passing the data 64K at a time doesn’t help your memory situation and will probably slow down the response. HandleSpecialURL caches all of those writes into memory (or a file if its > 256K) and then transfers them to the socket in chunks itself.

[quote=351362:@Greg O’Lone]There can be more than one range of bytes in a Range header. i.e. Range: bytes=0-100, 500-700, 800-1200
Negative values are allowed to count from the end of the file, so Range: bytes=0, -10 is perfectly valid.
Ranges without values are also allowed: Range: bytes=-500 or Range: bytes=9500- (both of which mean "the last 500 bytes of a 10000 byte file)[/quote]

Ah, those things I can add :slight_smile: though Safari does not appear to use any of that when loading HTML5 video files.

As to the other, I completely and totally understand that you don’t want to use the CGI app as a passthrough when you’ve already got apache or something else running on the machine. But there are truly legitimate situations where that is simply not the case. For my specific application this is a stand alone web interface to a home automation app. There is no way I can demand that they also install a separate dedicated web server and do double the passthrough configuration for their NAT routers to configure another port pass through and then enter that configuration into my app so it knows how to link to the files. There is only my app running, it has to serve all the files necessary for the interface. This also has the benefit of being a very limited user base so you’ll never have a situation where a hundred users are trying to download different movies. The movies are also fairly small in size as such things go. A dozen meg is about the largest capture I’ve ever seen in a single file.

The memory usage is potentially a problem, but reading it in chunks like that at least keeps me from having to store it in memory twice as I load it into a temporary variable and then pass it off to the handleSpecialURL function. So it’s actually re-writeing them out to a temporary file before sending it huh? Thats more work than is truly necessary, but I have no access to the lower level stream for writing down the pipe directly.

I have created a feature request for this ability though :slight_smile: <https://xojo.com/issue/42538> If I had access to the pipe I could manage the sending of the files myself and you could just disconnect all the automatic management of headers and caches and everything else.

Alternatively you could also just enhance the webFile system to handle range header requests :wink: But I would MUCH rather have access to the stream and read/write to and from it directly. I could do a lot of other things with that capability that I’ve been thinking about for a long time.

As far as the speed of the response. I have not tried to tune those values but there is no human discernible difference between loading it in one chunk in memory in one single read, doing it in 64k chunks or moving it to apache on the same machine and linking out to it. I’ve tried all that, while I”m sure apache is a lot faster, it just doesn’t make a bit of noticeable difference on how fast it starts to autoplay in the browser. Again, this is a single or at most a couple of users using the thing at a time, not something that needs to scale to hundreds or thousands.