make stuff work good
This commit is contained in:
parent
7a15b8c143
commit
0e97b07a3c
@ -14,7 +14,6 @@ var versionclient = "youtube.player.web_20250917_22_RC00"
|
|||||||
* Available under Apache License Version 2.0
|
* Available under Apache License Version 2.0
|
||||||
* <https://github.com/mozilla/vtt.js/blob/main/LICENSE>
|
* <https://github.com/mozilla/vtt.js/blob/main/LICENSE>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
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', {
|
||||||
@ -104,6 +103,11 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
let prevAudioMuted = false;
|
let prevAudioMuted = false;
|
||||||
let pendingUnmute = false;
|
let pendingUnmute = false;
|
||||||
|
|
||||||
|
// user intent + buffering flags
|
||||||
|
let userWantsPlay = false; // true when user pressed play (we’ll start as soon as it’s valid)
|
||||||
|
let videoBuffering = false;
|
||||||
|
let audioBuffering = false;
|
||||||
|
|
||||||
// New state: track whether user explicitly muted/unmuted
|
// New state: track whether user explicitly muted/unmuted
|
||||||
let userMutedVideo = false;
|
let userMutedVideo = false;
|
||||||
let userMutedAudio = false;
|
let userMutedAudio = false;
|
||||||
@ -174,6 +178,29 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// status / notice box (uses your loopedIndicator element)
|
||||||
|
const noticeBox = document.getElementById('loopedIndicator');
|
||||||
|
let statusHideTimer = null;
|
||||||
|
function showNotice(msg) {
|
||||||
|
if (!noticeBox) return;
|
||||||
|
try {
|
||||||
|
noticeBox.textContent = msg;
|
||||||
|
noticeBox.style.display = 'block';
|
||||||
|
noticeBox.style.width = 'fit-content';
|
||||||
|
if (statusHideTimer) { clearTimeout(statusHideTimer); statusHideTimer = null; }
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
function hideNotice(withDelay = 300) {
|
||||||
|
if (!noticeBox) return;
|
||||||
|
try {
|
||||||
|
if (statusHideTimer) clearTimeout(statusHideTimer);
|
||||||
|
statusHideTimer = setTimeout(() => {
|
||||||
|
noticeBox.style.display = 'none';
|
||||||
|
}, Math.max(0, withDelay));
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sync loop is cleared
|
||||||
function clearSyncLoop() {
|
function clearSyncLoop() {
|
||||||
if (syncInterval) {
|
if (syncInterval) {
|
||||||
clearInterval(syncInterval);
|
clearInterval(syncInterval);
|
||||||
@ -182,6 +209,21 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// helpers to decide if we can start or continue playback
|
||||||
|
function readyForPlayback() {
|
||||||
|
const t = Number(video.currentTime());
|
||||||
|
return videoReady && audioReady && bothPlayableAt(t) && !videoBuffering && !audioBuffering;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensurePausedTogether() {
|
||||||
|
try { video.pause(); } catch {}
|
||||||
|
try { audio.pause(); } catch {}
|
||||||
|
clearSyncLoop();
|
||||||
|
vIsPlaying = false;
|
||||||
|
aIsPlaying = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// playback is kept in sync between both elements
|
||||||
function startSyncLoop() {
|
function startSyncLoop() {
|
||||||
clearSyncLoop();
|
clearSyncLoop();
|
||||||
syncInterval = setInterval(() => {
|
syncInterval = setInterval(() => {
|
||||||
@ -231,13 +273,16 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
}, SYNC_INTERVAL_MS);
|
}, SYNC_INTERVAL_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we track playback state
|
||||||
const markVPlaying = () => { vIsPlaying = true; maybeUnmuteRestore(); };
|
const markVPlaying = () => { vIsPlaying = true; maybeUnmuteRestore(); };
|
||||||
const markAPlaying = () => { aIsPlaying = true; maybeUnmuteRestore(); };
|
const markAPlaying = () => { aIsPlaying = true; maybeUnmuteRestore(); };
|
||||||
const markVNotPlaying = () => { vIsPlaying = false; };
|
const markVNotPlaying = () => { vIsPlaying = false; };
|
||||||
const markANotPlaying = () => { aIsPlaying = false; };
|
const markANotPlaying = () => { aIsPlaying = false; };
|
||||||
|
|
||||||
|
// we know both are active
|
||||||
function bothActivelyPlaying() { return vIsPlaying && aIsPlaying; }
|
function bothActivelyPlaying() { return vIsPlaying && aIsPlaying; }
|
||||||
|
|
||||||
|
// both get unmuted when ready
|
||||||
function maybeUnmuteRestore() {
|
function maybeUnmuteRestore() {
|
||||||
if (!pendingUnmute) return;
|
if (!pendingUnmute) return;
|
||||||
if (bothActivelyPlaying()) {
|
if (bothActivelyPlaying()) {
|
||||||
@ -249,6 +294,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// helper: promise play with result
|
||||||
async function tryPlay(el) {
|
async function tryPlay(el) {
|
||||||
try {
|
try {
|
||||||
const p = el.play();
|
const p = el.play();
|
||||||
@ -257,9 +303,26 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
} catch { return false; }
|
} catch { return false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gate play attempts while loading/buffering
|
||||||
|
function blockIfNotReadyThenQueue() {
|
||||||
|
if (!readyForPlayback()) {
|
||||||
|
userWantsPlay = true; // remember intent
|
||||||
|
showNotice('Buffering…');
|
||||||
|
ensurePausedTogether();
|
||||||
|
return true; // blocked
|
||||||
|
}
|
||||||
|
hideNotice(200);
|
||||||
|
return false; // not blocked
|
||||||
|
}
|
||||||
|
|
||||||
|
// both play in sync
|
||||||
async function playTogether({ allowMutedRetry = true } = {}) {
|
async function playTogether({ allowMutedRetry = true } = {}) {
|
||||||
if (syncing || restarting) return;
|
if (syncing || restarting) return;
|
||||||
|
// don’t let it play when audio is loading or video is loading
|
||||||
|
if (blockIfNotReadyThenQueue()) return;
|
||||||
|
|
||||||
syncing = true;
|
syncing = true;
|
||||||
|
userWantsPlay = true; // explicit intent to be playing
|
||||||
try {
|
try {
|
||||||
const t = Number(video.currentTime());
|
const t = Number(video.currentTime());
|
||||||
if (isFinite(t) && Math.abs(Number(audio.currentTime) - t) > 0.05) {
|
if (isFinite(t) && Math.abs(Number(audio.currentTime) - t) > 0.05) {
|
||||||
@ -285,16 +348,16 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
if (!vOk && aOk) {
|
if (!vOk && aOk) {
|
||||||
try {
|
try {
|
||||||
video.play().catch(() => {
|
video.play().catch(() => {
|
||||||
showError('Video failed to start.');
|
showNotice('Video failed to start.');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
video.play().catch(() => showError('Video retry failed.'));
|
video.play().catch(() => showNotice('Video retry failed.'));
|
||||||
}, 3000);
|
}, 3000);
|
||||||
});
|
});
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
if (vOk && !aOk) {
|
if (vOk && !aOk) {
|
||||||
try {
|
try {
|
||||||
audio.play().catch(() => showError('Audio failed to start.'));
|
audio.play().catch(() => showNotice('Audio failed to start.'));
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,18 +367,25 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// both pause at once
|
||||||
function pauseTogether() {
|
function pauseTogether() {
|
||||||
if (syncing) return;
|
if (syncing) return;
|
||||||
syncing = true;
|
syncing = true;
|
||||||
try {
|
try {
|
||||||
|
userWantsPlay = false; // user paused explicitly
|
||||||
video.pause();
|
video.pause();
|
||||||
audio.pause();
|
audio.pause();
|
||||||
clearSyncLoop();
|
clearSyncLoop();
|
||||||
|
showNotice('Paused');
|
||||||
|
hideNotice(600);
|
||||||
} finally {
|
} finally {
|
||||||
syncing = false;
|
syncing = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// soooo i know what ur gonna say, its a looped indicator but ur using for errors, and what?
|
||||||
|
// like, why not i use the same element for both its not illegal...i think?
|
||||||
|
// search it up lol
|
||||||
const errorBox = document.getElementById('loopedIndicator');
|
const errorBox = document.getElementById('loopedIndicator');
|
||||||
function showError(msg) {
|
function showError(msg) {
|
||||||
if (errorBox) {
|
if (errorBox) {
|
||||||
@ -327,6 +397,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
|
|
||||||
const clamp = v => Math.max(0, Math.min(1, Number(v)));
|
const clamp = v => Math.max(0, Math.min(1, Number(v)));
|
||||||
|
|
||||||
|
// media session controls work, these are legit so anoying to work with
|
||||||
function setupMediaSession() {
|
function setupMediaSession() {
|
||||||
if ('mediaSession' in navigator) {
|
if ('mediaSession' in navigator) {
|
||||||
try {
|
try {
|
||||||
@ -361,6 +432,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// progress save/restore (safe bounds)
|
||||||
const PROGRESS_KEY = `progress-${vidKey}`;
|
const PROGRESS_KEY = `progress-${vidKey}`;
|
||||||
function restoreProgress() {
|
function restoreProgress() {
|
||||||
try {
|
try {
|
||||||
@ -369,47 +441,68 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
if (isFinite(saved) && saved > 3 && dur && saved < (dur - 10)) {
|
if (isFinite(saved) && saved > 3 && dur && saved < (dur - 10)) {
|
||||||
video.currentTime(saved);
|
video.currentTime(saved);
|
||||||
safeSetCT(audio, saved);
|
safeSetCT(audio, saved);
|
||||||
firstSeekDone = true;
|
firstSeekDone = true; // prevent immediate reseek jitter
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
function saveProgressThrottled() {
|
function saveProgressThrottled() {
|
||||||
|
// simple throttle by modulo of seconds
|
||||||
try {
|
try {
|
||||||
const t = Math.floor(Number(video.currentTime()) || 0);
|
const t = Math.floor(Number(video.currentTime()) || 0);
|
||||||
if (t % 2 === 0) localStorage.setItem(PROGRESS_KEY, String(t));
|
if (t % 2 === 0) localStorage.setItem(PROGRESS_KEY, String(t));
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
function wireResilience(el, label) {
|
// network resilience (stall/waiting recovery)
|
||||||
|
function wireResilience(el, label, isVideo) {
|
||||||
try {
|
try {
|
||||||
el.addEventListener('waiting', () => {
|
el.addEventListener('waiting', () => {
|
||||||
// when video or audio goes into waiting, pause both to avoid audio ghosting
|
// when video or audio goes into waiting, pause both to avoid audio ghosting
|
||||||
showError(`${label} waiting…`);
|
showNotice('Buffering…');
|
||||||
try { audio.pause(); } catch {}
|
if (isVideo) videoBuffering = true; else audioBuffering = true;
|
||||||
try { video.pause(); } catch {}
|
ensurePausedTogether();
|
||||||
});
|
});
|
||||||
el.addEventListener('stalled', () => {
|
el.addEventListener('stalled', () => {
|
||||||
showError(`${label} stalled`);
|
showNotice(`${label} stalled`);
|
||||||
// also pause the other
|
if (isVideo) videoBuffering = true; else audioBuffering = true;
|
||||||
try { audio.pause(); } catch {}
|
ensurePausedTogether();
|
||||||
try { video.pause(); } catch {}
|
|
||||||
});
|
});
|
||||||
el.addEventListener('suspend', () => {
|
el.addEventListener('suspend', () => {
|
||||||
// no-op
|
/* no-op; advisory */
|
||||||
});
|
});
|
||||||
el.addEventListener('emptied', () => {
|
el.addEventListener('emptied', () => {
|
||||||
showError(`${label} source emptied`);
|
showNotice(`${label} source emptied`);
|
||||||
try { audio.pause(); } catch {}
|
if (isVideo) videoBuffering = true; else audioBuffering = true;
|
||||||
try { video.pause(); } catch {}
|
ensurePausedTogether();
|
||||||
});
|
});
|
||||||
el.addEventListener('error', () => {
|
el.addEventListener('error', () => {
|
||||||
showError(`${label} error`);
|
showNotice(`${label} error`);
|
||||||
try { audio.pause(); } catch {}
|
if (isVideo) videoBuffering = true; else audioBuffering = true;
|
||||||
try { video.pause(); } catch {}
|
ensurePausedTogether();
|
||||||
|
});
|
||||||
|
// clear buffering flags on canplay / playing
|
||||||
|
el.addEventListener('canplay', () => {
|
||||||
|
if (isVideo) videoBuffering = false; else audioBuffering = false;
|
||||||
|
if (readyForPlayback() && userWantsPlay) {
|
||||||
|
hideNotice(150);
|
||||||
|
playTogether({ allowMutedRetry: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
el.addEventListener('playing', () => {
|
||||||
|
if (isVideo) videoBuffering = false; else audioBuffering = false;
|
||||||
|
if (readyForPlayback()) hideNotice(200);
|
||||||
|
});
|
||||||
|
el.addEventListener('canplaythrough', () => {
|
||||||
|
if (isVideo) videoBuffering = false; else audioBuffering = false;
|
||||||
|
if (readyForPlayback() && userWantsPlay) {
|
||||||
|
hideNotice(150);
|
||||||
|
playTogether({ allowMutedRetry: true });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// guards to ensure elements exist before heavy sync logic
|
||||||
const hasExternalAudio = !!audio && audio.tagName === 'AUDIO' && !!pickAudioSrc();
|
const hasExternalAudio = !!audio && audio.tagName === 'AUDIO' && !!pickAudioSrc();
|
||||||
|
|
||||||
if (qua !== "medium" && hasExternalAudio) {
|
if (qua !== "medium" && hasExternalAudio) {
|
||||||
@ -428,13 +521,19 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
|
|
||||||
const tryStart = () => {
|
const tryStart = () => {
|
||||||
if (audioReady && videoReady && !restarting) {
|
if (audioReady && videoReady && !restarting) {
|
||||||
|
// restore progress once we know duration/metadata
|
||||||
restoreProgress();
|
restoreProgress();
|
||||||
|
|
||||||
const t = Number(video.currentTime());
|
const t = Number(video.currentTime());
|
||||||
if (isFinite(t) && Math.abs(Number(audio.currentTime) - t) > 0.1) {
|
if (isFinite(t) && Math.abs(Number(audio.currentTime) - t) > 0.1) {
|
||||||
safeSetCT(audio, t);
|
safeSetCT(audio, t);
|
||||||
}
|
}
|
||||||
if (bothPlayableAt(t)) playTogether({ allowMutedRetry: true });
|
if (bothPlayableAt(t)) {
|
||||||
else pauseTogether();
|
// don’t auto-play unless user asked; just sync readiness
|
||||||
|
if (userWantsPlay) playTogether({ allowMutedRetry: true });
|
||||||
|
} else {
|
||||||
|
ensurePausedTogether();
|
||||||
|
}
|
||||||
setupMediaSession();
|
setupMediaSession();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -442,45 +541,40 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
attachRetry(audio, pickAudioSrc, () => { audioReady = true; });
|
attachRetry(audio, pickAudioSrc, () => { audioReady = true; });
|
||||||
attachRetry(videoEl, () => videoSrc, () => { videoReady = true; });
|
attachRetry(videoEl, () => videoSrc, () => { videoReady = true; });
|
||||||
|
|
||||||
// Listen for manual mute/unmute on video
|
// todo: fiixxx mute stuff lol
|
||||||
video.on('volumechange', () => {
|
video.on('volumechange', () => {
|
||||||
try {
|
try {
|
||||||
const isMuted = video.muted();
|
const isMuted = video.muted();
|
||||||
// If user toggles mute, record intent
|
// If user toggles mute, record intent
|
||||||
if (isMuted !== userMutedVideo) {
|
userMutedVideo = !!isMuted;
|
||||||
userMutedVideo = isMuted;
|
|
||||||
}
|
|
||||||
if (!isMuted && !userMutedVideo) {
|
|
||||||
// sync audio mute
|
|
||||||
audio.muted = false;
|
|
||||||
}
|
|
||||||
if (audio) {
|
if (audio) {
|
||||||
audio.muted = isMuted;
|
audio.muted = isMuted;
|
||||||
}
|
}
|
||||||
if (!video.muted()) {
|
if (!isMuted) {
|
||||||
audio.volume = clamp(video.volume());
|
audio.volume = clamp(video.volume());
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Listen manual mute/unmute on audio (if exposed)
|
// Listen manual mute/unmute on audio (if exposed somewhere)
|
||||||
audio.addEventListener('volumechange', () => {
|
audio.addEventListener('volumechange', () => {
|
||||||
try {
|
try {
|
||||||
const isMuted = audio.muted;
|
userMutedAudio = !!audio.muted;
|
||||||
if (isMuted !== userMutedAudio) {
|
|
||||||
userMutedAudio = isMuted;
|
|
||||||
}
|
|
||||||
if (!userMutedAudio && !userMutedAudio) {
|
|
||||||
// sync video mute
|
|
||||||
video.muted(false);
|
|
||||||
}
|
|
||||||
} catch {}
|
} catch {}
|
||||||
});
|
});
|
||||||
|
|
||||||
video.on('ratechange', () => { try { audio.playbackRate = video.playbackRate(); } catch {} });
|
video.on('ratechange', () => { try { audio.playbackRate = video.playbackRate(); } catch {} });
|
||||||
|
|
||||||
|
// capture user play intent; if not ready, queue and block actual play so one click is enough
|
||||||
video.on('play', () => {
|
video.on('play', () => {
|
||||||
vIsPlaying = true;
|
userWantsPlay = true;
|
||||||
|
if (!readyForPlayback()) {
|
||||||
|
showNotice('Buffering…');
|
||||||
|
ensurePausedTogether();
|
||||||
|
// will auto-resume on canplay/canplaythrough
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
markVPlaying();
|
||||||
if (!aIsPlaying) playTogether();
|
if (!aIsPlaying) playTogether();
|
||||||
});
|
});
|
||||||
audio.addEventListener('play', () => {
|
audio.addEventListener('play', () => {
|
||||||
@ -502,12 +596,16 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// large seeks pause and resync
|
||||||
let wasPlayingBeforeSeek = false;
|
let wasPlayingBeforeSeek = false;
|
||||||
let lastSeekTime = 0;
|
let lastSeekTime = 0;
|
||||||
video.on('seeking', () => {
|
video.on('seeking', () => {
|
||||||
if (restarting) return;
|
if (restarting) return;
|
||||||
wasPlayingBeforeSeek = !video.paused();
|
wasPlayingBeforeSeek = !video.paused();
|
||||||
lastSeekTime = Number(video.currentTime());
|
lastSeekTime = Number(video.currentTime());
|
||||||
|
// while seeking, block start until canplay
|
||||||
|
showNotice('Buffering…');
|
||||||
|
ensurePausedTogether();
|
||||||
});
|
});
|
||||||
|
|
||||||
video.on('seeked', () => {
|
video.on('seeked', () => {
|
||||||
@ -524,39 +622,53 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (seekDiff > threshold) {
|
if (seekDiff > threshold) {
|
||||||
pauseTogether();
|
ensurePausedTogether();
|
||||||
safeSetCT(audio, newTime);
|
safeSetCT(audio, newTime);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (wasPlayingBeforeSeek && bothPlayableAt(newTime)) {
|
if (wasPlayingBeforeSeek) {
|
||||||
playTogether({ allowMutedRetry: true });
|
userWantsPlay = true;
|
||||||
|
if (readyForPlayback()) playTogether({ allowMutedRetry: true });
|
||||||
|
else showNotice('Buffering…');
|
||||||
}
|
}
|
||||||
}, 180);
|
}, 180);
|
||||||
} else {
|
} else {
|
||||||
safeSetCT(audio, newTime);
|
safeSetCT(audio, newTime);
|
||||||
|
if (wasPlayingBeforeSeek) {
|
||||||
|
userWantsPlay = true;
|
||||||
|
if (readyForPlayback()) playTogether({ allowMutedRetry: true });
|
||||||
|
else showNotice('Buffering…');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// save progress periodically
|
||||||
try {
|
try {
|
||||||
video.on('timeupdate', saveProgressThrottled);
|
video.on('timeupdate', saveProgressThrottled);
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
wireResilience(videoEl, 'Video');
|
// wire stall/err resilience
|
||||||
wireResilience(audio, 'Audio');
|
wireResilience(videoEl, 'Video', true);
|
||||||
|
wireResilience(audio, 'Audio', false);
|
||||||
|
|
||||||
|
// looping restarts properly
|
||||||
|
// doesnt work LOOOOOOOOL
|
||||||
|
// sooo... I guess, TODO: fix the looping??????
|
||||||
async function restartLoop() {
|
async function restartLoop() {
|
||||||
if (restarting) return;
|
if (restarting) return;
|
||||||
restarting = true;
|
restarting = true;
|
||||||
try {
|
try {
|
||||||
clearSyncLoop();
|
clearSyncLoop();
|
||||||
pauseTogether();
|
ensurePausedTogether();
|
||||||
const startAt = 0.001;
|
const startAt = 0.001;
|
||||||
suppressEndedUntil = performance.now() + 800;
|
suppressEndedUntil = performance.now() + 800;
|
||||||
video.currentTime(startAt);
|
video.currentTime(startAt);
|
||||||
safeSetCT(audio, startAt);
|
safeSetCT(audio, startAt);
|
||||||
|
userWantsPlay = true;
|
||||||
await playTogether();
|
await playTogether();
|
||||||
} finally { restarting = false; }
|
} finally { restarting = false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// okay, this actually, legit, not working idk why guuuh
|
||||||
video.on('ended', () => {
|
video.on('ended', () => {
|
||||||
if (restarting) return;
|
if (restarting) return;
|
||||||
if (performance.now() < suppressEndedUntil) return;
|
if (performance.now() < suppressEndedUntil) return;
|
||||||
@ -570,17 +682,23 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
else pauseTogether();
|
else pauseTogether();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// try to resume if media becomes playable again after waiting
|
||||||
videoEl.addEventListener('canplay', () => {
|
videoEl.addEventListener('canplay', () => {
|
||||||
if (!video.paused() && !audio.paused()) return;
|
videoBuffering = false;
|
||||||
const t = Number(video.currentTime());
|
if (readyForPlayback() && userWantsPlay) {
|
||||||
if (bothPlayableAt(t)) playTogether({ allowMutedRetry: true });
|
hideNotice(150);
|
||||||
|
playTogether({ allowMutedRetry: true });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
audio.addEventListener('canplay', () => {
|
audio.addEventListener('canplay', () => {
|
||||||
if (!video.paused() && !audio.paused()) return;
|
audioBuffering = false;
|
||||||
const t = Number(video.currentTime());
|
if (readyForPlayback() && userWantsPlay) {
|
||||||
if (bothPlayableAt(t)) playTogether({ allowMutedRetry: true });
|
hideNotice(150);
|
||||||
|
playTogether({ allowMutedRetry: true });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// clean up on unload to avoid stray timers
|
||||||
try {
|
try {
|
||||||
window.addEventListener('pagehide', () => { clearSyncLoop(); });
|
window.addEventListener('pagehide', () => { clearSyncLoop(); });
|
||||||
window.addEventListener('beforeunload', () => {
|
window.addEventListener('beforeunload', () => {
|
||||||
@ -591,14 +709,28 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
} catch {}
|
} catch {}
|
||||||
} else {
|
} else {
|
||||||
// fallback when medium quality (no external <audio>) or audio element missing:
|
// fallback when medium quality (no external <audio>) or audio element missing:
|
||||||
|
// keep Media Session + progress for the single video element so UX is still solid
|
||||||
try {
|
try {
|
||||||
video.on('timeupdate', () => {
|
video.on('timeupdate', () => {
|
||||||
try { localStorage.setItem(`progress-${vidKey}`, String(Math.floor(Number(video.currentTime()) || 0))); } catch {}
|
try { localStorage.setItem(`progress-${vidKey}`, String(Math.floor(Number(video.currentTime()) || 0))); } catch {}
|
||||||
});
|
});
|
||||||
} catch {}
|
} catch {}
|
||||||
setupMediaSession();
|
setupMediaSession();
|
||||||
|
|
||||||
|
// also gate play here if the single stream is buffering
|
||||||
|
video.on('play', () => {
|
||||||
|
userWantsPlay = true;
|
||||||
|
if (!(videoReady && canPlayAt(videoEl, Number(video.currentTime())) && !videoBuffering)) {
|
||||||
|
showNotice('Buffering…');
|
||||||
|
video.pause();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
videoEl.addEventListener('waiting', () => { videoBuffering = true; showNotice('Buffering…'); video.pause(); });
|
||||||
|
videoEl.addEventListener('canplay', () => { videoBuffering = false; if (userWantsPlay) { hideNotice(150); video.play().catch(()=>{}); }});
|
||||||
|
videoEl.addEventListener('playing', () => { videoBuffering = false; hideNotice(200); });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// https://codeberg.org/ashley/poke/src/branch/main/src/libpoketube/libpoketube-youtubei-objects.json
|
// https://codeberg.org/ashley/poke/src/branch/main/src/libpoketube/libpoketube-youtubei-objects.json
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user