79 lines
2.5 KiB
JavaScript
79 lines
2.5 KiB
JavaScript
export const CURRENT_DISPLAY_SPEEDS = Object.freeze([0.5, 1, 1.5, 2]);
|
|
|
|
export function formatPlaybackTime(valueMs) {
|
|
const milliseconds = Math.max(0, Math.floor(Number(valueMs) || 0));
|
|
const minutes = Math.floor(milliseconds / 60000);
|
|
const seconds = Math.floor((milliseconds % 60000) / 1000);
|
|
const hundredths = Math.floor((milliseconds % 1000) / 10);
|
|
return `${minutes}:${String(seconds).padStart(2, "0")}.${String(hundredths).padStart(2, "0")}`;
|
|
}
|
|
|
|
export function projectPlaybackPosition(playback, elapsedMs = 0) {
|
|
if (!playback?.active || !Number.isFinite(Number(playback.total_duration_ms))) return null;
|
|
const total = Math.max(1, Number(playback.total_duration_ms));
|
|
const base = Math.max(0, Number(playback.position_ms) || 0);
|
|
if (playback.paused) return Math.min(total - 1, base);
|
|
const speed = Number(playback.speed) || 1;
|
|
const projected = base + Math.max(0, Number(elapsedMs) || 0) * speed;
|
|
return Math.min(total - 1, projected % total);
|
|
}
|
|
|
|
export class LatestSeekDispatcher {
|
|
constructor(send, {
|
|
intervalMs = 100,
|
|
now = () => performance.now(),
|
|
setTimer = (callback, delay) => window.setTimeout(callback, delay),
|
|
clearTimer = (timer) => window.clearTimeout(timer),
|
|
onError = () => {},
|
|
} = {}) {
|
|
this.send = send;
|
|
this.intervalMs = intervalMs;
|
|
this.now = now;
|
|
this.setTimer = setTimer;
|
|
this.clearTimer = clearTimer;
|
|
this.onError = onError;
|
|
this.inFlight = false;
|
|
this.lastStartedAt = Number.NEGATIVE_INFINITY;
|
|
this.queued = null;
|
|
this.timer = null;
|
|
}
|
|
|
|
enqueue(positionMs, { flush = false } = {}) {
|
|
this.queued = { positionMs, flush: flush || Boolean(this.queued?.flush) };
|
|
this.#drain();
|
|
}
|
|
|
|
clear() {
|
|
this.queued = null;
|
|
if (this.timer !== null) this.clearTimer(this.timer);
|
|
this.timer = null;
|
|
}
|
|
|
|
#drain() {
|
|
if (this.inFlight || this.queued === null) return;
|
|
const waitMs = this.intervalMs - (this.now() - this.lastStartedAt);
|
|
if (!this.queued.flush && waitMs > 0) {
|
|
if (this.timer === null) {
|
|
this.timer = this.setTimer(() => {
|
|
this.timer = null;
|
|
this.#drain();
|
|
}, waitMs);
|
|
}
|
|
return;
|
|
}
|
|
if (this.timer !== null) this.clearTimer(this.timer);
|
|
this.timer = null;
|
|
const item = this.queued;
|
|
this.queued = null;
|
|
this.inFlight = true;
|
|
this.lastStartedAt = this.now();
|
|
Promise.resolve()
|
|
.then(() => this.send(item.positionMs))
|
|
.catch((error) => this.onError(error, item.positionMs))
|
|
.finally(() => {
|
|
this.inFlight = false;
|
|
this.#drain();
|
|
});
|
|
}
|
|
}
|