Beautiful, Functional Websites for Creative Professionals and Small Businesses
 

Fixing MP4 autoplay in Safari and iOS for sites using Cloudflare

NOTE. This article was written in 2018. Cloudflare has depreciated its Page Rules dashboard and allows up to 10 rules on its free tier for specific cache configuration. It is easier now to exclude videos from the CF cache

I am a Cloudflare evangelist. Working on media heavy sites means Cloudflare has become a useful tool to speed up load times and offset bandwidth.

The service isn’t without it quirks. Over the last few months I have found media failing to auto-play in Safari and on iOS.

The problem was seemingly intermittent. Previously cached mp4 files auto play while newly cached files do not in Safari.

Common Issues with Auto-playing Video

MP4 files are well supported and should auto play across modern browsers and devices, including iOS and mobile when correctly exported.

Typical issues with mp4 auto-play are:

  • Not removing the audio track
  • Not including muted tag within the video element
  • Not including playsinline within the video element.

A correctly formatted mp4 element should look like this.

<video autoplay muted loop playsinline>
    <source src="video.mp4" type="video/mp4" />
</video>

Understanding the Problem

The reason that Cloudflare seems to cause issues is to do with how the video file headers are presented to Safari by Cloudflares server.

I would typically deploy a site with a Page Rule that caches everything within a sites media directory for as long as possible.

https://www.domain.tld/mediapath/*
Browser Cache TTL: a year
Cache Level: Cache Everything
Edge Cache TTL: a month

This means that all assets within that directory are served from Cloudflares edge-servers including video files.

You can check if a file is served by Cloudflare by inspecting the files header.

curl -svo /dev/null https://www.domain.tld/file.mp4 2>&1 | egrep "Date|cf-|HTTP/"

Content cached on Cloudflares edge servers will return

cf-cache-status:HIT

Ordinarily having static contents served from Cloudflare edge servers rather than your origin server is great. It reduces the load on your server and normally speeds up the load time of those assets for your visitors.

However, video files are different to images or other static content. Video doesn’t want to be loaded in one go.

Video should return a 206 Partial Content status in the file header.

The idea behind the 206 status is a file can quickly return a partial request that indicates the files Content Range followed by size.

This allows your browser to process larger files that require split or interrupted downloads with multiple simultaneous streams which improves latency.

The problem appears to be how Cloudflare receives and passes on files from the origin server that use a chunked or stream protocol rather than transmitting a file in full.

As Cloudflare does not receive the whole file so it cannot tell the full size. It isn’t able to pass a 206 status with a full file size.

'Safari Before'

While Chrome and Firefox happily take the 200 code and continue to download the file, streaming the video and allowing the file to play, Safari simply stops loading the video after the initial chunk and does not auto-play the video.

Solution 1.

We can bypass the problem by stopping Cloudflare caching video files and forcing them to be served by our origin server.

Either —

1) We can rewrite our initial Page Rule to include only JPGs.

https://www.domain.tld/mediapath*/*.jpg*
Browser Cache TTL: a year
Cache Level: Cache Everything
Edge Cache TTL: a month

2) We can leave the initial rule intact and introduce a second rule that explicitly excludes mp4 files. This is preferable as it allows other static files types to be cached.

https://www.domain.tld/mediapath*/*.mp4*
Cache Level: Bypass Cache

By default most video files seem not to be cached by Cloudflare; there is some discussion that now smaller mp4 files do seem to be cached meaning option #2 is the only one that will be guaranteed to force video to be loaded from your origin server

'Safari After'

As mp4 file is being served from your origin server with the correct 206 header it should now autoplay correctly in Safari.

The solution will allow video to autoplay but it is not ideal. The speed and bandwidth benefits of Cloudflare are lost as we end up being forced to serve video assets directly from the origin server.

Solution 2.

The other problem can be on server cache issue where GZIP is enabled an interfere with the streaming of the video file or the server is setup so it ignores chunked playback.

This can be resolved by ensuring chunked playback is set on video files and they are excluded from GZIP. You can do this by adding the following to your htaccess file.

<IfModule mod_headers.c>
    # Ensure Apache handles the Range header
    Header set Accept-Ranges bytes
    # Add headers to support chunked playback
    <FilesMatch "\.(mp4)$">
        Header set Content-Type "video/mp4"
        Header set Cache-Control "no-store, no-cache, must-revalidate, max-age=0"
        Header set Pragma "no-cache"
    </FilesMatch>
</IfModule>

<IfModule mod_rewrite.c>
    RewriteEngine On
    # Enable serving partial content
    RewriteCond %{HTTP:Range} !^$
    RewriteRule .* - [E=HTTP_RANGE:%{HTTP:Range}]
</IfModule>

SetOutputFilter DEFLATE
SetEnvIfNoCase Request_URI .(mp4|ogv|webm)$ no-gzip dont-vary

The problem arises due to the way iOS handles compressed video files.

Here’s why the solution works:

SetOutputFilter DEFLATE: This directive enables compression for the output sent by the Apache web server. By default, Apache compresses the output to reduce the amount of data transmitted over the network, which improves performance.

SetEnvIfNoCase Request_URI .(mp4|ogv|webm)$ no-gzip dont-vary: This line is the key to solving the iOS video playback issue. It sets environment variables based on the requested URL.

SetEnvIfNoCase is a directive that allows setting environment variables based on a regular expression match against the requested URL.

Request_URI .(mp4|ogv|webm)$ is a regular expression that matches any requested URL ending with the file extensions .mp4, .ogv, or .webm, which are common video file formats.

no-gzip and dont-vary are the environment variables set when the regular expression matches.

When a request is made for a video file with the specified extensions, the no-gzip and dont-vary environment variables are set.

The no-gzip environment variable tells Apache not to apply gzip compression to the response for the matched video files. iOS devices have issues playing video files that are compressed with gzip, so disabling gzip compression for these files ensures that iOS devices can play them correctly.

The dont-vary environment variable prevents Apache from sending the Vary: Accept-Encoding header in the response. This header is typically sent when the server applies content encoding (like gzip) based on the Accept-Encoding header sent by the client. However, sending this header can cause issues with iOS devices for video files.

Adding these to the .htaccess file allows Apache to disable gzip compression and omit the Vary: Accept-Encoding header specifically for video files with the extensions .mp4, .ogv, or .webm. This ensures that iOS devices receive the video files in an uncompressed format, which they can play without issues.

A further run down and discussion of the issue can be found at “MP4 Won’t Load in Safari Using CloudFlare