diff --git a/css/player-base.js b/css/player-base.js index 95132a70..fbeeca6b 100644 --- a/css/player-base.js +++ b/css/player-base.js @@ -3,543 +3,567 @@ var _yt_player = videojs; var versionclient = "youtube.player.web_20250917_22_RC00" - document.addEventListener("DOMContentLoaded", () => { - // video.js 8 init - source can be seen in https://poketube.fun/static/vjs.min.js or the vjs.min.js file - const video = videojs('video', { - controls: true, - autoplay: false, - preload: 'auto', - errorDisplay: false, - }); - - // todo : remove this code lol - const qs = new URLSearchParams(window.location.search); - const qua = qs.get("quality") || ""; - const vidKey = qs.get('v'); - try { localStorage.setItem(`progress-${vidKey}`, 0); } catch {} - - // raw media elements - const videoEl = document.getElementById('video'); - const audio = document.getElementById('aud'); - const audioEl = document.getElementById('aud'); - let volGuard = false; - - // FIX: ensure inline playback hint for iOS/Safari - try { videoEl.setAttribute('playsinline', ''); videoEl.setAttribute('webkit-playsinline', ''); } catch {} - - // global anti-ping-pong guard - let syncing = false; // prevents normal ping-pong - let restarting = false; // prevents loop-end ping-pong - - // FIX: explicit loop-state variables + +document.addEventListener("DOMContentLoaded", () => { + // video.js 8 init - source can be seen in https://poketube.fun/static/vjs.min.js or the vjs.min.js file + const video = videojs('video', { + controls: true, + autoplay: false, + preload: 'auto', + errorDisplay: false, + }); + + // todo : remove this code lol + const qs = new URLSearchParams(window.location.search); + const qua = qs.get("quality") || ""; + const vidKey = qs.get('v'); + try { localStorage.setItem(`progress-${vidKey}`, 0); } catch {} + + // raw media elements + const videoEl = document.getElementById('video'); + const audio = document.getElementById('aud'); + const audioEl = document.getElementById('aud'); + let volGuard = false; + + // FIX: ensure inline playback hint for iOS/Safari + try { videoEl.setAttribute('playsinline', ''); videoEl.setAttribute('webkit-playsinline', ''); } catch {} + + // NEW: keep hidden audio eager and ready sooner (helps first-seek stability) + try { audio.setAttribute('preload', 'auto'); } catch {} + + // global anti-ping-pong guard + let syncing = false; // prevents normal ping-pong + let restarting = false; // prevents loop-end ping-pong + + // FIX: explicit loop-state variables let desiredLoop = !!videoEl.loop || qs.get("loop") === "1" || qs.get("loop") === "true" || - window.forceLoop === true; - - // FIX: tracks the short window *during* a loop restart - let suppressEndedUntil = 0; - - // FIX: co-play tracking flags (true only when each element fires 'playing') - let vIsPlaying = false; - let aIsPlaying = false; - - // remember mute states for temporary autoplay retries - let prevVideoMuted = false; - let prevAudioMuted = false; - let pendingUnmute = false; - - // FIX: seeking coordination (prevents first-load seek ping-pong) - let seekingInProgress = false; - let resumeAfterSeek = false; + window.forceLoop === true; - // NEW FIX: grace window to silence arbiter + bridge immediately after seeks / restarts - let suppressBridgeUntil = 0; - const nowPerf = () => performance?.now?.() || Date.now(); - const bridgingSilenced = () => nowPerf() < suppressBridgeUntil; - function silenceBridging(ms = 900) { // tuned: long enough for decoders to settle after a seek - suppressBridgeUntil = nowPerf() + ms; + // FIX: tracks the short window *during* a loop restart + let suppressEndedUntil = 0; + + // FIX: co-play tracking flags (true only when each element fires 'playing') + let vIsPlaying = false; + let aIsPlaying = false; + + // remember mute states for temporary autoplay retries + let prevVideoMuted = false; + let prevAudioMuted = false; + let pendingUnmute = false; + + // FIX: seeking coordination (prevents first-load seek ping-pong) + let seekingInProgress = false; + let resumeAfterSeek = false; + + // NEW: short quiesce windows to ignore chatty events after seek or coordinator actions + const SEEK_QUIESCE_MS = 700; // time to ignore play/pause/arbiter after seek + const ACTION_COOLDOWN_MS = 550; // time to ignore arbiter after coordinated play/pause + let quiesceUntil = 0; // timestamp (ms) until which we ignore event noise + let arbiterCooldownUntil = 0; // timestamp (ms) for arbiter cool-down + + const now = () => performance.now(); + const inQuiesce = () => now() < quiesceUntil; + const inCooldown = () => now() < arbiterCooldownUntil; + + // FIX: state arbiter watchdog (forces both to share same paused/playing state) + let arbiterTimer = null; + const ARBITER_MS = 150; + function startArbiter() { + if (arbiterTimer) clearInterval(arbiterTimer); + arbiterTimer = setInterval(() => { + // NEW: do nothing during seek quiesce or cooldown; this is where ping-pong used to start + if (inQuiesce() || inCooldown() || syncing || restarting || seekingInProgress) return; + + // treat "playing" strictly; ended counts as paused + const vPlaying = !video.paused() && !video.ended(); + const aPlaying = !audio.paused && !audio.ended; + + // NEW: if both aren't actually playable at the current time, don't force a pause yet + const t = Number(video.currentTime()); + if (!bothPlayableAt(t)) return; + + // if exactly one is playing, pause the one that is playing (safe + deterministic) + if (vPlaying && !aPlaying) { + try { video.pause(); } catch {} + } else if (aPlaying && !vPlaying) { + try { audio.pause(); } catch {} + } + }, ARBITER_MS); } - - // FIX: state arbiter watchdog (forces both to share same paused/playing state) - let arbiterTimer = null; - const ARBITER_MS = 150; - function startArbiter() { - if (arbiterTimer) clearInterval(arbiterTimer); - arbiterTimer = setInterval(() => { - if (syncing || restarting || seekingInProgress || bridgingSilenced()) return; - - // treat "playing" strictly; ended counts as paused - const vPlaying = !video.paused() && !video.ended(); - const aPlaying = !audio.paused && !audio.ended; - - // if exactly one is playing, pause the one that is playing (safe + deterministic) - if (vPlaying && !aPlaying) { - try { video.pause(); } catch {} - } else if (aPlaying && !vPlaying) { - try { audio.pause(); } catch {} - } - }, ARBITER_MS); - } - function stopArbiter() { if (arbiterTimer) { clearInterval(arbiterTimer); arbiterTimer = null; } } - - // turn OFF native loop so 'ended' fires and we control both tracks together - try { videoEl.loop = false; videoEl.removeAttribute?.('loop'); } catch {} - try { audio.loop = false; audio.removeAttribute?.('loop'); } catch {} - - // If someone toggles the