diff --git a/quest/m1/README.md b/quest/m1/README.md index 9fba717eb3c..8aeeb3e2579 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -55,6 +55,11 @@ Added 2026-10-12 as PR follow-ups: fake timers for the JS pool tests (#5355), duration parser defaults (#5350), and cancelled origin demand (#5214), each ranked beside the work it shares code with. +Added 2026-10-12 from the Codex review of #5386: native egress skipping +disabled renditions, ranked just above simulcast rung disable, which +publishes them. Rung disable does not wait on it, but until it lands a +disabled rung strands WHEP, HLS, and moq-mux export viewers. + ## Required - [Interop close logs](/quest/m1/interop-close-log.md) - logging fix landed; delete once the required Interop run is green @@ -132,6 +137,7 @@ demand (#5214), each ranked beside the work it shares code with. - [CMAF surround Opus](/quest/m1/cmaf-opus-surround.md) - fMP4 import and export carry an Opus channel mapping table - [GPU CI](/quest/m1/gpu-ci.md) - NVIDIA tests run nightly on a self-hosted GPU runner, and `just rs nvidia` runs them locally instead of skipping - [Rendition preference](/quest/m1/rendition-preference.md) - automatic selection by ``, `Video::ranked`, and WHEP keeps the highest `preference` that decodes, so a compatibility transcode is only picked when nothing preferred decodes +- [Native egress skips disabled renditions](/quest/m1/egress-enabled.md) - WHEP, HLS and DASH export, and the moq-mux exports skip `enabled: false` renditions and follow a rendition when it flips - [Simulcast rung disable](/quest/m1/simulcast-rung-disable.md) - JS, Rust/FFI, and OBS publishers stop encoding the top renditions their grant cannot fund, advertise `enabled: false`, and re-enable with hysteresis - [Discover media headroom](/quest/m1/quic-probe.md) - test useful-media pacing before adding redundant probe traffic - [Viewer up-switch](/quest/m1/viewer-upswitch.md) - a viewer capped by its small rendition finds headroom through PROBE and moves up diff --git a/quest/m1/egress-enabled.md b/quest/m1/egress-enabled.md new file mode 100644 index 00000000000..7508916c62c --- /dev/null +++ b/quest/m1/egress-enabled.md @@ -0,0 +1,90 @@ +# [L] Native egress skips disabled renditions + +## Goal + +Every native consumer that picks or exports renditions from a hang catalog +skips a rendition marked `enabled: false`, as the hang draft requires ("a +consumer MUST NOT select the rendition"), and follows the catalog when a +rendition flips: a consumer serving a rendition that becomes disabled moves +to an enabled one, and one with nothing enabled serves nothing until a +rendition re-enables. This covers manual disable, simulcast rung disable, and +a failed rendition alike. No wire change. + +## Plan + +`@moq/watch` is the reference: video and audio selection drop disabled +renditions before ranking (`js/watch/src/video/source.ts`, +`js/watch/src/audio/source.ts`) and re-run whenever the catalog changes. + +Already correct, from #5273: + +- `moq play` (`rs/moq-cli/src/play/media.rs`) picks from `enabled()` and + stops a rendition the catalog disables, then reselects. +- `moqsrc` (`rs/moq-gst/src/source/imp.rs`). +- moq-ffi and moq-c refuse to subscribe a disabled rendition; the binding's + application owns the pick and sees the `enabled` field. +- moq-transcode prefers an enabled source and takes a disabled one only so + its rungs inherit `enabled: false`; that subscription carries no frames. + +Still serving disabled renditions: + +- WHEP (`rs/moq-rtc/src/egress.rs`): `pick_audio` and `pick_video` take the + first match from `ranked()`, which only sorts disabled entries last, so a + lone or all-disabled rendition is still picked. The catalog is a snapshot + from session start, so a flip is never seen. +- HLS and DASH export (`rs/moq-hls/src/export/renditions.rs`): `sync` + registers every rendition, so the multivariant playlist and manifest list + disabled ones. +- moq-mux exports, behind `moq subscribe`, RTMP play and dial, and SRT play: + `select::Broadcast` keeps disabled renditions; the H.264 and H.265 Annex-B + exports take the first matching rendition by name; single-track FLV uses + `ranked()` and binds before its header; fMP4, MKV, TS, and multitrack FLV + mux every selected rendition. RTMP's `play_selection` also refuses a + multitrack play over a disabled rendition it can't carry. + +Decided with the maintainer (2026-10-13): + +- **One rule in moq-mux, no new hang API.** `hang` already has + `Video::enabled()` and `Audio::enabled()`. Treat a disabled rendition as + absent where moq-mux already drops renditions on hang's rules: beside + `supported` in `select::Broadcast` and in each export's catalog intake + (where `Source::retain_valid_media` runs), so no export carries its own + check. Prefer extending an existing filter over adding a parallel one. +- **A muxed export puts only enabled renditions in its header.** After the + header, a rendition that gets disabled keeps its declared track, idle + until it re-enables, so MKV and fMP4 never end on a rung disable (a + post-header removal is an error in MKV, and fMP4 refuses a track added + after init). A rendition first enabled after the header follows the + format's existing rule for added tracks. +- **HLS and DASH omit a disabled rendition from the multivariant playlist + and manifest, but keep its window and media playlist serving.** Closing it + would end the playlist with `EXT-X-ENDLIST`, which reads as a finished + broadcast (see the [HLS export failure signal](/quest/m1/hls-failure-signal.md)), + and a re-enabled rendition keeps its media sequence and history. +- **WHEP follows the catalog for the session's life** and re-picks each + negotiated media line on a change, switching at the new track's first + keyframe rather than leaving a gap. With no ABR on the peer, it serves the + current best enabled rendition, so a re-enabled top rung wins back. The + SDP codec list stays fixed for the session. +- **Related to simulcast rung disable, not Required.** The publisher side + stands alone, and manual disable already has this gap, so rung disable + stays ready. + +Tests on mocked time with in-process broadcasts: a mixed catalog never +selects the disabled rendition; an all-disabled catalog selects nothing; a +disable mid-stream moves WHEP and the single-rendition exports to the +enabled sibling and a re-enable restores it; HLS drops and restores the +multivariant entry without resetting the media sequence; a muxed export +leaves a disabled rendition out of its header, and a rendition disabled +after the header idles its track without ending the export and resumes it +on re-enable. + +Public API: none expected beyond `select::Broadcast`'s documented behavior. +Update the egress docs (`doc/bin/cli.md`, `rtc.md`, `hls.md`, `rtmp.md`, +`srt.md`) where they describe rendition selection. + +## Related + +- [Simulcast rung disable](/quest/m1/simulcast-rung-disable.md) - publishers disabling top rungs, which strands WHEP and HLS viewers until this lands +- [Rendition preference](/quest/m1/rendition-preference.md) - reworks WHEP's pick across negotiated codecs; land the two picks together or rebase one on the other +- [Viewer up-switch](/quest/m1/viewer-upswitch.md) - the viewer side of moving between renditions as capacity changes diff --git a/quest/m1/simulcast-rung-disable.md b/quest/m1/simulcast-rung-disable.md index 949f345d5c9..c2ff5facf45 100644 --- a/quest/m1/simulcast-rung-disable.md +++ b/quest/m1/simulcast-rung-disable.md @@ -8,9 +8,12 @@ each as `enabled: false` in the catalog, and re-enables them with hysteresis once the grant recovers. The lower renditions keep their share instead of every rung degrading together. Covers the JS publisher (`js/publish`), the Rust and FFI publishers (the `rs/moq-video` producer under the `rs/moq-mux` -rate policy), and the OBS plugin. `@moq/watch` already skips disabled -renditions, and native consumers now skip them too, so a disabled rung -never strands a viewer. +rate policy), and the OBS plugin. `@moq/watch`, `moq play`, and `moqsrc` +already skip disabled renditions; moq-ffi and moq-c refuse to subscribe one +and leave the fallback pick to the application. WHEP, HLS, and the +moq-mux exports still serve them until +[Native egress skips disabled renditions](/quest/m1/egress-enabled.md) +lands, so a disabled rung can strand those viewers. ## Plan @@ -56,6 +59,7 @@ publishers expose for it. Wire: none; `enabled` already exists. ## Related +- [Native egress skips disabled renditions](/quest/m1/egress-enabled.md) - WHEP, HLS, and the moq-mux exports stop serving a disabled rung - [Ladder controller](/quest/m3/ladder/controller.md) - the same rung disable for generated transcode ladders - [Rendition preference](/quest/m1/rendition-preference.md) - viewer selection that drops disabled renditions before ranking the rest - [OBS multitrack](/quest/m1/obs-multitrack.md) - OBS's native multitrack encoders as the renditions this disables