How to Get Videos to Work in Safari With Gatsby and Service Workers
Tags: gatsby, react, javascript, serviceworkers
Published: 2020-02-18 08:59:48

*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)](https://cdn-images-1.medium.com/max/10808/0*hXI8dA6cR4Q34Wr8)_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)_

I was making a website where [hero components](https://en.wikipedia.org/wiki/Hero_image) have videos, and before adding [gatsby-plugin-offline](https://www.gatsbyjs.org/packages/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](https://adactio.com/journal/14452) 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](https://philna.sh/blog/2018/10/23/service-workers-beware-safaris-range-request/). It looks like Safari needs service workers to support _byte-range requests_ to play media. As the [documentation for Safari](https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/CreatingVideoforSafarioniPhone/CreatingVideoforSafarioniPhone.html#//apple_ref/doc/uid/TP40006514-SW6) 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](https://developers.google.com/web/tools/workbox) for generating service workers. Luckily, Workbox already has an [advanced recipe](https://developers.google.com/web/tools/workbox/guides/advanced-recipes?authuser=0#cached-av) 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:

```javascript
<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:

```javascript
// 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](https://developers.google.com/web/tools/workbox/guides/advanced-recipes?authuser=0#cached-av), 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:

```javascript
// 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](https://www.gatsbyjs.org/packages/gatsby-plugin-offline/#available-options) option in `gatsby-plugin-offline` config to append our file to the service worker. Add the `options` object to `gatsby-config.js`:

```javascript
{
  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](https://www.gatsbyjs.org/packages/gatsby-plugin-offline/#overriding-workbox-configuration) object inside the `options` object.

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

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

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

```javascript
{
  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](https://www.gatsbyjs.org/packages/gatsby-plugin-offline/#overriding-workbox-configuration) 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](https://developers.google.com/web/tools/workbox/guides/advanced-recipes?authuser=0#cached-av). If you’re using pure service worker implementation, then look at Jeremy Keith’s article “[Service Workers and Videos in Safari](https://adactio.com/journal/14452)” or the post by Phil Nash, “[Service Workers: Beware Safari’s Range Request](https://philna.sh/blog/2018/10/23/service-workers-beware-safaris-range-request/).” They give a more in-depth explanation about that problem.

Thanks.