From 4cd635b4d1fa81c687c7c59a7121daf065bba94b Mon Sep 17 00:00:00 2001 From: AAA <35992542+TNTcraftHIM@users.noreply.github.com> Date: Tue, 1 Sep 2026 02:07:32 +0800 Subject: [PATCH] build(deps): update LiveKit loss recovery stack (#361) Upgrade the validated LiveKit and SDP stack, pin Server 1.13.6 for simulcast RTX recovery, and compact Viewer text-mode context layout. --- deploy/livekit/livekit.yaml.example | 4 +- ...07-path-isolated-representation-quality.md | 7 ++- docs/operations/self-hosting.md | 8 ++- docs/research/realtime-quality-adaptation.md | 18 ++++++ docs/todo.md | 7 ++- package-lock.json | 62 ++++++++++++++----- package.json | 8 +-- src/client/styles.css | 12 ++-- 8 files changed, 88 insertions(+), 38 deletions(-) diff --git a/deploy/livekit/livekit.yaml.example b/deploy/livekit/livekit.yaml.example index 82d71f5..ee663f9 100644 --- a/deploy/livekit/livekit.yaml.example +++ b/deploy/livekit/livekit.yaml.example @@ -1,5 +1,5 @@ -# Minimal single-node LiveKit capacity for Screener's optional automatic SFU -# fallback. Copy this file to an untracked service-owned location such as +# Minimal single-node LiveKit 1.13.6 capacity for Screener's optional automatic +# SFU fallback. Copy this file to an untracked service-owned location such as # /etc/livekit/livekit.yaml. Store only the generated key mapping in # /etc/livekit/keys.yaml, owned by the livekit service user with mode 0600. diff --git a/docs/adr/0007-path-isolated-representation-quality.md b/docs/adr/0007-path-isolated-representation-quality.md index 561107a..e7a2649 100644 --- a/docs/adr/0007-path-isolated-representation-quality.md +++ b/docs/adr/0007-path-isolated-representation-quality.md @@ -2,7 +2,7 @@ - Status: Accepted - Date: 2026-08-19 -- Last reviewed: 2026-08-30 +- Last reviewed: 2026-09-01 ## Context @@ -152,5 +152,6 @@ than claiming complete source isolation. - [WebRTC Statistics](https://www.w3.org/TR/webrtc-stats/) - [LiveKit video simulcast and Dynacast](https://docs.livekit.io/transport/media/advanced/) - [LiveKit selective subscription](https://docs.livekit.io/transport/media/subscribe/) -- [LiveKit client 2.22.0 encoding construction](https://github.com/livekit/client-sdk-js/blob/v2.22.0/src/room/participant/publishUtils.ts) -- [LiveKit server 1.13.5 layer forwarding](https://github.com/livekit/livekit/blob/v1.13.5/pkg/sfu/forwarder.go) +- [LiveKit client 2.22.1 encoding construction](https://github.com/livekit/client-sdk-js/blob/v2.22.1/src/room/participant/publishUtils.ts) +- [LiveKit server 1.13.6 layer forwarding](https://github.com/livekit/livekit/blob/v1.13.6/pkg/sfu/forwarder.go) +- [LiveKit server 1.13.6 simulcast RTX repair](https://github.com/livekit/livekit/pull/4800) diff --git a/docs/operations/self-hosting.md b/docs/operations/self-hosting.md index be26cf2..b386f08 100644 --- a/docs/operations/self-hosting.md +++ b/docs/operations/self-hosting.md @@ -16,8 +16,10 @@ Screener -------- private RoomService -----> LiveKit :7880 ``` The tracked public baseline uses Node.js 24, nginx, a valid Web TLS -certificate, time synchronization, and coturn 4.17.2 or a newer patched release. -LiveKit is required for the accepted SFU fallback and tracked release wrapper. +certificate, time synchronization, coturn 4.17.2 or a newer patched release, +and LiveKit Server 1.13.6. LiveKit is required for the accepted SFU fallback +and tracked release wrapper. Keep the accepted patch pinned until its +replacement passes the same Browser/SFU acceptance boundary. Web and STUN may use different DNS names on the same public IP. Another proxy or P2P-only service shape needs its own matching updater and recovery checks. @@ -42,7 +44,7 @@ domains, certificates, users, and resource limits required by the host. ## Initial Setup 1. Configure DNS and TLS for the Web origin and STUN name. -2. Install Node.js, nginx, coturn, and pinned-compatible LiveKit. +2. Install Node.js, nginx, coturn, and the pinned LiveKit Server patch. 3. Create an unprivileged `screener` service account, `/opt/screener/releases`, `/opt/screener/uploads`, and an access-restricted environment file. 4. Provision one verified initial immutable release and atomically point diff --git a/docs/research/realtime-quality-adaptation.md b/docs/research/realtime-quality-adaptation.md index 8852895..16cc7e3 100644 --- a/docs/research/realtime-quality-adaptation.md +++ b/docs/research/realtime-quality-adaptation.md @@ -284,6 +284,24 @@ selection. A future event can therefore distinguish publisher layer disablement from subscriber-only forwarding changes without altering the LiveKit adaptation policy. +A 2026-09-01 production comparison separated server capacity from packet-loss +recovery. One two-Viewer SFU room kept its Host publication healthy with no +zero-frame publisher window, while a later room on the same node repeatedly +lost both Viewer outputs together. In the latter room the Host-to-SFU native +bandwidth estimate fell from about 12 Mbps to about 0.22 Mbps while capture +settings remained at 30 fps; the server process, kernel queues, and network +device reported no contemporaneous resource or drop pressure. The deployed +LiveKit Server 1.13.5 also predates the upstream simulcast RTX pairing repair: +under the affected Pion integration, repair streams were not paired and +retransmissions were silently dropped. LiveKit Server 1.13.6 is therefore the +accepted patch baseline. This repairs loss recovery; it does not claim to +prevent loss on the Host-to-SFU UDP path. + +The same later room also contained a separate interval in which the native +bandwidth estimate stayed near 13 Mbps while the sender media-source cadence +fell to about 1 fps. That interval is a capture/source-production boundary, not +an RTX-recovery result, and the LiveKit patch does not address it. + ## Host And Page Cost Boundaries Each ordinary Browser child is an independent PeerConnection and normally an diff --git a/docs/todo.md b/docs/todo.md index 3253c57..be32018 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -1,6 +1,6 @@ # Current TODO Ledger -Last reviewed: 2026-08-31 +Last reviewed: 2026-09-01 Only **Now** is executable. Observations, old branches, experiments, and parked topics are not implementation authority. @@ -62,8 +62,9 @@ until their stated boundary is met. 8. **Representative network acceptance.** Complete public-network direct, Browser relay, SFU, recovery, Pause/Resume, screen-audio, real-game A/V, two-room SFU, 20-Viewer endurance, and all-UDP-blocked bounded failure. - Correlate SFU freezes with publisher encoding counts before changing - Dynacast or representation policy. + Validate LiveKit Server 1.13.6 simulcast retransmission under controlled loss + and correlate any remaining SFU freezes with publisher encoding counts before + changing Dynacast or representation policy. 9. **Client-input security review.** Audit HTTP/WebSocket schema, auth, authorization, rate/body/resource bounds, errors, logs, and secrets without adding a parallel security framework. diff --git a/package-lock.json b/package-lock.json index 627b15a..75327c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,11 +8,11 @@ "name": "screener", "version": "0.1.0", "dependencies": { - "livekit-client": "2.22.0", - "livekit-server-sdk": "2.17.0", + "livekit-client": "2.22.1", + "livekit-server-sdk": "2.18.0", "react": "19.2.8", "react-dom": "19.2.8", - "sdp-transform": "2.15.0", + "sdp-transform": "3.0.0", "sirv": "3.0.2", "ws": "8.21.3", "zod": "4.4.3" @@ -21,7 +21,7 @@ "@types/node": "24.13.3", "@types/react": "19.2.18", "@types/react-dom": "19.2.4", - "@types/sdp-transform": "2.15.0", + "@types/sdp-transform": "3.0.0", "@types/ws": "8.18.1", "@vitejs/plugin-react": "6.0.5", "tsx": "4.23.12", @@ -852,9 +852,9 @@ } }, "node_modules/@types/sdp-transform": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/@types/sdp-transform/-/sdp-transform-2.15.0.tgz", - "integrity": "sha512-ikIFF0EaYt/2XetIYYVeMj6SB52oVXFasJUXDzWHgzNJS5ep2Pbsu7f8f3Za+dEie8HQtt3Zr9mHYBpWT0XgxQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/sdp-transform/-/sdp-transform-3.0.0.tgz", + "integrity": "sha512-TLwbrRILH3mwnrXgjZLb2LXursBR/dnumGgFWKm8Jw3nTJCAN0g80pvS4KVpXHz4DpYtne762lcXin9GmShGbg==", "dev": true, "license": "MIT" }, @@ -1445,9 +1445,9 @@ } }, "node_modules/livekit-client": { - "version": "2.22.0", - "resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.22.0.tgz", - "integrity": "sha512-GLtYQfRh/RsvXaOX1x609bFZ17yyKmKWDqu9JmkMKn9vIFLi2GsapRv9gT8OJO/1R2dsitrkbGmloyUfxWQsaA==", + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.22.1.tgz", + "integrity": "sha512-kCxTlGOV2XWYXZ7zK01UUKjgpVydvJ0CBgdHuv2I+s4q08tn0UTzpHfFqXPb5Aba4h6KdNU0TjQCHzv4Lww7fQ==", "license": "Apache-2.0", "dependencies": { "@livekit/mutex": "1.1.1", @@ -1455,6 +1455,7 @@ "events": "^3.3.0", "jose": "^6.1.0", "loglevel": "^1.9.2", + "machina": "^7.0.0", "sdp-transform": "^2.15.0", "tslib": "2.8.1", "typed-emitter": "^2.1.0", @@ -1464,20 +1465,38 @@ "@types/dom-mediacapture-record": "^1" } }, + "node_modules/livekit-client/node_modules/sdp-transform": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.15.0.tgz", + "integrity": "sha512-KrOH82c/W+GYQ0LHqtr3caRpM3ITglq3ljGUIb8LTki7ByacJZ9z+piSGiwZDsRyhQbYBOBJgr2k6X4BZXi3Kw==", + "license": "MIT", + "bin": { + "sdp-verify": "checker.js" + } + }, "node_modules/livekit-server-sdk": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/livekit-server-sdk/-/livekit-server-sdk-2.17.0.tgz", - "integrity": "sha512-GWNEQrUD13UwZNrmosyTVFPgeBvSU2lFKGxdA4DaUI5F4sMb1cGeep/PPEE9vvRNELJYpxWS0ImS4gQmovKUDA==", + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/livekit-server-sdk/-/livekit-server-sdk-2.18.0.tgz", + "integrity": "sha512-iUGmkbyi0g4DHKQ0Z4VSVs/V+ddp3T75ccQIe6LX94AYlqgyh+SVSM1lQMjFQuI3Qh6Z9v06T4xxV+vWmp7VeQ==", "license": "Apache-2.0", "dependencies": { "@bufbuild/protobuf": "^1.10.1", - "@livekit/protocol": "^1.48.0", + "@livekit/protocol": "1.48.0", "jose": "^5.1.2" }, "engines": { "node": ">=18" } }, + "node_modules/livekit-server-sdk/node_modules/@livekit/protocol": { + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.48.0.tgz", + "integrity": "sha512-fYHYgltH6YavAsokl3qsHLkBdQeKCl4UORVTub5crS3t8JtKFZ0uinHDFQ+XXdNKS6Ub9gcOjV+UHcDiqnWXoQ==", + "license": "Apache-2.0", + "dependencies": { + "@bufbuild/protobuf": "^1.10.0" + } + }, "node_modules/livekit-server-sdk/node_modules/jose": { "version": "5.10.0", "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", @@ -1500,6 +1519,15 @@ "url": "https://tidelift.com/funding/github/npm/loglevel" } }, + "node_modules/machina": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/machina/-/machina-7.0.1.tgz", + "integrity": "sha512-0Xkgs8NeBvO2hW6bjJT8ewLWrMfh1haMPstaghpASWjXkLjRhtDiaJG1Kb7mslaOHxZYxpH7PIjpmirRVYbMMA==", + "license": "MIT", + "engines": { + "node": ">=22.22" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -1685,9 +1713,9 @@ "license": "MIT" }, "node_modules/sdp-transform": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.15.0.tgz", - "integrity": "sha512-KrOH82c/W+GYQ0LHqtr3caRpM3ITglq3ljGUIb8LTki7ByacJZ9z+piSGiwZDsRyhQbYBOBJgr2k6X4BZXi3Kw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-3.0.0.tgz", + "integrity": "sha512-gfYVRGxjHkGF2NPeUWHw5u6T/KGFtS5/drPms73gaSuMaVHKCY3lpLnGDfswVQO0kddeePoti09AwhYP4zA8dQ==", "license": "MIT", "bin": { "sdp-verify": "checker.js" diff --git a/package.json b/package.json index ee6eebd..da3a464 100644 --- a/package.json +++ b/package.json @@ -23,11 +23,11 @@ "check": "npm run typecheck && npm test && npm run build" }, "dependencies": { - "livekit-client": "2.22.0", - "livekit-server-sdk": "2.17.0", + "livekit-client": "2.22.1", + "livekit-server-sdk": "2.18.0", "react": "19.2.8", "react-dom": "19.2.8", - "sdp-transform": "2.15.0", + "sdp-transform": "3.0.0", "sirv": "3.0.2", "ws": "8.21.3", "zod": "4.4.3" @@ -36,7 +36,7 @@ "@types/node": "24.13.3", "@types/react": "19.2.18", "@types/react-dom": "19.2.4", - "@types/sdp-transform": "2.15.0", + "@types/sdp-transform": "3.0.0", "@types/ws": "8.18.1", "@vitejs/plugin-react": "6.0.5", "tsx": "4.23.12", diff --git a/src/client/styles.css b/src/client/styles.css index 2e2eadb..ae48949 100644 --- a/src/client/styles.css +++ b/src/client/styles.css @@ -702,8 +702,8 @@ body.lr-theater-open { overflow: hidden; } } .lr-viewer-room-slot { flex: 0 0 auto; } .lr-viewer-host-slot { - flex: 0 1 180px; - min-width: min(96px, 30vw); + flex: 0 1 auto; + min-width: 0; max-width: 180px; flex-wrap: nowrap; font-size: 13px; @@ -717,7 +717,7 @@ html[data-theme="dark"] .lr-viewer-host-slot svg { color: #9bb0c5; } text-overflow: ellipsis; white-space: nowrap; } -.lr-viewer-state-slot { flex: 1 1 120px; } +.lr-viewer-state-slot { flex: 0 1 auto; min-width: 0; } .lr-viewer-state-slot:empty { display: none; } .lr-viewer-self-slot { display: flex; @@ -1436,11 +1436,11 @@ html[data-theme="dark"] .lr-switch::after { background: var(--ink); } .lr-viewer-summary-row { flex-direction: row; align-items: center; } .lr-viewer-summary-row .lr-row-group { width: auto; } .lr-viewer-host-slot { - flex: 0 1 180px; - min-width: min(96px, 30vw); + flex: 0 1 auto; + min-width: 0; max-width: 180px; } - .lr-viewer-state-slot { flex: 1 1 120px; } + .lr-viewer-state-slot { flex: 0 1 auto; min-width: 0; } .lr-viewer-self-slot { flex: 1 1 auto; gap: 8px; margin-left: 0; } .lr-host-personal-controls { width: 100%;