Search code examples
progressive-web-appsjplayerworkbox

Cannot scrub/scroll through jPlayer audio when mp3 is cached by Workbox


I have converted a single page HTML5 Cordova app into a PWA. The app uses jPlayer extensively to play mp3 files. I am using a variant of the circular jPlayer here: http://jplayer.org/latest/demo-05/. The circle player has a circular progress bar that can also be used to scrub backwards and forwards through the track.

Everything works fine in PWA mode until I cache the mp3 with Workbox (version 4.3.1). Then scrubbing fails. I can grab the scrub bar and move it but when I release it the track restarts from the beginning. This happens if I use precaching for the mp3 or a dedicated audio cache for all mp3 files. Turn off caching, update the service worker and refresh -- and I can scrub. Turn on caching and refresh and scrubbing fails.

I would really like scrubbing to work with cached audio files so that the app can work offline.

This seems similar in nature to Make mp3 seekable PHP.


Solution

  • I'm excerpting this from the Workbox documentation's recipe for serving cached audio and video.

    There are a few wrinkles in how some browsers request media assets (e.g., the src of a <video> or <audio> element) that can lead to incorrect serving behavior unless you take specific steps when configuring Workbox.

    Full details are available in this GitHub issue discussion; a summary of the important points is:

    • Workbox must be told to respect Range request headers by adding in the workbox-range-requests plugin to the strategy used as the handler.

    • The audio or video element needs to opt-in to CORS mode using the crossOrigin attribute, e.g. via .

    • 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.

    Putting this all together, here's an example of one approach to serving cached media content using Workbox:

    <!-- In your page: -->
    <!-- You currently need to set crossOrigin even for same-origin URLs! -->
    <video src="movie.mp4" crossOrigin="anonymous"></video>
    
    // In your service worker:
    // It's up to you to either precache or explicitly call cache.add('movie.mp4')
    // to populate the cache.
    //
    // This route will go against the network if there isn't a cache match,
    // but it won't populate the cache at runtime.
    // If there is a cache match, then it will properly serve partial responses.
    workbox.routing.registerRoute(
      /.*\.mp4/,
      new workbox.strategies.CacheFirst({
        cacheName: 'your-cache-name-here',
        plugins: [
          new workbox.cacheableResponse.Plugin({statuses: [200]}),
          new workbox.rangeRequests.Plugin(),
        ],
      }),
    );
    

    If you plan on precaching the media files, then you need to take an extra step to explicitly route things so that they're read from the precache, since the standard precache response handler won't use the range request plugins:

    workbox.routing.registerRoute(
      /.*\.mp4/,
      new workbox.strategies.CacheOnly({
        cacheName: workbox.core.cacheNames.precache,
        plugins: [
          new workbox.rangeRequests.Plugin(),
        ],
        // This is needed since precached resources may
        // have a ?_WB_REVISION=... URL param.
        matchOptions: {
          ignoreSearch: true,
        }
      }),
    );
    
    // List this *after* the preceding runtime caching route.
    workbox.precaching.precacheAndRoute([...]);