DEV Community

Gert Glükmann
Gert Glükmann

Posted on • Originally published at Medium

How to Get Videos to Work in Safari With Gatsby and Service Workers

Adding service workers to a site breaks videos in Safari and iOS devices

Photo by [Thomas Russell](https://unsplash.com/@truss?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral)Photo by Thomas Russell on Unsplash

I was making a website where hero components have videos, and before adding gatsby-plugin-offline, everything was working fine. I did some browser testing and all browsers seemed fined, videos were fine. Then I decided that it’s time to add the service worker and to make the website installable and work offline. I added the plug-in and tested everything with Chrome and Android. Everything as it should be! But then I opened it with my iPad and saw that videos are not playing at all, not even loading.

That seemed odd as videos were implemented with the <video> HTML tag and they were standard MP4 files. Luckily, I had only added the service worker, so I started suspecting it had something to do with that.

I came across Jeremy Keiths’ article where he describes how he had the same problem. He refers to a solution and a more in-depth explanation in a post by Phil Nash. It looks like Safari needs service workers to support byte-range requests to play media. As the documentation for Safari says:

“HTTP servers hosting media files for iOS must support byte-range requests, which iOS uses to perform random access in media playback.”

They both went with different approaches to a solution. Phil fixed the service worker to cache video files, but Jeremy opted for always loading videos from the network and never caching them.

I’m going to show you how to implement both solutions with gatsby-plugin-offline.

Get Videos From the Cache

gatsby-plugin-offline uses Workbox for generating service workers. Luckily, Workbox already has an advanced recipe for how to serve videos and audio. This is exactly how we’ll implement it. We’ll just have to append it to the service worker generated by Gatsby.

First, we need to add attribute crossOrigin="anonymous" to our HTML <video> tag:

<video src="movie.mp4" crossOrigin="anonymous"></video>

Second, we create a file that will be appended to the generated service worker file. Let’s name it sw-range-request-handler.js. We’ll put that into the root folder of our project.

Content of this file will be:

// Add Range Request support to fetching videos from cache
workbox.routing.registerRoute(
  /.*\.mp4/,
  new workbox.strategies.CacheFirst({
    plugins: [
      new workbox.cacheableResponse.Plugin({ statuses: [200] }),
      new workbox.rangeRequests.Plugin(),
    ],
  }),
  'GET',
);

We match all requested MP4 files and use CacheFirst strategy to search for video files from the cache. If there isn’t a cache match, then the file will be served from the network.

If you look at the example from Workbox advanced recipes, you’ll see that the usage of plugin functions are a bit different. That’s because as of now, gatsby-plugin-offline uses Workbox v.4, but the example is for v.5.

You’ll see that we didn’t import any functions from Workbox, either. That’s because we only append our file content to the generated service worker file, where all those plugins are already added to the workbox object.

When gatsby-plugin-offline updates to v.5 of Workbox, we’ll need to update how we use the plugins:

// Add Range Request support to fetching videos from cache
workbox.routing.registerRoute(
  /.*\.mp4/,
  new workbox.strategies.CacheFirst({
    plugins: [
      new workbox.cacheableResponse.CacheableResponsePlugin({
        statuses: [200],
      }),
      new workbox.rangeRequests.RangeRequestsPlugin(),
    ],
  }),
  'GET',
);

Now we need to use the appendScript option in gatsby-plugin-offline config to append our file to the service worker. Add the options object to gatsby-config.js:

{
  resolve: `gatsby-plugin-offline`,
  options: {
    appendScript: require.resolve(`./sw-range-request-handler.js`),
  },
},

Now running gatsby build command and looking into public/sw.js file, you’ll see that at the very end is our code. Showing videos in Safari and iOS devices will work again after the service worker is updated.

But this caches all our videos to Cache API storage, and when you have a lot them or they’re big files, then it’s probably not a very good idea to take that much space from the user’s device. And are the videos really that important when the user is offline?

Get Videos From the Network

To never cache videos to storage and only get them from the network, you need to overwrite the Workbox config. That can be done by overwriting the workboxConfig object inside the options object.

First, we should still add the crossOrigin="anonymous" attribute to our HTML <video> tag:

<video src="movie.mp4" crossOrigin="anonymous"></video>

Second, we modify the gatsby-config.js file to overwrite the existing workboxConfig:

{
  resolve: `gatsby-plugin-offline`,
  options: {
    workboxConfig: {
      runtimeCaching: [
        {
          urlPattern: /.*\.mp4/,
          handler: `NetworkOnly`,
        },
      ],
    },
  },
},

We use NetworkOnly strategy to get our MP4 file. It will never be searched from the cache.

But be aware that our code now overwrites the default caching for gatsby-plugin-offline. It would be better to add it to the existing list of caching options so that everything else will still be cached, and with the right strategies.

Conclusion

At first, it can be very confusing to understand why videos are not playing when the page has service workers, but these two solutions should fix that. This problem doesn’t occur only when using Gatsby, and there are solutions for those other situations, too.

If you’re using Workbox, then look at their advanced recipes. If you’re using pure service worker implementation, then look at Jeremy Keith’s article “Service Workers and Videos in Safari” or the post by Phil Nash, “Service Workers: Beware Safari’s Range Request.” They give a more in-depth explanation about that problem.

Thanks.

Top comments (0)