diff --git a/server/player/index.html b/server/player/index.html index 7f22285..6ba350e 100644 --- a/server/player/index.html +++ b/server/player/index.html @@ -588,12 +588,6 @@ emitWallSync(); }); - socket.on('device:content-delete', (data) => { - playlist = playlist.filter(p => p.content_id !== data.content_id); - savePlaylistCache(playlist); - if (playlist.length === 0) showStatus('Waiting for content...'); - }); - socket.on('device:screenshot-request', () => { console.log('Screenshot requested'); captureAndSend(); }); socket.on('device:remote-start', () => { console.log('Remote start received'); remoteStreaming = true; startStreaming(); }); socket.on('device:remote-stop', () => { console.log('Remote stop received'); remoteStreaming = false; stopStreaming(); }); @@ -910,10 +904,17 @@ } console.log('Playlist changed, updating'); + // Capture old state BEFORE mutating so continuity logic can find what was playing. + const identityOf = (x) => x ? `${x.content_id || ''}|${x.widget_id || ''}|${x.remote_url || ''}|${x.filepath || ''}` : ''; + const oldPlaylist = playlist; + const oldAnchorIdx = currentIndex; + const oldAnchorId = identityOf(oldPlaylist[oldAnchorIdx]); + playlist = newItems; savePlaylistCache(playlist); if (playlist.length === 0) { + teardownCurrentMedia(); showStatus('Waiting for content...'); isPlaying = false; return; @@ -921,23 +922,51 @@ document.getElementById('setupScreen').style.display = 'none'; - // Always restart playback when content changes - currentIndex = 0; + // Continuity: if the playing item survives the update, keep playing it. + // Just retarget the index pointer - no re-render, no interrupt. It will + // advance naturally via onended -> nextItem. + if (oldAnchorId && oldAnchorId !== '|||') { + const stillThereIdx = playlist.findIndex(x => identityOf(x) === oldAnchorId); + if (stillThereIdx !== -1) { + currentIndex = stillThereIdx; + isPlaying = true; + return; + } + } + + // Anchor is gone. Walk forward from the OLD position through the old playlist, + // pick the first item that still exists in the new one. Preserves "what was + // scheduled to play next, that still exists". Wraps past the end naturally. + let nextIdx = -1; + if (oldPlaylist.length > 0 && Number.isFinite(oldAnchorIdx)) { + for (let i = 1; i <= oldPlaylist.length; i++) { + const probe = oldPlaylist[(oldAnchorIdx + i) % oldPlaylist.length]; + const probeId = identityOf(probe); + if (!probeId || probeId === '|||') continue; + const found = playlist.findIndex(x => identityOf(x) === probeId); + if (found !== -1) { nextIdx = found; break; } + } + } + if (nextIdx === -1) nextIdx = 0; + + currentIndex = nextIdx; isPlaying = true; playCurrentItem(); } function playCurrentItem() { - if (currentIndex < 0 || currentIndex >= playlist.length) { - currentIndex = 0; - if (playlist.length === 0) { showStatus('Waiting for content...'); return; } + if (!playlist.length || !Number.isFinite(currentIndex)) { + teardownCurrentMedia(); + showStatus('Waiting for content...'); + isPlaying = false; + return; } + if (currentIndex < 0 || currentIndex >= playlist.length) currentIndex = 0; hideStatus(); const item = playlist[currentIndex]; console.log('Playing:', item.filename, `(${currentIndex + 1}/${playlist.length})`); currentItemStartedAt = Date.now(); - currentVideoEl = null; // Only the leader (or single, non-walled players) records a play_start — // followers would just spam duplicate proof-of-play rows for the same item. @@ -1107,13 +1136,46 @@ return playerDiv; } - function renderContent(item) { - // Clear any pending advance timer from previous content (image/widget duration timers) + // Stop and release all media in the player. pause() alone leaves the decoder + // buffering on some browsers; removeAttribute('src') + load() is what actually + // releases the decoder and kills audio. Null event handlers so a late onended + // can't fire into a stale playlist state. Queries all