Solve Your Game Audio Problems on iOS and Android with Web Audio API

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!

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download

Author: Rishabh

Rishabh is a full stack web and mobile developer from India. Follow me on Twitter.

4 thoughts on “Solve Your Game Audio Problems on iOS and Android with Web Audio API”

      1. 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.

  1. Hi Rishabh,

    I am building a game in html5 , I have tried to play audio with your code but it still fails on my andorid emulator. Still i am confused. One more thing i need to know. GAME variable it says its undefined.

    Thanks
    Ravish

Leave a Reply to Stefan Seifert Cancel reply

Your email address will not be published. Required fields are marked *