After receiving numerous requests on Twitter, I started working on a tutorial for playing sound in HTML5. You may have read about previous frustrations with Audio from the HTML5 developer community, and when I dug in recently, the situation was all-too familiar.
In the latest version of Chrome, sound playback was unreliable, bits and pieces were missing from the API, and things just did not work as expected. Safari faired better, but it still felt like Audio
was not ready for prime time. However, I think the problem was more of using the wrong tool for the job. Games need responsive, reliable, sometimes even dynamic sound playback, and because of this, Audio
is not the best choice for game development.
A good way to think about it is by comparing the Image
and Audio
tags. Both are simple HTML elements that really just want src
attributes, from which they'll fetch the media and display it in the document. For advanced image manipulation, Canvas
is used, and for advanced audio manipulation, AudioContext
can be used.
It turns out, the Web Audio API is here today and it is pretty badass. (By "here today" I really just mean Chrome, but it exists and works great even in the stable channel.)
Just about the only documentation I could find was the W3C Working Draft, but this was enough to meet feature parity with Audio
. Fair warning to developers looking to toy around with it: the API is extremely low-level and cumbersome to use (but quite fun if that floats your boat!).
Here's what it takes to simply load up a sound file and play it:
if (typeof AudioContext == "function") {
var audioContext = new AudioContext();
} else if (typeof webkitAudioContext == "function") {
var audioContext = new webkitAudioContext();
}
var source = audioContext.createBufferSource();
source.connect(audioContext.destination);
var xhr = new XMLHttpRequest();
xhr.open("GET", "your_sound_file.mp3", true);
xhr.responseType = "arraybuffer";
xhr.onload = function() {
var buffer = audioContext.createBuffer(xhr.response, false);
source.buffer = buffer;
source.noteOn(0);
};
xhr.send();
That's a lot of code! Let's walk through it.
if (typeof AudioContext == "function") {
var audioContext = new AudioContext();
} else if (typeof webkitAudioContext == "function") {
var audioContext = new webkitAudioContext();
}
Since this is bleeding-edge technology, we can't guarantee that AudioContext
exists. Indeed, Chrome currently has the webkitAudioContext
object (an experimental implementation). So before we can create a new instance we first have to check to see what we have available.
var source = audioContext.createBufferSource();
source.connect(audioContext.destination);
We request to create a buffer source and store it in the source
variable. Then we connect the source to the audio context's destination
property, which will hopefully be our computer's speakers!
var xhr = new XMLHttpRequest();
xhr.open("GET", "your_sound_file.mp3", true);
xhr.responseType = "arraybuffer";
xhr.onload = function() {
// See step 4
};
xhr.send();
This might be a tad unexpected for JavaScript programmers who are accustomed to simply setting a property on an HTML node (for example. myAudio.src = "my_sound.mp3";
), but to fetch our sound file we need to do it via an XMLHttpRequest
object. This is basic AJAXy stuff, aside from needing to set responseType
to "arraybuffer".
var buffer = audioContext.createBuffer(xhr.response, false);
source.buffer = buffer;
source.noteOn(0);
This code makes up the contents of the above XMLHttpRequest
object's onload
method. First we create a buffer, passing in the XHR response and false
. (That second parameter is "mixToMono", and we don't necessarily want our sound in mono, so we pass in false
.) This buffer gets passed to the source by setting its buffer
property.
Next, there's a call to the odd method noteOn
. In simpler English, this would be play
, but what is going on behind the scenes is much more complicated. The argument represents the time (in seconds) to begin playback. To make our playback begin immediately, we pass 0
.
Pretty involved just to play a sound, right? But the reward is well worth it, because now we have low-level access to our computer's sound and can implement all kinds of goodies like panning, filters and other neat effects.
While experimenting with this new API, I quickly discovered that a library to abstract away all this complication would be nice to have, so I wrote one. Here is what I call Audia, which implements a very straightforward API while still providing some of the power of the Web Audio API. Here's an example of how it can be used:
if (!Audia.supported) {
alert("Oops, no AudioContext object found ...");
}
var attackSound = new Audia();
attackSound.onload = function () {
attackSound.play();
};
attackSound.src = "audio/demoblin_attacks.mp3";
Or alternatively:
var backgroundMusic = new Audia({
src: "sewers.mp3",
loop: true,
volume: 0.5
});
Audio (especially as pertains to video games) is a little hobby of mine, so you can bet I'll be exposing more of the power behind the Web Audio API to Audia's simple interface. So do check out the demo and let me know in the comments about any issues, feature requests, etc.
LDG © 2022 • Blog • Terms of Service • Video Policy • v2.1.2