diff --git a/quest/m1/README.md b/quest/m1/README.md index c8fd34f95eb..918fcda9e85 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -131,7 +131,7 @@ removed). - [Announcement takeover](/quest/m1/announce-takeover.md) - a new independent route explicitly continues instances served through its predecessor, with shared Rust/JS semantics and lite-07 wire - [Subscribe ranges](/quest/m1/subscribe-ranges/README.md) - a lite-07 SUBSCRIBE asks for past and live ranges with absolute or relative ends, in either order, and replaces FETCH; merged ranges never starve a subscriber; relays fill misses by range, including over moq-transport - [Drop set](/quest/m1/drop-set.md) - each cursor judges missing groups against its own budget, so a narrow reader drops promptly and a widening re-offers groups already reported dropped on lite-07 -- [Broadcast archives](/quest/m1/archive/README.md) - ANNOUNCE, TRACKS, and a SUBSCRIBE per track record every track under a prefix to any object_store, a host serves a stored broadcast back, and the catalog drops its archive pointer for an opt-in `hls` field +- [Broadcast archives](/quest/m1/archive/README.md) - ANNOUNCE, TRACKS, and a SUBSCRIBE per track record every track under a prefix to any object_store, a host serves a stored broadcast back, and the catalog drops its archive pointer for a per-rendition `hls` field - [Compressed tracks](/quest/m1/compress/README.md) - a track flagged `compress` is DEFLATEd per group by moq-net, forwarded compressed, and inflated once on the relay for peers that cannot signal it; `catalog.json.z` and moq-flate go away - [Finalize moq-lite-07](/quest/m1/lite07-finalize.md) - when the maintainer cuts it, lite-07 negotiates as `moq-lite-07` and the next release ships it - [Moxygen minimal interop](/quest/m1/smoke-moxygen.md) - waiting on Moxygen to send a registered filter type, then the smoke lane stops hiding its failure diff --git a/quest/m1/archive/README.md b/quest/m1/archive/README.md index 9b9d927f841..b62432a2fc2 100644 --- a/quest/m1/archive/README.md +++ b/quest/m1/archive/README.md @@ -11,8 +11,8 @@ host publishes at a path of its choosing. Track, group, and frame structure and payloads survive byte for byte, so any consumer of the live broadcast, HLS included, works on the stored one. -The catalog carries no archive pointer. Timelines survive only as an opt-in -`hls` field for HLS compatibility. +The catalog carries no archive pointer. Timelines survive only as a +per-rendition `hls` field for HLS compatibility. ## Plan @@ -68,7 +68,7 @@ archive (per-track `.timeline.z` records named by the catalog's root - **Seek by time.** A `Time(pts)` range end ([Subscribe ranges](/quest/m1/subscribe-ranges/README.md)) is resolved by whoever holds the track, the cache or an archive consumer. DVR seeks with it. -- **`hls` is for HLS only, and opt-in.** A per-rendition catalog field names a +- **`hls` is for HLS only.** A per-rendition catalog field names a track whose (first group, pts) records mark each HLS segment, each rendition cutting its own on a pts grid so they line up wherever keyframes do (Catalog HLS field, landed). The root `archive` diff --git a/quest/m1/time-addressing/consumers.md b/quest/m1/time-addressing/consumers.md index 3d0a71bacf8..f1df2aa5e47 100644 --- a/quest/m1/time-addressing/consumers.md +++ b/quest/m1/time-addressing/consumers.md @@ -4,8 +4,9 @@ Every first-party consumer that numbered groups works by start timestamp, in Rust and JS, and hang stops writing duration and discontinuity markers -that group ends and gaps now carry. The HLS/DASH export lists segments -straight from group starts and ends, so the HLS records go away. +that group ends and gaps now carry. The HLS/DASH export cuts segments +from group starts and ends on the broadcast's declared join interval, so the +HLS records go away. ## Plan @@ -21,9 +22,49 @@ Decided 2026-10-25 (maintainer), from the spike: pause markers, which they already tolerate being absent. Decided 2026-10-27 (maintainer): moq-mux publishes HLS records by default -until this lands. Then the `.hls` record tracks, the catalog `hls` -field (hang draft and both catalog libraries), the `catalog::Config::with_hls` -opt-out, and any A/V segment alignment go away. +until this lands. Then the `.hls` record tracks, the per-rendition +`hls` catalog field, and any A/V segment alignment go away. + +Decided 2026-10-27 (maintainer, from an HLS audit): how the export +cuts segments without records. HLS numbers segments by position, so every +edge and every reload must derive the same consecutive numbers from +timestamps alone, and a pts grid is the only stateless rule that does. +Rejected: one platform grid (long-GOP publishers overrun it constantly), +per-edge counting (failover renumbers), and a publisher counter (per-producer +state again). + +- **A catalog `media` section** holds what applies to every audio and video + track. `media.join.interval` promises that each one starts a group at least + that often outside a pause, and is the export's grid, so every rendition + shares segment numbers. It is fixed for the broadcast instance, since the + numbering depends on it. The root `clock` moves to `media.clock` in the same + break. Nested so a later join mechanism that is not a fixed grid can sit + beside it. + moq-mux declares the interval from its GOP hint, and `@moq/publish` does + too. A broadcast without one is not HLS-renderable. +- **Each group belongs to one segment, numbered by grid line**: the first + line it contains, so an edge finds segment N from a `Time` start at pts + N × interval, the group that resolves to. A group containing no line + joins the segment of the group before it, or after a gap, the segment of + the line it starts after. The edge trims each segment to its own groups, + so ranges need no exclusive time end. The media sequence number is N. +- **Whole groups only.** Never split a group: a segment starting mid-GOP is + not independent. +- **A segment with no groups is `EXT-X-GAP`**, so numbering never skips + across a pause or an overrun. A group overrunning the interval is listed + at its first line, so its `EXTINF` understates the media; document the + deviation beside + [fixed target duration](/quest/m1/archive/hls-target.md)'s overrun rule, + whose target then comes from the interval. +- **Segments run under twice the interval** even without an overrun, since + the group containing a line can start up to one interval before it. Open + for the maintainer: a target of twice the interval (RFC 8216 compliant, + but players hold back twice as far) or the interval itself with longer + segments listed under the overrun rule. +- DASH lists the same segments by `$Time$`. + +Pin the segmentation with table-driven tests over pauses that end between +lines or past the next one, unequal GOPs, and overruns. Convert each consumer that numbers groups: hang and moq-mux timelines, HLS/DASH export, `seek` for GOP-aligned failover in moq-mux, HLS import's @@ -39,10 +80,11 @@ export reads media tracks directly, prove that an HLS-only viewer of an on-demand publisher starts capture and gets a playlist. Public API: breaking where a consumer exposes group numbers (CLI -`--group`, archive keys), and `moq_mux::hls`, `hang::hls`, and -`catalog::Config::with_hls` are deleted. Wire: hang drops empty duration -frames, discontinuity marker groups, the `hls` catalog field, and the record -tracks. +`--group`, archive keys), `hang::hls` and the per-rendition `hls` field are +deleted, and `moq_mux::hls` shrinks to declaring the interval. Wire: hang +drops empty duration frames, discontinuity marker groups, the `hls` field, +and the record tracks, adds `media.join.interval`, and moves `clock` to +`media.clock`. ## Required