Snapshot Model
Snapshots are the shared read model because consumers need a consistent view of playback at any moment, independent of runtime implementation details.
Why snapshots are central
Section titled “Why snapshots are central”A snapshot captures observable playback state as data, not behavior. This makes it portable across adapters, event streams, and UI layers.
In Legato, the read model is split into:
PlaybackSnapshotfor current playback projection,QueueSnapshotfor queue projection and active index.
This is not accidental duplication. It is a boundary choice: playback timeline and queue topology evolve together, but they are not the same concern.
PlaybackSnapshot and QueueSnapshot roles
Section titled “PlaybackSnapshot and QueueSnapshot roles”PlaybackSnapshotcarries state, active item pointers, timeline values, and the queue projection.QueueSnapshotcarries ordereditemsand nullablecurrentIndex.
This pairing keeps timeline and queue concerns explicit while still allowing a single playback payload to include queue context.
Snapshot evolution as a sequence
Section titled “Snapshot evolution as a sequence”Think in projections over time, not in imperative commands:
- No active item:
currentTrack: null,currentIndex: null, queue may be empty or preloaded. - Activation:
currentTrackbecomes non-null,currentIndexpoints to the active queue item. - Progress updates:
positionchanges over time;durationcan staynullif timeline is unknown/live-like. - Queue transition:
currentIndexchanges,currentTrackchanges, and queue projection remains the source of ordering truth. - Reset/clear phase: runtime can project back to
currentTrack: nullandcurrentIndex: null.
The point is that each snapshot is valid on its own; consumers should react to what is projected now, not to assumptions about what “must” have happened before.
Nullability as signal, not error
Section titled “Nullability as signal, not error”The contract uses nullability and optionality to represent runtime uncertainty explicitly:
duration: nullmeans unknown or live-like timeline semantics.currentTrackandcurrentIndexare nullable when no active item exists.bufferedPositionis optional and can benullwhen runtime data is unavailable.
These states are intentional parts of the model. Consumers should treat them as valid contract values, not exceptional conditions.
Anti-patterns to avoid
Section titled “Anti-patterns to avoid”- Treating
duration !== nullas automatic seek permission. Duration is evidence of media length, not seekability permission. - Treating nullable fields as runtime failure signals.
currentTrack: nullandcurrentIndex: nullcan represent legitimate idle or transition states. - Reconstructing queue truth from timeline events alone. Queue ordering and active selection belong to
QueueSnapshot. - Assuming snapshot updates are command acknowledgements. They are read-model projections and can include uncertainty.
Practical consequence for consumers
Section titled “Practical consequence for consumers”Because snapshots encode uncertainty in the type shape, consumers can apply policy without guessing runtime internals.
Practical UI/state decisions that follow from this model:
- Render timeline UI with an explicit unknown-duration state when
duration === null. - Keep transport-control availability separate from timeline rendering (capabilities decide controls, snapshots decide display state).
- Model queue selection as nullable in state stores and selectors, not as a forced default index.
- Write reducers/selectors as pure projection consumers: derive view state from each incoming snapshot rather than incremental guesswork.