fix(media): accept LiveKit two-layer screen RIDs (#110)
Match Piik's two-layer sender guard to LiveKit 2.22.0 q,h RID construction, restore private-link Viewer smoke entry, and retain the local one-root SFU/UDP functional evidence. TURN and performance remain outside this functional slice.
This commit is contained in:
1 parent
7b1e01ed6b
commit
d31912fe79
7 files changed
+44
-26
No files matched your search
@@ -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`.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
+4
-4
@@ -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.
|
||||
|
||||
@@ -1935,8 +1935,16 @@ async function runCase(
|
||||
signal,
|
||||
);
|
||||
const roomId = host.roomId!;
|
||||
const viewerUrl = await evaluate<string>(
|
||||
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",
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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 }) },
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user