From cec5969a26c711de301625f342d7ae07d4f5f9f3 Mon Sep 17 00:00:00 2001 From: Victor Westerlund Date: Mon, 27 Dec 2021 19:22:33 +0100 Subject: [PATCH] Add media element sync --- Monkeydo.mjs | 30 +++++++++++++++++++++++++++++- monkey/Monkey.js | 22 +++++++++++++++++++++- monkey/MonkeyMaster.mjs | 7 +++++++ 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/Monkeydo.mjs b/Monkeydo.mjs index 1255552..afabf9d 100644 --- a/Monkeydo.mjs +++ b/Monkeydo.mjs @@ -8,6 +8,25 @@ export default class Monkeydo extends MonkeyMaster { super(); this.methods = {}; Object.assign(this.methods,methods); + + this.media = { + _element: null, + get exists() { + return this._element instanceof HTMLMediaElement; + }, + bind: (element) => { + if(element instanceof HTMLMediaElement !== true) throw new TypeError("Not a media element"); + this.media.unbind(); + + this.media._element = element; + // Send timestamps to worker + this.media._element.addEventListener("timeupdate",event => this.mediaTimeUpdate(event.timeStamp)); + }, + unbind: () => { + this.stop(); + this.media._element = null; + } + } } // Execute a task @@ -38,6 +57,15 @@ export default class Monkeydo extends MonkeyMaster { async play(manifest = null) { if(!this.ready && !manifest) throw new Error("Can not start playback without a manifest"); if(manifest) await this.load(manifest); - return await this.start(); + + if(!this.media.exists) return await this.start(); + + // Start Monkeydo playback after media is playing + this.media._element.play() + .then(() => this.start()) + // Poll the play function until the user interacts with the page + .catch(error => { + if(error instanceof DOMException && error.name === "NotAllowedError") this.play(manifest); + }); } } \ No newline at end of file diff --git a/monkey/Monkey.js b/monkey/Monkey.js index d52dcbe..2da30f9 100644 --- a/monkey/Monkey.js +++ b/monkey/Monkey.js @@ -22,6 +22,18 @@ class Monkey { return this._target; } } + + // Sync delays with media element + this.media = { + _timeStamp: null, + get time() { + return this._timeStamp; + }, + set time(timeStamp) { + timeStamp = Math.floor(timeStamp); + this._timeStamp = timeStamp; + } + }; } // Advance to the next task or loop @@ -35,7 +47,9 @@ class Monkey { this.tasks._i++; const nextTask = this.tasks.task; - this.tasks._target = performance.now() + nextTask[0]; + const currentTime = this.media.time ? this.media.time : performance.now(); + + this.tasks._target = currentTime + nextTask[0]; } // Main event loop, runs on every frame @@ -61,6 +75,12 @@ class Monkey { return value ? this.flags[index] = value : this.flags[index]; } + // Use HTMLMediaElement.currentTime instead of performance.now() + setMedia(value = null) { + if(value instanceof HTMLMediaElement !== true) value = null; + this._media = value; + } + // Fetch and install manifest from URL async fetchManifest(url) { const manifest = await fetch(url); diff --git a/monkey/MonkeyMaster.mjs b/monkey/MonkeyMaster.mjs index fd7c35d..ea8d439 100644 --- a/monkey/MonkeyMaster.mjs +++ b/monkey/MonkeyMaster.mjs @@ -101,6 +101,12 @@ export default class MonkeyMaster { return update; } + // Update worker's media time override with a new timestamp + async mediaTimeUpdate(time) { + if(!this.ready) await this.init(); + this.comlink.media.time = time; + } + // Load a Monkeydo manifest by URL or JSON string async loadManifest(manifest) { if(!this.ready) await this.init(); @@ -122,6 +128,7 @@ export default class MonkeyMaster { } async stop() { + if(!this.ready) await this.init(); return await this.comlink.abort(); }