From bdaa6df9d27f706a1b718a30bc8c3f2b5904b9d1 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Mon, 12 Oct 2026 21:06:52 -0700 Subject: [PATCH 1/4] quest(m1): plan native egress skipping disabled renditions WHEP, HLS/DASH export, and the moq-mux exports still serve renditions the catalog marks enabled: false. Add a quest for them and correct the simulcast rung disable quest's claim that native consumers already skip disabled renditions. Co-Authored-By: Claude Opus 5.5 --- quest/m1/README.md | 5 ++ quest/m1/egress-enabled.md | 82 ++++++++++++++++++++++++++++++ quest/m1/simulcast-rung-disable.md | 9 ++-- 3 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 quest/m1/egress-enabled.md diff --git a/quest/m1/README.md b/quest/m1/README.md index 9fba717eb3c..3efca38e33f 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -55,6 +55,10 @@ 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. + ## Required - [Interop close logs](/quest/m1/interop-close-log.md) - logging fix landed; delete once the required Interop run is green @@ -132,6 +136,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..1893ed5113d --- /dev/null +++ b/quest/m1/egress-enabled.md @@ -0,0 +1,82 @@ +# [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. + +Recommendations, pending the maintainer: + +- **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. +- **Muxed exports drop a disabled rendition like a removed one**, reusing + each format's existing path for a rendition leaving and returning. Do not + keep an idle track in the mux. +- **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. + +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 +drops and restores the track. + +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..6ae61d7a37c 100644 --- a/quest/m1/simulcast-rung-disable.md +++ b/quest/m1/simulcast-rung-disable.md @@ -8,9 +8,11 @@ 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`, `moqsrc`, and +the FFI consumers already skip disabled renditions. 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 +58,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 From 78deef2d055e9ff4838baed7ff2a1e2081e6c5d6 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Mon, 12 Oct 2026 21:15:46 -0700 Subject: [PATCH 2/4] quest(m1): record the maintainer's egress-enabled decisions Co-Authored-By: Claude Opus 5.5 --- quest/m1/egress-enabled.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/quest/m1/egress-enabled.md b/quest/m1/egress-enabled.md index 1893ed5113d..0fe649db9ed 100644 --- a/quest/m1/egress-enabled.md +++ b/quest/m1/egress-enabled.md @@ -42,7 +42,7 @@ Still serving disabled renditions: mux every selected rendition. RTMP's `play_selection` also refuses a multitrack play over a disabled rendition it can't carry. -Recommendations, pending the maintainer: +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 @@ -63,6 +63,9 @@ Recommendations, pending the maintainer: 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 From 885aa6101b75f0d7062bcf0e981d44f80d407758 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Mon, 12 Oct 2026 21:24:14 -0700 Subject: [PATCH 3/4] quest(m1): narrow the FFI claim and the rung-disable dependency note Co-Authored-By: Claude Opus 5.5 --- quest/m1/README.md | 3 ++- quest/m1/simulcast-rung-disable.md | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/quest/m1/README.md b/quest/m1/README.md index 3efca38e33f..8aeeb3e2579 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -57,7 +57,8 @@ 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. +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 diff --git a/quest/m1/simulcast-rung-disable.md b/quest/m1/simulcast-rung-disable.md index 6ae61d7a37c..c2ff5facf45 100644 --- a/quest/m1/simulcast-rung-disable.md +++ b/quest/m1/simulcast-rung-disable.md @@ -8,8 +8,9 @@ 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`, `moq play`, `moqsrc`, and -the FFI consumers already skip disabled renditions. WHEP, HLS, and the +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. From 13441f7dd01d9a9360ea643523f6ebc27ea68757 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Mon, 12 Oct 2026 21:31:12 -0700 Subject: [PATCH 4/4] quest(m1): muxed exports idle a disabled rendition after the header Co-Authored-By: Claude Opus 5.5 --- quest/m1/egress-enabled.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/quest/m1/egress-enabled.md b/quest/m1/egress-enabled.md index 0fe649db9ed..7508916c62c 100644 --- a/quest/m1/egress-enabled.md +++ b/quest/m1/egress-enabled.md @@ -50,9 +50,12 @@ Decided with the maintainer (2026-10-13): `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. -- **Muxed exports drop a disabled rendition like a removed one**, reusing - each format's existing path for a rendition leaving and returning. Do not - keep an idle track in the mux. +- **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 @@ -72,7 +75,9 @@ 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 -drops and restores the track. +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`,