Reputation: 23224
I am playing an ogg file using an Audio
object in JavaScript. I am trying to synchronise visual events with the position of the audio.
audio.ontimeupdate
seems to trigger with a specific regularity. Is there a way I can instead set time markers and have an event fired as close as possible to the exact moment that the audio plays that point in the sample.
let audio = new Audio(`audio/${filename}`);
let timings = [1, 1.1, 1.2, 1.3, 1.4, 1.5, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5];
audio.ontimeupdate = function () {
let time = audio.currentTime;
while (timings.length > 0 && timings[0] <= time) {
console.log(`Waiting for ${timings[0]}, audio time = ${time} : late by ${time - timings[0]}`);
timings.splice(0, 1);
}
}
The output here is just not close enough to the subscription points. One is late by a quarter of a second.
Upvotes: 0
Views: 357
Reputation: 23224
This is what I did. I set a timeout for the first event time and then start to play the audio.
When the event is triggered I check if it is too soon (delay playing the audio) and if it is then I set another timeout for nextEventTime - currentTime
.
When the event is triggered and it is not too soon, then I dequeue event times until the next value is in the future. This is giving me a discrepancy of only about 20 milliseconds, which is definitely good enough!
class AudioClip {
constructor(url, eventTimings) {
this._audio = new Audio(url);
this._filename = filename;
this._eventTimings = eventTimings || [];
this._timeoutId = null;
}
play() {
if (!this._audio.paused) {
return;
}
this._setTimeoutForNextEvent();
this._audio.play();
}
pause() {
if (this._audio.paused) {
return;
}
this._clearTimeout();
this._audio.pause();
}
dispose() {
this.pause();
}
_setTimeoutForNextEvent() {
let _this = this;
this._clearTimeout(this);
if (this._eventTimings.length > 0) {
let delayMs = this._eventTimings[0] - (this._audio.currentTime * 1000);
this._timeoutId = setTimeout(() => { _this._triggerCurrentEvent(); }, delayMs);
}
}
_triggerCurrentEvent() {
this._clearTimeout();
// If we are too early, wait
let nextEventTimeSeconds = this._eventTimings[0] / 1000;
let audioTime = this._audio.currentTime;
if (audioTime < nextEventTimeSeconds) {
this._setTimeoutForNextEvent();
return;
}
// Otherwise, trigger the event
while (this._eventTimings.length > 0 && this._eventTimings[0] <= audioTime) {
console.log(`At ${this._audio.currentTime}: Event = ${this._eventTimings[0]}`);
this._eventTimings.splice(0, 1);
}
this._setTimeoutForNextEvent(this);
}
_clearTimeout() {
if (this._timeoutId) {
clearTimeout(this._timeoutId);
this._timeoutId = null;
}
}
}
Upvotes: 1