fix stuff + add stuff
This commit is contained in:
parent
5f4689c3b9
commit
bf9a0ac97d
@ -3,7 +3,6 @@ var _yt_player = videojs;
|
|||||||
|
|
||||||
var versionclient = "youtube.player.web_20250917_22_RC00"
|
var versionclient = "youtube.player.web_20250917_22_RC00"
|
||||||
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
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
|
// 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', {
|
const video = videojs('video', {
|
||||||
@ -32,11 +31,6 @@ var versionclient = "youtube.player.web_20250917_22_RC00"
|
|||||||
let syncing = false; // prevents normal ping-pong
|
let syncing = false; // prevents normal ping-pong
|
||||||
let restarting = false; // prevents loop-end ping-pong
|
let restarting = false; // prevents loop-end ping-pong
|
||||||
|
|
||||||
// FIX: protected seek transaction + desired state
|
|
||||||
let seekingActive = false;
|
|
||||||
let seekToken = 0;
|
|
||||||
let desiredPlaying = false; // tracks user intent (play/pause) during seeks
|
|
||||||
|
|
||||||
// FIX: explicit loop-state variables
|
// FIX: explicit loop-state variables
|
||||||
let desiredLoop =
|
let desiredLoop =
|
||||||
!!videoEl.loop ||
|
!!videoEl.loop ||
|
||||||
@ -56,6 +50,10 @@ var versionclient = "youtube.player.web_20250917_22_RC00"
|
|||||||
let prevAudioMuted = false;
|
let prevAudioMuted = false;
|
||||||
let pendingUnmute = false;
|
let pendingUnmute = false;
|
||||||
|
|
||||||
|
// FIX: seeking coordination (prevents first-load seek ping-pong)
|
||||||
|
let seekingInProgress = false;
|
||||||
|
let resumeAfterSeek = false;
|
||||||
|
|
||||||
// turn OFF native loop so 'ended' fires and we control both tracks together
|
// turn OFF native loop so 'ended' fires and we control both tracks together
|
||||||
try { videoEl.loop = false; videoEl.removeAttribute?.('loop'); } catch {}
|
try { videoEl.loop = false; videoEl.removeAttribute?.('loop'); } catch {}
|
||||||
try { audio.loop = false; audio.removeAttribute?.('loop'); } catch {}
|
try { audio.loop = false; audio.removeAttribute?.('loop'); } catch {}
|
||||||
@ -100,10 +98,6 @@ var versionclient = "youtube.player.web_20250917_22_RC00"
|
|||||||
const MICRO_DRIFT = 0.05;
|
const MICRO_DRIFT = 0.05;
|
||||||
const SYNC_INTERVAL_MS = 250;
|
const SYNC_INTERVAL_MS = 250;
|
||||||
|
|
||||||
// FIX: seek classification thresholds
|
|
||||||
const LARGE_SEEK_SEC = 3.0; // "big" jump => pause immediately
|
|
||||||
const PREVIEW_SEEK_SEC = 1.5; // "small" jump => allow audio preview if playable
|
|
||||||
|
|
||||||
const EPS = 0.15;
|
const EPS = 0.15;
|
||||||
function timeInBuffered(media, t) {
|
function timeInBuffered(media, t) {
|
||||||
try {
|
try {
|
||||||
@ -188,10 +182,9 @@ var versionclient = "youtube.player.web_20250917_22_RC00"
|
|||||||
|
|
||||||
// FIX: unified play/pause coordinators (no ping-pong)
|
// FIX: unified play/pause coordinators (no ping-pong)
|
||||||
async function playTogether({ allowMutedRetry = true } = {}) {
|
async function playTogether({ allowMutedRetry = true } = {}) {
|
||||||
if (syncing || restarting || seekingActive) return; // don't fight seek
|
if (syncing || restarting || seekingInProgress) return; // FIX: don't start while seeking
|
||||||
syncing = true;
|
syncing = true;
|
||||||
try {
|
try {
|
||||||
desiredPlaying = true; // remember intent
|
|
||||||
// align clocks first
|
// align clocks first
|
||||||
const t = Number(video.currentTime());
|
const t = Number(video.currentTime());
|
||||||
if (isFinite(t) && Math.abs(Number(audio.currentTime) - t) > 0.05) safeSetCT(audio, t);
|
if (isFinite(t) && Math.abs(Number(audio.currentTime) - t) > 0.05) safeSetCT(audio, t);
|
||||||
@ -201,7 +194,7 @@ var versionclient = "youtube.player.web_20250917_22_RC00"
|
|||||||
try { const p = video.play(); if (p && p.then) await p; } catch { vOk = false; }
|
try { const p = video.play(); if (p && p.then) await p; } catch { vOk = false; }
|
||||||
try { const p = audio.play(); if (p && p.then) await p; } catch { aOk = false; }
|
try { const p = audio.play(); if (p && p.then) await p; } catch { aOk = false; }
|
||||||
|
|
||||||
// autoplay policy: retry muted once and restore later
|
// if either failed due to autoplay policy, retry both muted exactly once
|
||||||
if (allowMutedRetry && (!vOk || !aOk)) {
|
if (allowMutedRetry && (!vOk || !aOk)) {
|
||||||
prevVideoMuted = !!video.muted();
|
prevVideoMuted = !!video.muted();
|
||||||
prevAudioMuted = !!audio.muted;
|
prevAudioMuted = !!audio.muted;
|
||||||
@ -224,7 +217,6 @@ var versionclient = "youtube.player.web_20250917_22_RC00"
|
|||||||
if (syncing) return;
|
if (syncing) return;
|
||||||
syncing = true;
|
syncing = true;
|
||||||
try {
|
try {
|
||||||
desiredPlaying = false; // remember intent
|
|
||||||
try { video.pause(); } catch {}
|
try { video.pause(); } catch {}
|
||||||
try { audio.pause(); } catch {}
|
try { audio.pause(); } catch {}
|
||||||
clearSyncLoop();
|
clearSyncLoop();
|
||||||
@ -238,7 +230,7 @@ var versionclient = "youtube.player.web_20250917_22_RC00"
|
|||||||
const t = Number(video.currentTime());
|
const t = Number(video.currentTime());
|
||||||
if (isFinite(t) && Math.abs(Number(audio.currentTime) - t) > 0.1) safeSetCT(audio, t);
|
if (isFinite(t) && Math.abs(Number(audio.currentTime) - t) > 0.1) safeSetCT(audio, t);
|
||||||
if (bothPlayableAt(t)) {
|
if (bothPlayableAt(t)) {
|
||||||
// don't force autoplay on load; just prep media session
|
playTogether({ allowMutedRetry: true });
|
||||||
} else {
|
} else {
|
||||||
pauseTogether();
|
pauseTogether();
|
||||||
}
|
}
|
||||||
@ -352,15 +344,36 @@ var versionclient = "youtube.player.web_20250917_22_RC00"
|
|||||||
|
|
||||||
video.on('ratechange', () => { try { audio.playbackRate = video.playbackRate(); } catch {} });
|
video.on('ratechange', () => { try { audio.playbackRate = video.playbackRate(); } catch {} });
|
||||||
|
|
||||||
// FIX: event bridging uses coordinators, but is disabled during seek
|
// sync-safe event bridging using the coordinators (no ping-pong)
|
||||||
video.on('play', () => { if (seekingActive || restarting) return; vIsPlaying = true; if (!aIsPlaying) playTogether({ allowMutedRetry: true }); });
|
video.on('play', () => {
|
||||||
audio.addEventListener('play', () => { if (seekingActive || restarting) return; aIsPlaying = true; if (!vIsPlaying) playTogether({ allowMutedRetry: true }); });
|
if (seekingInProgress) return; // FIX
|
||||||
|
vIsPlaying = true;
|
||||||
|
if (!aIsPlaying) playTogether({ allowMutedRetry: true });
|
||||||
|
});
|
||||||
|
audio.addEventListener('play', () => {
|
||||||
|
if (seekingInProgress) return; // FIX
|
||||||
|
aIsPlaying = true;
|
||||||
|
if (!vIsPlaying) playTogether({ allowMutedRetry: true });
|
||||||
|
});
|
||||||
|
|
||||||
video.on('pause', () => { if (seekingActive || restarting) return; vIsPlaying = false; pauseTogether(); });
|
video.on('pause', () => {
|
||||||
audio.addEventListener('pause', () => { if (seekingActive || restarting) return; aIsPlaying = false; pauseTogether(); });
|
if (restarting || seekingInProgress) return; // FIX
|
||||||
|
vIsPlaying = false;
|
||||||
|
pauseTogether();
|
||||||
|
});
|
||||||
|
audio.addEventListener('pause', () => {
|
||||||
|
if (restarting || seekingInProgress) return; // FIX
|
||||||
|
aIsPlaying = false;
|
||||||
|
pauseTogether();
|
||||||
|
});
|
||||||
|
|
||||||
video.on('waiting', () => { if (restarting || seekingActive) return; vIsPlaying = false; try { audio.pause(); } catch{}; clearSyncLoop(); });
|
video.on('waiting', () => {
|
||||||
audio.addEventListener('waiting', () => { if (seekingActive) return; aIsPlaying = false; });
|
if (restarting || seekingInProgress) return; // FIX
|
||||||
|
vIsPlaying = false;
|
||||||
|
try { audio.pause(); } catch{};
|
||||||
|
clearSyncLoop();
|
||||||
|
});
|
||||||
|
audio.addEventListener('waiting', () => { aIsPlaying = false; });
|
||||||
|
|
||||||
video.on('playing', markVPlaying);
|
video.on('playing', markVPlaying);
|
||||||
audio.addEventListener('playing', markAPlaying);
|
audio.addEventListener('playing', markAPlaying);
|
||||||
@ -380,107 +393,56 @@ var versionclient = "youtube.player.web_20250917_22_RC00"
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---------- SMART SEEK HANDLING (fast pause for big jumps; audio preview for small) ----------
|
// FIX: tiny helper to wait until both are actually playable near a time
|
||||||
let wasPlayingBeforeSeek = false;
|
const waitUntilPlayable = (t, timeoutMs = 800) => new Promise(resolve => {
|
||||||
let lastTimeupdate = 0;
|
|
||||||
|
|
||||||
// track last "solid" time (used to classify seek size)
|
|
||||||
video.on('timeupdate', () => {
|
|
||||||
if (!seekingActive) {
|
|
||||||
const vt = Number(video.currentTime());
|
|
||||||
if (isFinite(vt)) lastTimeupdate = vt;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// helper: small-preview while seeking (if buffered)
|
|
||||||
function maybePreviewAudioDuringSeek(targetT, myToken) {
|
|
||||||
// only during active seek, only the latest seek, only when playable
|
|
||||||
if (!seekingActive || myToken !== seekToken) return;
|
|
||||||
const playable = canPlayAt(audio, targetT);
|
|
||||||
if (!playable) return;
|
|
||||||
try {
|
|
||||||
if (Math.abs(Number(audio.currentTime) - targetT) > 0.04) safeSetCT(audio, targetT);
|
|
||||||
if (desiredPlaying) {
|
|
||||||
const p = audio.play(); if (p && p.then) p.catch(()=>{});
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const waitUntilPlayable = (t, timeoutMs = 1000) => new Promise(resolve => {
|
|
||||||
const start = performance.now();
|
const start = performance.now();
|
||||||
const tick = () => {
|
const tick = () => {
|
||||||
const ok = bothPlayableAt(t);
|
if (bothPlayableAt(t)) return resolve(true);
|
||||||
if (ok) return resolve(true);
|
|
||||||
if (performance.now() - start > timeoutMs) return resolve(false);
|
if (performance.now() - start > timeoutMs) return resolve(false);
|
||||||
setTimeout(tick, 40);
|
setTimeout(tick, 40);
|
||||||
};
|
};
|
||||||
tick();
|
tick();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// suppress spurious 'ended' right after seeks (mobile/browser quirk guard)
|
||||||
|
let wasPlayingBeforeSeek = false;
|
||||||
|
|
||||||
video.on('seeking', () => {
|
video.on('seeking', () => {
|
||||||
if (restarting) return;
|
if (restarting) return;
|
||||||
seekingActive = true;
|
seekingInProgress = true; // FIX
|
||||||
seekToken++;
|
wasPlayingBeforeSeek = !video.paused();
|
||||||
|
resumeAfterSeek = wasPlayingBeforeSeek; // FIX
|
||||||
// classify seek size
|
try { audio.pause(); } catch {}
|
||||||
const vt = Number(video.currentTime());
|
|
||||||
const delta = Math.abs(vt - Number(lastTimeupdate || 0));
|
|
||||||
|
|
||||||
// remember intent at the *moment* seeking begins
|
|
||||||
wasPlayingBeforeSeek = desiredPlaying || !video.paused();
|
|
||||||
|
|
||||||
// always stop sync loop immediately
|
|
||||||
clearSyncLoop();
|
clearSyncLoop();
|
||||||
|
const vt = Number(video.currentTime());
|
||||||
if (delta >= LARGE_SEEK_SEC) {
|
if (Math.abs(vt - Number(audio.currentTime)) > 0.1) safeSetCT(audio, vt);
|
||||||
// BIG JUMP: pause *immediately* (no delay) to avoid ping-pong
|
vIsPlaying = false; aIsPlaying = false; // FIX
|
||||||
try { video.pause(); } catch {}
|
|
||||||
try { audio.pause(); } catch {}
|
|
||||||
vIsPlaying = false; aIsPlaying = false;
|
|
||||||
// keep clocks roughly aligned while dragging
|
|
||||||
if (Math.abs(vt - Number(audio.currentTime)) > 0.05) safeSetCT(audio, vt);
|
|
||||||
} else {
|
|
||||||
// SMALL JUMP: keep things responsive; don't hard-pause the video.js player
|
|
||||||
// but pause the audio only if the target isn't playable (avoid glitches)
|
|
||||||
if (!canPlayAt(audio, vt)) { try { audio.pause(); } catch {} aIsPlaying = false; }
|
|
||||||
// snap audio to target and (if ready) let it preview during the seek
|
|
||||||
if (Math.abs(vt - Number(audio.currentTime)) > 0.04) safeSetCT(audio, vt);
|
|
||||||
maybePreviewAudioDuringSeek(vt, seekToken);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
video.on('seeked', () => {
|
video.on('seeked', async () => {
|
||||||
if (restarting) return;
|
if (restarting) return;
|
||||||
const myToken = seekToken;
|
|
||||||
const vt = Number(video.currentTime());
|
const vt = Number(video.currentTime());
|
||||||
(async () => {
|
if (Math.abs(vt - Number(audio.currentTime)) > 0.05) safeSetCT(audio, vt);
|
||||||
// wait until both can actually play at vt (or timeout)
|
|
||||||
|
// FIX: only resume once the new point is playable; avoid first-load ping-pong
|
||||||
|
if (resumeAfterSeek) {
|
||||||
await waitUntilPlayable(vt, 1000);
|
await waitUntilPlayable(vt, 1000);
|
||||||
|
playTogether({ allowMutedRetry: false });
|
||||||
|
} else {
|
||||||
|
pauseTogether();
|
||||||
|
}
|
||||||
|
|
||||||
// realign just in case
|
seekingInProgress = false; // FIX
|
||||||
if (Math.abs(vt - Number(audio.currentTime)) > 0.05) safeSetCT(audio, vt);
|
resumeAfterSeek = false; // FIX
|
||||||
|
|
||||||
// only the latest seek unblocks
|
|
||||||
if (myToken !== seekToken) return;
|
|
||||||
|
|
||||||
seekingActive = false;
|
|
||||||
|
|
||||||
// apply remembered intent *immediately*, no post-seek delay
|
|
||||||
if (wasPlayingBeforeSeek || desiredPlaying) {
|
|
||||||
playTogether({ allowMutedRetry: true });
|
|
||||||
} else {
|
|
||||||
pauseTogether();
|
|
||||||
}
|
|
||||||
})().catch(()=>{ seekingActive = false; });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
video.on('canplaythrough', () => {
|
video.on('canplaythrough', () => {
|
||||||
if (restarting || seekingActive) return;
|
if (restarting || seekingInProgress) return; // FIX
|
||||||
const vt = Number(video.currentTime());
|
const vt = Number(video.currentTime());
|
||||||
if (Math.abs(vt - Number(audio.currentTime)) > 0.1) safeSetCT(audio, vt);
|
if (Math.abs(vt - Number(audio.currentTime)) > 0.1) safeSetCT(audio, vt);
|
||||||
});
|
});
|
||||||
audio.addEventListener('canplaythrough', () => {
|
audio.addEventListener('canplaythrough', () => {
|
||||||
if (restarting || seekingActive) return;
|
if (restarting || seekingInProgress) return; // FIX
|
||||||
const vt = Number(video.currentTime());
|
const vt = Number(video.currentTime());
|
||||||
if (Math.abs(vt - Number(audio.currentTime)) > 0.1) safeSetCT(audio, vt);
|
if (Math.abs(vt - Number(audio.currentTime)) > 0.1) safeSetCT(audio, vt);
|
||||||
});
|
});
|
||||||
@ -500,7 +462,7 @@ var versionclient = "youtube.player.web_20250917_22_RC00"
|
|||||||
video.currentTime(startAt);
|
video.currentTime(startAt);
|
||||||
safeSetCT(audio, startAt);
|
safeSetCT(audio, startAt);
|
||||||
|
|
||||||
// resume together; allow muted retry if policy blocks
|
await waitUntilPlayable(startAt, 1000);
|
||||||
await playTogether({ allowMutedRetry: true });
|
await playTogether({ allowMutedRetry: true });
|
||||||
} finally {
|
} finally {
|
||||||
restarting = false;
|
restarting = false;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user