From ad3095cdf57ada5f75f976f26f56a809148d4f56 Mon Sep 17 00:00:00 2001 From: ScreenTinker Date: Mon, 13 Apr 2026 22:34:48 -0500 Subject: [PATCH] Fix player video cycling bug and connecting overlay during cached playback Clear pending advance timers when switching content items to prevent stale image/widget duration timers from interrupting video playback. Also skip showing "Connecting..." overlay when cached playlist is already playing. Co-Authored-By: Claude Opus 4.6 --- server/player/index.html | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/server/player/index.html b/server/player/index.html index 7d6b2b6..54e451b 100644 --- a/server/player/index.html +++ b/server/player/index.html @@ -109,6 +109,7 @@ let layout = null; let zones = {}; let userHasInteracted = false; + let advanceTimer = null; // Track user interaction for autoplay policy ['click', 'touchstart', 'keydown'].forEach(evt => { @@ -191,7 +192,7 @@ tapOverlay.onclick = () => { unlockAudio(); tapOverlay.remove(); - showStatus('Connecting...'); + if (!isPlaying) showStatus('Connecting...'); connect(config.serverUrl); }; document.body.appendChild(tapOverlay); @@ -200,7 +201,7 @@ setTimeout(() => { if (tapOverlay.parentNode) { tapOverlay.remove(); - showStatus('Connecting (audio muted)...'); + if (!isPlaying) showStatus('Connecting (audio muted)...'); connect(config.serverUrl); } }, 5000); @@ -700,6 +701,9 @@ } function renderContent(item) { + // Clear any pending advance timer from previous content (image/widget duration timers) + if (advanceTimer) { clearTimeout(advanceTimer); advanceTimer = null; } + const container = document.getElementById('playerContainer'); container.style.display = 'block'; container.innerHTML = ''; @@ -727,7 +731,7 @@ video.style.cssText = 'width:100%;height:100%;object-fit:contain;background:#000'; video.loop = (playlist.length === 1); video.onended = () => { if (!video.loop) nextItem(); }; - video.onerror = (e) => { console.error('Video error:', src, e); setTimeout(nextItem, 3000); }; + video.onerror = (e) => { console.error('Video error:', src, e); advanceTimer = setTimeout(nextItem, 3000); }; video.onloadeddata = () => { console.log('Video loaded:', item.filename, 'muted:', video.muted); }; @@ -740,10 +744,10 @@ const img = document.createElement('img'); img.src = src; img.style.cssText = 'width:100%;height:100%;object-fit:contain'; - img.onerror = () => { console.error('Image error'); setTimeout(nextItem, 3000); }; + img.onerror = () => { console.error('Image error'); advanceTimer = setTimeout(nextItem, 3000); }; container.appendChild(img); // Auto advance for images - setTimeout(nextItem, (item.duration_sec || 10) * 1000); + advanceTimer = setTimeout(nextItem, (item.duration_sec || 10) * 1000); } else if (item.widget_id) { const iframe = document.createElement('iframe'); iframe.src = `${serverUrl}/api/widgets/${item.widget_id}/render`; @@ -751,7 +755,7 @@ iframe.allow = 'autoplay; fullscreen'; container.appendChild(iframe); // Auto advance for widgets - setTimeout(nextItem, (item.duration_sec || 30) * 1000); + advanceTimer = setTimeout(nextItem, (item.duration_sec || 30) * 1000); } } } @@ -793,7 +797,7 @@ img.src = src; img.style.cssText = `width:100%;height:100%;object-fit:${zone.fit_mode || 'cover'}`; div.appendChild(img); - if (playlist.length > 1) setTimeout(nextItem, (assignment.duration_sec || 10) * 1000); + if (playlist.length > 1) advanceTimer = setTimeout(nextItem, (assignment.duration_sec || 10) * 1000); } container.appendChild(div);