i just spent a lot of time figuring this out because i want to use positional audio for a web project and finally got it working, so i figured i'd share here since this got me going in the right direction. basically the sourcecomes from the audioListener context.
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.
Fix iOS AudioContext on Safari not playing any audio. It needs to be "warmed up" from a user interaction, then you can play audio with it as normal throughout the rest of the life cycle of the page.
<p>Load on iOS device without touching the screen and it will not play a noise after 3 seconds.</p>
<p>Refresh the page touch the screen (within 3 seconds of it loading) and the audio and any other audio will play outside of a user generated event.</p>
<pid="status">Waiting 3 seconds to play audio...</p>
<scripttype="text/javascript">
// Fix iOS Audio Context by Blake Kus https://gist.github.com/kus/3f01d60569eeadefe3a1
I just tried this, copying your example exactly, except for putting my own sample audio file, and it doesn't work on iPad an iPhone. No errors, and the server did serve the audio file to the application. Works fine as-is on desktop Safari and Chrome; doesn't play anything on firefox. I refresh and pressed the screen within 3 seconds. I tried adding a button just to have something to press, and that didn't help. Did Apple change something recently? I put it up here so you can see for yourself: http://side.band/audio
VirtualAirwaves commented on 4 Nov 2016 •
I refresh and pressed the screen within 3 seconds. I tried adding a button just to have something to press, and that didn't help.
Did Apple change something recently?
I put it up here so you can see for yourself:
http://side.band/audio