Let’s Program A Javascript 19: Cross Browser Chaos

Forgive me readers for I have sinned.

I wrote this entire series of articles using nothing but Firefox on Linux to test my code. Only now that we’ve reached the end of the project do I notice that when you try to open it with anything else it crashes.

I should have prevented this by testing every code update on multiple browsers like a good software developer, but late is better than never so let’s get this working in all browsers.

fastSeek Was A Bad Idea

You hopefully remember that we use the Audio class function fastSeek to rewind our background music and sound effects. Game over? Reset the music with fastSeek(0). Did the player stop grazing enemies? Reset the grazing sound effect with fastSeek(0).

But it turns out Firefox is the only browser that actually supports fastSeek. Every other browser will throw an error if you try to use it. So if you open Defrag Cycle in Chrome the whole game will freeze up the first time a sound effect has to reset itself because you either died or stopped grazing.

What other browsers prefer is for you to directly manipulate the currentTime variable inside of each audio object.

So instead of :

bgMusic.fastSeek(0);

We need to do:

bgMusic.currentTime=0;

This also works in Firefox, so just replace all the fastSeek instances in your code with currentTime and you are now cross browser ready.

Sound Format Headaches

Try to open this Defrag Cycle in Internet Explorer and the game won’t even load.

This is because IE doesn’t support the Ogg audio standard and throws an error when you try to ask it to load a .ogg file.

After a bit more research it looks like the only file format that actually has universal browser support is mp3.

So easy fix, right? Just convert our two sound files to MP3.

WRONG!

It turns out there’s a whole bunch of really weird legal issues relating to using MP3. Basically if you make a game with MP3s that more than 5,000 people access you now technically owe $2,500 to the company that owns the patent on MP3 technology. You can read more about that here.

Now as hobby developers we probably don’t want to mess with the legal issues and cost of MP3.

Instead we’re going to stick with Ogg and modify our code to only try to load and play sound files when the user’s browser supports them.

Different Code For Different Browsers

This sound file problem, while frustrating, actually gives us a good opportunity to play with an important JavaScript feature: detecting the what the user’s browser can and can’t do and then running browser specific code.

This is an important technique in non-game related web development too. Not only do different browsers support different audio formats, they support different JavaScript functions and sometimes have conflicting support for things like CSS styling rules. So a lot of professional code starts by figuring out what browser the user has and then making slight changes to the page to make sure everything stays compatible.

For our needs we can rely on the canPlayType function of the Audio class. Give the function the name of an audio standard and it will return whether or not the browser can actually play it.

We can use this to improve our code to only load and play our .ogg files in browsers than support them, like so:

function loadGrazeSFX(){
   grazeSFX = new Audio();
   if(grazeSFX.canPlayType('audio/ogg')){
      grazeSFX.oncanplaythrough = function(){ grazeSFX.oncanplaythrough=0; loadBackgroundMusic();};
      grazeSFX.src = 'wubwub.ogg';
      grazeSFX.loop=true;
   }
   else{
      loadBackgroundMusic();
   }
}
function loadBackgroundMusic(){
   bgMusic = new Audio();
   if(bgMusic.canPlayType('audio/ogg')){
      bgMusic.oncanplaythrough = function(){bgMusic.oncanplaythrough=0;gameLoop();};
      bgMusic.src = 'Ouroboros.ogg';
      bgMusic.loop=true;
   }
   else{
      gameLoop();
   }
}

Pretty simple, right? If the browser supprots Ogg audio we set up the code to load like usual. If it doesn’t we just skip straight to the next function in our loading chain.

We also need similar logic when we try to actually play our music so we don’t accidentally try to reset an audio file that was never actually loaded. So anywhere the code looks like this:

bgMusic.pause();
bgMusic.currentTime=0;
grazeSFX.pause();
grazeSFX.currentTime=0;

We need to expand it to look like this:

if(bgMusic.canPlayType('audio/ogg')){
   bgMusic.pause();
   bgMusic.currentTime=0;
}
if(grazeSFX.canPlayType('audio/ogg')){
   grazeSFX.pause();
   grazeSFX.currentTime=0;
}

Now we only play and load music on browsers that can play and load music. We’re cross browser compatible again!

We Could Have Done That Better

I will be the first to admit that this isn’t particularly elegant code. Nor is it particularly good behavior to just completely drop our music and sound effects on a major browser like IE.

Now this is just a demo project so I’m not too torn up about the code being less than perfect. But if this was a serious project for a paying customer we’d want to be a little bit smarter.

So what could we have done instead?

Well, probably the smartest thing to do (that doesn’t involve buying a MP3 license) is to include multiple versions of each audio file on your server. For instance, IE may not play Ogg but it apparently will play MP4 and WAV.

Then when loading music you use canPlay to decide which file version to load. Ogg for Firefox and Chrome. WAV for IE. Things like that.

This also cleans up the code that plays the audio. Since you know the audio objects will get a file one way or the other you don’t have to worry about wrapping them up in tons of ugly if statements.

Now We’re Really Done… Please?

Looks like Defrag Cycle is running in all my browsers, so time to get back on track and wrap up this Let’s Program with a final post about where we can go from here.