diff --git a/server/lib/player-media-health.js b/server/lib/player-media-health.js index f460780..4322fd8 100644 --- a/server/lib/player-media-health.js +++ b/server/lib/player-media-health.js @@ -44,5 +44,17 @@ return !s.surfaceAttached; } - return { needsReattach: needsReattach }; + // Whether the idle "Waiting for content..." screen should be shown, given player state. + // THE RECONNECT BUG: the server re-emits device:paired on every re-register of an already- + // paired device, and the player showed the idle overlay UNCONDITIONALLY — covering live + // content (audio kept playing underneath), and the following "Playlist unchanged" left it + // up. Rule: only fall to idle when nothing is playing AND there is genuinely no content to + // play. Already playing, or content present and about to render, is NEVER idle. + function shouldShowIdle(state) { + var s = state || {}; + if (s.isPlaying) return false; // something is playing -> never cover it with idle + return !s.hasContent; // idle only when there's genuinely no content + } + + return { needsReattach: needsReattach, shouldShowIdle: shouldShowIdle }; }); diff --git a/server/player/index.html b/server/player/index.html index d95cd08..dc788c3 100644 --- a/server/player/index.html +++ b/server/player/index.html @@ -807,7 +807,15 @@ saveConfig(config); console.log('Paired as:', data.name); document.getElementById('setupScreen').style.display = 'none'; - showStatus('Waiting for content...'); + // #146 fix: the server re-emits device:paired on EVERY re-register of an already- + // paired device (deviceSocket.js), i.e. on every reconnect — while content is already + // playing. Showing the idle "Waiting for content..." overlay unconditionally covered + // the live video (audio kept playing underneath) and the subsequent "Playlist + // unchanged" left it up. Only fall to idle when nothing is actually playing. + const showIdle = window.PlayerMediaHealth + ? PlayerMediaHealth.shouldShowIdle({ isPlaying: isPlaying, hasContent: playlist.length > 0 }) + : !isPlaying; + if (showIdle) showStatus('Waiting for content...'); }); socket.on('device:unpaired', () => { @@ -1333,6 +1341,11 @@ if (window.PlayerMediaHealth && PlayerMediaHealth.needsReattach(state)) { console.log('[refresh] media surface lost on no-change refresh — re-attaching current item'); playCurrentItem(); + } else if (isPlaying) { + // #146 fix: unchanged + already playing must LEAVE playback exactly as-is. Clear + // any stale idle overlay (e.g. the one a reconnect's device:paired put up) so the + // "unchanged" confirmation never leaves "Waiting for content..." over live content. + hideStatus(); } } catch (e) { /* never let the health check break a refresh */ } return; diff --git a/server/player/sw.js b/server/player/sw.js index 08a6af9..bb78c16 100644 --- a/server/player/sw.js +++ b/server/player/sw.js @@ -1,4 +1,4 @@ -const CACHE_NAME = 'rd-player-v10'; +const CACHE_NAME = 'rd-player-v11'; // Install: skip waiting to activate immediately self.addEventListener('install', (event) => { diff --git a/server/test/player-media-health.test.js b/server/test/player-media-health.test.js index a1fb825..afdd0ba 100644 --- a/server/test/player-media-health.test.js +++ b/server/test/player-media-health.test.js @@ -6,7 +6,7 @@ const { test } = require('node:test'); const assert = require('node:assert/strict'); -const { needsReattach } = require('../lib/player-media-health'); +const { needsReattach, shouldShowIdle } = require('../lib/player-media-health'); const video = (o) => ({ isPlaying: true, hasCurrentItem: true, itemKind: 'video', videoEl: o, surfaceAttached: true }); @@ -40,3 +40,16 @@ test('non-video surface (image/youtube/widget): re-attach only when the surface assert.equal(needsReattach({ ...base, itemKind: 'youtube', surfaceAttached: false }), true); assert.equal(needsReattach({ ...base, itemKind: 'widget', surfaceAttached: true }), false); }); + +// shouldShowIdle — the reconnect reset guard (device:paired / unchanged reconciliation). +test('THE RECONNECT BUG: already playing -> NEVER show the idle "Waiting" screen', () => { + // reconnect re-emits device:paired while content is playing: must stay on the content + assert.equal(shouldShowIdle({ isPlaying: true, hasContent: true }), false); + assert.equal(shouldShowIdle({ isPlaying: true, hasContent: false }), false); +}); + +test('idle screen shows ONLY when genuinely no content and nothing playing', () => { + assert.equal(shouldShowIdle({ isPlaying: false, hasContent: false }), true); // first pair, empty + assert.equal(shouldShowIdle({ isPlaying: false, hasContent: true }), false); // content present, about to render + assert.equal(shouldShowIdle(undefined), true); +});