diff --git a/draft-lcurley-moq-hang.md b/draft-lcurley-moq-hang.md index d8bbfdf..9c63573 100644 --- a/draft-lcurley-moq-hang.md +++ b/draft-lcurley-moq-hang.md @@ -50,6 +50,7 @@ Hang introduces additional terminology: - **Participant**: A moq-lite broadcaster that may produce any number of media tracks. - **Catalog**: A JSON document that describes each available media track, supporting live updates. - **Container**: A tiny header in front of each media payload containing the timestamp. +- **Timeline**: An optional index mapping each media Group to the presentation timestamp of its first frame, used for seeking. # Discovery @@ -123,6 +124,7 @@ A video track contains the necessary information to decode a video stream. ~~~ type VideoSchema = { "renditions": Map, + "timeline": TrackName | undefined, "priority": u8, "display": { "width": number, @@ -137,6 +139,8 @@ The `renditions` field contains a map of track names to video decoder configurat See the [WebCodecs specification](https://www.w3.org/TR/webcodecs/#video-decoder-config) for specifics and registered codecs. Any Uint8Array fields are hex-encoded as a string. +The optional `timeline` field names a [Timeline](#timeline) track that indexes this media for seeking. + For example: ~~~ @@ -157,6 +161,7 @@ For example: "framerate": 30.0 } }, + "timeline": "video/timeline", "priority": 2, "display": { "width": 1280, @@ -174,6 +179,7 @@ An audio track contains the necessary information to decode an audio stream. ~~~ type AudioSchema = { "renditions": Map, + "timeline": TrackName | undefined, "priority": u8, } ~~~ @@ -182,6 +188,8 @@ The `renditions` field contains a map of track names to audio decoder configurat See the [WebCodecs specification](https://www.w3.org/TR/webcodecs/#audio-decoder-config) for specifics and registered codecs. Any Uint8Array fields are hex-encoded as a string. +The optional `timeline` field names a [Timeline](#timeline) track that indexes this media for seeking. + For example: ~~~ @@ -200,6 +208,7 @@ For example: "bitrate": 64000 } }, + "timeline": "audio/timeline", "priority": 1, } ~~~ @@ -217,6 +226,56 @@ The remainder of the payload is codec specific; see the WebCodecs specification For example, h.264 with no `description` field would be annex.b encoded, while h.264 with a `description` field would be AVCC encoded. +# Timeline +The timeline is an optional track that indexes a media track's Groups by presentation timestamp. +It lets a subscriber seek — resolving a presentation timestamp to a Group sequence — without first downloading and parsing the start of every Group. +It carries no media payload and is typically given a lower priority than the media tracks it indexes. + +A media schema references its timeline track by name via the `timeline` field (see [Catalog](#catalog)). +A timeline reflects the Group and timestamp structure of one media track. +The renditions of a single media already share that structure — matching Group sequence numbers and per-group timestamps so a subscriber can switch between them — so one timeline serves all of a media's renditions. +Audio and video do not share that structure and use separate timelines. + +The timeline track is a single Group that grows over the lifetime of the broadcast. +Each frame appends one entry, in Group order, mapping a Group to the presentation timestamp of that Group's first frame. +A subscriber builds the full index by reading the Group from its first frame; because moq-lite always retains the latest Group, a subscriber that joins late still receives every prior entry. +A publisher SHOULD keep the whole timeline in this single Group rather than starting a new one: moq-lite guarantees retention only of the latest Group, so splitting the index across Groups risks the earlier entries being dropped before a late subscriber can read them. + +To seek to a presentation timestamp, a subscriber selects the entry with the greatest timestamp not exceeding the target and subscribes to (or fetches) that Group, which begins with a keyframe (see [Container](#container)). + +## Encoding +The timeline reuses the moq-lite `FRAME` to carry the presentation timestamp, leaving a single value in the payload: + +~~~ +Timeline Frame { + Group Delta (i), +} +~~~ + +The presentation timestamp is expressed in the track's `Publisher Timescale` ([moql]). +This is application-defined and SHOULD match the timescale of the media track being indexed so the values are directly comparable when seeking. + +**Group Delta**: +The increase in Group sequence from the previous entry, encoded as an unsigned variable-length integer. +The first entry in the Group encodes the absolute Group sequence (a delta from 0). +This is normally `1`, since a publisher assigns Group sequences sequentially, but a larger value lets a timeline index Groups sparsely (for example, only every Nth Group). + +**Presentation Timestamp**: +The presentation timestamp of the Group's first frame is not stored in the payload; it is carried by the moq-lite frame's `Timestamp Delta` field ([moql]), a zigzag-encoded signed delta from the previous entry in the track's timescale. +The first entry in the Group therefore carries the absolute timestamp, following the moq-lite frame rules. +A signed delta also accommodates the occasional backward step caused by frame reordering (e.g. an open GOP). + +For example, a video track using a microsecond timescale with a constant two-second GOP starting at Group 100 produces these entries (the timestamp is carried by the moq-lite frame, the group delta by the payload): + +~~~ +entry 0: timestamp=0 group=100 # absolute anchor +entry 1: timestamp=+2000000 group=+1 +entry 2: timestamp=+2000000 group=+1 +~~~ + +Each entry occupies only a few bytes, and a regular cadence repeats identical deltas, so the index stays small even for long broadcasts. + + # Security Considerations TODO Security