4

I would like to play music (with HTML5 audio) and show some graphical elements at particular times (should be synced with audio timing).

Would it be OK to use JavaScript's window.setTimeout function to do it?

setTimeout(function() {
    requestAnimationFrame(function() {
        // display something
    });
}, 1400);

setTimeout(function() {
    requestAnimationFrame(function() {
        // display something else
    });
}, 2300);

Or, are there any better ways to do this?

Alexander O'Mara
  • 58,688
  • 18
  • 163
  • 171
LA_
  • 19,823
  • 58
  • 172
  • 308
  • `requestAnimationFrame` renders `setTimeout` obsolete for these kinds of scenario. Why are you using both? – Dai Jan 15 '15 at 20:01
  • @Dai `requestAnimationFrame` is much different from `setTimeout`. I think he's trying to make it render on the next draw after the specified timeout. IDK if that makes any real performance improvements though. – Alexander O'Mara Jan 15 '15 at 20:02
  • @Dai Using both is good for when you want a specific framerate. – Brennan Jan 15 '15 at 20:02
  • 5
    `setTimeout` has no accuracy beyond it being a _guaranteed minimum_ – James Thorpe Jan 15 '15 at 20:03
  • If you want to maintain a framerate you can use a stepper. Read about delta timing. – elclanrs Jan 15 '15 at 20:10

3 Answers3

3

"Accurate enough" is subjective, but in general, no. You don't know when the audio will finish downloading and start playing, or if it will play through without buffering or being paused by the user.

Instead you could use the timeupdate event and currentTime property, to properly sync events to the timeline.

var exampleElement = document.getElementById('example');
var lastTime = 0;
exampleElement.addEventListener('timeupdate', function(e) {
  var nowTime = this.currentTime;
  //Check if just passed the 1.4 second time mark.
  if (nowTime > 1.4 && lastTime < 1.4) {
    //Add a message to the debug element.
    var logElement = document.getElementById('log');
    logElement.textContent += 'Do something at 1.4 seconds\n';
  }
  lastTime = nowTime;
});
<audio id="example" controls="controls" autoplay="autoplay">
  <source src="http://media.w3.org/2010/07/bunny/04-Death_Becomes_Fur.mp4" type='audio/mp4'>
  <source src="http://media.w3.org/2010/07/bunny/04-Death_Becomes_Fur.oga" type='audio/ogg; codecs=vorbis'>
</audio>
<pre id="log"></pre><!--debug element-->
Alexander O'Mara
  • 58,688
  • 18
  • 163
  • 171
3

setTimeout only usually executes after the given delay, and when the browser's thread is clear.

So no, it isn't perfectly accurate, but I think that for your purposes it will be close enough, often less than 0.2% delay,

delay

Image from this question

However, on a slower user's machine, the effect might be more noticeable, and using setTimeout isn't recommended.

Instead, you can use the timeupdate event and the currentTime property to more accurately match the audio,

var audio = document.querySelector('audio#my-audio');

audio.addEventListener('timeupdate', function(){
  switch (this.currentTime){
    case 1:
      //stuff
      break;
    /* etc. */
  }
}

currentTime uses seconds to count though, so you'll have to make do with a less precise metronome, but the accuracy's there.

Community
  • 1
  • 1
theonlygusti
  • 11,032
  • 11
  • 64
  • 119
0

If you are able to start the timeout at the same instance as the audio starts, then it's accurate enough. With a screen frequency of 60 Hz for example, you have 16 ms until the next frame, so it doesn't actually have to be that accurate.

Depending on what you are displaying, even a few frames off might be acceptable. Consider for example that animated film updates at a rate of 12 Hz (cinema film runs at 24 Hz, and animations are updated every other frame).

Note however that the timout callback runs on the same thread as everything else, so it will only be accurate as long as you don't have anything else running that occupies the main thread for too long. If you for example have some other event handlers that do some heavy work, that could delay the timeout enough to be visible.

Guffa
  • 687,336
  • 108
  • 737
  • 1,005