Update css/player-base-new.js
This commit is contained in:
parent
1d4bf3748b
commit
1f004898e7
@ -2,8 +2,7 @@
|
|||||||
var _yt_player = videojs;
|
var _yt_player = videojs;
|
||||||
|
|
||||||
var versionclient = "youtube.player.web_20250917_22_RC00"
|
var versionclient = "youtube.player.web_20250917_22_RC00"
|
||||||
|
// video.js boot
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
const player = videojs("video", {
|
const player = videojs("video", {
|
||||||
controls: true,
|
controls: true,
|
||||||
@ -12,17 +11,17 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
errorDisplay: false,
|
errorDisplay: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// quick params + tiny progress seed
|
// tiny params stash
|
||||||
const qs = new URLSearchParams(location.search);
|
const qs = new URLSearchParams(location.search);
|
||||||
const qua = qs.get("quality") || "";
|
const qua = qs.get("quality") || "";
|
||||||
const vidKey = qs.get("v");
|
const vidKey = qs.get("v");
|
||||||
try { if (vidKey) localStorage.setItem(`progress-${vidKey}`, 0); } catch {}
|
try { if (vidKey) localStorage.setItem(`progress-${vidKey}`, 0); } catch {}
|
||||||
|
|
||||||
// raw nodes
|
// raw tags
|
||||||
const videoEl = document.getElementById("video");
|
const videoEl = document.getElementById("video");
|
||||||
const audioEl = document.getElementById("aud");
|
const audioEl = document.getElementById("aud");
|
||||||
|
|
||||||
// keep the hidden audio truly hidden/quiet
|
// keep the hidden audio really hidden
|
||||||
try {
|
try {
|
||||||
audioEl.controls = false;
|
audioEl.controls = false;
|
||||||
audioEl.setAttribute("aria-hidden", "true");
|
audioEl.setAttribute("aria-hidden", "true");
|
||||||
@ -34,7 +33,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
audioEl.preload = "auto";
|
audioEl.preload = "auto";
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
// source sniffers
|
// poke sources
|
||||||
const pickAudioSrc = () => {
|
const pickAudioSrc = () => {
|
||||||
const s = audioEl?.getAttribute?.("src");
|
const s = audioEl?.getAttribute?.("src");
|
||||||
if (s) return s;
|
if (s) return s;
|
||||||
@ -46,17 +45,17 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
let audioSrc = pickAudioSrc();
|
let audioSrc = pickAudioSrc();
|
||||||
|
|
||||||
const srcObj = player.src();
|
const srcObj = player.src();
|
||||||
const initialVideoSrc = Array.isArray(srcObj) ? (srcObj[0] && srcObj[0].src) : srcObj;
|
const initialVideoSrc = Array.isArray(srcObj) ? (srcObj[0] && srcObj[0].src) : srcObj;
|
||||||
const initialVideoType = Array.isArray(srcObj) ? (srcObj[0] && srcObj[0].type) : undefined;
|
const initialVideoType = Array.isArray(srcObj) ? (srcObj[0] && srcObj[0].type) : undefined;
|
||||||
|
|
||||||
// small state
|
// a little state
|
||||||
let audioReady = false, videoReady = false;
|
let audioReady = false, videoReady = false;
|
||||||
let mediaSessionReady = false;
|
let mediaSessionReady = false;
|
||||||
|
|
||||||
// sync knobs
|
// sync knobs
|
||||||
const BIG_DRIFT = 0.45; // snap if beyond this (s)
|
const BIG_DRIFT = 0.45;
|
||||||
const MICRO_DRIFT = 0.035; // tiny nudge (s)
|
const MICRO_DRIFT = 0.035;
|
||||||
const EPS = 0.12; // buffer epsilon (s)
|
const EPS = 0.12;
|
||||||
|
|
||||||
// guards + timers
|
// guards + timers
|
||||||
let volGuard = false;
|
let volGuard = false;
|
||||||
@ -65,18 +64,14 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
let autosyncTimer = null;
|
let autosyncTimer = null;
|
||||||
const AUTOSYNC_MS = 400;
|
const AUTOSYNC_MS = 400;
|
||||||
|
|
||||||
// buffering/loader awareness
|
// seek flags
|
||||||
let bufferingHold = false; // we decided to pause both because it's buffering
|
|
||||||
let resumeAfterBuffer = false; // restore play once it’s ready again
|
|
||||||
|
|
||||||
// seek flow flags
|
|
||||||
let audioPauseForSeek = false;
|
let audioPauseForSeek = false;
|
||||||
let playerSeekWasPlaying = false;
|
let playerSeekWasPlaying = false;
|
||||||
|
|
||||||
// loop tracking
|
// last video time (for loop wrap)
|
||||||
let lastVideoTime = 0;
|
let lastVideoTime = 0;
|
||||||
|
|
||||||
// basic helpers
|
// helper: can we play at time t?
|
||||||
function timeInBuffered(media, t) {
|
function timeInBuffered(media, t) {
|
||||||
try {
|
try {
|
||||||
const br = media.buffered;
|
const br = media.buffered;
|
||||||
@ -90,9 +85,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
}
|
}
|
||||||
function canPlayAt(media, t) {
|
function canPlayAt(media, t) {
|
||||||
try {
|
try {
|
||||||
const rs = Number(media.readyState || 0); // 0..4
|
const rs = Number(media.readyState || 0);
|
||||||
if (!isFinite(t)) return false;
|
if (!isFinite(t)) return false;
|
||||||
if (rs >= 3) return true; // HAVE_FUTURE_DATA
|
if (rs >= 3) return true;
|
||||||
return timeInBuffered(media, t);
|
return timeInBuffered(media, t);
|
||||||
} catch { return false; }
|
} catch { return false; }
|
||||||
}
|
}
|
||||||
@ -101,19 +96,21 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
const clamp01 = v => Math.max(0, Math.min(1, Number(v)));
|
const clamp01 = v => Math.max(0, Math.min(1, Number(v)));
|
||||||
function isLooping() { try { return !!player.loop?.() || !!videoEl.loop; } catch { return !!videoEl.loop; } }
|
function isLooping() { try { return !!player.loop?.() || !!videoEl.loop; } catch { return !!videoEl.loop; } }
|
||||||
|
|
||||||
// check if the “loading bar/spinner” is up (real-world test: UI class + readystate + buffer)
|
// helper: is the vjs spinner actually up?
|
||||||
function isBufferingUI() {
|
function spinnerUp() {
|
||||||
try {
|
try {
|
||||||
const el = player.el();
|
const root = player.el();
|
||||||
const uiWait = player.hasClass("vjs-waiting") || player.hasClass("vjs-seeking");
|
if (!root) return false;
|
||||||
const rs = Number(videoEl.readyState || 0);
|
const hasWaiting = player.hasClass?.("vjs-waiting") || player.hasClass?.("vjs-seeking") || player.hasClass?.("vjs-processing");
|
||||||
const t = Number(player.currentTime());
|
const sp = root.querySelector(".vjs-loading-spinner");
|
||||||
const atEdge = !timeInBuffered(videoEl, isFinite(t) ? t + 0.05 : 0);
|
if (!sp) return !!hasWaiting;
|
||||||
return !!(uiWait || rs < 3 || atEdge);
|
const cs = window.getComputedStyle(sp);
|
||||||
|
const visible = cs.visibility !== "hidden" && cs.display !== "none" && Number(cs.opacity || "1") > 0;
|
||||||
|
return !!hasWaiting || visible;
|
||||||
} catch { return false; }
|
} catch { return false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// keep mute/volume mirrored either way
|
// mute/volume mirror either way
|
||||||
function mirrorFromPlayerVolumeMute() {
|
function mirrorFromPlayerVolumeMute() {
|
||||||
if (volGuard) return; volGuard = true;
|
if (volGuard) return; volGuard = true;
|
||||||
try {
|
try {
|
||||||
@ -140,13 +137,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
audioEl.addEventListener("volumechange", mirrorFromAudioVolumeMute);
|
audioEl.addEventListener("volumechange", mirrorFromAudioVolumeMute);
|
||||||
player.ready(() => mirrorFromPlayerVolumeMute());
|
player.ready(() => mirrorFromPlayerVolumeMute());
|
||||||
|
|
||||||
// one-button control
|
// single control helpers
|
||||||
function playBoth() {
|
function playBoth() {
|
||||||
if (couplingGuard) return;
|
if (couplingGuard) return;
|
||||||
if (isBufferingUI()) { // don’t try to play while loader is up
|
|
||||||
resumeAfterBuffer = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
couplingGuard = true;
|
couplingGuard = true;
|
||||||
player.play()?.catch(()=>{});
|
player.play()?.catch(()=>{});
|
||||||
audioEl.play()?.catch(()=>{});
|
audioEl.play()?.catch(()=>{});
|
||||||
@ -162,14 +155,14 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
couplingGuard = false;
|
couplingGuard = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// master behavior: video dictates play/pause; audio mirrors
|
// buffering-aware gate: if spinner is up, neither should be playing
|
||||||
|
function enforceBufferingGate() {
|
||||||
|
if (spinnerUp()) pauseBoth();
|
||||||
|
}
|
||||||
|
|
||||||
|
// video is the boss
|
||||||
player.on("play", () => {
|
player.on("play", () => {
|
||||||
if (isBufferingUI()) { // if UI shows loader, cancel play and wait
|
if (spinnerUp()) { pauseBoth(); return; }
|
||||||
bufferingHold = true;
|
|
||||||
resumeAfterBuffer = true;
|
|
||||||
pauseBoth();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (audioEl.paused) audioEl.play()?.catch(()=>{});
|
if (audioEl.paused) audioEl.play()?.catch(()=>{});
|
||||||
startAutosync();
|
startAutosync();
|
||||||
try { if ("mediaSession" in navigator) navigator.mediaSession.playbackState = "playing"; } catch {}
|
try { if ("mediaSession" in navigator) navigator.mediaSession.playbackState = "playing"; } catch {}
|
||||||
@ -180,14 +173,17 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
try { if ("mediaSession" in navigator) navigator.mediaSession.playbackState = "paused"; } catch {}
|
try { if ("mediaSession" in navigator) navigator.mediaSession.playbackState = "paused"; } catch {}
|
||||||
});
|
});
|
||||||
|
|
||||||
// don’t let the hidden audio go rogue
|
// platform trying to mess with audio alone? keep them glued
|
||||||
audioEl.addEventListener("play", () => { if (player.paused()) playBoth(); });
|
audioEl.addEventListener("play", () => {
|
||||||
|
if (spinnerUp()) { audioEl.pause(); return; }
|
||||||
|
if (player.paused()) playBoth();
|
||||||
|
});
|
||||||
audioEl.addEventListener("pause", () => {
|
audioEl.addEventListener("pause", () => {
|
||||||
if (audioPauseForSeek) return;
|
if (audioPauseForSeek) return;
|
||||||
if (!player.paused()) pauseBoth();
|
if (!player.paused()) pauseBoth();
|
||||||
});
|
});
|
||||||
|
|
||||||
// media-session timeline (keeps OS seekbar calm)
|
// OS timeline sync (calm, monotonic)
|
||||||
let lastMSPos = 0, lastMSAt = 0;
|
let lastMSPos = 0, lastMSAt = 0;
|
||||||
const MS_THROTTLE_MS = 250;
|
const MS_THROTTLE_MS = 250;
|
||||||
function getDuration() {
|
function getDuration() {
|
||||||
@ -195,25 +191,21 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
if (!isFinite(d) || d <= 0) d = Number(player.duration());
|
if (!isFinite(d) || d <= 0) d = Number(player.duration());
|
||||||
if (!isFinite(d) || d <= 0) d = Number(audioEl.duration);
|
if (!isFinite(d) || d <= 0) d = Number(audioEl.duration);
|
||||||
return isFinite(d) && d > 0 ? d : null;
|
return isFinite(d) && d > 0 ? d : null;
|
||||||
}
|
}
|
||||||
function updateMSPositionState(throttle = true) {
|
function updateMSPositionState(throttle = true) {
|
||||||
if (!("mediaSession" in navigator)) return;
|
if (!("mediaSession" in navigator)) return;
|
||||||
const now = performance.now();
|
const now = performance.now();
|
||||||
if (throttle && (now - lastMSAt) < MS_THROTTLE_MS) return;
|
if (throttle && (now - lastMSAt) < MS_THROTTLE_MS) return;
|
||||||
const dur = getDuration();
|
const dur = getDuration();
|
||||||
if (!dur) return;
|
if (!dur) return;
|
||||||
|
|
||||||
let pos = Number(player.currentTime());
|
let pos = Number(player.currentTime());
|
||||||
if (!isFinite(pos) || pos < 0) pos = 0;
|
if (!isFinite(pos) || pos < 0) pos = 0;
|
||||||
|
|
||||||
// avoid flicker; allow jump-back only on loop wrap
|
|
||||||
if (pos + 0.2 < lastMSPos && isLooping()) lastMSPos = 0;
|
if (pos + 0.2 < lastMSPos && isLooping()) lastMSPos = 0;
|
||||||
else {
|
else {
|
||||||
if (pos < lastMSPos && (lastMSPos - pos) < 0.2) pos = lastMSPos;
|
if (pos < lastMSPos && (lastMSPos - pos) < 0.2) pos = lastMSPos;
|
||||||
lastMSPos = pos;
|
lastMSPos = pos;
|
||||||
}
|
}
|
||||||
lastMSAt = now;
|
lastMSAt = now;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ("setPositionState" in navigator.mediaSession) {
|
if ("setPositionState" in navigator.mediaSession) {
|
||||||
navigator.mediaSession.setPositionState({
|
navigator.mediaSession.setPositionState({
|
||||||
@ -242,7 +234,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
updateMSPositionState(false);
|
updateMSPositionState(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// when OS seekbar moves: pause audio first (to avoid ping-pong), mirror time, then optionally resume
|
|
||||||
function msSeekTo(targetTime) {
|
function msSeekTo(targetTime) {
|
||||||
const wasPlaying = !player.paused();
|
const wasPlaying = !player.paused();
|
||||||
audioPauseForSeek = true;
|
audioPauseForSeek = true;
|
||||||
@ -257,11 +248,11 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
audioPauseForSeek = false;
|
audioPauseForSeek = false;
|
||||||
}
|
}
|
||||||
tagState();
|
tagState();
|
||||||
if (wasPlaying && !isBufferingUI()) playBoth();
|
if (spinnerUp()) return; // if still buffering, stay paused
|
||||||
else if (wasPlaying) resumeAfterBuffer = true;
|
if (wasPlaying) playBoth();
|
||||||
}
|
}
|
||||||
|
|
||||||
navigator.mediaSession.setActionHandler("play", () => { playBoth(); tagState(); });
|
navigator.mediaSession.setActionHandler("play", () => { if (!spinnerUp()) playBoth(); tagState(); });
|
||||||
navigator.mediaSession.setActionHandler("pause", () => { pauseBoth(); tagState(); });
|
navigator.mediaSession.setActionHandler("pause", () => { pauseBoth(); tagState(); });
|
||||||
navigator.mediaSession.setActionHandler("stop", () => {
|
navigator.mediaSession.setActionHandler("stop", () => {
|
||||||
pauseBoth();
|
pauseBoth();
|
||||||
@ -294,11 +285,12 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
mediaSessionReady = true;
|
mediaSessionReady = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// media keys fallback
|
// hardware keys
|
||||||
document.addEventListener("keydown", e => {
|
document.addEventListener("keydown", e => {
|
||||||
switch (e.code) {
|
switch (e.code) {
|
||||||
case "AudioPlay":
|
case "AudioPlay":
|
||||||
case "MediaPlayPause":
|
case "MediaPlayPause":
|
||||||
|
if (spinnerUp()) { pauseBoth(); break; }
|
||||||
if (player.paused()) playBoth(); else pauseBoth();
|
if (player.paused()) playBoth(); else pauseBoth();
|
||||||
break;
|
break;
|
||||||
case "AudioPause":
|
case "AudioPause":
|
||||||
@ -308,30 +300,27 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
case "MediaTrackNext": {
|
case "MediaTrackNext": {
|
||||||
const t = Number(player.currentTime()) + 10;
|
const t = Number(player.currentTime()) + 10;
|
||||||
player.currentTime(t); safeSetCT(audioEl, t);
|
player.currentTime(t); safeSetCT(audioEl, t);
|
||||||
|
enforceBufferingGate();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "AudioPrevious":
|
case "AudioPrevious":
|
||||||
case "MediaTrackPrevious": {
|
case "MediaTrackPrevious": {
|
||||||
const t = Math.max(0, Number(player.currentTime()) - 10);
|
const t = Math.max(0, Number(player.currentTime()) - 10);
|
||||||
player.currentTime(t); safeSetCT(audioEl, t);
|
player.currentTime(t); safeSetCT(audioEl, t);
|
||||||
|
enforceBufferingGate();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// one sync tick (events + autosyncer use this)
|
// single sync step
|
||||||
function doSyncOnce() {
|
function doSyncOnce() {
|
||||||
if (syncGuard) return;
|
if (syncGuard) return;
|
||||||
if (isBufferingUI()) { // loader up → hold steady, no cute tricks
|
if (spinnerUp()) { pauseBoth(); return; }
|
||||||
try { audioEl.playbackRate = 1; } catch {}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const vt = Number(player.currentTime());
|
const vt = Number(player.currentTime());
|
||||||
const at = Number(audioEl.currentTime);
|
const at = Number(audioEl.currentTime);
|
||||||
if (!isFinite(vt) || !isFinite(at)) return;
|
if (!isFinite(vt) || !isFinite(at)) return;
|
||||||
|
|
||||||
// handle loop wrap cleanly
|
|
||||||
if (vt + 0.02 < lastVideoTime && isLooping()) {
|
if (vt + 0.02 < lastVideoTime && isLooping()) {
|
||||||
safeSetCT(audioEl, vt);
|
safeSetCT(audioEl, vt);
|
||||||
if (player.paused()) playBoth();
|
if (player.paused()) playBoth();
|
||||||
@ -345,11 +334,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
if (!bothPlayableAt(vt)) return;
|
if (!bothPlayableAt(vt)) return;
|
||||||
|
|
||||||
const delta = vt - at;
|
const delta = vt - at;
|
||||||
if (Math.abs(delta) > BIG_DRIFT) {
|
if (Math.abs(delta) > BIG_DRIFT) { safeSetCT(audioEl, vt); try { audioEl.playbackRate = 1; } catch {} return; }
|
||||||
safeSetCT(audioEl, vt);
|
|
||||||
try { audioEl.playbackRate = 1; } catch {}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Math.abs(delta) > MICRO_DRIFT) {
|
if (Math.abs(delta) > MICRO_DRIFT) {
|
||||||
const target = 1 + (delta * 0.12);
|
const target = 1 + (delta * 0.12);
|
||||||
try { audioEl.playbackRate = Math.max(0.97, Math.min(1.03, target)); } catch {}
|
try { audioEl.playbackRate = Math.max(0.97, Math.min(1.03, target)); } catch {}
|
||||||
@ -358,13 +343,16 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// foreground sync
|
// main sync
|
||||||
player.on("timeupdate", doSyncOnce);
|
player.on("timeupdate", doSyncOnce);
|
||||||
|
|
||||||
// background/lockscreen helper
|
// cheap autosync + also our buffering gate keeper
|
||||||
function startAutosync() {
|
function startAutosync() {
|
||||||
if (autosyncTimer) return;
|
if (autosyncTimer) return;
|
||||||
autosyncTimer = setInterval(doSyncOnce, AUTOSYNC_MS);
|
autosyncTimer = setInterval(() => {
|
||||||
|
enforceBufferingGate();
|
||||||
|
doSyncOnce();
|
||||||
|
}, AUTOSYNC_MS);
|
||||||
}
|
}
|
||||||
function stopAutosync() {
|
function stopAutosync() {
|
||||||
if (!autosyncTimer) return;
|
if (!autosyncTimer) return;
|
||||||
@ -372,34 +360,32 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
autosyncTimer = null;
|
autosyncTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// when user drags the player's seekbar: pause audio immediately (no flapping), mirror time, then resume if needed
|
// when user drags the player's seekbar: kill audio first, line up, then resume if we were playing
|
||||||
player.on("seeking", () => {
|
player.on("seeking", () => {
|
||||||
syncGuard = true;
|
syncGuard = true;
|
||||||
playerSeekWasPlaying = !player.paused();
|
playerSeekWasPlaying = !player.paused();
|
||||||
resumeAfterBuffer = playerSeekWasPlaying; // if we were playing, we’ll resume after seek/buffer
|
|
||||||
audioPauseForSeek = true;
|
audioPauseForSeek = true;
|
||||||
try { audioEl.pause(); } catch {}
|
try { audioEl.pause(); } catch {}
|
||||||
safeSetCT(audioEl, Number(player.currentTime()));
|
safeSetCT(audioEl, Number(player.currentTime()));
|
||||||
audioPauseForSeek = false;
|
audioPauseForSeek = false;
|
||||||
updateMSPositionState(false);
|
updateMSPositionState(false);
|
||||||
|
enforceBufferingGate();
|
||||||
});
|
});
|
||||||
player.on("seeked", () => {
|
player.on("seeked", () => {
|
||||||
safeSetCT(audioEl, Number(player.currentTime()));
|
safeSetCT(audioEl, Number(player.currentTime()));
|
||||||
syncGuard = false;
|
syncGuard = false;
|
||||||
if (!isBufferingUI() && resumeAfterBuffer && bothPlayableAt(Number(player.currentTime()))) {
|
if (!spinnerUp() && playerSeekWasPlaying && bothPlayableAt(Number(player.currentTime()))) playBoth();
|
||||||
playBoth();
|
|
||||||
resumeAfterBuffer = false;
|
|
||||||
}
|
|
||||||
updateMSPositionState(false);
|
updateMSPositionState(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
// if some UI seeks the audio directly, mirror it to video (still respecting loader)
|
// if some UI seeks audio directly, mirror to video, but obey the spinner gate
|
||||||
audioEl.addEventListener("seeking", () => {
|
audioEl.addEventListener("seeking", () => {
|
||||||
const at = Number(audioEl.currentTime) || 0;
|
const at = Number(audioEl.currentTime) || 0;
|
||||||
syncGuard = true;
|
syncGuard = true;
|
||||||
player.currentTime(at);
|
player.currentTime(at);
|
||||||
syncGuard = false;
|
syncGuard = false;
|
||||||
updateMSPositionState(false);
|
updateMSPositionState(false);
|
||||||
|
enforceBufferingGate();
|
||||||
});
|
});
|
||||||
audioEl.addEventListener("seeked", () => {
|
audioEl.addEventListener("seeked", () => {
|
||||||
const at = Number(audioEl.currentTime) || 0;
|
const at = Number(audioEl.currentTime) || 0;
|
||||||
@ -409,37 +395,13 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
updateMSPositionState(false);
|
updateMSPositionState(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
// buffering hooks — if the loader shows, *nothing* plays
|
// buffering hooks: rely on real spinner, not guesses
|
||||||
function enterBuffering(reason) {
|
player.on("waiting", enforceBufferingGate);
|
||||||
bufferingHold = true;
|
player.on("progress", enforceBufferingGate);
|
||||||
if (!player.paused() || !audioEl.paused) {
|
player.on("playing", () => {
|
||||||
resumeAfterBuffer = true;
|
if (!spinnerUp() && !player.paused()) audioEl.play()?.catch(()=>{});
|
||||||
pauseBoth();
|
startAutosync();
|
||||||
}
|
|
||||||
}
|
|
||||||
function exitBuffering() {
|
|
||||||
bufferingHold = false;
|
|
||||||
if (resumeAfterBuffer && !isBufferingUI() && bothPlayableAt(Number(player.currentTime()))) {
|
|
||||||
playBoth();
|
|
||||||
}
|
|
||||||
resumeAfterBuffer = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
player.on("waiting", () => enterBuffering("waiting"));
|
|
||||||
player.on("stalled", () => enterBuffering("stalled"));
|
|
||||||
player.on("suspend", () => enterBuffering("suspend"));
|
|
||||||
player.on("seeking", () => enterBuffering("seeking"));
|
|
||||||
player.on("canplay", () => exitBuffering());
|
|
||||||
player.on("playing", () => exitBuffering());
|
|
||||||
player.on("canplaythrough", () => exitBuffering());
|
|
||||||
player.on("progress", () => { if (!isBufferingUI()) exitBuffering(); });
|
|
||||||
|
|
||||||
// just to be safe, also react to class flips (loading spinner on/off)
|
|
||||||
const spinnerObserver = new MutationObserver(() => {
|
|
||||||
if (isBufferingUI()) enterBuffering("ui");
|
|
||||||
else exitBuffering();
|
|
||||||
});
|
});
|
||||||
try { spinnerObserver.observe(player.el(), { attributes: true, attributeFilter: ["class"] }); } catch {}
|
|
||||||
|
|
||||||
// simple error surfacing
|
// simple error surfacing
|
||||||
const errorBox = document.getElementById("loopedIndicator");
|
const errorBox = document.getElementById("loopedIndicator");
|
||||||
@ -457,12 +419,12 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// loop/end behavior — never stop on loop
|
// never stop on loop
|
||||||
player.on("ended", () => {
|
player.on("ended", () => {
|
||||||
if (isLooping()) {
|
if (isLooping()) {
|
||||||
safeSetCT(audioEl, 0);
|
safeSetCT(audioEl, 0);
|
||||||
player.currentTime(0);
|
player.currentTime(0);
|
||||||
if (!isBufferingUI()) playBoth(); else resumeAfterBuffer = true;
|
if (!spinnerUp()) playBoth();
|
||||||
lastMSPos = 0;
|
lastMSPos = 0;
|
||||||
updateMSPositionState(false);
|
updateMSPositionState(false);
|
||||||
} else {
|
} else {
|
||||||
@ -475,7 +437,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
safeSetCT(audioEl, 0);
|
safeSetCT(audioEl, 0);
|
||||||
if (player.paused()) {
|
if (player.paused()) {
|
||||||
player.currentTime(0);
|
player.currentTime(0);
|
||||||
if (!isBufferingUI()) playBoth(); else resumeAfterBuffer = true;
|
if (!spinnerUp()) playBoth();
|
||||||
}
|
}
|
||||||
lastMSPos = 0;
|
lastMSPos = 0;
|
||||||
updateMSPositionState(false);
|
updateMSPositionState(false);
|
||||||
@ -484,12 +446,12 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// leaving fullscreen? pause cleanly
|
// leaving fullscreen? chill everything
|
||||||
document.addEventListener("fullscreenchange", () => {
|
document.addEventListener("fullscreenchange", () => {
|
||||||
if (!document.fullscreenElement) pauseBoth();
|
if (!document.fullscreenElement) pauseBoth();
|
||||||
});
|
});
|
||||||
|
|
||||||
// tiny quiet retry on load error
|
// tiny quiet ready/retry helper
|
||||||
function attachReady(elm, resolveSrc, markReady) {
|
function attachReady(elm, resolveSrc, markReady) {
|
||||||
const onLoaded = () => { try { markReady(); } catch {} tryStart(); };
|
const onLoaded = () => { try { markReady(); } catch {} tryStart(); };
|
||||||
elm.addEventListener("loadeddata", onLoaded, { once: true });
|
elm.addEventListener("loadeddata", onLoaded, { once: true });
|
||||||
@ -507,18 +469,18 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
}, { once: true });
|
}, { once: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// spin-up when both sides are ready
|
// bring up both only when ready and not buffering
|
||||||
function tryStart() {
|
function tryStart() {
|
||||||
if (audioReady && videoReady) {
|
if (audioReady && videoReady) {
|
||||||
const t = Number(player.currentTime());
|
const t = Number(player.currentTime());
|
||||||
if (isFinite(t) && Math.abs(Number(audioEl.currentTime) - t) > 0.1) safeSetCT(audioEl, t);
|
if (isFinite(t) && Math.abs(Number(audioEl.currentTime) - t) > 0.1) safeSetCT(audioEl, t);
|
||||||
setupMediaSession();
|
if (!spinnerUp() && bothPlayableAt(t)) playBoth();
|
||||||
if (!isBufferingUI() && bothPlayableAt(t)) playBoth();
|
|
||||||
else { audioEl.pause(); player.pause(); }
|
else { audioEl.pause(); player.pause(); }
|
||||||
|
setupMediaSession();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// wire readiness unless medium quality shortcut
|
// readiness wires
|
||||||
if (qua !== "medium") {
|
if (qua !== "medium") {
|
||||||
attachReady(audioEl, pickAudioSrc, () => { audioReady = true; });
|
attachReady(audioEl, pickAudioSrc, () => { audioReady = true; });
|
||||||
attachReady(videoEl, () => {
|
attachReady(videoEl, () => {
|
||||||
@ -527,13 +489,12 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
}, () => { videoReady = true; });
|
}, () => { videoReady = true; });
|
||||||
}
|
}
|
||||||
|
|
||||||
// one-time click to satisfy autoplay policies
|
// one-tap gesture nudge
|
||||||
player.ready(() => {
|
player.ready(() => {
|
||||||
const tryKick = () => {
|
const tryKick = () => {
|
||||||
if (audioReady && videoReady && player.paused()) {
|
if (audioReady && videoReady && player.paused()) {
|
||||||
const t = Number(player.currentTime());
|
const t = Number(player.currentTime());
|
||||||
if (!isBufferingUI() && bothPlayableAt(t)) playBoth();
|
if (!spinnerUp() && bothPlayableAt(t)) playBoth();
|
||||||
else resumeAfterBuffer = true;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
player.el().addEventListener("click", tryKick, { once: true });
|
player.el().addEventListener("click", tryKick, { once: true });
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user