It’s almost 2017, and HTML5 audio is still broken on iOS

Summary: Back in 2012, I wrote that HTML5 audio is broken on iOS. Now as we enter 2017, it turns out things are still horribly broken. It is currently impossible to build a music player web app that works on iOS.

Last week I published a big update to Chavah Messianic Jewish radio. It’s an HTML5 music player in the vein of Pandora (users can thumb up songs, influencing what gets played) for the Messianic Jewish faith.

chavahv3-small

And thanks to the magic of the web and HTML5 audio, it works flawlessly on PCs, Macs, and Linux. Sweet!

What about mobile? Well, yeah. Umm. Apple’s mobile implementation of HTML5 audio is still busted.

After releasing the new version last week, my users reported things still busted on iOS devices:

image

Well, I did have a look. And what I found is that iOS still cripples web apps that use HTML5 audio.

I was hoping that the new (July 2016) relaxed web media restrictions in iOS 10+ would un-cripple HTML5 audio in iOS.

I was disappointed to find it’s still broken. It’s currently impossible to write a working audio player using modern web technologies.

Here are the working things:

  1. You can play MP3 audio* ** *** ****

* Only after user interaction
** Only while the page is active and in the fore
*** Only while the phone screen is on
**** There’s no way to keep the screen from turning off, so your audio will stop after the first song.

So yes, you can play audio (10 asterisks here). Or more precisely, you can play a single audio track, but not much more.

Here are the busted things:

  1. Minor: Audio doesn’t play until user interaction.
  2. Major: Audio can’t play the next track when the page is in the background. (JavaScript execution is suspended; no way to set audio.src for the next song.)
  3. Major, blocking: Audio can’t play the next track when the phone screen is off.
    (JavaScript execution is suspended when the phone screen is off; no way to set audio.src for the next song)

Details on each of these below:

Audio doesn’t play until user interaction

This is the most minor of the busted things. But it’s an artificial restriction by Apple, likely for user experience and battery life reasons.

None of the other operating systems, mobile or desktop, do this. So we have to have special handling for iOS to require the user to interact with the UI before playing.

As for battery life, 3d content, muted <video>, gifs, ads and more don’t require interaction before start. Why hurt real web apps and real users by singling out audio apps?

Audio can’t play next track in background

Common user scenario: they go to my web app, hit play, and the music starts streaming in. Now they switch over to Twitter. When the current song ends, the music just stops. No new track is played.

Why?

Upon investigation, HTMLAudioElement ended event is never fired. Why? Because Apple suspends all JavaScript execution in the name of performance.

This sounds good in theory: you’re not using a web site, so Safari will just stop executing any JavaScript since you’re not using it anyways.

But in practice, this kills real web apps on iOS. Music playing apps need to know when the song is finished in order to play the next song. So, we call

audio.addEventListener(“ended”, playNextSong)

But the playNextSong function never fires; JavaScript has been suspended, and users are disappointed.

Audio can’t play the next track when the phone screen is off.

The most common scenario for my app: user goes to my web app, the app starts playing music. Then, the user leaves her phone alone; perhaps she’s in the car driving while listening to the music.

After a short period of time, the phone screen turns off. The music keeps playing…until the current song ends. Once again, iOS has suspended JavaScript execution, resulting in the audio.ended event never firing, meaning I can’t set audio.src to the next song.

Upon further investigation, I tried to find out if it’s possible to prevent the screen from sleeping.

For a native iOS app, you can set application.idleTimerDisabled = YES. Super simple.

But for a web app? Nope, there’s no supported way to tell iOS, “Hey, keep the screen on, the user is in the middle of listening to music so don’t disrupt them.”

Some dated information on StackOverflow suggests looping a silent audio or video may prevent sleep. I built a little test app to try this out, and it appears to no longer work on iOS 10.

Additional answers on StackOverflow suggest pseudo page navigation every few seconds to prevent phone sleep/lock. I tried this as well, and it likewise appears to no longer work on iOS 10. The phone still sleeps even with page navigation going on.

Bottom line: there appears to be no way for a web app to prevent an iPhone from sleeping/locking.

And since sleeping/locking will cause a suspension of JavaScript execution, there’s no way to play the next song. End result is your audio web app stops playing audio, making it pretty useless.

Apple Webkit team action items

Here’s what we web developers need to make audio web apps a first class citizen on iOS:

  • Don’t suspend JavaScript execution for web apps playing audio. Don’t suspend JS execution if we’re in the background. Don’t suspend JS if the phone is locked. The user is playing our audio for a reason, don’t disrupt the user.

It’s not enough to let the audio finish and then suspend JS; this breaks the user experience and causes audio web apps to stop working.

This would solve the other problems.

Alternately, don’t sleep/lock the phone if an active web app is playing audio. While this alternate solution doesn’t fix all the problems, it would address the most blocking use case for using audio web apps unattended.

  • [this comment posted from Twitter by members of Apple’s Webkit team]

    Nothing incorrect [in this post] per se. However, I think your some of your proposed solutions aren’t very realistic.

    Audio should never, ever begin without a user gesture, esp. b/c media ignores the users’s mute switch.

    Audio playback should never, ever keep the screen from locking. It’s bad for performance and bad for security.

    However, we can definitely look into allowing JavaScript execution to continue past the end of audio playback.

    But this is at the heart of a shortcoming in the HTML5 specification; there is no concept of a “playlist” for <audio>.

    Why should it require JS to have a queue of audio tracks? A DOM level playlist concept would sure be nice.

    • Thanks for the reply, Jer.

      “we can definitely look into allowing JavaScript execution to continue past the end of audio playback.”

      That would be so awesome and would open up new app possibilities on iOS. Please, please, please do this.

      “Audio playback should never, ever keep the screen from locking. It’s bad for performance and bad for security.”

      So things like GPS and YouTube can keep the screen alive. Why is audio different? If you fix the JavaScript suspension issue, then this matters less. Still, I’d like to understand the reasoning.

    • metzomagic

      Jer Noble said:

      But this is at the heart of a shortcoming in the HTML5 specification; there is no concept of a “playlist” for .

      Why should it require JS to have a queue of audio tracks? A DOM level playlist concept would sure be nice.

      But it’s real easy to build a playlist using an unnumbered list (ul) with each list item representing a track, and using HTML5’s new ‘data’ attribute to hold the info for that track, like: data-src=”Track1.mp3″ data-artist=”My band”, etc. Why lumber the HTML5 spec with excess baggage when it’s so easy to write an app to do it?

      Of all the mobile browsers I have tested, only Safari on iOS will not automatically start playing the next track in my jukebox app (well, OK, for Chrome on Android you do have to set the Disable gesture requirement for media playback flag in chrome://flags to allow this).

      So yeah, I get that playing continuous audio is tough on the phone’s battery usage, but if the user is making a conscious decision to do this, why prevent it?