screentinker/server
ScreenTinker 26c72d62bf fix(#146): web player — no-change refresh loses video (keeps audio); re-attach idempotently
ROOT CAUSE (hypothesis A, pre-existing — NOT a beta7 regression; server/player/index.html
is untouched since v1.9.2-beta6): handlePlaylistUpdate's "Playlist unchanged" branch blindly
returned. The media re-attach (renderContent) lives ONLY in the content-changed branch, so
if the <video> surface was lost (element detached from the DOM while still decoding — video
gone, audio still playing) a no-new-content refresh never re-attached it. New-content
refreshes were fine because they re-render.

FIX (make the refresh idempotent for the media surface, no flicker on the healthy path):
- server/lib/player-media-health.js (new, UMD + unit-testable, mirrors schedule-eval.js):
  needsReattach(state) — re-attach ONLY when playback should be happening but the current
  item's surface is actually lost (video null / detached / ended / errored; non-video: no
  mounted surface). A healthy attached+live video returns false, so a routine poll stays a
  no-op (no re-render, no flicker). Served at /player/player-media-health.js from the single
  source; loaded by the player.
- index.html no-change branch: extract the current item's DOM facts and, iff
  PlayerMediaHealth.needsReattach, call playCurrentItem() to re-render the current item.
  Wrapped so the health check can never break a refresh.
- teardownCurrentMedia: also release currentVideoEl even when it was DETACHED from the
  container — a detached-but-playing <video> keeps emitting audio and the container-scoped
  querySelectorAll can't find it. This kills the "ghost audio" on re-attach.
- sw.js cache bumped v9 -> v10 so players pick up the new index.html + module.

Tests: test/player-media-health.test.js (6) exercises the branch selection — healthy video
-> no re-attach; detached/null/ended/errored -> re-attach; idle -> never; non-video by
surface presence. Inline player JS syntax-checked; module served + referenced verified on a
booted server. Suite 316/316.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 21:51:52 -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 — no-change refresh loses video (keeps audio); re-attach idempotently 2026-07-01 21:51:52 -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 — no-change refresh loses video (keeps audio); re-attach idempotently 2026-07-01 21:51:52 -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 — no-change refresh loses video (keeps audio); re-attach idempotently 2026-07-01 21:51:52 -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