Wednesday, February 17, 2010

Background Music in JavaScript without Flash

So for the last half a year, I've been playing a bit with making larger and more complicated games in JavaScript. I've found a few neat new tricks, but I haven't gotten around to writing about them until now. One thing I figured out is how to play background music in modern browsers without using Flash. This is useful for JavaScript games where you need some music to help set the mood in a game.

So for non-IE browsers, you can use HTML5. Although the APIs seem to work reasonably well, I'm not sure how stable the specification is. There's some occasional weird things in the specification that suggest that the specification is immature. For example, the audio specification makes use of a lot of "boolean attributes" which are HTML attributes that can be defined or not defined instead of simply being true or false (so setting the attribute to false is equivalent to defining it as true). This sort of bizarre, unnecessarily complex, probably XML-incompatible stuff suggests that the specification still contains idiosyncrasies and pet projects of individual developers instead of being polished into a consistent spec. I'd be concerned of a problem like with ECMAScript 4 where once Microsoft and Yahoo! applied some common sense to the specification, a lot of things might end up changing. So I suggest using HTML5 with caution--you should always check to make sure that every method and field that you want to use is supported by your browser before relying on their existence.

Well, anyways, for HTML5 audio, you first need to check if it's supported:

var hasAudio = true;
try {
var audiotest = new Audio();
if (!audiotest.canPlayType) hasAudio = false;
} catch (e) { hasAudio = false; }


Every browser supports different audio formats, so you need to make an MP3 version of your files (you can use iTunes for that) for Safari/Chrome and an OGG version (you can use Audacity for that) for Firefox/Chrome. For some reason, the Chrome browser doesn't support WAV files because they claim the files are "too large," and I think this is a clear example of the immaturity surrounding the HTML5 specification in general. There are many good reasons for supporting WAV files in browsers, and for short sound effects, WAV files can even be smaller than OGG or MP3 files. The reason for not including WAV support in Chrome is simply the result of bias and ignorant opinions as opposed to a mature approach to the problem. So anyways, you need to test which background music file to load depending on which formats are supported.

var clip = new Audio();
clip.autoplay = false;
clip.autobuffer = true;
var canOgg = clip.canPlayType("audio/ogg");
var canMp3 = clip.canPlayType("audio/mpeg");
var canMp3Alt = clip.canPlayType("audio/mp3");
var canWav = clip.canPlayType("audio/wav");
if (canMp3 != "" && canMp3 != "no")
clip.src = mp3File;
else if (canMp3Alt != "" && canMp3Alt != "no")
clip.src = mp3File;
else if (canOgg != "" && canOgg != "no")
clip.src = oggFile;
else if (canWav != "" && canWav != "no")
clip.src = wavFile;


And from there, playing the file is easy (though Firefox 3.5 doesn't support looping music, so you need to do that manually).

clip.addEventListener("ended", function() {
// I'm not sure if this setTimeout() thing is necessary
window.setTimeout(function() {clip.play();}, 1);});
clip.play();


For Internet Explorer, you can use Windows Media Player to play music. First, you have to check to see if Windows Media Player is available using ActiveX:

var hasWmp = true;
try {
var player = new ActiveXObject("WMPlayer.OCX.7");
} catch (e) {hasWmp = false;


For some reason, when you create the ActiveX object, it isn't bound to your web page properly, so you can't use relative URLs to refer to music files. If you do want to use relative URLs, you actually have to create the player object as an HTML object in your document.

var tmp = document.createElement('div');
tmp.innerHTML = '<OBJECT CLASSID="CLSID:6BF52A52-394A-11d3-B153-00C04F79FAA6"></OBJECT>';
player = tmp.getElementsByTagName('OBJECT')[0];
tmp = null;
if (!player || !player.versionInfo) hasWmp = false;


From there, playing an mp3 file is easy:

player.settings.setMode("loop", true);
player.URL = mp3File;
player.controls.play();


Update 2010-5-3: Apparently, using MP3 files on a web page or in a game requires an mp3 license. In the future, I guess I will only support Ogg Vorbis and let users of Safari and IE endure the silence.