diff --git a/quest/m1/README.md b/quest/m1/README.md index c772a56bdbb..42bc5efe160 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -42,6 +42,7 @@ moves what remains of them once their claims merge. - [Datagram tracks](/quest/m1/datagram-tracks/README.md) - a track is datagrams or streams; datagram tracks have their own request and sequence, no ranges, no cache, and map to object ID 0 on moq-transport - [Stream credit by priority](/quest/m1/stream-credit/README.md) - every stream opens through one priority entry point, and a higher-priority group takes over or resets a lower one when credit runs out - [Audio jitter target](/quest/m1/audio-jitter-target/README.md) - the playout target is default in both languages; time-stretched browser playback without steady-state underruns and a native trace replay remain +- [Native WebSocketStream keeps datagrams flowing](/quest/m1/websocket-stream-datagrams.md) - on Chromium's native `WebSocketStream`, QMux over WebSocket sheds a datagram only on a real send backlog, proven in headless Chromium - [Container reads drop backfill](/quest/m1/read-backfill.md) - the JS container consumer drops groups below its read cursor like Rust - [Buffered playback subscribes in ascending order](/quest/m1/buffered-ascending.md) - a buffered `` asks for the oldest group first, so a voice reply's opening arrives first, and audio's head hold shrinks to the floor - [Push-to-talk loses no speech](/quest/m1/push-to-talk/README.md) - an unmute is heard from its first frame with no catalog round trip: mute moves from holding a disabled rendition to suppressed silence with comfort noise, after a route spike; capture delivers frames on a steady cadence diff --git a/quest/m1/websocket-stream-datagrams.md b/quest/m1/websocket-stream-datagrams.md new file mode 100644 index 00000000000..75b408af974 --- /dev/null +++ b/quest/m1/websocket-stream-datagrams.md @@ -0,0 +1,45 @@ +# [S] Native WebSocketStream keeps datagrams flowing + +## Goal + +On Chromium's native `WebSocketStream`, a QMux session over WebSocket sheds a +datagram only when the socket has a real send backlog, not whenever any write +is in flight. A headless-Chromium test writes datagrams back to back on an idle +socket and proves every one arrives, and qmux is fixed if they don't. + +## Plan + +Planned 2026-11-07 (maintainer, /quest-plan of the follow-ups from the /quest-complete run) as the follow-up of +[moq-dev/moq#8054](https://github.com/moq-dev/moq/pull/8054). Decision prompts: + +- ✅ Plan it, goal as stated +- Plan it, different goal +- Drop it + +Milestone: + +- ✅ m1 +- m0 +- m4 + +8054 found that the `WebSocketStream` ponyfill reported backpressure during +every write, because its writable used the default one-chunk high-water mark. +`WritableStreamSink.wantsMore()` in `js/qmux/src/scheduler.ts` sheds a datagram +as soon as `writer.desiredSize` reaches 0, so two datagrams written back to +back lost one. 8054 fixed only the ponyfill. When the platform has a native +`WebSocketStream` (Chromium), qmux uses it instead, and its writable may report +the same in-flight `desiredSize` of 0. + +- Measure first: drive a QMux session over native `WebSocketStream` in + headless Chromium, as the ponyfill's case in 8054's `test/datagram-tracks` + does over Node, and record whether `desiredSize` drops to 0 while a write is + in flight with nothing buffered. +- If it does, fix the native path in qmux so that only a real socket backlog + sheds datagrams, and keep the test as the regression. If it doesn't, keep the + test and finish. + +Public API: none expected. Wire: none. + +## Related + +- [Datagram tracks](https://github.com/moq-dev/moq/pull/8054) - the PR whose ponyfill fix this extends to the native API diff --git a/quest/m2/probe/browser-send-estimate.md b/quest/m2/probe/browser-send-estimate.md index d9c58207d42..55898a60348 100644 --- a/quest/m2/probe/browser-send-estimate.md +++ b/quest/m2/probe/browser-send-estimate.md @@ -2,7 +2,9 @@ ## Goal -A browser publisher on shipping Chrome, Firefox, and Safari gets a send-rate estimate, so its bandwidth reservations (`Reservation.rate`) follow the path and `@moq/publish` encoders adapt, instead of holding their ceiling because the estimate is always `undefined`. +A browser publisher on shipping Chrome and Firefox gets a send-rate estimate, so its bandwidth reservations (`Reservation.rate`) follow the path and `@moq/publish` encoders adapt, instead of holding their ceiling because the estimate is always `undefined`. + +The WebSocket fallback is out of scope: it has no browser estimate to read, so its publishers keep holding their rate (decided 2026-11-07, below). That includes every WebKit browser, which `js/net` routes to the fallback (`isWebTransportUserAgentSupported` in `js/net/src/connection/browser.ts`) until [Safari WebTransport](/quest/m5/safari-webtransport.md) lifts the gate; that quest owns Safari's estimate. ## Plan @@ -24,15 +26,21 @@ Reason: one rule decided at setup fixes the RTT, the polling, and the counters. What remains once the waits clear: - In stable Chrome (no flag) and Firefox, verify through a shaped link that a publisher's bitrate follows a capacity drop. Reuse the [probe epic](/quest/m2/probe/README.md)'s shaped-uplink harness if it has landed. -- The WebSocket fallback has no estimate whatever the browsers ship, so its publishers still hold their rate; the maintainer decision below covers it. +- The WebSocket fallback has no estimate whatever the browsers ship, so its publishers still hold their rate, as decided below. + +Decided 2026-11-07 (maintainer, /quest-unblock): Safari publishers and the WebSocket fallback get no estimate before WebKit reports one. + +- ✅ (a) Wait for WebKit, as today. +- (b) A lite-07 receiver report, where the relay sends back the rate it receives from us, which can only lower the estimate while app-limited. +- (c) Estimate locally from how fast the transport drains our writes, which depends on each browser's buffering. + +Reason: Chrome and Firefox need nothing. For Safari, (b) and (c) each build an app-layer controller above the browser's own that a later WebKit release makes redundant. For the WebSocket fallback, (b) or (c) would be the only source, since it never reads WebTransport stats; building one waits until fallback adaptation is worth it on its own. -Public API: none expected. Wire: none, unless the decision below picks a receiver report. +Public API: none expected. Wire: none. ## Waiting - Chrome: ships `WebTransport.getStats()` with `estimatedSendRate` enabled for all users in 159, per the 2026-11-05 Intent to Ship. Check that stable Chrome's `getStats()` reports a non-null `estimatedSendRate` on a connection that has been sending media for a few seconds. -- WebKit: Safari's `getStats()` reports a non-null `estimatedSendRate`. Check the `return_object_property_estimatedSendRate` entry under `api.WebTransport.getStats` in browser-compat-data, or a Safari publisher's `Reservation.rate`. -- Maintainer: whether Safari publishers (and the WebSocket fallback) get an estimate before WebKit reports one. An agent can't pick this because each option changes the wire or adds a congestion estimator above the browser's own. Options: (a) wait for WebKit, as today; (b) a lite-07 receiver report, where the relay sends back the rate it receives from us, which can only lower the estimate while app-limited; (c) estimate locally from how fast the transport drains our writes, which depends on each browser's buffering. Recommendation: (a), since Chrome and Firefox need nothing, and (b) and (c) each build an app-layer controller that a later WebKit release makes redundant. ## Related diff --git a/quest/m5/safari-webtransport.md b/quest/m5/safari-webtransport.md index 04c018e981c..e3deb862bbf 100644 --- a/quest/m5/safari-webtransport.md +++ b/quest/m5/safari-webtransport.md @@ -30,6 +30,7 @@ tracking since PR #3345 and closes with this. Playwright has a worker `MediaStreamTrackProcessor` or transfers a `MediaStreamTrack`; the harness's capability probe already reports the path taken, and the fixture must take it. +- Safari publishers then pace from `getStats()`'s `estimatedSendRate`, which Safari 26.4 still reports as null, so they hold their rate until WebKit fills it (decided 2026-11-07 in [Browser publishers adapt to their send estimate](/quest/m2/probe/browser-send-estimate.md)). Check the `return_object_property_estimatedSendRate` entry under `api.WebTransport.getStats` in browser-compat-data, or a Safari publisher's `Reservation.rate`, and record which. - Update `doc/lib/js/index.md`. - `parseUserAgent` in `js/net/src/connection/browser.ts` already returns the Safari and iOS versions (bowser left in