Files
v10/internal/decisions/live-presentation-anchor.md
T
Christian PillsburyandClaude Opus 4.8 f531e1055c docs(spf): decide the single presentation-level live timeline anchor
Capture the decision to hold one rolling (media-time ↔ PDT) anchor — learned
from the first A/V track to obtain SourceBuffer ground truth and applied to all
selected tracks (incl. text) by per-segment PDT — replacing today's per-track
buffer pins, which are redundant under the no-skew assumption and can't anchor
text (no SourceBuffer). New decision doc live-presentation-anchor.md; resolves
the deferred single-rolling-anchor question in live-timeline-anchoring and
promotes [4] in live-presentation-modeling to presentation-level.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 10:00:45 -07:00

122 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
status: decided
date: 2026-06-24
---
# Single presentation-level live timeline anchor
## Decision
Hold **one** rolling presentation-level anchor — a `(media-time ↔ PDT)`
correspondence — and apply it to **every** selected track, rather than pinning
each track to its own buffer.
The anchor is **learned from whichever selected audio/video track first has
SourceBuffer ground truth**: the buffer pin (`resolveBufferedAnchor`) reports a
buffered segment's actual native-PTS start `M₀`, paired with that segment's PDT
`P₀`. From that one pair, any track's segment is placed by its own PDT:
```
segment.startTime = M₀ + (segment.programDateTime P₀)
```
Established **once** per source (pin-once — it surfaces drift rather than
masking it; the parser's PDT carry-forward maintains the timeline across
reloads). Before any A/V track has buffer ground truth, the manifest estimate
(the existing bootstrap) supplies an *estimated* anchor of the same shape, which
the buffer pin later upgrades.
This anchor covers **text tracks too** — they have no SourceBuffer to pin, so
the shared anchor is the *only* way to place them. WebVTT cues are assumed
already time-aligned (the analog of assuming encoded A/V samples are aligned);
what we align is the text track's **segment** timeline, by PDT, exactly as for
A/V. (That cues are sparse/overlapping — a cue may begin or end outside its
segment's nominal bounds — is a text *loader/eviction* concern, not an
anchoring one.)
Scope: this fixes the **single rolling anchor vs. per-segment measured times**
question deferred by [live-timeline-anchoring](./live-timeline-anchoring.md),
and realizes the *manifest → buffer* half of that decision's Boundary as a
single shared anchor.
## Context
[live-timeline-anchoring](./live-timeline-anchoring.md) settled the anchor
*source* — PDT, the universal wall clock shared across renditions — but left
open how that anchor drives a shared presentation timeline. Today's
`anchor-live-tracks` pins each selected A/V track to its own buffer
independently, and doesn't anchor text at all.
Two facts make a single shared anchor sufficient — and per-track pinning
redundant:
1. **MSE plays all SourceBuffers on one element timeline.** There is a single
`currentTime`; audio and video are coerced onto one native-PTS coordinate.
2. **Native-PTS default** ([mse-timestamp-offset](./mse-timestamp-offset.md)) —
no `timestampOffset`, so every track shares the encoder's PTS clock.
Under (1) + (2) there is no inter-track skew, so one track's `(media-time ↔
PDT)` pair describes all tracks; further pins would only restate it. And text —
which has no buffer to pin — can be placed *only* by a shared anchor.
**Assumption committed:** no inter-track PTS skew. This is the existing
live-model assumption (native-PTS default); per-track skew correction remains
deferred to
[non-zero-pts-support](../design/spf/features/non-zero-pts-support.md). If skew
ever needs handling it layers a per-track offset on top of the shared anchor —
it does not require returning to per-track pinning.
## Alternatives Considered
- **Per-track buffer pins (today's `anchor-live-tracks`).** Pin each selected
A/V track to its own buffered segment. Rejected: under the no-skew assumption
the pins necessarily *agree*, so the extra pins are redundant restatement;
and the approach structurally **cannot anchor text**, which has no
SourceBuffer — keeping it would force a separate, divergent text path for the
same concern.
- **Per-track PDT estimate only (no buffer truth).** Place every track from the
manifest estimate (`sequence × avgDuration` + PDT) and never pin. Rejected:
drops the authoritative correction — the estimate is duration-variance-prone
(it produced an observed ~27 s drift at window turnover), and the buffer pin
is what makes the timeline exact. The estimate is the right *bootstrap*, not
the final answer.
## Rationale
PDT is already the cross-track anchor source; this decision commits to **one**
anchor instead of N. The win is uniformity: audio, video, and text all position
by the same `(media-time ↔ PDT)` rule, so the behavior has one code path, text
stops being a special case, and "first track to buffer wins" makes the anchor
available as early as possible. Pinning once (not per reload) keeps the
drift-surfacing property: if a track's PDT disagrees with the established
anchor, it shows up as a visible desync rather than being silently re-corrected
every reload.
This promotes open question **[4] sync anchor** in
[live-presentation-modeling](../design/spf/live-presentation-modeling.md) from
"per-segment / per-track" to "presentation-level," as that doc anticipated.
## Verification
Not yet implemented. The implementation converts `anchor-live-tracks` into a
reactor (`unanchored → anchored`; a single entry establishes the shared anchor
once an A/V track has buffer ground truth, then positions all selected tracks)
— tracked separately. Verification will cover: one A/V pin placing audio + text
by PDT; first-track-wins; pre-pin estimate → buffer-pin upgrade; pin-once (no
re-pin across reloads); inert when no PDT / no resolved track.
## See also
- [live-timeline-anchoring](./live-timeline-anchoring.md) — the anchor *source*
(PDT); this decision resolves its deferred "single rolling anchor vs.
per-segment measured times" question.
- [mse-timestamp-offset](./mse-timestamp-offset.md) — native-PTS default; the
manifest → buffer offset half this anchor realizes.
- [non-zero-pts-support](../design/spf/features/non-zero-pts-support.md) — where
per-track skew correction lands if the no-skew assumption ever breaks.
- [live-presentation-modeling](../design/spf/live-presentation-modeling.md) —
open question [4], promoted to presentation-level here.
- [live-stream-support](../design/spf/features/live-stream-support.md) — the
`anchor-live-tracks` behavior that implements this.