Skip to content

Instantly share code, notes, and snippets.

@jeffposnick
Created October 17, 2018 20:48
Show Gist options
  • Save jeffposnick/814b13353f16b8171999febfeb8b7938 to your computer and use it in GitHub Desktop.
Save jeffposnick/814b13353f16b8171999febfeb8b7938 to your computer and use it in GitHub Desktop.
Example of precaching video content, and using it with a Workbox-powered SW.
<!doctype html>
<html>
<head>
<title>Video Test</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<p>
Here's a small local video. (Copy from http://techslides.com/demos/sample-videos/small.mp4)
</p>
<video controls
src="small.mp4">
</video>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('sw.js');
});
}
</script>
</body>
</html>
importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.0.0-alpha.0/workbox-sw.js');
workbox.setConfig({debug: true});
workbox.clientsClaim();
// Your *full* video file needs to be added to the cache at some point.
// Doing it during `install` is cleanest, but you pay the upfront cost of caching.
// You can't rely on runtime caching to implicitly cache your entire video,
// because the first incoming requests will include a Range: header,
// and the subsequent network response will only be for a small portion of the total video.
// You want the entire video, not only that small portion to be cached!
self.addEventListener('install', event => {
event.waitUntil(
caches.open('video')
.then(c => c.add('/small.mp4'))
);
});
workbox.routing.registerRoute(
// This is only going to work reliably for same-origin video requests.
// (Or maybe CORS-enabled ones, but https://bugs.webkit.org/show_bug.cgi?id=184447
// suggests that could be a problem in Safari.)
// We need access to the video data in the response body, so opaque responses are a no-no.
new RegExp('\\.mp4$'),
workbox.strategies.cacheOnly({
// Use the same cache name as we used when manually populating the cache.
cacheName: 'video',
plugins: [
// If we have the *entire* video in the cache,
// then this plugin will properly honor the Range: header on incoming requests,
// and slice up the response body, giving back only what's asked for.
new workbox.rangeRequests.Plugin()
]
})
);
@bseib
Copy link

bseib commented Oct 11, 2019

I wanted to add to this for those who find this gist on caching and serving video. Depending on your server, you might need the ignoreVary option. I was using Firebase to serve my app, and bumped into this exact issue as described on stackoverflow: https://stackoverflow.com/a/58276728/516910

I had loaded up my cache with videos correctly, but when they were fetched by the app's <video>, I was getting the error:

  resulted in a network error response: an object that was not a Response was passed to respondWith()

I found that the cache key was not matching because of the Vary header, which was the same as the SO issue above. (And yes, that was your answer on SO Jeff. 👍 )

Here's where the ignoreVary option goes:

  workbox.strategies.cacheOnly({
    // Use the same cache name as we used when manually populating the cache.
    cacheName: 'video',
    plugins: [
      // If we have the *entire* video in the cache,
      // then this plugin will properly honor the Range: header on incoming requests,
      // and slice up the response body, giving back only what's asked for.
      new workbox.rangeRequests.Plugin()
    ],
    matchOptions: {
      ignoreVary: true
    }
  })

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment