WIP: Phase 3B playback engine handoff

This commit is contained in:
Media
2026-08-16 21:50:19 -05:00
parent e2ecdc97e2
commit 68273d457f
16 changed files with 3506 additions and 244 deletions
+55 -2
View File
@@ -110,17 +110,70 @@ server-evaluated eligibility, immutable display/thumbnail URLs, and the
settings revision and evaluation time used. Playlist revisions increment once
per semantic content change. Exact no-ops and reads do not increment them.
Before a playback-relevant playlist, settings, or asset mutation, the playback
service catches up using the old metadata. The domain transaction then commits
and playback synchronously reconciles the new values at the mutation instant.
This prevents a late timer or restart boundary from being rewritten by a later
edit. If the post-commit reconciliation unexpectedly fails, the API returns an
error even though the preceding domain commit may already be durable; clients
must refetch current state before deciding whether to retry.
Activation is explicit. A request is held in the existing pending-activation
state while a playable normal slide is mid-duration; otherwise the target
playlist is prepared immediately at its first currently playable entry. The
output-off flag remains only a visual gate, and static hold is preserved while
the hidden normal playlist changes. Phase 3A does not include the advancing
playback timer that will consume pending activation at a slide boundary.
the hidden normal playlist changes. The authoritative playback service consumes
a pending activation at the current slide boundary and never merges the old and
new playlists.
Socket.IO publishes `playlists:changed` as a refetch invalidation after
committed playlist changes and after settings or referenced-asset edits that
affect resolved playlist data. Socket.IO is not a mutation API.
## Authoritative playback
One serialized service owns persisted global playback, timer reconciliation,
and every playback control path. Its REST surface is:
- `GET /api/playback`
- `POST /api/playback/pause`
- `POST /api/playback/resume`
- `POST /api/playback/next`
- `POST /api/playback/previous`
- `POST /api/playback/static`
- `PUT /api/playback/mode`
- `POST /api/playback/reload`
Visual output precedence is mode off (black), a valid static asset, the current
normal entry, then black when no playable target exists. Mode off is only a
visual gate: normal playback continues underneath unless it is independently
paused or held by static mode. Pause and static both preserve the normal cursor
and remaining duration. Resume clears both static and paused state together and
continues the normal entry with its preserved remaining time. Next and previous
wrap across currently playable entries; they change the held entry without
unpausing when paused and are no-ops while static.
Normal slides use persisted absolute start times and deadlines. Chained
timeouts reconcile from the prior authoritative deadline, including late
callbacks and restart catch-up, so delays do not accumulate as timing drift.
The service also arms exact schedule and asset-expiration boundaries. It skips
disabled, out-of-schedule, expired, and non-ready entries, and safely returns to
an eligible entry when one becomes available. Pending playlist activation is
applied at the current normal slide boundary; an empty target playlist still
becomes active with black normal output.
Socket.IO sends `playback:state` immediately on connection and after meaningful
committed playback changes. The authoritative snapshot includes server time and
revisions, mode and transport state, active playlist and timeline data, current
and static targets, crossfade settings, and a display-cache manifest with
effective duration, presentation, scheduling, expiration, and immutable media
metadata. Enabled future-scheduled entries remain in the manifest; disabled
entries and private S3 details do not. Reads and connections do not increment
the playback revision.
Phase 3B exposes this state and behavior through REST and Socket.IO only. MQTT
command wiring and the browser display renderer remain later phases.
## Storage and health
The API uses a small path-style S3 adapter configured by `S3_ENDPOINT`,