Published on June 7, 2021 (about 3 years ago)

Mobile Safari HLS bug with short form looping videos

Dylan Jhaveri
By Dylan Jhaveri4 min readEngineering

This is a fun one. There appears to be a bug in Mobile Safari’s default HLS implementation when it comes to looping short-form videos.

Here’s an example of some code that will play an HLS video. Note that because we are setting the src attribute directly and not running any javascript this code will only work in browsers that support HLS natively (mobile and desktop Safari do this).

<video playsinline src="" muted controls loop ></video>

We’re using the loop attribute so that the video loops (when it reaches the end, it sets back to the beginning and plays again).

Should be easy enough, right? Not quite. We’re seeing a bug at the start of the 3rd loop, the player stutters like this:

This was first reported by Justin Greer on our team when he noticed it on a looping video on the Mux blog, and that’s where this journey began.

The first culprit seemed like something weird going on with the react component, so we started peeling back those layers. That didn’t help so then we went to plain ‘ol html <video> with no JavaScript involved. Still no help. Now we were really worried that it was something specific with Mux HLS videos. Is there something quirky about Mux’s HLS that is causing this behavior? Turns out it’s not, it happens with HLS sources of all flavors. Here’s a code sandbox that demonstrates the issue:

As of now (April 2021), if you’re on an iPhone 12+ with iOS 14.3+ you should experience the bug when playing any of the videos. Hit play, let it loop. At the beginning of the 3rd loop you’ll see a visual stutter.

LinkWhat to do now

This seems to only happen under these conditions:

  • Short videos
  • Looping
  • HLS
  • iOS Safari*

*Note that there are other browsers available on iOS like Chrome and Firefox, but due to Apple’s App Store rules every browser in the App Store is simply a UI wrapper around the Safari rendering engine and JavaScript engine. If a bug exists on iOS Safari, it also exists in iOS Chrome, iOS Firefox, etc.

LinkPossible Option 1: Skip the loop attribute?

Alright, so the problem seems to be with the `loop` attribute specifically. What if we do the looping ourselves by listening for the video end and calling play() programmatically with a 10ms timeout?

const video = document.getElementById('video'); video.addEventListener('ended', () => { // wait 10ms then play the video again manually setTimeout(() => { => { console.log('Video played'); }).catch((error) => { console.log('Error calling play() on video', error); }); }, 10); });

Not so easy, unfortunately, that didn’t fix the bug. Nice try though.

LinkPossible Option 2: Don’t use iOS Safari’s native HLS implementation

Hls.js works great, can we use that instead of relying on Safari’s native HLS support? Unfortunately, we cannot. Hls.js relies on Media Source Extensions, which iOS Safari does not support:

Hls.isSupported() // false in iOS Safari

LinkPossible Option 3: Fallback to mp4

As long as your video is short, the only reasonable solution if you want to get looping working in iOS Safari is to use an mp4 version of the video. You will forgo the advantages of HLS (adaptive bitrate streaming, being able to deliver a quality level that suits your viewer) but at least Mux mp4s are streamable, so the player is able to start playback without downloading the entire file.

MP4 support can be added to Mux Assets at no additional cost. You can then use the low.mp4 or medium.mp4 or high.mp4 file and drop that into the <video> element. The tradeoff you’re making here is that if the user is on a low bandwidth connection they will see a slower startup time compared to if they played one of the lower renditions from the HLS version.

For short videos that are less than a couple of minutes, the fallback to mp4 doesn’t hurt as bad, so this seems like the best option. The tradeoff is that users on low bandwidth connections will have a slower time starting playback, so make sure to use a poster= image on the video element so that the user doesn’t see an empty player while the beginning of the video is downloading.

LinkHave any other ideas?

If you have run into this and found another workaround, we’d love to hear from you. Or if you have dug deeper to understand more of what’s going on please reach out. Right now we’re tracking this in Safari’s bug tracker:, stay tuned for updates as we learn more.

Written By

Dylan Jhaveri

Software Engineer and cold water surfer. Previously startup co-founder. Trying to find the best cheeseburger in San Francisco.

Leave your wallet where it is

No credit card required to get started.