From Chris's live debug logs on the L-Bar layout:
- ZoneManager only rendered the FIRST assignment per zone -> the Main zone (3
images) never rotated ('says it's switching but it's not'). Now each zone
cycles its own assignments: images/widgets on a duration timer, videos on
end (single-item zones still loop).
- The fullscreen PlaylistController kept running BEHIND the zones (playItem every
10s, would leak audio for a zone video) because startIfNeeded() ran after every
playlist update. Now only start it when not in multi-zone (zoneManager.hasZones).
- renderAssignments still called container.removeAllViews() (the same static-view
nuke the cleanup() fix addressed) -> now removes only its own zone views.
Widgets worked in multi-zone layouts (ZoneManager renders them in a WebView) but
were broken in "default fullscreen" (no layout) and the fullscreen template (a
single-zone layout) - both take the single-zone PlaylistController path, which:
1) called getString("content_id"), throwing on a widget assignment (no
content_id) - in both the playlist builder AND the pre-download loop, which
could break the whole fullscreen playlist; and
2) had no widget render case in playItem (so a widget never displayed).
Fix:
- PlaylistItem gains widgetId/widgetType + isWidget; the builder reads them and
tolerates a missing content_id.
- playItem renders a widget fullscreen via MediaPlayerManager.showWidget() (loads
/api/widgets/:id/render in the full-screen WebView, mirroring ZoneManager).
- Widgets auto-advance on their duration like images.
- Pre-download loop skips widget assignments (no file to fetch).
Compile-checked; signed APK builds. Needs on-device check: a widget plays in
default-fullscreen and the fullscreen template, and mixed widget+media playlists
advance correctly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A 4K image assigned to a 1080p display decoded as a ~33 MB ARGB_8888
bitmap and OOM'd. Worse, the cached playlist on disk meant relaunch
hit the same image and crashed again — only a reinstall recovered.
New ImageLoader utility reads bounds via inJustDecodeBounds, computes
inSampleSize against the device screen (or zone size for multi-zone
layouts), and returns null on OOM/Throwable so callers skip the item
instead of crashing. MediaPlayerManager exposes an onImageError
callback wired to playlistController.next() so a bad item advances
the playlist. The cached-playlist restore in onCreate now catches
Throwable (was Exception) and clears the cache on any failure,
breaking the crash loop. android:largeHeap="true" added as belt and
braces.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Web player:
- Cache playlist JSON to localStorage on every update
- Restore and start playing immediately on boot before connecting
- Clear cache on unpair/reset
Android app:
- Cache playlist JSON to EncryptedSharedPreferences on every update
- Restore cached playlist on cold-start, play from disk-cached content
- Update cache on content deletion, clear on unpair
Server (device socket):
- Fingerprint reconnect: issue fresh token instead of rejecting
- Send device:paired on fingerprint recovery for claimed devices
- Add status logging and dashboard notification on fingerprint reconnect
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>