From 5ac0a306beaf5f364ff11fedcf4a3ba19ec0522c Mon Sep 17 00:00:00 2001 From: codex Date: Wed, 10 Jun 2026 20:27:36 +0200 Subject: [PATCH] Retry failed synced YouTube starts --- public/app.js | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/public/app.js b/public/app.js index 66c5347..8f1026b 100644 --- a/public/app.js +++ b/public/app.js @@ -50,6 +50,7 @@ let youtubeReady = false; let currentYoutubeVideoId = ""; let applyingWatchStateUntil = 0; let pauseEnforcementUntil = 0; +let syncedPlaybackRetryUntil = 0; let desiredYoutubePlaying = false; let pendingWatchState = null; let pendingWatchStateOptions = null; @@ -332,6 +333,8 @@ async function ensureYoutubePlayer() { disablekb: 0, modestbranding: 1, playsinline: 1, + autoplay: 1, + origin: window.location.origin, rel: 0 }, events: { @@ -353,6 +356,13 @@ async function ensureYoutubePlayer() { return youtubePlayer; } +function loadYoutubeVideo(player, videoId, position) { + player.loadVideoById({ + videoId, + startSeconds: position + }); +} + function requestYoutubePlayback(player, videoId, position) { const requestId = ++youtubePlaybackRequestId; const startedAt = Date.now(); @@ -373,6 +383,9 @@ function requestYoutubePlayback(player, videoId, position) { const elapsed = Math.max(0, (Date.now() - startedAt) / 1000); const nextPosition = Math.max(0, position + elapsed); const loadedVideoId = player.getVideoData?.().video_id; + if (loadedVideoId !== videoId) { + loadYoutubeVideo(player, videoId, nextPosition); + } if ( loadedVideoId === videoId && ![YT.PlayerState.CUED, YT.PlayerState.UNSTARTED].includes(playerState) @@ -426,7 +439,17 @@ function currentWatchState(playing) { } function handleYoutubeStateChange(event) { - if (Date.now() < applyingWatchStateUntil) return; + const now = Date.now(); + if (now < applyingWatchStateUntil) return; + if ( + desiredYoutubePlaying && + now < syncedPlaybackRetryUntil && + [YT.PlayerState.UNSTARTED, YT.PlayerState.CUED, YT.PlayerState.PAUSED].includes(event.data) + ) { + requestYoutubePlayback(youtubePlayer, currentYoutubeVideoId, currentYoutubeSnapshot().position); + return; + } + if (![YT.PlayerState.PLAYING, YT.PlayerState.PAUSED, YT.PlayerState.ENDED].includes(event.data)) { return; } @@ -442,6 +465,9 @@ function handleYoutubeStateChange(event) { const action = event.data === YT.PlayerState.PLAYING ? "play" : "pause"; desiredYoutubePlaying = action === "play"; + if (desiredYoutubePlaying) { + syncedPlaybackRetryUntil = 0; + } const state = currentWatchState(action === "play"); if (state) { state.action = action; @@ -477,6 +503,7 @@ async function applyWatchState(state, { suppressEvents = true } = {}) { if (!currentYoutubeVideoId) { desiredYoutubePlaying = false; youtubePlaybackRequestId++; + syncedPlaybackRetryUntil = 0; youtubePlayer?.stopVideo?.(); youtubeShell.classList.remove("is-active"); setWatchCollapsed(true); @@ -494,8 +521,12 @@ async function applyWatchState(state, { suppressEvents = true } = {}) { } if (shouldPlay) { pauseEnforcementUntil = 0; + if (suppressEvents) { + syncedPlaybackRetryUntil = Date.now() + 8000; + } } else { youtubePlaybackRequestId++; + syncedPlaybackRetryUntil = 0; pauseEnforcementUntil = Math.max(pauseEnforcementUntil, Date.now() + 3000); } @@ -509,7 +540,7 @@ async function applyWatchState(state, { suppressEvents = true } = {}) { if (sameVideo && ![YT.PlayerState.UNSTARTED, YT.PlayerState.CUED].includes(playerState)) { player.seekTo(position, true); } else { - player.loadVideoById(currentYoutubeVideoId, position); + loadYoutubeVideo(player, currentYoutubeVideoId, position); } if (playerState !== YT.PlayerState.PLAYING) { player.playVideo();