Playout preview: add hls.js ERROR handler (recover media/network errors,
resume on stall) + live-edge tuning — first transient error no longer halts
<video> to black. Purge stale HLS segments before re-mux (re)start so a
prior/duplicate sidecar session can't corrupt the live playlist.
Growing files: normalize growing_smb_mount (smb://, \host\share, host/share)
to CIFS UNC //host/share in capture-manager — mount no longer fails and
falls back to S3.
Monitors: surface playout channels as monitor tiles (live HLS on-air,
idle placeholder otherwise) in a labeled Playout group.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dashboard (screens-home.jsx): rebuild to new design, fully live-wired.
Dropped fabricated figures per "real data" rule (object-store %, uptime,
storage breakdown); repurposed ingest cell to real Assets-24h count.
Fixed undefined refs and double-rendered Resources section.
Playout: as-run writer in scheduler.js writeAsRun() off the health-tick
/status poll; AsRunPanel UI + missing CSS in styles-playout.css.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Root cause of the persistent black preview, fully isolated: ZAMPP1's nginx
serves the live .m3u8 fresh on every request (no-store works there), but
the PUBLIC reverse proxy (159.112.211.103 -> ZAMPP1) caches the static
.m3u8 by path with a multi-second TTL, ignoring both the origin's no-store
and query params. hls.js reloads the playlist ~every second, always landing
inside that TTL, so it sees the live playlist as never advancing
("live playlist MISSED" forever), never establishes the timeline, and never
loads a fragment -> readyState 0 (black). Proven: rapid reads via ZAMPP1
localhost advance (404->405); the same rapid reads via the public URL are
stuck; query-busting doesn't help (proxy caches by path).
Fix: serve the playlist through GET /api/v1/playout/channels/:id/hls/index.m3u8
instead of the static /media/live path. /api/ is not proxy-cached (the live
status poll already updates fine through it), so hls.js always gets the fresh
live edge. Segment (.ts) lines are rewritten to absolute /media/live/<id>/
URLs so they still load from the static path (immutable; caching them is
correct). ProgramMonitor points hls.js at the /api playlist and sends the
session cookie (xhrSetup withCredentials) since /api is auth-gated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- nginx.conf: add /media/live/ location serving from the media volume
mount. CasparCG sidecar writes HLS preview to /media/live/<id>/ but
nginx only had /live/ (capture volume). Without this, preview
requests returned the SPA shell instead of the .m3u8 playlist.
- ProgramMonitor: add live elapsed counter (MM:SS, ticks every 500ms)
driven by engine.currentItemStartedAt. Shows alongside clip index.
Adds a ⚠ pip when lastError is set (e.g. NDI SDK missing) without
blocking operation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- playout-stage: skip loudnorm pass 2 when measured_I=-inf (silent or
no-audio clip); fall back to plain AAC transcode so staging completes
instead of erroring out
- screens-home: add Playout tile; replace Premiere panel tile with
Downloads tile opening a combined modal (Premiere panel releases +
Dragon-ISO link to forge.wilddragon.net/WildDragonLLC/dragon-iso)
- screens-playout: add Delete channel button (visible only when stopped);
removes channel from list and selects next on confirm
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix event bubbling: e.stopPropagation() in onItemDrop prevents
duplicate POST when dropping on an existing playlist item
- Wrap all drop handlers in try/catch with inline error display
- ProgramMonitor: replace text placeholder with hls.js video player
loading /media/live/<channel_id>/index.m3u8; falls back to native
HLS on Safari; destroys Hls instance on channel stop/unmount
- Playlist: per-item duration (MM:SS), staging progress bar with
animated stripe while staging, now-playing highlight + ▶ indicator
driven by engine.currentIndex from 4s status poll
- Playlist footer: clip count + total duration sum
- Transport: Play button disabled + shows '⏳ N staging' until all
items are media_status=ready, eliminating the staging-not-ready 409
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
screens-playout.jsx + styles-playout.css: program monitor (HLS preview from
the sidecar), media bin, drag-drop playlist editor, transport controls. Plain
HTML5 drag-drop, no extra library. Talks to /api/v1/playout via
ZAMPP_API.fetch.
Wired into the shell: "Playout" under Operations, breadcrumb mapping, route
case in app.jsx, stylesheet + dist/screens-playout.js script in index.html.
Format dropdown defaults to 1080p5994 (matches the new channel default).