18 KiB
status, date, definition
| status | date | definition |
|---|---|---|
| draft | 2026-05-20 | technical |
Non-zero PTS support
Time-mapping primitive for sources where media-segment PTS
(Presentation Timestamps) don't start at zero. Three motivating
source types — re-mastered VOD, deliberate instant clips, and live
streams — all share the same underlying problem: detect the first
segment's PTS, then make user-visible currentTime start at zero.
The cluster B foundation; consumed by every cluster A feature
(live-stream-support,
dvr-event-stream-support,
ll-hls-support) for correct currentTime /
seekable semantics. Implementation mechanism is an open
architectural question — two viable approaches with different
trade-offs (see Open questions): apply the offset to the SourceBuffer
via timestampOffset (browser handles the translation; A/V sync risk
from per-buffer offset divergence) vs simulate the offset via adapter
- math in behaviors/compositions (buffer holds original PTS; translation happens at every consumer boundary; no A/V drift risk but bigger translation surface).
A Media-src feature in the framing from
clusters.md § Feature classification axes:
without it, live and instant-clip sources play with wrong
currentTime (a live stream's playhead jumps to hours/days into the
session because PTS is large; an instant clip's playhead starts at
the original media's PTS rather than 0).
Combines Notion epic #6 ("Non-zero PTS / Instant Clip Support") scope: non-zero PTS and instant clips share the same time-mapping primitive, distinct motivations.
Status
- Composition: not implemented in
createSimpleHlsEngine. No PTS-handling code inpackages/spf/src/(notimestampOffset,initPTS, orEXT-X-DISCONTINUITYreferences). Engine implicitly assumes zero-based PTS — works for typical Mux Video VOD sources (transcoded with PTS rebased to zero) but breaks for live + clips. - Definition depth: technical — scope and SPF touchpoints
articulated against MSE
timestampOffsetsemantics + HLS spec; implementation specifics open. Source material: SPF Epics Working Doc — epic #6 Non-zero PTS / Instant Clip Support (cluster B, Media-src, eng L, validation M). - Foundational for cluster B — first cluster B doc. Consumed by
every cluster A feature (live, DVR, LL-HLS) for correct
currentTimesemantics. Discontinuity-handling (mid-stream PTS jumps viaEXT-X-DISCONTINUITYor encoder restart) is carved out as a sister candidate feature.
Phases of complexity
Content phases by source-type. All three use the same underlying
mechanism (first-segment-PTS detection + timestampOffset
application); they differ in motivation, customer-data context, and
composition with other cluster A features.
| Phase | Source type | What | Notes |
|---|---|---|---|
| Non-zero-PTS VOD | Re-mastered or processed VOD where the manifest retains original PTS rather than zero-rebasing the source. Source has stable, non-zero PTS throughout. Often the result of clipping / transcoding pipelines that preserve PTS | Detect first-segment baseMediaDecodeTime from init segment (MP4 tfdt box, timescale-aware). Compute presentationTimeOffset = -baseMediaDecodeTime / timescale. Apply the offset via the chosen mechanism (see Open questions on mechanism choice). User-visible currentTime resolves to zero at source start |
The baseline non-zero PTS case. Offset is one-time setup (detected once at first segment, stable during source lifetime). Mechanism choice (timestampOffset vs simulated translation) affects everything downstream but doesn't change the per-phase user-visible semantics |
| Instant clips (deliberate offset) | Sources where the consumer / Mux Video creates a clip by serving a manifest starting at a non-zero PTS (the clip's start position in the original media). User-visible currentTime should still start at zero |
Same offset-detection mechanism as non-zero-PTS VOD. Plus: optionally surface the original PTS to the consumer for telemetry / "watch from minute N of source" use cases. Customer-data hooks: HLS EXT-X-START time-offset attribute (if present) or manifest-level clip metadata can hint at the offset |
The deliberate-clip case. Engine treats it identically to non-zero-PTS VOD; the difference is that the offset is intentional and the value carries meaning customer-side (e.g., for Mux Data telemetry mapping the clipped-content currentTime back to original-media position) |
| Live streams with non-zero PTS | Live encoder PTS counts from broadcast / encoder start. First segment loaded by the engine has a large PTS (potentially hours/days). Sliding-window updates roll segments off the start as live progresses; the seekable range slides forward but the offset itself is stable within an encoder run | Same first-segment-PTS-detection mechanism. Composes with: live-stream-support's setLiveSeekableRange — the live window's start/end are user-visible-time values (offset-corrected, however the mechanism translates). Composes with: dvr-event-stream-support's growing-window — start = user-visible time of first retained segment; end = user-visible time of live edge. Composes with: ll-hls-support's partial-segment edge tracking — same offset applies |
The live-specific case. Encoder restart (new encoder run on the same source URL) is one of the discontinuity scenarios carved out to the sister feature |
What's in scope vs out of scope
In scope:
- All three source-type phases for HLS content with non-zero PTS
- Init-segment PTS extraction (MP4
tfdtparsing; HLS specEXT-X-PROGRAM-DATE-TIMEcorrelation when present) SourceBuffer.timestampOffsetapplication before initial segment append- Engine-side
currentTime/seekablemapping (engine reports offset-corrected values throughout) EXT-X-STARTHLS spec attribute parsing for clip-start offset hints- Composition with cluster A features (live, DVR, LL-HLS) — their seekable-range writers consume the offset-corrected values
Out of scope (separate Borderline-flavored candidate features):
[discontinuity-handling](new candidate, sibling of this feature) — mid-stream PTS jumps viaEXT-X-DISCONTINUITYtags, encoder restarts on the same source URL, 33-bit PTS rollover (~26 hours of 90kHz timestamps). The initial offset is stable; this feature handles that. The changes mid-stream are the sister feature's concern. Both consume the same time-mapping primitive internally.- Cluster B sub-cluster: Borderline content compensation —
[pseudo-ended-detection],[edit-list-compensation],[buffer-stall-recovery]. All build on this feature's primitive but address different content-defect concerns.
Out of scope (different architectural layer):
- Customer-facing display of original-media position (e.g., "watching minute 3 of 60-minute source via 10-minute clip"). Engine surfaces the offset; adapter/customer renders the UI.
- Mux Data telemetry mapping of clipped currentTime → original-media position. Service-side / adapter-side.
EXT-X-PROGRAM-DATE-TIMEwall-clock display (synced to original broadcast time). Engine exposes the value; consumer UI renders "broadcast wall clock" if needed.
Likely cross-cutting impact
Things this feature probably forces decisions on, not just additions:
-
PTS-detection location: parser vs MSE-side. Two shapes for extracting baseMediaDecodeTime: (a) parse the init segment in the engine (MP4 box parsing for
tfdt+ timescale) before passing to SourceBuffer; (b) attach a parser to the streamingfetchStreamchunks that emits a PTS-detected event. Option (a) is simpler but adds MP4-box-parsing code; option (b) leverages the streaming pipeline. Lean: (a) — init segments are small and already buffered before the segment-stream begins; a one-shot parser at append time is the cleanest insertion point. -
Mechanism choice:
timestampOffsetvs simulated translation (load-bearing). Two viable approaches with different trade-offs:(a)
SourceBuffer.timestampOffset: SetsourceBuffer.timestampOffset = -firstPTSbefore appending the first segment; browser shifts all timestamps;currentTimeandseekableare naturally offset-corrected. Sequences against mse-mms-pipeline's setup: setup → setMediaKeys (if DRM) → addSourceBuffer → detect-PTS-from-init-segment → set timestampOffset → append. Risks: per-SourceBuffer write (video and audio buffers have separate offset values); sub-millisecond detection-time precision differences between video and audio init segments can cause A/V drift that compounds. Mid-source offset changes (discontinuity) require coordinated multi-buffer writes. Browser quirks during append (Safari especially).(b) Simulated translation: Don't set
timestampOffset; buffer holds original PTS. The<video>element'scurrentTimereads in original PTS (large for non-zero-PTS sources). Engine + adapter translate at every consumer boundary: customer-readingcurrentTime→ subtract offset; customer seeking → add offset;setLiveSeekableRange→ write original-PTS values to MSE, expose user-visible-time values through engine state. Risks: translation layer at every boundary; consumer-facing APIs must consistently use translated values (any leak of raw<video>.currentTimeshows wrong time); bigger code surface than mechanism (a). Benefits: no A/V drift risk (no per-buffer offset divergence); MSE buffer stays "pure"; discontinuity handling can change the translation offset without any MSE coordination.Both mechanisms preserve user-visible semantics; the choice is engineering trade-off (browser quirks + A/V sync risk vs translation surface area). Mechanism (b) is more SPF-natural — behavior-driven translation at boundaries fits the composition model; (a) is more browser-API-natural — uses what the spec provides. See Open questions.
-
State slot for the offset. New slot:
presentationTimeOffset: { [trackType: 'video' | 'audio']: number }or similar. Per-type (video and audio can have independent PTS origins; usually correlated but not guaranteed). Read by:setLiveSeekableRangewriter (live + DVR + LL-HLS), engine-sidecurrentTimeconsumers (telemetry, customer hooks), and any cluster B sister feature. -
currentTimeandseekablesemantics — depends on mechanism. Under (a)timestampOffset: browser does the translation; DOM-sidecurrentTimeandseekableare naturally offset-corrected; engine-side mirrors DOM-side. Under (b) simulated translation: DOM-side reads original PTS; engine/adapter applies offset at every read boundary; consumer-facingstate.currentTimeis the offset-corrected value;setLiveSeekableRangewrites original-PTS values (so MSE knows what it's been fed) but engine state exposes user-visible-time values. The mechanism choice is invisible to consumers — both paths preserve user-visible-time semantics — but the implementation surface differs substantially. -
Live + encoder-restart scenario. When a live encoder restarts on the same source URL, the new PTS series starts at a different value than the previous one. Without
EXT-X-DISCONTINUITYtags, this looks identical to a malformed stream; with them, it's a proper discontinuity. The sister[discontinuity-handling]candidate covers this. Boundary: this feature handles "single stable PTS origin per source"; discontinuity handles "PTS origin changes mid-source." -
Init-segment PTS vs first-media-segment PTS. The init segment doesn't contain media samples (no
tfdtper the spec strictly); the first media segment carries thetfdt. PTS detection happens on the first media segment append, not on init append. This affects the offset-write sequencing:timestampOffsetmust be set before the first media segment append, not before the init segment append. Verify behavior.
Open questions
- Mechanism choice:
timestampOffsetvs simulated translation (load-bearing). Per the cross-cutting note. Both viable. (a) uses the browser's MSE API directly but exposes A/V sync risk from per-buffer offset divergence + browser-specific quirks. (b) keeps the MSE buffer "pure" with original PTS and translates at every consumer boundary; bigger translation surface but eliminates the A/V drift risk and simplifies discontinuity handling (sister feature). The choice constrains the entire feature's implementation; resolving it requires empirical work (test A/V sync drift on target browsers under mechanism (a)) and scoping the translation surface under (b). No clear lean yet. - PTS-detection implementation: parser depth. Full MP4 box
parser inside SPF vs targeted
tfdt-only extractor vs depend on an externalmp4box.js-style library. Lean: targeted extractor (small surface, only what's needed). Independent of mechanism choice. EXT-X-PROGRAM-DATE-TIMEconsumption. HLS spec attribute giving wall-clock time per segment. Useful for: (a) correlating user-visible time with broadcast time; (b) cross-checking PTS-detected offset (ifEXT-X-PROGRAM-DATE-TIMEis present, verify the offset computation is consistent); (c) exposing to consumer telemetry. Open whether to surface this through engine state (new slot) or only via parser-output.EXT-X-STARTparsing. HLS spec attribute giving a desired playback-start offset (typically a few seconds before live edge for live, or arbitrary for VOD). Should the engine respect this by setting initialcurrentTime, or expose to consumer? Lean: expose; let consumer decide.- Per-type offset (video vs audio). Theoretically independent;
usually correlated. Treat as
{ video: number, audio: number }or assume video offset applies to both? Empirical question; start with per-type to be safe. - Discontinuity boundary semantics. The discontinuity sister
feature handles mid-stream offset changes. The boundary between
this feature (stable offset) and that one (offset changes) is
drawn at "single PTS origin per source." A stream with one
EXT-X-DISCONTINUITYafter segment 100 sits in the sister feature's territory. - Telemetry / customer-data offset exposure. Surface
presentationTimeOffsetto consumers for telemetry (Mux Data, custom analytics)? Lean: yes — read-only via the engine state surface or a derived signal. currentTimenear zero for non-zero-PTS VOD. After offset applied,currentTimestarts at zero. Some browsers may behave unexpectedly whenseekable.start(0)is exactly 0 (vs floating- point slightly above zero). Worth empirical verification on Safari, especially.- Composition with capability-probing. When the browser doesn't decode the codec, the engine fails before reaching this feature's PTS handling. Boundary: capability-probing filters unsupported variants upstream; this feature operates on the variants that pass. No direct dependency.
Related features
- live-stream-support (consumer) —
consumes this feature's offset for
setLiveSeekableRangestart / end and forcurrentTimesemantics during live playback. Cluster A foundation that this feature is the cluster B foundation prerequisite for. - dvr-event-stream-support (consumer) — same as live-stream-support but with growing-window semantics; the offset still applies, the seekable start is offset-corrected from segment 0.
- ll-hls-support (consumer) — partial- segment edge tracking + live-edge updates all use offset-corrected values.
- mse-mms-pipeline — under mechanism
(a),
SourceBuffer.timestampOffsetis the MSE API consumed; the segment-append sequence gains a PTS-detection + offset-write stage. Under mechanism (b), MSE-side is unchanged; translation lives in adapter / behavior boundary instead. - buffer-management — forward-buffer
planner operates on
currentTimeand segment time ranges; both are offset-corrected naturally. Planner code doesn't change. - presentation-modeling — parser
may surface PTS-related HLS attributes (
EXT-X-START,EXT-X-PROGRAM-DATE-TIME) on the resolved presentation. Parser extension for HLS metadata. [discontinuity-handling](new candidate, this session) — sister feature carved out of this one's scope. Mid-stream PTS jumps viaEXT-X-DISCONTINUITY, encoder restart, 33-bit PTS rollover. Same underlying primitive (re-detect-and-apply offset), different motivation (changes mid-source vs stable).- Cluster B Borderline content compensation sub-cluster: pseudo-ended-detection, edit-list-compensation, buffer-stall-recovery — all build on this feature's time-mapping primitive but address different content-defect concerns. See clusters.md § Time normalization's sub-cluster note.
See also
- clusters.md § Time normalization — cluster B description; this feature is the foundation
- clusters.md § Feature classification axes — Media-src feature framing
- live-stream-support.md — primary consumer; cluster A foundation
- mse-mms-pipeline.md —
SourceBuffer.timestampOffsetAPI surface - SPF Epics Working Doc — source material; epic #6 (Non-zero PTS / Instant Clip Support)
- HLS Spec —
EXT-X-STARTandEXT-X-PROGRAM-DATE-TIME— HLS spec time-related metadata attributes - MSE Spec —
SourceBuffer.timestampOffset— the MSE API for shifting appended segment timestamps; one of the two candidate mechanisms (A/V sync risk from per-buffer offset divergence) - ISO BMFF —
tfdtbox (Track Fragment Decode Time) — MP4 init/media segment PTS source