Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions quest/m1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<moq-watch>` 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
Expand Down
45 changes: 45 additions & 0 deletions quest/m1/websocket-stream-datagrams.md
Original file line number Diff line number Diff line change
@@ -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
18 changes: 13 additions & 5 deletions quest/m2/probe/browser-send-estimate.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down
1 change: 1 addition & 0 deletions quest/m5/safari-webtransport.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading