video.play().catch(() => {}) silently swallowed the rejection from the
browser's autoplay policy, so when a user click triggered the unmute
path the video paused (browser side-effect of unmuting a muted-autoplay
video) and never resumed.
Surface the play() rejection in the log, and fall back to muted playback
if the unmuted play() is blocked. Same for the YT side: explicitly set
volume on unmute. Bumped SW cache to v9.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The remote-control feature dispatches synthetic click events on the
player when the dashboard forwards touches. The global click handler
called requestFullscreen() on every click, but the browser only honors
that API for trusted user gestures — synthetic events rejected with
"Permissions check failed" / "API can only be initiated by a user
gesture", spamming the console for the duration of any remote session.
Gate the fullscreen request on event.isTrusted. Local user clicks still
trigger fullscreen; remote-control taps no longer try (and fail).
Bumped SW cache to v8.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
createYoutubeEmbed set container.style.position = 'relative' to anchor
the click-to-unmute overlay. That overrode #playerContainer's
position:fixed/inset:0 — the container fell into normal flow with
zero height (the YT iframe inside has no intrinsic size), so the new
absolute-positioned iframe rendered as 100% of 0 = black screen.
The container is already position:fixed, so absolute children anchor
to it correctly without the override. Removed the line. Bumped SW
cache to v7.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous CSS fix used 100% width/height but YT.Player can bake in
300x150 fallback pixel dimensions if the placeholder isn't laid out at
construction time. Inline pixel dimensions beat percentage CSS at
equal specificity, so the iframe stayed small.
Use absolute positioning with !important to force fullscreen over
whatever YT set inline. Bumped sw cache to v6 to invalidate the
previously-cached player HTML.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The SW was causing "unexpected error" on video/image fetches due to
range request handling, opaque response caching, and stale SW races.
Fix: SW now ONLY caches player page + socket.io JS for offline boot.
Content files are left to browser native HTTP cache (server already
sets Cache-Control: public, max-age=2592000, immutable).
Also: auto-reload player when new SW activates so deploys take effect
immediately without manual hard refresh.
Bumped cache to v5 — activate purges all old caches (including the
broken rd-content-v1 content cache).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bug 1 (SW): Rewrote service worker fetch handler:
- Skip range requests (video seeking) to avoid caching partial responses
- Skip non-GET requests entirely
- Use ignoreSearch on cache match to avoid query-param misses
- Don't cache opaque cross-origin responses
- Outer catch on Cache API failures
- Don't intercept catch-all requests (let browser handle natively)
- Bump cache version to v4 to purge broken cached responses
Bug 2 (auth): Playlist refresh register was missing device_token,
causing auth rejection every 5 minutes. Fixed by including token
in the refresh-register emit. Added diagnostic logging on both
client and server for token validation failures.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace raw iframe YouTube embeds with official YT IFrame Player API for proper
error handling (150/153/100/101) and unmute support
- Fix playlist not updating when single item changes by comparing full content
fingerprint (id + url + filepath + filename) instead of just content_id
- Add click-to-unmute overlay for YouTube since iframe swallows click events
- Remove hardcoded origin param from server-side YouTube URLs (caused Error 153
when player domain differs from server)
- Switch service worker to network-first for player assets so deploys take effect
without hard refresh; keep cache-first for uploaded content
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Loop single-video playlists natively instead of destroying/recreating the element
- Skip caching HTTP 206 partial responses in service worker (video range requests)
- Bump service worker cache version to invalidate old cache
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ScreenTinker - open source digital signage management software.
MIT License, all features included, no license gates.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>