1

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 :(

Dan
  • 55,715
  • 40
  • 116
  • 154
  • 1
    Does the `onplaying` event not fulfill your requirements? It triggers when the media is actually being played. – Justin May 29 '14 at 23:58
  • I don't think it has poor support. It is HTML5 and on their compatibility list, it just shows mobile browsers, which android version 2.X.X is older and the [distribution](https://developer.android.com/about/dashboards/index.html?utm_source=ausdroid.net) of it isn't as great. For [desktop](http://caniuse.com/audio) browsers, it is pretty well supported. – Justin May 30 '14 at 02:07

2 Answers2

3

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.

brianchirls
  • 7,661
  • 1
  • 32
  • 32
  • I have played with events and they seem not to work. You can check out my updated http://jsfiddle.net/LnTWS/3/. After spending a day on research, I end up with polling using your advise, `requestAnimationFrame`. – Dan May 31 '14 at 10:57
  • Glad it's working out for you. But FYI, all the events in your jsfiddle work fine for me in Firefox and Chrome. Not in Safari. Try calling `audioEl.play()` immediately after you create the element. – brianchirls May 31 '14 at 12:54
  • @brianchris I have spent some time on trying my test fiddle on Webkit browsers among different OSes, and what I get everywhere is a time gap between `play` event and actually playing sound. This can be discovered visually by viewing the console log. The audio is still freezed after `play` is fired. – Dan Jun 02 '14 at 08:54
  • My problem is that I have a huge delay between firing the event and audio start. I hope I was clear ) – Dan Jun 02 '14 at 09:16
  • It makes sense that there might be a delay between `play` and hearing sound. But you should hear something (and see `currentTime` increase) immediately after `playing`. – brianchirls Jun 02 '14 at 13:08
  • For me, the audio starts playing pretty much immediately. – brianchirls Jun 02 '14 at 13:09
-1

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();
}
Mark Smit
  • 582
  • 4
  • 12
  • Unfortunately, `oncanplay` if fired far before the moment when sound actually starts palying – Dan May 30 '14 at 09:51