screentinker/server
ScreenTinker b57e7eec7f fix(#146): web player — reconnect drops video to "Waiting for content" (idle reset over live playback)
CONFIRMED from a live console capture (restore cache -> video plays -> reconnect ->
"Playlist unchanged" -> screen falls to "Waiting for content..."). Audio survived because
only the "showing content" VIEW was covered, not the audio path.

ROOT CAUSE: the server re-emits device:paired on EVERY re-register of an already-paired
device (ws/deviceSocket.js:510) — i.e. on every reconnect, while content is already playing.
The player's device:paired handler called showStatus('Waiting for content...') UNCONDITIONALLY
(the "falls through to idle" sibling), putting the idle overlay OVER the live video. The
following device:playlist-update -> "Playlist unchanged" branch returned early and never
cleared it, so the idle screen stuck on top of playing content.

FIX (idle screen only when genuinely idle; unchanged is a strict no-op that keeps playback):
- lib/player-media-health.js: new shouldShowIdle(state) — idle ONLY when nothing is playing
  AND there's genuinely no content. Already-playing (or content-present-about-to-render) is
  never idle.
- device:paired handler: gate showStatus on shouldShowIdle({isPlaying, hasContent}) instead
  of showing it unconditionally. On a reconnect while playing -> no-op.
- "Playlist unchanged" branch: when healthy playback is confirmed, hideStatus() to clear any
  stale idle overlay a reconnect's device:paired may have put up — so the confirmation can
  never leave "Waiting for content..." over live content. Still leaves the actual media
  element exactly as-is (no teardown, no flicker).
- sw.js cache v10 -> v11.

SIBLING SCAN: device:paired was the only unconditional idle reset. The connect() idle
prompts (connecting / connecting_muted) were already guarded by !isPlaying; empty-playlist
and no-renderable idles are genuine.

Tests: player-media-health.test.js +2 (shouldShowIdle: playing never idle; idle only when
empty+not-playing). Inline player JS syntax-checked; module served + guard referenced on a
booted server. Suite 318/318.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 22:03:44 -05:00
..
config PiP overlay MVP: push image/web overlays to a device or group (#109) (#127) 2026-06-18 14:54:44 -05:00
db feat(#146): billing:read scoped token — dual-path auth for the Usage Report (Option C) 2026-07-01 21:16:21 -05:00
lib fix(#146): web player — reconnect drops video to "Waiting for content" (idle reset over live playback) 2026-07-01 22:03:44 -05:00
middleware feat(#146): billing:read scoped token — dual-path auth for the Usage Report (Option C) 2026-07-01 21:16:21 -05:00
player fix(#146): web player — reconnect drops video to "Waiting for content" (idle reset over live playback) 2026-07-01 22:03:44 -05:00
routes feat(#146): billing:read scoped token — dual-path auth for the Usage Report (Option C) 2026-07-01 21:16:21 -05:00
scripts feat(scheduling): per-item schedule blocks (#74 dayparting, #75 auto-expire) 2026-06-11 15:46:41 -05:00
services feat(#146): usage metering + admin-gated Billable Screens report (contract system-of-record) 2026-07-01 20:45:27 -05:00
test fix(#146): web player — reconnect drops video to "Waiting for content" (idle reset over live playback) 2026-07-01 22:03:44 -05:00
ws fix(#146) P0: auto-quarantine is in-memory + time-limited, never a DB block 2026-06-30 21:56:20 -05:00
.gitignore feat(email): Microsoft Graph send + alert spam protection + preferences UI 2026-05-12 18:16:40 -05:00
config.js feat(#146): usage metering + admin-gated Billable Screens report (contract system-of-record) 2026-07-01 20:45:27 -05:00
package-lock.json chore(release): v1.9.2-beta7 2026-06-30 22:24:40 -05:00
package.json chore(release): v1.9.2-beta7 2026-06-30 22:24:40 -05:00
server.js fix(#146): web player — no-change refresh loses video (keeps audio); re-attach idempotently 2026-07-01 21:51:52 -05:00
version.js chore(version): single-source VERSION, env-configurable data paths, bump tooling 2026-06-10 12:56:03 -05:00