diff --git a/quest/m3/README.md b/quest/m3/README.md index e1ba55da51..24c5fbff0b 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -67,6 +67,9 @@ federation) land. - [OBS native codecs](/quest/m3/obs-moq-video/README.md) - replace FFmpeg video and audio decoding with moq-video and moq-audio, deliver GPU frames, and use native audio/video encoders - [MoQ in obs-studio](/quest/m3/obs-studio/README.md) - a native MoQ output and service merged into obs-studio, once its maintainers agree; the plugin carries it until then - [AudioToolbox decode](/quest/m3/audio-decode-audiotoolbox.md) - macOS and iOS decode HE-AAC, multichannel AAC, and what else the framework offers +- [AudioToolbox encode](/quest/m3/audio-encode-audiotoolbox.md) - macOS and iOS encode AAC-LC, for moq-dev/studio's RTMP destinations +- [Media Foundation decode](/quest/m3/audio-decode-mediafoundation.md) - Windows decodes HE-AAC, multichannel AAC, and what else the MFTs offer +- [Media Foundation encode](/quest/m3/audio-encode-mediafoundation.md) - Windows encodes AAC-LC, for moq-dev/studio's RTMP destinations - [GPU CI](/quest/m3/gpu-ci.md) - NVIDIA tests gate the merge queue on a self-hosted GPU runner VM, and `just rs nvidia` runs them locally instead of skipping - [Media stats](/quest/m3/stats/README.md) - publishers announce a stats track in the catalog, viewers answer a soliciting catalog through a per-catalog `.echo` broadcast, and one model turns both into a health verdict and a preflight report - [Wire stats](/quest/m3/wire-stats/README.md) - `Presence` counts each root's transport bytes on the wire, `wire_tx` and `wire_rx`, from QUIC, WebSocket, and embedder gateway sources @@ -136,6 +139,7 @@ federation) land. - [Lint override and deprecation audit](/quest/m3/audit-lints.md) - repeat the suppression and deprecation audit after the release ships, starting with clippy on the macOS and Windows targets - [Clean ALSA playback onset](/quest/m3/audio-alsa-onset.md) - no onset restarts or clicks over PulseAudio - [Read a bus source's own samples](/quest/m3/audio-strip-tap.md) - a tap reads one source's samples before its delay with their bus-clock positions, for the studio's clap sync +- [moq-audio filter chain](/quest/m3/audio-filters.md) - per-source suppression, gate, compressor, limiter, ducking, and plugins, which moq-dev/studio configures - [Interop audit](/quest/m3/audit-interop.md) - our interop runner entries match what we ship, and every red interop cell with a moq.dev side is fixed, reported, or noted as down or unsupported - [moq-hls exposes each rendition's bound source](/quest/m3/hls-rendition-source.md) - playlists and segments report the source path and instance they read, so a cache can key on the source - [The memory bench finishes at 1 MiB frames](/quest/m3/memory-bench-hang.md) - the allocation mode's park at `size=1048576` is root-caused and fixed @@ -189,6 +193,8 @@ federation) land. - [Padding probes from the browser](/quest/m3/probe-padding.md) - parked: the maintainer doubts padding is the right way for a browser to probe - [JS rung disable](/quest/m3/probe-rung-disable-js.md) - `@moq/publish` applies the native rung rule, recovering through padding; parked with it - [Layered broadcasts](/quest/m3/layers/README.md) - hang specifies a layer layout track, and `moq play` and `@moq/watch` composite layers over the main picture +- [moq-rtmp dials rtmps:// and always advertises a tcUrl](/quest/m3/rtmp-rtmps-tcurl.md) - the RTMP client dials an `rtmp://` or `rtmps://` URL and sends its `tcUrl` on both, for moq-dev/studio's destinations +- [moq-webrtc and moq-rtmp dial through a caller's policy](/quest/m3/webrtc-dial-policy.md) - a host that dials untrusted URLs refuses private, loopback, and link-local addresses on every WHIP, WHEP, and RTMP dial - [A room keeps a participant while one of its aliases is announced](/quest/m3/room-alias-withdraw.md) - withdrawing `camera` while `camera.hang` stays announced keeps the participant - [@moq/auth key derivation and key sets validate like single keys](/quest/m3/auth-key-validation.md) - `Key.public` shares no scope and never yields an invalid key, and key sets and Rust refuse mismatched keys at parse - [Binding server helpers observe session errors and describe close truthfully](/quest/m3/serve-session-errors.md) - Python retrieves session task errors, and the Kotlin, Go, and Dart docs say close ends every connection diff --git a/quest/m3/audio-decode-audiotoolbox.md b/quest/m3/audio-decode-audiotoolbox.md index a6fdec784a..dc3890aaa2 100644 --- a/quest/m3/audio-decode-audiotoolbox.md +++ b/quest/m3/audio-decode-audiotoolbox.md @@ -34,3 +34,7 @@ alongside the `objc2-core-audio-types` the crate already carries. on a device like the rest of the mobile line. - Docs: the `decode` row in `doc/lib/rs/moq-audio.md` ("AAC-LC (mono or stereo)") names what macOS and iOS also decode. + +## Related + +- [Media Foundation decode](/quest/m3/audio-decode-mediafoundation.md) - the same shape on Windows diff --git a/quest/m3/audio-decode-mediafoundation.md b/quest/m3/audio-decode-mediafoundation.md new file mode 100644 index 0000000000..e99358ed39 --- /dev/null +++ b/quest/m3/audio-decode-mediafoundation.md @@ -0,0 +1,31 @@ +# [M] Media Foundation decode on Windows + +## Goal + +On Windows, `moq-audio` decodes AAC-LC, HE-AAC v1 and v2, and multichannel +AAC through the Media Foundation AAC decoder MFT, and whichever of MP3, FLAC, +AC-3, and E-AC-3 the installed MFTs open. + +## Plan + +The audio counterpart of `rs/moq-video/src/decode/backend/mediafoundation.rs`, +using the `windows` crate features moq-video already enables plus the audio +ones. Behind the decode seam as the first candidate on `target_os = +"windows"`. + +- The AAC decoder MFT takes an `MF_MT_USER_DATA` blob built from the catalog + AudioSpecificConfig and reports the output `WAVEFORMATEXTENSIBLE`, whose + channel mask maps to `Layout`. +- Optional MFTs (Dolby, FLAC) are probed at open: absent means the codec is + not advertised on that host, not an error at decode time. +- Fixtures and layout-order tests as in the AudioToolbox quest. +- Verification runs on a Windows host; the per-PR CI only compiles the + platform code (`just rs windows`). + +Changed 2026-11-07 (maintainer, /quest-plan from the API sweep of open PRs and remaining quests): moq-audio's codecs return zero or more outputs with `flush` before the cut, so this backend adds no break. `decode::Consumer` still places each packet by the one output it returns and refuses any other count, so if this backend holds or splits output, it also teaches the consumer to place that output. + +Changed 2026-11-09 (moq-dev/studio /quest-audit): this moved from m5 to m3 +with [Media Foundation encode](/quest/m3/audio-encode-mediafoundation.md), +which the maintainer moved for moq-dev/studio's destinations and which +requires this for its round-trip regression. A quest an m3 quest requires +joins m3. diff --git a/quest/m3/audio-encode-audiotoolbox.md b/quest/m3/audio-encode-audiotoolbox.md new file mode 100644 index 0000000000..2b4a1e1195 --- /dev/null +++ b/quest/m3/audio-encode-audiotoolbox.md @@ -0,0 +1,51 @@ +# [M] AudioToolbox AAC encode on macOS and iOS + +## Goal + +On macOS and iOS, `Codec::Aac` encodes through AudioToolbox at the input's +layout, mono through 5.1 and 7.1, and the result plays in the browser, +`moq play`, and OBS. 6.1 is refused: since #4973 a synthesized ASC names no +program config element. Carrying the converter's magic cookie verbatim would +lift that once a caller needs it. + +## Plan + +An `AudioConverter` from interleaved `f32` to `kAudioFormatMPEG4AAC`, behind +the encode seam as the platform candidate on macOS and iOS. + +- The catalog ASC is synthesized at construction per the encode seam; read + `kAudioConverterCompressionMagicCookie` at open and assert it matches. + `kAudioConverterPrimeInfo` gives the delay the timestamps fold in. +- Bitrate through `kAudioConverterEncodeBitRate`, updated live where the + converter allows. +- Gate the seam's "AAC refused without a platform encoder" test to hosts + without one. +- Regression: a stereo and a 5.1 encode round-trip through the AudioToolbox + decoder and through symphonia (stereo only), with timestamps continuous + across the priming. +- That continuity fails today for an epoch below the delay: `Producer::timestamp` + (`rs/moq-audio/src/encode/producer.rs`) clamps each priming packet to zero, + so a zero epoch stamps `[0, 0, 0, 20_000]`, and `decode::Consumer` reads the + duplicates as holes and resets AAC between them. Shift the whole timeline by + the deficit instead, including the terminal end (found in #4081 review). + +This backend implements the zero-or-more packet return and `flush` that +moq-audio's encoder took in m2 before the cut +(decided 2026-11-07, maintainer, /quest-plan from the API sweep of open PRs +and remaining quests), so it adds no break and waits on no other encoder backend; its round-trip test still needs its platform decoder. + +Decided 2026-11-09 (maintainer, moq-dev/studio /quest-audit): this moved from +m5 to m3. The studio's m0 +[destinations](https://github.com/moq-dev/studio/blob/main/quest/m0/destinations.md) +wait on it through the +[AAC pin quest](https://github.com/moq-dev/studio/blob/main/quest/m0/pin-aac-encode.md) +for RTMP ingests that require AAC, and m3 is where work a named consumer +waits on belongs. + +## Required + +- [AudioToolbox decode](/quest/m3/audio-decode-audiotoolbox.md) - the round-trip regression decodes through it + +## Related + +- [Media Foundation AAC encode](/quest/m3/audio-encode-mediafoundation.md) - the same encode seam on Windows diff --git a/quest/m3/audio-encode-mediafoundation.md b/quest/m3/audio-encode-mediafoundation.md new file mode 100644 index 0000000000..03f39ed9e5 --- /dev/null +++ b/quest/m3/audio-encode-mediafoundation.md @@ -0,0 +1,37 @@ +# [M] Media Foundation AAC encode on Windows + +## Goal + +On Windows, `Codec::Aac` encodes through the Media Foundation AAC encoder +MFT, mono, stereo, and up to 5.1. + +## Plan + +The audio counterpart of `rs/moq-video/src/encode/backend/mediafoundation.rs`, +behind the encode seam on Windows. + +- The encoder MFT fixes its output rates and bitrates per channel count; + refuse configurations outside that table at construction rather than + letting the MFT pick silently. +- The catalog ASC is synthesized at construction per the encode seam; the + output type's `MF_MT_USER_DATA` must match it, asserted in tests. +- Round-trip regression through the Media Foundation decoder; verification on + a Windows host, since the per-PR CI compiles only. + +This backend implements the zero-or-more packet return and `flush` that +moq-audio's encoder took in m2 before the cut +(decided 2026-11-07, maintainer, /quest-plan from the API sweep of open PRs +and remaining quests), so it adds no break and waits on no other encoder backend; its round-trip test still needs its platform decoder. + +Decided 2026-11-09 (maintainer, moq-dev/studio /quest-audit): this moved from +m5 to m3. The studio's m0 +[destinations](https://github.com/moq-dev/studio/blob/main/quest/m0/destinations.md) +wait on it through the +[AAC pin quest](https://github.com/moq-dev/studio/blob/main/quest/m0/pin-aac-encode.md) +for RTMP ingests that require AAC, and m3 is where work a named consumer +waits on belongs. + +## Required + +- [Media Foundation decode](/quest/m3/audio-decode-mediafoundation.md) - the round-trip regression decodes through it + diff --git a/quest/m3/audio-filters.md b/quest/m3/audio-filters.md new file mode 100644 index 0000000000..f8a2a05e5d --- /dev/null +++ b/quest/m3/audio-filters.md @@ -0,0 +1,36 @@ +# [L] moq-audio filter chain + +## Goal + +Each audio source can run an ordered processing chain in moq-audio before the +program bus: noise suppression, noise gate, compressor, and limiter, plus +ducking, which lowers one source (music) when another (the microphone) is +active. Third-party VST3 and CLAP plugins load into the chain. The +[studio's audio filters](https://github.com/moq-dev/studio/blob/main/quest/m0/audio-filters.md) in moq-dev/studio +configure it per source. + +## Plan + +Changed 2026-11-02 (maintainer): the studio moved to moq-dev/studio. The +chain lands here with its own tests and benchmark; the studio's panel and +scene-file shape follow a pin bump there. + +Decided 2026-11-09 (maintainer, moq-dev/studio /quest-audit): this moved from +m5 to m3. The studio's m0 audio filters wait on it through the +[filter chain pin quest](https://github.com/moq-dev/studio/blob/main/quest/m0/pin-audio-filters.md), +and m3 is where work a named consumer waits on belongs. + +Decided 2026-11-02 (maintainer): the chain runs on the capture producer +before `capture::Producer` fans out to its consumers, so +the bus and a source's own track both carry filtered audio; gain and mute +stay after the split, in the bus `Strip`, so the chain has no gain stage of +its own; a compressor's makeup gain covers level fixes. + +Prefer maintained crates, such as `nnnoiseless` for RNNoise suppression, over +hand-rolled DSP. Benchmark the chain per source so its cost per added source +shows up as a slope. Host plugins out of process, so a crashing or hung plugin +drops that filter rather than the audio bus: the audio thread never blocks on +the plugin process, a block the plugin misses its deadline for passes through +unfiltered, and a plugin that keeps missing is bypassed and reported as failed. +Use a maintained host crate (such as `clack` for CLAP) rather than +hand-rolling the plugin ABIs. diff --git a/quest/m3/layers/README.md b/quest/m3/layers/README.md index 26358332db..48371c383e 100644 --- a/quest/m3/layers/README.md +++ b/quest/m3/layers/README.md @@ -29,7 +29,8 @@ Decided 2026-11-08 (maintainer, moq-dev/studio /quest-plan): - The studio prototypes the format first (`moq-studio --watch`, reading a provisional `dev.moq.studio.layers` track), and the spec starts from what that prototype settles (rejected: drafting the spec in parallel). -- Players mix layers' audio in, at the gain and mute the layout carries. +- Players mix layers' audio in, at the gain, mute, pan, and delay the layout + carries (pan and delay added 2026-11-09, as the spec quest records). - Each layer carries a priority and whether a viewer may toggle it. A player maps priority onto moq-lite's subscription priorities and unsubscribes a toggled-off layer. @@ -38,5 +39,5 @@ Decided 2026-11-08 (maintainer, moq-dev/studio /quest-plan): ## Required - [hang specifies a layer layout track](/quest/m3/layers/spec.md) - a versioned layout track in the hang draft, `rs/hang`, and `js/hang` that both players composite -- [moq play composites layers](/quest/m3/layers/play.md) - `moq play` composites visible layers and mixes their audio +- [moq play composites layers](/quest/m3/layers/play.md) - `moq play` composites visible layers, mixes their audio, and toggles them - [The watch player composites layers](/quest/m3/layers/watch.md) - `@moq/watch` composites visible layers, mixes their audio, and toggles them diff --git a/quest/m3/layers/play.md b/quest/m3/layers/play.md index 5761e0e0cb..e40cb13ca4 100644 --- a/quest/m3/layers/play.md +++ b/quest/m3/layers/play.md @@ -3,16 +3,25 @@ ## Goal `moq play` plays a broadcast that carries a layer layout track: it composites -each visible layer over the main picture where and when the layout says, and -mixes each audio layer in at its gain and mute. A broadcast without one plays -as today. +each visible layer over the main picture where and when the layout says, +mixes each audio layer in at its gain, mute, pan, and delay, and lets a +viewer toggle each layer the layout marks toggleable. A broadcast without one +plays as today. ## Plan Fetch only visible layers, at the layout's priorities through moq-lite's -subscription priorities. Prove placement and timing against the epic's -fixture. moq-dev/studio's `hang-layers` quest deletes its watch prototype -once this lands. +subscription priorities, and unsubscribe a toggled-off layer. Prove placement +and timing against the epic's fixture. + +Decided 2026-11-09 (maintainer, moq-dev/studio /quest-audit): the viewer +toggle and the priority mapping are this quest's work, not only +`@moq/watch`'s. moq-dev/studio decided on 2026-11-08 (its +`quest/m2/compose/controls.md`) that viewer controls land in the native +player, and its `hang-layers` quest deletes its prototype player once this +lands, so this quest is their home. Subscriptions are per track, not per +layer, as that quest settled: a track several layers show stays subscribed +while any of them is on, at the highest priority among them. ## Required diff --git a/quest/m3/layers/spec.md b/quest/m3/layers/spec.md index da1074e7ae..d59c2608e8 100644 --- a/quest/m3/layers/spec.md +++ b/quest/m3/layers/spec.md @@ -5,8 +5,8 @@ `drafts/draft-lcurley-moq-hang.md`, `rs/hang`, and `js/hang` define a layer layout track: a versioned JSON track in snapshot mode on the media clock that names each layer's track and gives its transform, crop, z-order, opacity, -visibility, keyframes, gain and mute, priority, and whether a viewer may -toggle it. +visibility, keyframes, priority, and whether a viewer may toggle it. An audio +layer also carries its gain, mute, pan, and delay. ## Plan @@ -15,6 +15,14 @@ what every player can reproduce. A consumer refuses an unknown version loudly and plays the main picture alone. Layers name tracks and catalog references, never files. +Decided 2026-11-09 (maintainer, moq-dev/studio /quest-audit): an audio layer +carries pan and delay as well as gain and mute. The studio's strip has all +four (`rs/moq-scene/src/collection.rs` in moq-dev/studio; pan shipped in +[moq-dev/studio#116](https://github.com/moq-dev/studio/pull/116)), and a +source's own track is taken before the strip applies them. Without the delay +in the layout, a microphone the creator delayed for sync and published on its +own track would play out of sync for viewers. + ## Waiting - moq-dev/studio: its `moq-studio --watch` prototype plays program plus diff --git a/quest/m3/layers/watch.md b/quest/m3/layers/watch.md index f9fec74a15..f2d1059db0 100644 --- a/quest/m3/layers/watch.md +++ b/quest/m3/layers/watch.md @@ -4,9 +4,9 @@ `@moq/watch` plays a broadcast that carries a layer layout track: it composites each visible layer over the main picture where and when the layout -says, mixes each audio layer in at its gain and mute, and lets a viewer -toggle each layer the layout marks toggleable. A broadcast without one plays -as today. +says, mixes each audio layer in at its gain, mute, pan, and delay, and lets a +viewer toggle each layer the layout marks toggleable. A broadcast without one +plays as today. ## Plan diff --git a/quest/m3/obs-moq-video/README.md b/quest/m3/obs-moq-video/README.md index 8c4de7db7e..d9ebfec9b2 100644 --- a/quest/m3/obs-moq-video/README.md +++ b/quest/m3/obs-moq-video/README.md @@ -29,3 +29,4 @@ Decided in the 2026-09-30 audit: the Windows and Linux GPU decode paths and the - [Video hardware validation](/quest/m5/video-hardware.md) - physical hardware evidence is required for each claimed GPU path - [AudioToolbox decode](/quest/m3/audio-decode-audiotoolbox.md) - HE-AAC and multichannel AAC reach the OBS source on macOS through moq-ffi +- [AudioToolbox encode](/quest/m3/audio-encode-audiotoolbox.md) - native AAC encode reaches the OBS encoder adapter on macOS through moq-ffi diff --git a/quest/m3/rtmp-rtmps-tcurl.md b/quest/m3/rtmp-rtmps-tcurl.md new file mode 100644 index 0000000000..f03cff1302 --- /dev/null +++ b/quest/m3/rtmp-rtmps-tcurl.md @@ -0,0 +1,51 @@ +# [S] moq-rtmp dials rtmps:// and always advertises a tcUrl + +## Goal + +moq-rtmp's client dials an `rtmp://` or `rtmps://` URL itself: it resolves +the host, connects, and for `rtmps://` completes the TLS handshake against +the host's name. Its `connect` command advertises a `tcUrl` derived from that +URL on both schemes: its scheme, host, port if it names one, and app, never +the stream key. +`moq import rtmp` and `moq export rtmp` take `rtmps://` in `--connect`. + +A test dials the crate's own TLS listener by URL and asserts the server saw +that `tcUrl`, and a server certificate the client does not trust fails the +dial. + +## Plan + +Decided 2026-11-09 (maintainer, moq-dev/studio /quest-audit): this is its own +quest in m3, since moq-dev/studio's +[destinations](https://github.com/moq-dev/studio/blob/main/quest/m0/destinations.md) +wait on it to push to RTMPS ingests. + +Today (`rs/moq-rtmp/src/dial.rs`): + +- `Client::connect(addr, app)` takes a resolved `SocketAddr`, dials plaintext + TCP, and advertises `rtmp:///`. That `tcUrl` carries the + resolved IP, not the name the URL gave, so a server that picks a virtual + host from it sees the wrong one. +- `Client::with_stream(stream, app)` is the only route to `rtmps://`: the + caller makes the TLS stream. It passes `tc_url: None`, and several ingest + servers reject a `connect` without one. +- moq-cli resolves the host itself and refuses every scheme but `rtmp` + (`parse_url` in `rs/moq-cli/src/rtmp.rs`), so `doc/bin/rtmp.md` says the + CLI is plaintext only. + +Guidance: + +- No route leaves the `tcUrl` out. `with_stream` either takes what it needs + to advertise one or goes away; propose the shape, changing `connect` + rather than adding a second entry point beside it. +- The caller supplies the TLS client configuration, as `Server::with_tls` + takes the server's, behind the same `tls` feature. +- `Request::tc_url()` on the server already reports what a client sent, + and `tls_fixture` in `rs/moq-rtmp/src/server.rs` builds a self-signed + listener, so the test needs no new harness. +- Update `doc/bin/rtmp.md`, and put the migration note in the PR's Impact + section if a published entry point changes. + +## Related + +- [moq-webrtc and moq-rtmp dial through a caller's policy](/quest/m3/webrtc-dial-policy.md) - applies a host's address policy to the URL dial this adds diff --git a/quest/m3/webcodecs-priming.md b/quest/m3/webcodecs-priming.md index 14984f4ab9..0480a65e15 100644 --- a/quest/m3/webcodecs-priming.md +++ b/quest/m3/webcodecs-priming.md @@ -68,4 +68,5 @@ Decided 2026-11-04 (maintainer, /quest-plan): ## Related - [moq-audio in the browser](/quest/m5/audio-web.md) - its WebCodecs backend's `delay()` reuses this measurement +- [AudioToolbox encode](/quest/m3/audio-encode-audiotoolbox.md) - the native AAC encoder's delay, folded the same way - [Chromium's Opus encoder stamps chunks from their input](/quest/m4/chromium-opus-timestamps.md) - another WebCodecs `AudioEncoder` timestamp bug diff --git a/quest/m3/webrtc-dial-policy.md b/quest/m3/webrtc-dial-policy.md new file mode 100644 index 0000000000..be5d093f83 --- /dev/null +++ b/quest/m3/webrtc-dial-policy.md @@ -0,0 +1,61 @@ +# [M] moq-webrtc and moq-rtmp dial through a caller's policy + +## Goal + +A host that dials URLs it does not trust, such as a service pushing a +creator's broadcast to destinations the creator typed in, gives moq-webrtc's +WHIP and WHEP client and moq-rtmp's client a dial policy. A policy that +refuses private, loopback, and link-local addresses stops every connection +those clients would make to one, and the dial fails with an error that says +the policy refused it. The default policy allows every address, so a desktop +app and the `moq` CLI still reach an ingest on the LAN or on loopback. + +The policy judges the address a socket is about to connect or send to, not +the URL's text, so it holds for each of: + +- a literal address in the URL, in any spelling, IPv4-mapped IPv6 included, +- a hostname, checked on the addresses it resolved to for this dial, so a + second lookup cannot swap in another, +- an HTTP redirect from the WHIP or WHEP endpoint, +- the remote ICE candidates in the SDP answer, which the session then sends + UDP to, +- moq-rtmp's `rtmp://` and `rtmps://` dial. + +Tests prove each case refuses without a listener on the refused address +seeing a connection or a datagram, and that the default still dials loopback. + +## Plan + +Decided 2026-11-09 (maintainer, moq-dev/studio /quest-audit): this is its own +quest in m3. moq-dev/studio's +[destinations](https://github.com/moq-dev/studio/blob/main/quest/m0/destinations.md) +build an egress library a hosted service reuses, and that service must not +be a way to reach its own network. + +Today: + +- `moq_webrtc::Client` (`rs/moq-webrtc/src/client/mod.rs`) builds its own + `reqwest::Client` with default settings and POSTs the offer to whatever + URL `publish` or `subscribe` is handed. reqwest resolves the host and + follows redirects, and nothing checks the answer's candidates. +- moq-rtmp has no gap yet, only because it never resolves a name: + `Client::connect` takes a `SocketAddr` and `Client::with_stream` a + finished stream, so the caller picks the address. The + [rtmps:// quest](/quest/m3/rtmp-rtmps-tcurl.md) makes it dial a URL, and + from then on it has the same gap, which is why this waits on it. + +Guidance: + +- One policy type serves both crates, so a host states its rule once for + every destination kind. Propose its shape and its home; a value the host + builds is preferred over a callback, per the root `AGENTS.md`. +- A refusal is terminal for that URL, and its error is distinct from a + failed connection, so a caller that redials on failure does not retry it. +- Check at connect time on the resolved address. Checking the URL and then + letting the HTTP client resolve again is the DNS rebinding hole. +- moq-srt's client takes a `SocketAddr` like moq-rtmp does today. It is out + of scope until it dials a URL. + +## Required + +- [moq-rtmp dials rtmps:// and always advertises a tcUrl](/quest/m3/rtmp-rtmps-tcurl.md) - the URL dial in moq-rtmp that the policy covers diff --git a/quest/m5/audio-web.md b/quest/m5/audio-web.md index 915d7d547d..78c412393e 100644 --- a/quest/m5/audio-web.md +++ b/quest/m5/audio-web.md @@ -3,7 +3,7 @@ ## Goal moq-audio runs in the browser for the -[web studio](https://github.com/moq-dev/studio/blob/main/quest/m0/web.md) in moq-dev/studio: +[web studio](https://github.com/moq-dev/studio/blob/main/quest/m2/web.md) in moq-dev/studio: - **Capture:** getUserMedia into an AudioWorklet. - **Playback:** through an AudioWorklet. - **Codecs:** encode and decode Opus and AAC through WebCodecs @@ -15,6 +15,12 @@ mixing on the bus, and an encode and decode round trip. ## Plan +Decided 2026-11-09 (maintainer, moq-dev/studio /quest-audit): the studio's +web build moved from its m0 to its m2, so this stays deferred in m5 with +[moq-video in the browser](/quest/m5/video-web.md). The studio waits on it +through its +[browser audio pin quest](https://github.com/moq-dev/studio/blob/main/quest/m2/pin-browser-audio.md). + Changed 2026-11-02 (maintainer): the studio moved to moq-dev/studio, so this lands here first with its own browser test rather than in the same PR as the studio page's audio slice. diff --git a/quest/m5/video-web.md b/quest/m5/video-web.md index edcd680a54..b112ef15fc 100644 --- a/quest/m5/video-web.md +++ b/quest/m5/video-web.md @@ -3,7 +3,7 @@ ## Goal moq-video runs in the browser for the -[web studio](https://github.com/moq-dev/studio/blob/main/quest/m0/web.md) in moq-dev/studio: +[web studio](https://github.com/moq-dev/studio/blob/main/quest/m2/web.md) in moq-dev/studio: - **Capture:** getUserMedia for cameras and getDisplayMedia for screens, producing `Frame`s backed by WebCodecs `VideoFrame`s. - **Codecs:** encode and decode through WebCodecs for whichever of H.264, @@ -14,13 +14,19 @@ moq-video runs in the browser for the `fs` feature and file IO sit behind a native-only cfg, the prerequisite for browser media muxing. A CI `cargo check` for wasm32 keeps them building. moq removed its WASM support on 2026-10-31, so this quest restores the wasm32 -target; confirm the web studio still wants it before starting. +target. A test in a headless browser proves capture from a fake device, an encode and decode round trip, and the import, so this lands without the studio page. ## Plan +Decided 2026-11-09 (maintainer, moq-dev/studio /quest-audit), answering +whether the web studio still wants this: the studio's web build moved from +its m0 to its m2, so it is still planned and this stays deferred in m5. The +studio waits on it through its +[browser video pin quest](https://github.com/moq-dev/studio/blob/main/quest/m2/pin-browser-video.md). + Changed 2026-11-02 (maintainer): the studio moved to moq-dev/studio. This was planned to land in the same PR as the first studio page; it now lands here first with its own browser test, and the studio's page follows a pin