diff --git a/docs/project-memory.md b/docs/project-memory.md index 3565c42..ba774a3 100644 --- a/docs/project-memory.md +++ b/docs/project-memory.md @@ -25,7 +25,7 @@ Last updated: 2026-08-21 - Keep experiments bounded: the standard representation sequence is below; native RTP relay, encoded-object striping, and FEC remain separate. - ADR-0006's historical browser-bridge canary remains no-go; VP8 stays default. Browser H.264 rendered 298/299; Native WGC/MF rendered 203 with PID/LUID-correlated `VideoEncode`. Downloaded use, multi-viewer/endurance/public proof remain open. - Mobile Web Host unsupported. Android 14+ sender is direct-child video-only; device, audio, rotation, and SFU remain open. iOS deferred; TV output is local-only P2. -- ADR-0005 accepts direct/peer UDP, bounded SFU roots, then optional selected-edge TURN. Production now enables the one-shot selected-edge tuple after SFU; participant-wide TURN remains rejected and ordinary peer ICE stays STUN-only. No real TURN/SFU media canary has yet been run. +- ADR-0005 accepts direct/peer UDP, bounded SFU roots, then optional selected-edge TURN. Production enables the one-shot selected-edge tuple after SFU; participant-wide TURN remains rejected and ordinary peer ICE stays STUN-only. No production SFU/TURN media canary has run; latest source has one local SFU/UDP functional proof and TURN remains unverified. - The controller is process-enabled, not room-allowlisted: `PEER_ASSISTED_MEDIA=true` gives every normal room the same direct/peer -> SFU -> selected-edge path with per-room state. Room `1` is historical smoke. Preserve sticky P2P, mobile leaves and break-before-make; recovery gets one attempt per layer (ICE restart, same-parent rebuild, alternate peer, then SFU), never three identical retries. - C+B quality reparenting is edge-local and cooldown-bound; no score, timer, or global parent penalty. - Flagship media is UDP; HTTPS/WSS stays TLS/TCP; the old release remains rollback-only. @@ -44,13 +44,13 @@ Last updated: 2026-08-21 - The repository is one npm package using Node.js 24, React, TypeScript, Vite, native WebRTC, `ws`, Zod, Vitest, and separate coturn. - Production serves exact `fd76277b05d491af8840b28f3132b7ff445d3cbe` at `https://share.bonfire.icu`. The immutable artifact is 915,377 bytes with SHA-256 `8da4b8b2b1ae4b82add615f4367ee447b5ade86c4e58a938e51085940b9867d3`; the 2026-08-20T20:53:59Z UTC cutover held the deployment lock for 8,389 ms and reached local health in 520 ms from stop (478 ms from symlink switch). Exact `5e4a3679076a9ea2fe7a41fadf4be65a439db450` is the immediate rollback release. - The current Screener, LiveKit, coturn, and nginx services are active/running with `NRestarts=0`; local and public health are 200. SQLite v3 is intact with five rooms including room `1`, and the DB owner/mode remain `screener:screener`/0600. -- Production ICE remains ordinary STUN-only. `PEER_ASSISTED_MEDIA=true` enables the controller for every normal room, and selected-edge TURN is now configured for the single UDP tuple with TTL 120; the coturn daemon and nginx/LiveKit/firewall/listener baselines were unchanged. No real TURN/SFU media canary or quality claim is implied. +- Production ICE remains ordinary STUN-only. `PEER_ASSISTED_MEDIA=true` enables the controller for every normal room, and selected-edge TURN is configured for the single UDP tuple with TTL 120; the coturn daemon and nginx/LiveKit/firewall/listener baselines were unchanged. No production SFU/TURN media canary or quality claim is implied. - Production requires the independent site-access secret through `SITE_ACCESS_PASSWORD`. Its stateless cookie authorizes creation/Host and permits code-only Viewer attempts; valid private fragment grants remain direct. Public-watch accepts site access plus code, while private code-only entry additionally requires the room password. The env, endpoint, cookie, and nginx limiter naming migrated atomically; anonymous failures stay neutral, and there are no accounts/JWT/session rows. - Production uses nginx, Node.js 24.19.0, coturn 4.17.2, and LiveKit 1.13.5 on UDP 7882 (TCP fallback off). Ordinary peer ICE receives no TURN; the selected-edge tuple is configured only for the current controller edge, with no credential pre-advertised to ordinary peers. Coturn retains the old authenticated-relay ports. Services are healthy with zero restarts. - ADR-0005 source rejects `PEER_ASSISTED_ROOM_IDS`; `PEER_ASSISTED_MEDIA=true` enables every room, while ordinary peer ICE stays STUN-only. A stale room-ID variable fails startup. `screener-v2` is the only deployed signaling literal; there is no v1 parser or translator. - Chrome 151/LiveKit localhost A/B cut failure-to-active/render from 1.481/2.257 seconds to 0.200/0.320; 31 new frames and 25 ms sampling kept host edges at two. It is headless synthetic 720p30 and includes SDK/network prewarm, not public-network evidence. - Chrome 151 synthetic topology/quality runs kept fanout 2/1 and decoding; one relay close recovered in 5.32 seconds. Control evidence only. -- Room `1` publishes `HIGH+LOW` with Dynacast off and subscriber `HIGH` ceilings, and now has bounded local quality reparenting. Both remain unverified on real media; test zero-child leaves and calibrated C+B loss before broad rollout. +- Latest source matches LiveKit 2.22.0's two-layer `q,h` sender layout. Chrome 151 against local LiveKit 1.13.5 retained one SFU/UDP root with increasing inbound/decoded/rendered counters and clean client leaves; TURN was absent and no performance claim follows. Production deployment, zero-child BWE, and calibrated C+B loss remain open. - C+B uses three hard-bad pairs, one-use samples and guarded intent/cooldown. Deployed admission rescue moves the oldest childless zero-capacity Host leaf below an unassigned one-slot relay in one revision; no score/timer/global rebalance. Deployment passed, but no real room triggered it. - Web Host name/presence and window-audio hint are deployed. Native accepts bounded peer-assisted direct children and fails unsupported SFU/selected ingress; it has no LiveKit/TURN media. Its WGC/MF H.264 direct-child smoke rendered 203 frames plus 500 Opus packets and matched helper PID/LUID `VideoEncode`; packaging is source-only, unreleased and undeployed. VP8 remains the Web/production default. See `docs/research/native-h264-opt-in-path.md`. diff --git a/docs/research/low-server-media-routes.md b/docs/research/low-server-media-routes.md index 6b1650a..d2f28aa 100644 --- a/docs/research/low-server-media-routes.md +++ b/docs/research/low-server-media-routes.md @@ -7,7 +7,8 @@ then optional authenticated TURN/UDP for one controller-selected exceptional edge. Production enables the controller for all rooms and has a configured selected-edge TURN tuple; ordinary peer connections remain STUN-only, while - real SFU/TURN media behavior remains unverified. + production SFU/TURN media remains unverified. Latest source passed one local + SFU/UDP functional path; TURN remains unverified. ## Current Route Ladder @@ -36,9 +37,16 @@ Any browser-spike failure other than isolated relay re-encoding closes that browser-relay route. It does not cancel the separate native sender plan. Closed PR #12's explicit whole-room SFU mode is superseded. ADR-0005 and merged PR #17 own the automatic cross-mode controller. Production first enabled it only for -room `1` and later removed that rollout boundary. Participant entry was -observed, but retained media was not. Corrected localhost Chrome/LiveKit functional recovery passed, while public -transport, quality, load, and browser validation remain open. +room `1` and later removed that rollout boundary. The deployed release observed +participant entry but did not retain media. On 2026-08-21, a latest-source run +forced the first direct edge to fail and then passed one local SFU/UDP root with +Chrome 151, LiveKit 1.13.5, and pinned client 2.22.0. LiveKit produced two sender +RIDs in `q,h` order; Screener's former `q,f` guard caused the Host publisher to +fail closed at `sender-config`. After correcting that contract, Viewer inbound +packets and decoded/rendered frames increased, endpoint edges stayed bounded, +and both clients left cleanly. TURN did not participate. This is functional, +not performance, evidence; production/public transport and browser validation +remain open. ## Token-Free SFU Standby Prewarm @@ -287,7 +295,7 @@ lost. | Fixed two-chain browser relay | Host emits at most two copies; each relay emits at most one | Ordinary browser, but every relay decodes and re-encodes and adds a hop | Current default-off, maximum-eight-viewer spike | | Native shared-encode host | Host targets one encode for at most two standard WebRTC edges | libwebrtc public-API proxy risk spike, with Pion as fallback | Planned separate sender phase; still pays per-edge upload | | Native volunteer encoded-RTP relay | Each volunteer forwards one encoded copy | Native install, RTP/RTCP forwarding, packaging, and opt-in relay policy | Conditional experiment only if relay re-encoding is the sole browser-spike failure | -| SFU virtual parent | SFU normally emits one or two root copies; roots keep peer descendants | Service pays measured root egress; host sends one publication | Accepted primary central fallback after direct/peer UDP; current code is failure-only | +| SFU virtual parent | SFU normally emits one or two root copies; roots keep peer descendants | Service pays measured root egress; host sends one publication | Accepted primary central fallback after direct/peer UDP; latest source passes one local SFU/UDP root, while production evidence remains open | | Exceptional server-fed viewers | SFU/TURN emits necessary copies that no healthy root can distribute | Additional capped central egress | Explicit compatibility exception only; never unbounded whole-room fanout | | SVC plus multiple trees | Peers emit striped layer copies across several trees | Layer scheduling, reassembly, redundancy, and more churn state | Separate conditional spike; target endpoint upload near `B` | | Network coding | Peers or servers emit coded blocks | Generations, buffering, decoding, integrity, and a custom media plane | Trace/FEC spike only; optimize loss recovery, not clean bandwidth | @@ -355,7 +363,7 @@ The measurable gates and exact staged experiments are in ## Sources And License Boundary -Primary sources checked on 2026-08-19: +Primary sources checked on 2026-08-19 and 2026-08-21: - [Pion WebRTC](https://github.com/pion/webrtc) - MIT; no code copied. - [Pion WebRTC v4 API](https://pkg.go.dev/github.com/pion/webrtc/v4) - API @@ -406,9 +414,11 @@ Primary sources checked on 2026-08-19: - [LiveKit selective subscription](https://docs.livekit.io/transport/media/subscribe/) and [end-to-end encryption](https://docs.livekit.io/transport/encryption/) - official behavior and operator-boundary references. -- [LiveKit client 2.22.0 `Room.prepareConnection`](https://github.com/livekit/client-sdk-js/blob/v2.22.0/src/room/Room.ts) +- [LiveKit client 2.22.0 `Room.prepareConnection`](https://github.com/livekit/client-sdk-js/blob/v2.22.0/src/room/Room.ts), + [two-layer RID construction](https://github.com/livekit/client-sdk-js/blob/v2.22.0/src/room/participant/publishUtils.ts), and [official usage](https://github.com/livekit/client-sdk-js#usage) - - Apache-2.0; API behavior was inspected, with no source copied. + Apache-2.0; API behavior and RID construction were inspected, with no source + copied. - [Grozev, *Towards a Scalable Video Conferencing System*](https://publication-theses.unistra.fr/public/theses_doctorat/2019/Grozev_Boris_2019_ED269.pdf) - one Jitsi profiling breakdown, not a coturn/LiveKit cross-system CPU ratio. diff --git a/docs/status.md b/docs/status.md index b3db49b..089719e 100644 --- a/docs/status.md +++ b/docs/status.md @@ -6,7 +6,7 @@ Last updated: 2026-08-21 `https://share.bonfire.icu` serves exact `fd76277b05d491af8840b28f3132b7ff445d3cbe`; the 915,377-byte artifact SHA-256 is `8da4b8b2b1ae4b82add615f4367ee447b5ade86c4e58a938e51085940b9867d3`. The cutover held the lock 8,389 ms and returned local health 520 ms after stop. SQLite v3 has five rooms; Screener, LiveKit, coturn and nginx are healthy with zero restarts. -Ordinary ICE is STUN-only; all rooms use the bounded peer/SFU-UDP controller and selected-edge UDP TTL 120. Room `1` is historical smoke and no real TURN/SFU media canary has run. +Ordinary ICE is STUN-only; all rooms use the bounded peer/SFU-UDP controller and selected-edge UDP TTL 120. Room `1` is historical smoke; production SFU/TURN media remains unverified. Web names/audio hint are deployed. Native WGC/MF/package is source-only; Web defaults VP8. ## Execution Principle @@ -21,13 +21,14 @@ Flagship; parallel; min run/smoke/rollback; benchmark later. Active UI/config/lo - Native v2 is source-only (memory rooms, 300s reclaim); it follows authenticated direct-child assignments up to two and fails unsupported SFU/selected ingress boundedly. - PR #44's STUN-only/SFU-UDP router and token-free fallback prewarm are process-enabled for all normal rooms: roots <=2, browser relay <=1, bounded failure. Room `1` is historical smoke; mobile/iPad viewers are leaves, healthy edges sticky, and HTTPS/WSS stays TLS/TCP. - After an answer, a generation-bound 15s initial-connect deadline enters ICE restart; success/replacement/disposal cancels it. It is deployed but not mobile-verified. -- Room `1` can publish exactly `HIGH+LOW` with Dynacast/backup codec off and subscriber `HIGH` ceilings; it has no retained real media frame. +- Latest source fixes LiveKit's two-layer `q,h` sender guard; production predates it. - Room `1` C+B reparenting remains uncalibrated. Deployed admission rescue promotes an unassigned one-slot relay over the oldest childless zero-capacity Host leaf, with no scores or periodic optimization. - Web roster includes Host self-name and requests window-scoped display audio where supported; both are hints, and Native wire/media is unchanged. ## Verified Evidence - Chrome 151 synthetic `1/3/5/8` and 720p30 quality-change runs kept 2/1 fanout and decoding; slowest first frame was 1.05 seconds and one relay close recovered in 5.32 seconds. This is control evidence only. +- Chrome 151 + local LiveKit 1.13.5/client 2.22.0 SFU/UDP passed after `q,f` -> `q,h`: one root, decoded 3 -> 23, rendered 26, Host edge one, clean leaves, no TURN. Functional only. - Host A+B and authenticated P2P Viewer C are sanitized, generation-bound, read-only, and fail closed for stale, ambiguous, or SFU-fed evidence; no raw media metadata is retained. - The `a11a73d` built-in TURN canary is rejected: local allocation passed, but direct Host/Pion Viewer failed before forced relay. Full rollback restored STUN-only client ICE and zero allocations. - Chrome 151 browser loopbacks decoded VP8 299 and H.264 opt-in 299/298 rendered at 1280x720 without fatal/encoder errors. Browser hardware attribution, multi-viewer/endurance and public proof remain open; production stays VP8. @@ -48,13 +49,12 @@ Flagship; parallel; min run/smoke/rollback; benchmark later. Active UI/config/lo - ADR-0006 has one Viewer proof; two-edge/FIFO, hardware, endurance, public/downloaded-package, and browser-diversity proof remain open. - Browser fanout is host two/viewer one; any accepted endpoint relay stays capped at two downstream edges. - PR #49/#50 BWE is unverified/default-off: test zero-child leaves, then root impact. No local per-leaf UDP shaper; resume with Linux `tc` or public canary, never CDP. Web P2P/SVC shortcuts remain no-go. -- The corrected standby smoke is localhost/headless/video-only; public DNS/TLS reuse, transport, audio, shaping, load, mobile, endurance, and sub-25 ms overlap remain open. - Headful fragment-to-sessionStorage consumption, invitation rotation after migration, and request/log leak inspection remain production UX gates. Accounts stay out. - Structure debt: split `HybridMediaRouter`, `HostPage`, `SignalingServer`, and `ViewerPage` only at proven consumer boundaries, never by file length. ## Next Milestone -Repeat room `1` and fix only the identified SFU connect/source/publish/audio/transport layer; retain selected UDP, a rendered frame, edge caps and resource deltas. +Deploy the `q,h` fix; one bounded production room `1` canary must retain UDP, a rendered frame, edge caps, and clean stop. TURN/performance follow later. Recovery uses one ICE restart, one same-parent rebuild, one alternate peer, then SFU. Next gate exactly-two/Dynacast-off BWE on a zero-child SFU leaf, then root evacuation. Native WGC/MF passed one-Viewer hardware; package download, Viewer2/FIFO and game A/V remain. Benchmark later. Ordinary Peer ICE stays STUN-only. Selected-edge Host ingress still needs one forced-relay canary before a media-success claim. diff --git a/scripts/peer-assisted-benchmark.ts b/scripts/peer-assisted-benchmark.ts index 3ac4d6b..a570a73 100644 --- a/scripts/peer-assisted-benchmark.ts +++ b/scripts/peer-assisted-benchmark.ts @@ -1935,8 +1935,16 @@ async function runCase( signal, ); const roomId = host.roomId!; + const viewerUrl = await evaluate( + cdp, + hostPage, + "document.querySelector('.invite-url')?.getAttribute('title') || ''", + ); + if (!viewerUrl.startsWith(`${baseUrl}/r/${roomId}#v=`)) { + throw new Error("Host private Viewer invite was unavailable"); + } for (let viewerIndex = 1; viewerIndex <= viewerCount; viewerIndex += 1) { - const viewerPage = await createPage(cdp, `${baseUrl}/r/${roomId}`, { + const viewerPage = await createPage(cdp, viewerUrl, { ...commonInit, label: `viewer-${viewerIndex}`, role: "viewer", diff --git a/src/client/media/quality.ts b/src/client/media/quality.ts index a6417c3..5ffe66f 100644 --- a/src/client/media/quality.ts +++ b/src/client/media/quality.ts @@ -226,9 +226,9 @@ function requiredTwoLayerEncodingIndexes( } if ( parameters.encodings[0]?.rid !== "q" || - parameters.encodings[1]?.rid !== "f" + parameters.encodings[1]?.rid !== "h" ) { - throw new Error("SFU simulcast requires ordered q and f video encodings"); + throw new Error("SFU simulcast requires ordered q and h video encodings"); } return { low: 0, high: 1 }; } diff --git a/tests/quality.test.ts b/tests/quality.test.ts index c7a80c7..47ae5de 100644 --- a/tests/quality.test.ts +++ b/tests/quality.test.ts @@ -222,7 +222,7 @@ describe("realtime quality controls", () => { it("configures ordered LOW and HIGH simulcast encodings without flattening LOW", async () => { let applied = { - encodings: [{ rid: "q" }, { rid: "f" }], + encodings: [{ rid: "q" }, { rid: "h" }], } as RTCRtpSendParameters; const sender = { track: { @@ -249,7 +249,7 @@ describe("realtime quality controls", () => { scaleResolutionDownBy: 8 / 3, }, { - rid: "f", + rid: "h", maxBitrate: 8_000_000, maxFramerate: 60, scaleResolutionDownBy: 4 / 3, @@ -281,8 +281,8 @@ describe("realtime quality controls", () => { }, { label: "reorders the RIDs", - after: { encodings: [{ rid: "f" }, { rid: "q" }] }, - message: "ordered q and f video encodings", + after: { encodings: [{ rid: "h" }, { rid: "q" }] }, + message: "ordered q and h video encodings", }, { label: "adds a third RID", @@ -291,7 +291,7 @@ describe("realtime quality controls", () => { }, ])("fails closed when sender readback $label", async ({ after, message }) => { const before = { - encodings: [{ rid: "q" }, { rid: "f" }], + encodings: [{ rid: "q" }, { rid: "h" }], } as RTCRtpSendParameters; const sender = { track: { getSettings: () => ({ width: 1920, height: 1080 }) }, diff --git a/tests/sfu-client.test.ts b/tests/sfu-client.test.ts index 74423c1..cca9b05 100644 --- a/tests/sfu-client.test.ts +++ b/tests/sfu-client.test.ts @@ -104,7 +104,7 @@ const livekit = vi.hoisted(() => { scaleResolutionDownBy: 2, }, { - rid: "f", + rid: "h", maxBitrate: high.maxBitrate, maxFramerate: high.maxFramerate, scaleResolutionDownBy: 1, @@ -400,7 +400,7 @@ describe("SfuPublisher", () => { scaleResolutionDownBy: 2, }), expect.objectContaining({ - rid: "f", + rid: "h", maxBitrate: 8_000_000, maxFramerate: 60, scaleResolutionDownBy: 1, @@ -484,7 +484,7 @@ describe("SfuPublisher", () => { scaleResolutionDownBy: 2, }), expect.objectContaining({ - rid: "f", + rid: "h", maxBitrate: 3_000_000, maxFramerate: 30, scaleResolutionDownBy: 1,