diff --git a/public/app.js b/public/app.js index 5e61999..594198f 100644 --- a/public/app.js +++ b/public/app.js @@ -52,6 +52,7 @@ let currentYoutubeVideoId = ""; let applyingWatchStateUntil = 0; let pauseEnforcementUntil = 0; let syncedPlaybackRetryUntil = 0; +let youtubeStartPromptAllowedUntil = 0; let desiredYoutubePlaying = false; let youtubeReadyResolvers = []; let watchApplySequence = 0; @@ -348,7 +349,12 @@ async function ensureYoutubePlayer(initialState = {}) { iframe?.setAttribute("allow", "autoplay; encrypted-media; fullscreen; picture-in-picture"); youtubeReadyResolvers.splice(0).forEach((resolve) => resolve()); if (shouldAutoplay) { - requestYoutubePlayback(youtubePlayer, initialVideoId, initialPosition); + requestYoutubePlayback( + youtubePlayer, + initialVideoId, + initialPosition, + Boolean(initialState.allowStartPrompt) + ); } }, onStateChange: handleYoutubeStateChange @@ -382,7 +388,7 @@ function setYoutubeStartPromptVisible(visible) { youtubeStartButton.hidden = !visible; } -function requestYoutubePlayback(player, videoId, position) { +function requestYoutubePlayback(player, videoId, position, allowStartPrompt = false) { const requestId = ++youtubePlaybackRequestId; const startedAt = Date.now(); @@ -420,6 +426,8 @@ function requestYoutubePlayback(player, videoId, position) { requestId === youtubePlaybackRequestId && desiredYoutubePlaying && currentYoutubeVideoId === videoId && + allowStartPrompt && + Date.now() < youtubeStartPromptAllowedUntil && player.getPlayerState?.() !== YT.PlayerState.PLAYING ) { setYoutubeStartPromptVisible(true); @@ -476,7 +484,12 @@ function handleYoutubeStateChange(event) { now < syncedPlaybackRetryUntil && [YT.PlayerState.UNSTARTED, YT.PlayerState.CUED, YT.PlayerState.PAUSED].includes(event.data) ) { - requestYoutubePlayback(youtubePlayer, currentYoutubeVideoId, currentYoutubeSnapshot().position); + requestYoutubePlayback( + youtubePlayer, + currentYoutubeVideoId, + currentYoutubeSnapshot().position, + Date.now() < youtubeStartPromptAllowedUntil + ); return; } if (event.data === YT.PlayerState.PLAYING) { @@ -500,6 +513,7 @@ function handleYoutubeStateChange(event) { desiredYoutubePlaying = action === "play"; if (desiredYoutubePlaying) { syncedPlaybackRetryUntil = 0; + youtubeStartPromptAllowedUntil = 0; setYoutubeStartPromptVisible(false); } else { setYoutubeStartPromptVisible(false); @@ -523,7 +537,7 @@ function handleYoutubeStateChange(event) { } } -async function applyWatchState(state, { suppressEvents = true } = {}) { +async function applyWatchState(state, { suppressEvents = true, allowStartPrompt = false } = {}) { const applySequence = ++watchApplySequence; const shouldPlay = state?.action === "play" || (!state?.action && Boolean(state?.playing)); watchPlaylist = normalizedPlaylist(state?.playlist); @@ -541,6 +555,7 @@ async function applyWatchState(state, { suppressEvents = true } = {}) { desiredYoutubePlaying = false; youtubePlaybackRequestId++; syncedPlaybackRetryUntil = 0; + youtubeStartPromptAllowedUntil = 0; setYoutubeStartPromptVisible(false); youtubePlayer?.stopVideo?.(); youtubeShell.classList.remove("is-active"); @@ -560,7 +575,8 @@ async function applyWatchState(state, { suppressEvents = true } = {}) { const player = await ensureYoutubePlayer({ videoId: currentYoutubeVideoId, position, - autoplay: shouldPlay + autoplay: shouldPlay, + allowStartPrompt }); await whenYoutubeReady(); if (applySequence !== watchApplySequence) return; @@ -572,15 +588,17 @@ async function applyWatchState(state, { suppressEvents = true } = {}) { if (suppressEvents) { syncedPlaybackRetryUntil = Date.now() + 8000; } + youtubeStartPromptAllowedUntil = allowStartPrompt ? Date.now() + 10000 : 0; } else { youtubePlaybackRequestId++; syncedPlaybackRetryUntil = 0; + youtubeStartPromptAllowedUntil = 0; setYoutubeStartPromptVisible(false); pauseEnforcementUntil = Math.max(pauseEnforcementUntil, Date.now() + 3000); } if (shouldPlay) { - setYoutubeStartPromptVisible(true); + setYoutubeStartPromptVisible(allowStartPrompt); const sameVideo = player.getVideoData?.().video_id === currentYoutubeVideoId; const playerState = player.getPlayerState?.(); if (sameVideo && ![YT.PlayerState.UNSTARTED, YT.PlayerState.CUED].includes(playerState)) { @@ -591,7 +609,7 @@ async function applyWatchState(state, { suppressEvents = true } = {}) { if (playerState !== YT.PlayerState.PLAYING) { player.playVideo(); } - requestYoutubePlayback(player, currentYoutubeVideoId, position); + requestYoutubePlayback(player, currentYoutubeVideoId, position, allowStartPrompt); } else { if (player.getVideoData?.().video_id !== currentYoutubeVideoId) { player.cueVideoById(currentYoutubeVideoId, position); @@ -871,7 +889,7 @@ async function handleSignal(message) { peerId = message.peerId; setStatus(`Joined the call as ${displayName}. ${message.peers.length} other participant(s).`); if (message.watchState) { - await applyWatchState(message.watchState); + await applyWatchState(message.watchState, { allowStartPrompt: true }); } if (message.presentationState?.sharing) { setTimeout(() => {