Originally created by: daffinm
Originally owned by: jeffposnick
Library Affected:
"workbox-sw"
Browser & Platform:
"all browsers".*
Issue or Feature Request Description:
As @jeffposnick says here:
If you want to serve the media from the cache, you should explicitly add it to the cache ahead of time. This could happen either via precaching, or via calling cache.add() directly. Using a runtime caching strategy to add the media file to the cache implicitly is not likely to work, since at runtime, only partial content is fetched from the network via a Range request.
The good news is that workbox will cache and serve media perfectly well (once you have ironed out the usual assortment of wrinkles).
Consequences?
The bad news is that no media at all will serve until the service worker activates, and this only happens once all media files in the app have been cached. You cannot even get metadata for a media file until the service worker goes into the activated state...
Not very user-friendly?
Picture an app that makes extensive use of media. These are not uncommon. If you click a link to the site you will have to wait for every cached media file to download before being able to play just one of them. But what if you just wanted to "try before you buy"? And what if you don't like my PWA? Tough. You just had to download a ton of stuff you are not interested in.
In order to release my app I have had to create two service workers for it. The first is the default one that does not cache any media. The second one precaches all media in the app and I switch over to this once the user has chosen to install the app (add to home screen). This seems fair. But the user still has to wait until every media file has downloaded before being able to play one of them after they install.
On-demand caching needed for media?
Ideally, media files would start playing immediately and get cached in the background so that the user would not have to wait for them to finish downloading before playing.
Alternatively, first request for a media file would result in the file being downloaded and fully cached before playing. Subsequently, the file would play from the cache. This seems technically simper.
And if the remote file gets updated you would want to update the cache.
Anyone caching media in a PWA is going to run across this issue.
Thoughts?
Originally posted by: jeffposnick
Hello!
Could you provide some more information so that I could better understand what your service worker is doing?
This is the part I don't understand—why won't any media serve until the service worker activates? Is it because the precaching that's happening in parallel during the
installevent causes enough bandwidth contention that the client page can't stream bytes quickly enough for smooth playback?What I would assume is that before the service worker activates and takes control of any client pages, all existing media files would be just as playable—from a technical, if not bandwidth point of view—as they would be if there were no service worker involvement at all.
The issue here is that each browser handles streams of media files differently, varying on the media format, and some of the ways that streaming takes place does not lend itself to runtime caching in a service worker. I can't think of any viable approach that could, for instance, account for the out-of-order
Rangesegments that might be streamed to the browser when a user scrubs through a media's timeline, and pieces them all together (hopefully not missing any bytes!) into a cacheable, completeResponseobject.If you're playing back media in a controlled environment—perhaps you disable scrubbing and other playback controls, and you know that the media formats you're using will always trigger a single HTTP request that doesn't use
Rangeheaders in all browsers—then you could just use Workbox's runtime caching out of the box, without having to do anything special. It's just that I'm not aware of any playback scenarios where that's what happens.Originally posted by: daffinm
Hi @jeffposnick. Thanks for responding.
I think that the problem I am seeing may be caused by the fact that my single page app has a lot of audio files in it. I am using lazy loading for the audio (pseudo) pages, so the audio elements are not all present in the DOM at first. They get added when the page is accessed and added for the first time. But some audio pages have over 10 audio elements. Further testing reveals that the audio is playable before the service worker activates, so the delay I am seeing is probably to do with how the browser (in this case Chrome) is handling multiple audio elements. It looks more like a bandwidth issue.
As for the second part of my question - and the title - could one intercept a request for an audio (or other media) file in the service worker, ignore the range request headers, cache the entire file and then respond to the original range request with a response from the cache (which would use a range requests plugin)? Then you would have cache on demand (runtime caching) for media files without the issues you point out.
Originally posted by: jeffposnick
What you describe is possible, using the building blocks that are already available in Workbox, but I'm not sure that it would lead to a great experience for your users. The reason that many browsers will use
Range:request headers to stream media files is that waiting for 1% of a media file (or some other small percentage) to finish transferring before starting playback leads can often lead to a better experience than waiting for the entirety of a media file to finish transferring.If you wanted to try it for yourself, you could play around with something like (untested):
Ticket changed by: jeffposnick
Originally posted by: daffinm
Thanks @jeffposnick. But I think the real issue here is bandwidth contention as per [#570]. On a slow mobile data connection Workbox precaching hogs the bandwidth. When precaching completes, and the service worker activates, the connection is free again. This makes it look like media cannot be served until the service worker activates.
I am solving this now with a combination of
preload=nonefor the audio elements so that adding them to the page does not create a tussle for bandwidth, and delaying service worker registration until I am pretty sure that the app is entirely functional. But this is kind of sub-optimal..Related
Tickets:
#570Originally posted by: jeffposnick
It doesn't sound like precaching is going to end up being the best solution for you, unless you do something like show a "Downloading assets..." loading screen when the SW is first installed, preventing users from doing anything else while that's going on, like some native apps might do.
Runtime caching, possibly triggered when you detect that a user is idle, sounds like the most reasonable balance.
Originally posted by: daffinm
Just in case anyone else needs this, I now have a nice solution to runtime audio (or media) caching with Workbox, based on @jeffposnick 's code above.
In your service worker:
The first time a user clicks on an audio player (
preload=none) a full response is cached first and then the request is served from the cache. If they are on a fast network this is instantaneous. If they are on a slow network the audio player spins until the media is cached and then it plays. This is just what I have been looking for.Advanced javascript sytax still baffles me so I had to break it down into bits, and put them together until they worked. (No doubt some brighter person can do it all in one line :-)
Originally posted by: jeffposnick
Thanks for sharing that code!
Originally posted by: daffinm
No problem.
And here is a version of the audio cache function that updates the cache if the external resource has been updated.
Only problem I can see with this is that the cache can update when you are playing a track since we are dealing with partial/range requests. Not such a big deal in my case.
Originally posted by: daffinm
Final comment here. I have finished my solution for media caching using Workbox and documented it in the following, working project. https://github.com/daffinm/audio-cache-test
Thanks everyone. And stay well.