So you made your game or some other app that has multiple sound effects produced by several audio files. It works great on your desktop, laptop, etc. browsers but then you realize that you have some major issues with multiple sounds (or even single) on mobile and tablet platforms like iOS and android browsers. Sad!
You try various methods to fix it but no luck! Don’t worry, there’s a really good way to solve it with – as the title says – Web Audio API.
What's the one thing every developer wants? More screens! Enhance your coding experience with an external monitor to increase screen real estate.
Web Audio API
The HTML5 audio
element has some serious limitations making it pretty much unusable in various environments, especially mobile browsers. We discussed them in one of the previous posts. This is one of the reasons why the Web Audio API has been introduced, to work around those restrictions and do low level processing and synthesizing of single or multiple audio streams.
HTML5 Rocks has a very solid introduction on this very topic along with some helpful FAQs. Do read them to get acquainted with this API.
Implementation
If you’ve read that html5rocks tutorial, then you probably already know what to do next, although it would be nice to share what I tried in my recent game that worked like a charm on iOS 6 browsers.
When the game loads, we create an audio context and load all the audio data.
if (typeof webkitAudioContext !== 'undefined') { var audio_ctx = new webkitAudioContext(); } function loadMusic(url, cb) { var req = new XMLHttpRequest(); req.open('GET', url, true); // XHR2 req.responseType = 'arraybuffer'; req.onload = function() { audio_ctx.decodeAudioData(req.response, cb); }; req.send(); }
loadMusic
is just a utility function that loads the audio via XHR. Let’s look at how we invoke it.
GAME.audio = {}; var audio = { enemy: 'sfx/enemy.mp3', normal_jump: 'sfx/normal_jump.mp3', super_jump: 'sfx/super_jump.mp3', space_belt: 'sfx/space_belt.mp3', game_over: 'sfx/game_over.mp3' } var loadAudioData = function(name, url) { // Async loadMusic(url, function(buffer) { GAME.audio[name] = buffer; }); }; for (var name in audio) { var url = audio[name]; loadAudioData(name, url); }
We just loaded all our audio data and stored them in an object available on a global variable called GAME
that acts as a namespace. So how do we play the sound exactly ? Let’s write a few helper functions that can help us achieve this.
function playSound (buffer, opt, cb) { if (!opt) cb = opt; opt = opt || {}; var src = audio_ctx.createBufferSource(); src.buffer = buffer; gain_node = audio_ctx.createGainNode(); src.connect(gain_node); gain_node.connect(audio_ctx.destination); //console.log(gain_node); if (typeof opt.sound !== 'undefined') gain_node.gain.value = opt.sound; else gain_node.gain.value = 1; // Options if (opt.loop) src.loop = true; src.noteOn(0); cb(src); } function stopSound (src) { src.noteOff(0); }
Do you notice the playSound
method has a callback parameter cb
which is called with the buffer source object ? I’ll show you why I did that in a bit (basically so that we can stop/pause the source later). So how do we go about calling playSound
to play our sound at different actions in the game ?
function playGameSound(name, opt) { opt = opt || {}; var cb = function(src) { GAME.audio_src[name] = src; }; playSound( GAME.audio[name], opt, cb ); } // This his how we call it playGameSound('enemy', {loop: true, sound: 0.5})
Take a close look at this piece of code GAME.audio_src[name] = src;
– we’re doing this so that we can stop the sound, whenever we want to, pretty easily. We store the source buffer object in the audio_src
object by the name
and later when we want to stop it, we just call stopSound
with the same object.
stopSound(GAME.audio_src[name]);
Hopefully, you get the entire idea of using the web audio API to play multiple audio in Webkit browsers now! This really works well in iOS safari/chrome. But remember, it is only supported in iOS 6 (with safari 6), but not iOS 5. Supporting flawless audio playback in iOS 5 is still a pain. We simply decided to give up on it.
Falling Back to HTML5 Audio
At the moment, Web Audio API is only supported in webkit browsers. So we need to gracefully fallback to html5 audio elements for others like Firefox, Opera and Internet Explorer 9+. Doing this is pretty straightforward, just check for Web Audio API support – something like typeof webkitAudioContext !== 'undefined'
should be good enough for now – and when there’s no support start creating audio
elements and append them to the DOM.
for (var name in audio) { var url = audio[name]; // Create an audio node var audio_el = document.createElement('audio'); // Source nodes for mp3 and ogg var src1_el = document.createElement('source'); var src2_el = document.createElement('source'); audio_el.id = name; src1_el.src = url; src1_el.type = 'audio/mp3'; src2_el.src = url.replace('.mp3', '.ogg'); src2_el.type = 'audio/ogg'; // Append OGG first (else firefox cries) audio_el.appendChild(src2_el); // Append MP3 second/next audio_el.appendChild(src1_el); $$('body').appendChild(audio_el); GAME.audio[name] = audio_el; }
When you need to play the sound, check for web audio API support again and when there’s no support, call play()
to start and pause()
to stop the audio files (on GAME.audio[name]
).
That’s pretty much it. Now you’ll be able to start playing audio flawlessly in iOS 6 mobiles and ipads atleast. Let’s hope that the audio support across all platforms gets rock solid soon!
Hi Rishabh,
Is it possible by your method to control the volume of an iOS app (in ipad home screen use), for example to make a JS fader Funktion?
Stefan
Hi Stefan,
You can change the volume of any audio object by changing the
volume
property. It accepts values between 0 and 1. For example,audio.volume = 0.5;
You can learn more about it here.
Thanks for your that, Kushagra,
I read here:
https://developer.apple.com/library/safari/documentation/audiovideo/conceptual/using_html5_audio_video/device-specificconsiderations/device-specificconsiderations.html
that the volume property is not settable with js on iOS7 and in fact my fader function does not work on my ipad. In safari iOS browser the control over the audio tag is completely only for user, in home screen mode it is working as regular, except volume control.