Blog Home

Multiple Channels for HTML5 Audio

Now that I've calmed down a bit after my The State of HTML5 Audio article, let's see if can actually write something productive.

Here's the deal: for a typical game you may have a sound effect that you want to play very often. In Biolab Disaster the sound of your plasma gun is such an effect. This particular sound has a length of 0.6 seconds, but you sure can mash the shoot button quicker than 1.66 times per second – so to not interrupt the plasma sound each time you press the button, but instead play a new sound we need to have multiple sound “channels”.

There are a few different ways to create <audio> elements in JavaScript:

var a1 = new Audio();
var a2 = document.createElement( 'audio' );
var a3 = a1.cloneNode( true );

(You could also use .innerHTML or document.write() if you're of the adventurous type.)

An important thing to know about the <audio> element is that it doesn't load the specified source file automatically. Well, at least not in some browsers. But that's ok – with the preload property or the load() method we can tell the Browser to always preload our sound file (note that Mobile Safari ignores both).

So, the most logical way to create our sound channels I can think of is this (let's say we want to have four channels):

var channels = [];
for( var i = 0; i < 4; i++ ) {
    var a = new Audio( 'sound.ogg' );
    a.preload = 'auto';
    channels.push( a );

But hold on! Doesn't this result in the Browser requesting that sound file 4 times? Well, it really shouldn't – if I create 100 <img> elements with the same source file, that source file is only loaded once. The same should apply to all other resources.

But guess what? <audio> is different! Firefox 4, Chrome 11, Safari 5 and IE9 load that sound multiple times from the server. Firefox 3.6 doesn't load the sound at all, because it ignores the preload property. At least Opera 11 gets it right and only loads that file once. Thanks for keeping me sane Opera.

Second try. Let's create only one <audio> element, use the load() method to preload it and clone it 3 times:

var a = new Audio( 'sound.ogg' );

// Add the audio element to the DOM, because otherwise a certain 
// retarded Browser from Redmond will refuse to properly clone it
document.body.appendChild( a );

var channels = [a];
for( var i = 0; i < 3; i++ ) {
    channels.push( a.cloneNode(true) );

Result? Firefox 3.6 and Opera 11 load the sound file only once. Firefox 4, Chrome 11, Safari 5 and IE9 still request it multiple times.

Ok, what if we wait till the sound file has been loaded completely and only then clone it? Pfft, fine:

a.addEventListener( 'canplaythrough', function(ev){
    for( var i = 0; i < 3; i++ ) {
        channels.push( a.cloneNode(true) );
}, false );

(Boring test page)

With that, Firefox 3.6, Firefox 4, Opera 11, Chrome 11 and IE9 all load the sound file just once. Note however, that the canplaythrough event is not exactly the right place to clone the audio element. The audio file might not be loaded completely, but just enough to play the whole thing without interrupting, provided that the download rate doesn't change. It's a nice event to have for long sound files (music), but a bit pointless for short samples.

Why didn't I use the onload event? Well, the HTML5 Media Elements define a number of events, but onload is not one of them. There's also no “completed” event ore something like that.

Why didn't I use the progress event and check for e.loaded and Only Firefox supports those two properties. For other browsers there's the buffered object that specifies a number of “TimeRanges”.

In a quick test though, Chrome only fired the progress event once for my short sound file. And at the time it did that, the buffered object was still empty. If I wanted to check if the file has been loaded completely, I would need to set up an interval and poll for the loaded ranges. Pretty.

At this point I stopped for a moment and remembered why I'm doing this in the first place: Browsers, could you please properly cache audio files? Not you Opera; today I like you.

But there's more: Did you notice that Safari was missing in the list of browsers that only loaded the sound file once for the last example? Did you also notice how I always wrote the file was loaded “multiple times” instead of just saying “4 times” as in "once for each element"?

Here's the reason: for that simple HTML page with one external resource, Safari does not send 2 HTTP requests as one might expect, but 18:

"GET /html5audio/ HTTP/1.1" 200 506
"GET /html5audio/beep.mp3 HTTP/1.1" 206 2
"GET /html5audio/beep.mp3 HTTP/1.1" 206 16744
"GET /html5audio/beep.mp3 HTTP/1.1" 206 2
"GET /html5audio/beep.mp3 HTTP/1.1" 206 16744
"GET /html5audio/beep.mp3 HTTP/1.1" 206 2
"GET /html5audio/beep.mp3 HTTP/1.1" 206 2
"GET /html5audio/beep.mp3 HTTP/1.1" 304 -
"GET /html5audio/beep.mp3 HTTP/1.1" 206 16744
"GET /html5audio/beep.mp3 HTTP/1.1" 206 16744
"GET /html5audio/beep.mp3 HTTP/1.1" 206 2
"GET /html5audio/beep.mp3 HTTP/1.1" 304 -
"GET /html5audio/beep.mp3 HTTP/1.1" 304 -
"GET /html5audio/beep.mp3 HTTP/1.1" 206 16744
"GET /html5audio/beep.mp3 HTTP/1.1" 206 7068
"GET /html5audio/beep.mp3 HTTP/1.1" 206 15620
"GET /html5audio/beep.mp3 HTTP/1.1" 206 5620
"GET /html5audio/beep.mp3 HTTP/1.1" 206 14172

The last number on each line indicates the number of bytes sent. The beep.mp3 file is 17kb in size, but Safari only requested certain byte ranges of it. Now that's clever!

Friday, March 11th 2011
— Dominic Szablewski, @phoboslab


#1Dustin Montgomery – Friday, March 11th 2011, 05:20

Your pain is not in vain! Thanks for posting this. It would benice if we get some browsers to shape up.

#2Jonas – Friday, March 11th 2011, 09:41

My solution to this was to use a html5 cache manifest plus pooling. But it's still a mess. I guess I should write down my experience too - just to make a little bit more noise about the mess that is html5 audio right now.

#3 – Berenger – Friday, March 11th 2011, 16:11

I'm not a programmer, but I wonder if you could use libpd : it's a port of Pure Data the famous realtime audio software. It should enable you to developp the audio with pd and just interact with messages back and forth to the html code.
This way you deal with the polyphony inside pd which is pretty easy

#5Paul – Wednesday, March 23rd 2011, 22:09

Hi, I fixed all the sound issues* I was having with Impactjs and Chrome by modifying some of the functions . It's a bit of a hack as I have to sniff for Chrome and add extra line of code here and there. Also I noticed (for me and my older pc's) that the sound playing breaks up more when you have a high cycle interval (you default to 60 fps) but lower it it 24fps and that helps too.

I understand your pain with html5 audio.... (and iOS 'fixes')

* Sound only plays once then never again.

#6Andrew Chen – Wednesday, April 6th 2011, 21:31

Dominic, how do I get ahold of you? I couldn't find an email. Here's my bio so you know I'm for real:

Wanted to talk to you about your blogging project and some other stuff. My email is on that page above.

#7 – Jason P – Friday, December 16th 2011, 22:48

There is an Audio load event which seems to be supported by Firefox 8, Safari 5 (windows) and by Chrome: 'loadeddata'

I don't know if this could be used in place of 'canplaythrough' or not.

#8game audio – Thursday, February 2nd 2012, 06:52

great article - keep us posted on the HTML 5 audio front!

#9 – TG – Sunday, September 2nd 2012, 03:46

Any update on this - I've spent the last week using various pooling techniques and I cannot get Firefox (v15) or Chrome (21.0.1180.89 m) to play overlapping sounds. Background music and short (<1 sec) clips.

Amazingly, the lastest Opera and even IE9 work great. IE9 even worked when I only had a single audio element.... It must create several buffers behind the scene.

I have an app using RequestAnimationFrame and realized that Opera and IE9 are falling back to my timer shim for animation. Thought that perhaps that had some weird impact on sound but no.... Stumped an annoyed at all this still not working as much as anyone else. I had multiple sound channels on my Amiga back in the eighties...

#10 – Alex – Monday, March 18th 2013, 12:46

Just checked the test page with Chrome 25... 9 requests, so not working anymore :-/

#11namuol – Tuesday, July 2nd 2013, 08:42

Greetings from over a year after your article was written, in the land where HTML5 audio is still a joke.

#12 – blabla – Saturday, March 15th 2014, 08:34

@Jason P
loadeddata is announced only once once the file is loaded.
canplaythrough is announced every time the file is playable

#13 – NewCompte – Monday, June 23rd 2014, 21:46

I tested the boring test page on IE 11, it loaded the file 4 times for me.

When I run this script in the console, it loaded the file a lot of times (30ish):

var source = 'beep.' + ((new Audio).canPlayType('audio/ogg') ? 'ogg' : 'mp3');

var a = new Audio( source );
document.body.appendChild( a ); // Fix for retarded Browsers (IE) :\

var clips = [a];
a.addEventListener( 'canplaythrough', function(ev){
for( var i = 0; i < 30; i++ ) {
clips.push( a.cloneNode(true) );
}, false );