I need to catch the exact moment when HTML5 audio starts producing sound.

It turns out not so simple as it seems.

You might expect audio starts playing when onplay or onplaying event is fired? No way. At least in WebKit family, it seems to be no browser event that fires exactly at this point of time. In Chrome, Safari and Firefox onplay and onplaying events are just faking their behaviour by simply firing together with oncanplay!

I've prepared a simple test to prove that fact. It demonstrates that audio actually starts playing after some reasonable time (over 100ms - 400ms) when all the events had already been fired.

You can notice this by your ears and ears if you look at console log. In the log I output currentTime every 15ms. It seems to reflect the actual audio state correctly, and it starts changing 10-40 polls after any event has been fired. So the audio is still freezed after play is fired.

Test code looks like this:

    var audioEl = new Audio('http://www.w3schools.com/tags/horse.ogg');

    audioEl.oncanplay = function () {
        console.log('oncanplay');

        audioEl.currentTime = 1;    

        console.log('ready state is: ' + audioEl.readyState);
        audioEl.play();
    }


    audioEl.oncanplay = function () {
        console.log('oncanplay again');
    }

    audioEl.onplay = function() {
        console.log('onplay -----------------');
    }

    audioEl.onplaying = function() {
        console.log('onplaying ----------------');
    }

    setInterval(function () {
        console.log(audioEl.currentTime);
    }, 15);

JsFiddle

I critically need to know the exact moment when the audio starts playing for precise synchronisation with visual animations.

Of course, I can find this moment roughly using quick polling. This is very bad for performance in real-time app, especially on mobile.

I'm asking if anyone knows any better solution for this. HTML audio implementation looks to be still so poor in 2014 :(

2

There are 2 answers

6
brianchirls On

As @justin says, you can listen for the playing event to get the (more or less) precise moment the media starts actually playing. But yeah I've been seeing some spotty support for media events and readyState in general, even in latest Chrome.

Whether those events work or not, I advise against using setInterval for animation (or just about anything else, for that matter). Instead, use requestAnimationFrame, which will fire more often and should synchronize with the browser and video card's repaint. And you can poll for the currentTime value on every frame to calculate where you should be in your animation. Performance shouldn't be a problem; your case is exactly what requestAnimationFrame was designed for.

function update() {
    var currentTime = audioEl.currentTime;
    // update your animation here
    requestAnimationFrame(update);
}

update();

While you're at it, don't set currentTime to 5 until after readyState > 0 or the loadedmetadata event fires. If you try to set currentTime before the browser has loaded enough of the video file to know the duration, it will throw an error. But you can call play() whenever you want; it doesn't have to wait for canplay.

1
Mark Smit On

Try the canplaythrough instead. Might help and would be better to be sure your audio can be palyed all the way to the end anyway..

audioEl.oncanplay = function () {
      console.log('ready state is: ' + audioEl.readyState);
      audioEl.play();
}