First things first: Web Audio is the real deal, and works impressively well on iOS-running devices. Even the biggest annoying limitation of the ugly HTML5 Audio past is gone – being able to play back only one sound at a time.

Though, one important limitation remains on iOS: Web Audio is effectively muted until user activation.

What does this mean and what do I have to do?

This means that you can use almost any facette of the Web Audio API, but won’t actually hear anything. This is different from HTML5′s audio tag, as that one wouldn’t even load files. But since the preferred way of loading files in Web Audio is via XHR, Apple can”t block it. The official reason for this is to not annoy users with large upfront downloads and noisy websites.

The only way to unmute the Web Audio context is to call noteOn() right after a user interaction. This can be a click or any of the touch events (AFAIK – I only tested click and touchstart).

As soon as the Web Audio context is “unmuted”, it will stay that way for the entire session, and every other action won’t require a user event. Thus, if you are for instance building a HTML5 game, add a “tap to play” button for the iOS version in which an empty sound (or another non-empty one) is played to unlock the phone.

Example 1: Unlocking Web Audio, the simple way

window.addEventListener('touchstart', function() {

	// create new buffer source for playback with an already
	// loaded and decoded empty sound file
	var source = myContext.createBufferSource();
	source.buffer = myDecodedBuffer;

	// connect to output (your speakers)
	source.connect(myContext.destination);

	// play the file
	source.noteOn(0);

}, false);

This solution is decent and easy, but requires an additional HTTP request for the empty sound (not shown above).

Example 2: Unlocking Web Audio, the smart way

window.addEventListener('touchstart', function() {

	// create empty buffer
	var buffer = myContext.createBuffer(1, 1, 22050);
	var source = myContext.createBufferSource();
	source.buffer = buffer;

	// connect to output (your speakers)
	source.connect(myContext.destination);

	// play the file
	source.noteOn(0);

}, false);

Much better! We simply create a very low profile empty sound on the fly and play it back. Does the job, and doesn’t hurt.

How do I know when my context is unlocked?

Sure, the above will unlock the context, but there is no property on the context that you can query to find out about the locked/unlocked state.Turns out there is a way to find (thanks Richard!), but Apple doesn’t mention it, and hid it extremely well (gnah!).

To find out whether the context is still locked, query the playbackState on the source node shortly after calling noteOn(0) (but not directly – use a timeout). If the state is in PLAYING_STATE or FINISHED_STATE, your context is unlocked.

Example 3: Generic unlock function

var isUnlocked = false;
function unlock() {
			
	if(isIOS || this.unlocked)
		return;

	// create empty buffer and play it
	var buffer = myContext.createBuffer(1, 1, 22050);
	var source = myContext.createBufferSource();
	source.buffer = buffer;
	source.connect(myContext.destination);
	source.noteOn(0);

	// by checking the play state after some time, we know if we're really unlocked
	setTimeout(function() {
		if((source.playbackState === source.PLAYING_STATE || source.playbackState === source.FINISHED_STATE)) {
			isUnlocked = true;
		}
	}, 0);

}

The beauty of above's unlock function is that you can call internally within a web audio library whenever the user is trying to play back a file, as it doesn't cost much. In my case, I have a play() function that also loads the file if it isn't loaded yet. Due to its async nature, it would never play the audio even though it came through a touch event. By calling unlock() internally right away and hoping the user comes from a touch event, all is good!


If you want to have future-proof, low latency polyphonic sound, this is the technology you are looking for. Today it is only available in Chrome and Safari, but Mozilla is working on an implementation.

Have a comment or question?