--- status: draft date: 2026-05-20 definition: 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](./live-stream-support.md), [dvr-event-stream-support](./dvr-event-stream-support.md), [ll-hls-support](./ll-hls-support.md)) 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](./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 in `packages/spf/src/` (no `timestampOffset`, `initPTS`, or `EXT-X-DISCONTINUITY` references). 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 `timestampOffset` semantics + HLS spec; implementation specifics open. Source material: [SPF Epics Working Doc — epic #6 Non-zero PTS / Instant Clip Support](https://www.notion.so/35f97a7f89d08123a13fecab1ca1cac4) (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 `currentTime` semantics. Discontinuity-handling (mid-stream PTS jumps via `EXT-X-DISCONTINUITY` or 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](./live-stream-support.md)'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](./dvr-event-stream-support.md)'s growing-window — start = user-visible time of first retained segment; end = user-visible time of live edge. **Composes with:** [ll-hls-support](./ll-hls-support.md)'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 `tfdt` parsing; HLS spec `EXT-X-PROGRAM-DATE-TIME` correlation when present) - `SourceBuffer.timestampOffset` application before initial segment append - Engine-side `currentTime` / `seekable` mapping (engine reports offset-corrected values throughout) - `EXT-X-START` HLS 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 via `EXT-X-DISCONTINUITY` tags, 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-TIME` wall-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 streaming `fetchStream` chunks 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: `timestampOffset` vs simulated translation (load-bearing).** Two viable approaches with different trade-offs: *(a) `SourceBuffer.timestampOffset`*: Set `sourceBuffer.timestampOffset = -firstPTS` before appending the first segment; browser shifts all timestamps; `currentTime` and `seekable` are naturally offset-corrected. Sequences against [mse-mms-pipeline](./mse-mms-pipeline.md)'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 `