Audio

Load and play a soundtrack and sound effects

HTML5 provides a nice Audio object that is really easy to use. Though it doesn't come without its fair share of problems: some browser support some audio types, other browsers support other audio types, looping a sound is done differently on different browsers, etc. The usual web "standards" fun.

Luckily WADE takes care of all that for you. Let's see how, by adding a soundtrack to our game. You'll need these two files - right-click to download them and save them into the data folder of your project: vivaldi.ogg and vivaldi.aac.

It's actually the same music file in two different formats, to make sure that every browser can play it. Now let's load one of them in our load function.

    // load audio
    wade.loadAudio('data/vivaldi.aac');

And at the end of the init function:

    // play background music
    this.music = wade.getAudio('data/vivaldi.aac');
    this.music.play();

Note that we are using the 'aac' version of the file. Internally though, WADE will decide which file to use depending on what each browser supports - if AAC is not supported it will load and play the OGG file instead. For this to work it's important that both files have the same name, just a different extension.

Maybe you want your soundtrack to start from the beginning when it's over. You can achieve this by passing additional arguments to the loadAudio function, like this:

    wade.loadAudio('data/vivaldi.aac', false, true);

Of course we also want to play some sound effects when our players make a correct match. This is done exactly the same way: we load a match.wav file in the load function:

    wade.loadAudio('data/match.wav');

And we play it when appropriate:

    wade.getAudio('data/match.wav').play();

Note that getAudio returns an HTML5 Audio object. You can then use it as you like - a good reference guide of all the things you can do with it and which browsers support them can be found here.

One thing to mention about audio is that some browsers will have problems playing the same audio object multiple times concurrently. In such a situation, you can get around this problem by creating a new Audio object every time you need to play it, like this:

    // play audio
    var audio = wade.getAudio('data/match.wav');
    var newAudio = new Audio(audio.src);
    newAudio.play();

By doing this, the creation of the new audio object will happen immediately, because the source audio file will be in the browser's cache and won't need to be re-downloaded. Anyway, WADE can do this for you if you play the audio file through WADE's playAudio function:

    // play audio
    var audio = wade.getAudio('data/match.wav');
    wade.playAudio(audio);

While this is all very nice and easy, sadly it won't work in many mobile browsers (notably in iOS Safari). This is because of some extra limitations of these browsers. There is a good article about it here.

Basically these browsers refuse to download audio or video (unlike images), unless this is triggered by an input event from the user: for example, it's ok to load audio in an onMouseDown function, but doing so somewhere else won't work. So by trying to load audio in our load function, we are telling WADE to stop executing our app until the audio has been loaded. In mobile Safari, this will never happen, and our init function will never be called - so our app will just be a blank page on mobile Safari.

Audio support in mobile Safari is pretty awful anyway, and you can only play or load one sound at a time. However, we probably don't want our app to just not work at all in mobile Safari: we want to write some code that works as well as it can on the device it's running on. Ideally, on devices that support multiple audio streams, we want the device to play multiple audio streams, and on other devices it should fall back to one audio stream, or no audio at all depending on the device's capabilities.

Let's start with Safari's limitation as to when audio files can be loaded. The easiest way to get around this is to have a button to start the app, that will be displayed from our init function. When that button is clicked, we load the audio and when the audio is loaded we actually start our game.

It is possible to do this by using the optional arguments that WADE provides when calling loadAudio. Specifically, the 4th argument is a callback function to execute when the audio is finished loading.

First of all, let's remove the loadAudio calls from the load function. Let's then rename our init function to something else, for example initialize. Then let's create another init function that displays some text, and an onMouseDown function to load some audio:

this.load = function()
{
    // load behavior
    wade.loadScript('behaviors/match3Item.js', 0, 1);

    // load sprites
    for (var i=0; i<6; i++)
    {
        wade.loadImage('data/item' + i + '.png');
    }
    wade.loadImage('data/cursor.png');
    wade.loadImage('data/background.jpg');
};

this.init = function()
{
    // add some text
    var textSprite = new TextSprite('Click to start', '48px Verdana', 'red', 'center');
    this.clickToStart = new SceneObject(textSprite);
    wade.addSceneObject(this.clickToStart);
};

 this.
{
    if (!this.audioLoaded)
    {
        this.clickToStart.getSprite(0).setText('Loading');

        // load audio
        wade.loadAudio('data/match.wav', false, false, function() {wade.app.initialize();});

        // don't do this again
        this.audioLoaded = true;
    }
};

this.initialize = function()
{
    // remove text
    wade.removeSceneObject(this.clickToStart);

    // define a grid
    this.numCells = {x: 8, y: 8};
    ....

It is also possible to preload some audio using the preloadAudio function. Unlike the loadAudio function, this one is asynchronous; that is, WADE carries on with the execution of your app while the audio is being loaded in the background. You can also tell WADE to start playing the audio as soon as it's loaded, which would be useful for our soundtrack. By doing this, your players won't have to wait a potentially long time for the music to load, but will be able to start playing immediately, with the soundtrack playing when it's ready.

    // load audio
    wade.loadAudio('data/match.wav', false, false, function() {wade.app.initialize();});
    wade.preloadAudio('data/vivaldi.aac', true, true);

More importantly, since preloadAudio doesn't block the execution of the app, even if it fails to load the audio (it will fail in mobile Safari if we are already loading a different audio stream), the app will just go on, happily ignoring the missing soundtrack.

Now we have a robust app that does its best to play audio on any device - where a device doesn't support multiple audio streams, it works with a single sound only.

While it's nice that now we have a sound effect playing even in iOS Safari, you probably should be doing this only if you're desperate for your iPhone and iPad users to hear that one sound (they won't be able to hear more than one anyway, unless you use more complicated solutions such as audio sprites). As we are still relying on a blocking loadAudio call, the above code may not work in browsers where that call will never be successful (it does happen in some particular Android browsers, on some particular Android devices).

In general, audio is so poorly supported on so many mobile browsers, with some of them implementing the most bizarre of restrictions like iOS Safari, that the most sensible thing to do, if you do need an app that runs everywhere, is to never rely on audio being there. That is, you can preload all your audio files in the load function, rather than using loadAudio.

Then, when it's time to play them, use the wade.playAudioIfAvailable function. In this way, your app will not get stuck waiting for audio files that never load, and will just not play sounds in browsers where HTML5 audio is unsupported, or supported with nonsensical restrictions. Unfortunately the price to pay for being compatible with everything, is that even in the better browsers your app will be missing sounds the first time it's run, until they get loaded. When it's run a second time, the audio files are likely to be in the cache, so calling preloadAudio will actually load the files immediately, even if preloadAudio is asynchronous.

 

Try it right here

 

And here is our app, in the version that plays sound on iOS but may or may not work on Android. You'll appreciate that while the "click to start" thing is an annoying workaround, it's actually been good for you in this instance - if we had started playing the looping background music immediately on load, by now you would have heard vivaldi.aac a dozen times.