Simplifying audio in the browser

The last few years have seen tremendous gains in the capabilities of browsers, as the latest HTML5 standards continue to get implemented. We can now render advanced graphics on the canvas, communicate in real-time with WebSockets, access the local filesystem, create offline apps and more. However, the one area that has lagged behind is audio.

The HTML5 Audio element is great for a small set of uses (such as playing music), but doesn’t work so well when you need low-latency, precision playback.

Over the last year, a new audio standard has been developed for the browser, which gives developers direct access to the audio data. Web Audio API allows for high precision and high performing audio playback, as well as many advanced features that just aren’t possible with the HTML5 Audio element. However, support is still limited, and the API is considerably more complex than HTML5 Audio.

Introducing howler.js

The most obvious use-case for high-performance audio is games, but most developers have had to settle for HTML5 Audio with a Flash fallback to get browser compatibility. My company, GoldFire Studios, exclusively develops games for the open web, and we set out to find an audio library that offered the kind of audio support a game needs, without relying on antiquated technologies. Unfortunately, there were none to be found, so we wrote our own and open-sourced it: howler.js.

Howler.js defaults to Web Audio API and uses HTML5 Audio as the fallback. The library greatly simplifies the API and handles all of the tricky bits automatically. This is a simple example to create an audio sprite (like a CSS sprite, but with an audio file) and play one of the sounds:

var sound = new Howl({
  urls: ['sounds.mp3', 'sounds.ogg'],
  sprite: {
    blast: [0, 2000],
    laser: [3000, 700],
    winner: [5000, 9000]
  }
});

// shoot the laser!
sound.play('laser');

Using feature detection

At the most basic level, this works through feature detection. The following snippet detects whether or not Web Audio API is available and creates the audio context if it is. Current support for Web Audio API includes Chrome 10+, Safari 6+, and iOS 6+. It is also in the pipeline for Firefox, Opera and most other mobile browsers.

var ctx = null,
  usingWebAudio = true;
if (typeof AudioContext !== 'undefined') {
  ctx = new AudioContext();
} else if (typeof webkitAudioContext !== 'undefined') {
  ctx = new webkitAudioContext();
} else {
  usingWebAudio = false;
}

Audio support for different codecs varies across browsers as well, so we detect which format is best to use from your provided array of sources with the canPlayType method:

var audioTest = new Audio();
var codecs = {
  mp3: !!audioTest.canPlayType('audio/mpeg;').replace(/^no$/,''),
  ogg: !!audioTest.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,''),
  wav: !!audioTest.canPlayType('audio/wav; codecs="1"').replace(/^no$/,''),
  m4a: !!(audioTest.canPlayType('audio/x-m4a;') || audioTest.canPlayType('audio/aac;')).replace(/^no$/,''),
  webm: !!audioTest.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,'')
};

Making it easy

These two key components of howler.js allows the library to automatically select the best method of playback and source file to load and play. From there, the library abstracts away the two different APIs and turns this (a simplified Web Audio API example without all of the extra fallback support and extra features):

// create gain node
var gainNode, bufferSource;
gainNode = ctx.createGain();
gainNode.gain.value = volume;
loadBuffer('sound.wav');

var loadBuffer = function(url) {
  // load the buffer from the URL
  var xhr = new XMLHttpRequest();
  xhr.open('GET', url, true);
  xhr.responseType = 'arraybuffer';
  xhr.onload = function() {
    // decode the buffer into an audio source
    ctx.decodeAudioData(xhr.response, function(buffer) {
      if (buffer) {
        bufferSource = ctx.createBufferSource();
        bufferSource.buffer = buffer;
        bufferSource.connect(gainNode);
        bufferSource.start(0);
      }
    });
  };
  xhr.send();
};

(Note: some old deprecated names were createGainNode and noteOn, if you see them in other examples on the web)

Into this:

var sound = new Howl({
  urls: ['sound.wav'],
  autoplay: true
});

It is important to note that neither Web Audio API nor HTML5 Audio are the perfect solution for everything. As with anything, it is important to select the right tool for the right job. For example, you wouldn’t want to load a large background music file using Web Audio API, as you would have to wait for the entire data source to load before playing. HTML5 Audio is able to play very quickly after the download begins, which is why howler.js also implements an override feature that allows you to mix-and-match the two APIs within your app.

Audio in the browser is ready

I often hear that audio in the browser is broken and won’t be useable for anything more than basic audio streaming for quite some time. This couldn’t be further from the truth. The tools are already in today’s modern browsers. High quality audio support is here today, and Web Audio API and HTML5 combine to offer truly plugin-free, cross-browser audio support. Browser audio is no longer a second-class citizen, so let’s all stop treating it like one and keep making apps for the open web.

About James Simpson

James is the CEO and founder of GoldFire Studios, who use HTML5 to develop real-time, multiplayer games for the web. Over the last decade-plus, his games and game-related products have reached millions around the world since starting to develop at age 13. He frequently tweets about Javascript, HTML5, game development and startups on Twitter.

More articles by James Simpson…

About Robert Nyman [Editor emeritus]

Technical Evangelist & Editor of Mozilla Hacks. Gives talks & blogs about HTML5, JavaScript & the Open Web. Robert is a strong believer in HTML5 and the Open Web and has been working since 1999 with Front End development for the web - in Sweden and in New York City. He regularly also blogs at http://robertnyman.com and loves to travel and meet people.

More articles by Robert Nyman [Editor emeritus]…


10 comments

  1. Alex

    How about audioTest.canPlayType(‘audio/mpeg;’) !== ‘no’?

    February 21st, 2013 at 06:44

  2. Paul Morris

    This looks wonderful!

    Just to clarify… If I create an audio sprite with howler.js will it work in a browser that does not yet support the Web Audio API? In other words, does the sprite functionality require the Web Audio API or will it also work with HTML5 Audio?

    February 21st, 2013 at 09:17

    1. James Simpson

      Thanks! It works the same whether Web Audio API or HTML5 Audio is being used. There is a demo of the sprite functionality at howlerjs.com.

      February 21st, 2013 at 09:25

      1. Paul Morris

        Nice! I see from the demo at howlerjs.com that you have a second of silence between the sounds in the audio sprite. I assume you’ve found that to be a good practice?

        Thanks for howler.js! Looks like my days wrestling with getting audio to work across browsers may soon be over. o/

        February 21st, 2013 at 13:49

        1. James Simpson

          It isn’t necessary with Web Audio to have the full second, but it helps to keep sounds from overlapping with the lower precision of HTML5 Audio. Also, it is just easier to define a sprite that way.

          February 21st, 2013 at 13:50

  3. Tom Pouce

    From the article : “Current support for Web Audio API includes Chrome 10+, Safari 6+, and iOS 6+. It is also in the pipeline for Firefox, Opera and most other mobile browsers.”
    … and then “Browser audio is no longer a second-class citizen”.

    As long as web audio api is not included in standard versions of Firefox, it’s clear that browser audio is still a second-class citizen imho.

    February 21st, 2013 at 10:23

    1. Robert Nyman [Editor]

      We wrote about Web Audio API yesterday on this blog and how we’re making progress. Yes, it’s early days, but it’s definitely happening.

      February 21st, 2013 at 13:20

      1. James Cready

        Please be sure to fully support MediaElementAudioSource before claiming support for the Web Audio API (like Safari did).

        February 21st, 2013 at 14:37

        1. Robert Nyman [Editor]

          Good point! Please feel free to contribute any information or input to the implementation bug for WebAudio API.

          February 21st, 2013 at 14:45

  4. bekam

    var ctx = null,
    usingWebAudio = true;
    if (typeof AudioContext !== ‘undefined’) {
    ctx = new AudioContext();
    } else if (typeof webkitAudioContext !== ‘undefined’) {
    ctx = new webkitAudioContext();
    } else {
    usingWebAudio = false;
    }

    it’s useful..i have some idea to solve my problem

    February 21st, 2013 at 14:32

Comments are closed for this article.