I was looking at my CodePlex Project to see if I could ease debugging a bit by logging the request and response headers. Before I got into the meat of things I enabled the Trace attribute in the web.config of my test project. This gave me the request headers but not the response. Before I had a chance to really dig more into this, I noticed my iPad was sending If-Modified-Since HTTP header in its request for a media file.
I thought about this for a minute and realized that in some cases I’m aware of the LastModified time for a media file and I should be able to compare the two. I started hacking at the project to get initial support to see how it would work. I ran into a couple things:
Not all timestamps are created equal.
The date and time my iPad sends with the request is GMT. The DateTime on my media file was localized (GMT -5:00). I decided it should be very quick to just convert them both to UTC and compare them. I wrote the code to parse the request headers, pass through the new Precondition in the ResumambleDownloadRequest object, and compare them in the ResumableDownloadResult base class. Unfortunately the comparison always thought the file had been modified more recently than the request and despite re-streaming the file, subsequent requests still contained the same If-Modified-Since timestamp.
I was about to reach for my hair and start pulling when I noticed while stepping through debugger than the string representations of the DateTime objects were exactly the same. In the immediate window I subtracted the parsed HTTP request timestamp from the LastModified value and took a look at the result. It turns out that the file was maintaining millisecond data which wasn’t present in the HTTP request. The TotalSeconds difference in times was less than 1 second. So I took the easy way out and rather than compare, I’m subtracting and checking TotalSeconds to see if the TimeSpan difference is less than 1 second.
I redeployed the updated (and messy) code and fired up Safari on my iPad. This time I was able to see the Trace request for the file return 304 (Not Modified). Success! Oh, but there was another request with 206 (Partial Content) return. And another. And another. My iPad requested the stream again even though it was unmodified.
Mobile devices have short memories.
Looking at the range requests following the HTTP 304 status I saw that the iPad started requesting from 65536 to the length of the file. Since the iPad is still running 3.2.2 I pulled out my iPhone 3G running 4.1 to see if the results were the same which they appeared to be. After some more testing it looks like the last media stream (assuming your iOS device hasn’t needed to recover the memory for something else) will cache the first 64KB of a media file to start playing while the rest of the stream is downloaded again.
Well, a savings of 64KB when repeating a stream that’s several MB doesn’t seem like much but it might help boost the speed. As long as it was the last stream played. And your device hasn’t cleared the memory. And… Huh. That Trace log has more entries again. Looking into each request in the trace I see that indeed the device skips the first 64KB after a 304 response but then it ends up requesting the whole thing all over again!
It seems like iOS devices might not be perfect when it comes to caching. I should point out though that these are only the headers from the request. It’s already known that iOS devices request and abort connections in order to determine if ranges are supported before they’ll stream. It’s entirely possible the device is closing the HTTP connection without actually receiving the full stream over and over but I haven’t had my own bandwidth to test this (especially since it just came to mind as I’m typing.)
One thing I’d like to try is consuming from a Mac since the videos will play right in the browser. I’m curious to see how Firefox and Safari handle the stream and whether they will properly cache. On my Windows laptop, requesting the file from Firefox just downloads the full file and Windows Media Player opens up and plays it. There’s no integration.
Update Nov 18 2010 @ 11:37 pm: I tried out the streaming using Firefox on Mac. BigBuckBunny at 110MB is rather large and it was downloaded each time I navigated to the page with no range requests. The browser never checked for updates. But using a smaller file of 7MB did. After the 304 it streamed the file without downloading it again. It seems size matters.
I ran the same through Safari on Mac. This time range requests were issued and partial results returned. The behavior was a little different than iOS devices though with a partial request inserted in the middle of the stream checking to see if the file was modified. 304 was returned and the subsequent request was for the remainder of the stream. This was on the 7MB file. For BigBuckBunny it was the same except there were a lot more range requests before and after the mid-point date check.
The most surprising result for me was what happened when I tried opening the files a second time. After the initial request for each file there were two range requests which both returned 304 (for each file; 4 requests total) and the file played as expected without being re-downloaded. Of all the combinations, Safari on Mac has been the most efficient so far.