fix(media): restore peer-assisted 16-viewer admission (#170)

Keep the default at eight while restoring the documented explicit 1-16 admission range and its cap2/cap3 functional gate.
This commit is contained in:
AAA authored and GitHub committed 2026-08-22 05:00:26 +08:00
1 parent 42397e18ce
commit b7de6ed24f
11 files changed
+61 -42

No files matched your search

@@ -42,15 +42,11 @@ not be read as requiring users to select a topology or rejecting automatic
cross-mode fallback as a product goal.
The experiment is gated by `PEER_ASSISTED_MEDIA`, which defaults to `false`.
Enabling it requires `MAX_VIEWERS_PER_ROOM` at or below eight; larger configured
rooms fail configuration rather than silently running a different topology.
A required non-empty `PEER_ASSISTED_ROOM_IDS` deployment allowlist restricts
the experiment to exact valid room IDs. Non-allowlisted rooms use the current
ordinary P2P authenticated wire, signaling, quality behavior, and lifecycle and
must never enter `HybridMediaRouter`. Missing, blank, invalid, empty-list-entry,
or duplicate values fail startup when the experiment is enabled; there is no
all-room fail-open. This exact-room gate is temporary validation scope and adds
no UI, percentage rollout, or routing score.
When enabled, it applies to every room. `MAX_VIEWERS_PER_ROOM` remains one
shared admission limit: it defaults to eight and accepts explicit values from
one through sixteen. The upper admission bound is not a media-capacity or
performance claim. The removed `PEER_ASSISTED_ROOM_IDS` allowlist fails startup;
there is no second rollout mode, UI selector, percentage rollout, or route score.
The signaling server assigns a sticky, bounded DAG with a deterministic
breadth-first walk. The host and each ordinary Web viewer currently have
@@ -127,9 +123,10 @@ Chrome and Edge form the controlled relay cohort; Android Chrome and iOS Safari
remain compatibility observations. ADR-0005 now gives every ordinary Web
Viewer the same two-edge capacity without UA or visibility detection. Mobile
resource behavior remains unverified, but does not define a separate route
class. This depth-three gate remains an eight-viewer experiment; it cannot
authorize the accepted 20-viewer default. That later release requires a separate
20-viewer matrix or bounded central exceptions.
class. The resource/quality gate remains an eight-viewer experiment. A separate
sixteen-viewer loopback may prove only admission, bounded topology, and decode
function; neither result can authorize a 20-viewer default. That later release
requires a separate 20-viewer matrix or bounded central exceptions.
The proposal advances only if every condition holds:
+1 -1
View File
@@ -53,7 +53,7 @@ Last updated: 2026-08-22
- 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 rejects `PEER_ASSISTED_ROOM_IDS`; `PEER_ASSISTED_MEDIA=true` enables every room and ordinary peers stay STUN-only. Initial or active Host ingress may consume one bound relay-only grant; ready/abort clears it and failure restores peer baseline. Stale room-ID config fails startup. Production and source-only Native use `screener-v5`; Native does not subscribe to Web presence.
- Defaults are balanced with automatic codec and 128 kbps screen audio; clarity/fluid, H.264/VP8, and 64/128/256 kbps audio remain explicit. Codec and audio quality are locked during a share. LiveKit 2.22.0 SFU uses `q,h`; production exposes one Host-local current-`h` A+B snapshot and clears it on publisher identity changes. Target-browser field availability, actual media values, BWE, and route observation remain open.
- Chrome 151 five-Viewer 720p30 cap2 evidence: Host/relay fanout two; both relay children +80 decoded frames/~1.1 MB; every Viewer decoded; clean stop. Functional only; resource and heterogeneous-network behavior remain open.
- Chrome 151 local functional evidence includes five-Viewer cap2 and sixteen-Viewer cap2/cap3 runs. All Viewers decoded; the sixteen-Viewer runs bounded Host/relay edges at 2/2 and 3/3, but final samples were only 320x180 at 9-10 fps and omitted process resource totals. Resource, visual quality, endurance, and heterogeneous-network behavior remain open.
## Provisional Quality Targets
+7 -6
View File
@@ -1,8 +1,8 @@
# Low-Server-Cost Media Routes
- Research date: 2026-08-21
- Scope: one broadcaster, at most eight trusted viewers, low latency, and host
media fanout at most two
- Scope: one broadcaster, explicit admission up to sixteen trusted viewers,
low latency, and bounded host media fanout
- Status: ADR-0005 accepts STUN-only direct/peer UDP, bounded SFU/UDP roots,
then optional authenticated TURN/UDP for one controller-selected exceptional
edge. Production enables the controller for all rooms and has a configured
@@ -16,9 +16,10 @@
The smallest current plan is:
1. Use direct host P2P for one or two viewers.
2. For later viewers, use the fixed two-chain browser relay in ADR-0004. It is
controlled by `PEER_ASSISTED_MEDIA`, is limited to at most
eight viewers, and decodes and re-encodes at every relay.
2. For later viewers, use the bounded browser relay DAG in ADR-0004. It is
controlled by `PEER_ASSISTED_MEDIA`, shares the one-through-sixteen room
admission limit, and decodes and re-encodes at every relay. The retained
representative resource/quality gate remains eight viewers.
3. Plan a separate native shared-encode sender regardless of the browser relay
result. It reduces duplicate host encoding while retaining at most two
standard WebRTC edges, so it does not remove their upload cost. The bounded
@@ -527,7 +528,7 @@ lost.
| Route | Where copies are emitted | Endpoint cost | Current disposition |
| --- | --- | --- | --- |
| Direct host P2P | Host emits one copy per viewer | Host upload and sender pipelines grow with viewers | Keep for one or two viewers |
| Bounded browser relay DAG | Host and each Web relay emit at most two copies | Ordinary browser, but every relay decodes and re-encodes and adds a hop | Current capacity-two source candidate, maximum-eight-viewer gate |
| Bounded browser relay DAG | Host and each Web relay emit at most the configured endpoint cap | Ordinary browser, but every relay decodes and re-encodes and adds a hop | Admission defaults to eight and permits 1-16; representative resource/quality gate remains eight viewers |
| 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; local SFU/UDP and active Host-ingress relay function pass, while public-room/performance evidence remains open |
+20 -7
View File
@@ -1,8 +1,9 @@
# Peer-Assisted Media Research
- Research date: 2026-08-21
- Scope: one game-screen broadcaster, at most eight trusted viewers, host media fanout at most two
- Status: historical evidence plus the current capacity-two source candidate;
- Scope: one game-screen broadcaster, explicit admission up to sixteen trusted
viewers, with the retained resource/quality gate at eight
- Status: historical evidence plus the current bounded source candidate;
accepted ADR-0005 owns automatic peer/SFU routing. Production later removed
the room-`1` rollout boundary; retained SFU media remains unverified.
@@ -260,6 +261,17 @@ configures only its local server and gate. A 2026-08-22 Chrome 151 two-second
smoke passed with three Viewers at Host2/relay1 and six at Host3/relay3, with all
Viewers decoding. This is not a production-default, SFU-capacity, or resource claim.
A 2026-08-22 Windows Chrome 151.0.7922.138 headless loopback then ran sixteen
Viewers for two seconds at 720p30 with cap2 and cap3. Both runs had all sixteen
Viewers decoding and no fatal/check failure. Cap2 peaked at Host2/relay2 with
depth four and a 1,167 ms maximum first-decode diagnostic; cap3 peaked at
Host3/relay3 with depth three and 977 ms. Final samples were only 320x180 at
9-10 fps. The 8-core/16-thread Ryzen 7 9700X runner had 47.1 GiB RAM and 13.7
GiB free after the runs, but no process CPU, GPU, NIC, or peak-memory totals were
captured. This proves the explicit admission, bounded topology, and decode paths
only; resource, visual quality, endurance, heterogeneous networks, and the
separate 20-viewer gate remain open.
Peer multicast research such as SplitStream demonstrates why load-balanced,
failure-tolerant overlays normally introduce multiple trees and content
striping. Those mechanisms are intentionally excluded: needing them is a reason
@@ -315,11 +327,12 @@ longer has that room boundary. There is no mobile/iPad, UA, or visibility branch
This historical gate assumed default-off configuration through
`PEER_ASSISTED_MEDIA=false`; current production enables the controller for all
rooms.
It cannot be enabled above eight viewers and uses the existing standard WebRTC
screen stream, current Chrome/Edge as the controlled relay cohort, Android
Chrome and iOS Safari as compatibility observations, at most two children per
viewer, and at most eight viewers. It may carry the
existing screen-audio track when the browser provides one. SVC/simulcast,
It uses the existing standard WebRTC screen stream, current Chrome/Edge as the
controlled relay cohort, Android Chrome and iOS Safari as compatibility
observations, and the configured downstream edge cap. The runner accepts the
shared room admission ceiling of sixteen while the representative
resource/quality gate remains at eight. It may carry the existing screen-audio
track when the browser provides one. SVC/simulcast,
custom encoded transport, FEC changes, multi-tree striping, transcoding,
background mobile relay, and the later ADR-0005 SFU controller are outside this
historical browser-relay gate.
+1 -1
View File
@@ -16,7 +16,7 @@ This file is the current execution index. Load [verification status](./verificat
- Capture precedes room creation. Balanced, automatic codec, and 128 kbps screen audio are defaults; clarity/fluid, H.264/VP8, and 64/128/256 kbps audio are deployed next-share choices. Codec and audio quality lock during a share.
- Local-only connection details expose current Host SFU sender evidence, actual codec/fmtp and encoder fields, media-source/encode FPS evidence, A/V playout/jitter/concealment evidence, and selected candidate endpoints. These fields do not enter quality signaling, server state, logs, persistence, or route selection.
- Private fragment grants enter directly; code-only Viewers need site access and private rooms additionally need their password. Credentials remain unstored. Web names and presence are session-only; duplicate names alone show an ID suffix.
- Roots and ordinary Web relays are bounded to two downstream edges, Viewer upstream receive is free, and source permits an explicit deployment cap from one through three. SFU root capacity remains separate.
- Rooms default to eight Viewers and accept an explicit admission limit from one through sixteen. Roots and ordinary Web relays are bounded to two downstream edges, Viewer upstream receive is free, and source permits an explicit endpoint cap from one through three. SFU root capacity remains separate.
- Relay quality handling compares fresh child FPS with parent input. Two current corroborating children can pause one Viewer relay for 30 seconds and request peer-only reassignment; no candidate keeps the old edge.
- A generation-bound 15-second initial-connect deadline enters ICE restart. One-root healthy-SFU make-before-break is deployed but media-unverified; capacity `0 -> 1`, multi-root, browser, and mobile gates remain open.
+1 -1
View File
@@ -16,7 +16,7 @@ This is the demand-loaded ledger for cross-cutting validation evidence and open
## Retained Functional Evidence
- Chrome 151 synthetic `1/3/5/8` and 720p30 runs preserved bounded fanout and decoding. In one five-Viewer cap2 run, one relay served two children, each advanced 80 decoded frames and about 1.1 MB, every Viewer decoded, and shutdown was clean. This proves function, not capacity or heterogeneous-network quality. See [peer-assisted media](./research/peer-assisted-media.md).
- Chrome 151 synthetic `1/3/5/8` and 720p30 runs preserved bounded fanout and decoding. A later two-second sixteen-Viewer loopback decoded all Viewers at Host2/relay2 depth four and Host3/relay3 depth three, but ended at only 320x180 and 9-10 fps without process resource totals. These prove function, not sustainable capacity or heterogeneous-network quality. See [peer-assisted media](./research/peer-assisted-media.md).
- Chrome 151 with local LiveKit 1.13.5/client 2.22.0 proved one SFU/UDP root after `q,f` -> `q,h`: decoded frames advanced 3 -> 23, rendered frames reached 26, Host used one edge, leaves were clean, and TURN was absent. This is functional evidence only. See [low-server media routes](./research/low-server-media-routes.md).
- Host A+B and authenticated P2P Viewer C evidence is sanitized, generation-bound, read-only, and closed for stale, ambiguous, or SFU-fed reports. Production can expose a Host-local current SFU `h` snapshot and clears it on publisher identity changes; raw media metadata is not retained. See [realtime quality adaptation](./research/realtime-quality-adaptation.md).
- The built-in peer TURN canary was rejected after allocation passed but the direct Host/Pion Viewer path failed before forced relay. Full rollback restored STUN-only client ICE and zero allocations. See [built-in peer ICE TURN](./research/built-in-peer-ice-turn.md).
+1 -1
View File
@@ -407,7 +407,7 @@ Peer-assisted 信令只接受一个严格对象:`resolution` 为 `720p | 1080p
Viewer 在处理 peer-assisted 鉴权快照时先更新 `ViewerRelay` 的 desired settings,再应用拓扑分配;同一个串行队列协调 remote stream、当前 child 更新和以后重建的 child,因此它们读取同一最新目标。每个 `HostPeer` 也把初始 sender 配置、`replaceStream()` 和 `updateProfile()` 放进同一 sender-mutation queue;desired settings 在入队前同步更新,已排队操作执行时读取最新值,使快速连续修改保持 last-wins,而不增加版本号或确认协议。这里同步的是浏览器 sender 的目标上限,不是实际分辨率、帧率、码率或 shared encoding 的性能保证。
spike 复用现有 screen stream 和浏览器 codec 协商,当前普通 Web relay 不按桌面、Android 或 iOS UA 拆分 capacity,最多八名 viewer;捕获存在时沿同一标准 WebRTC 路径带上音频。移动真机表现作为非阻断兼容观察;Encoded Transform/DataChannel/WebCodecs 媒体、SVC/simulcast、多树、转码、codec ladder、综合评分、移动端后台 relay 和跨模式 SFU 迁移不在本次 spike 内实现。该范围限制不能解释为拒绝产品所需的自动兜底;验收与失败即放弃条件以 ADR-0004 为准,自动路由由后续独立 ADR 定义。
spike 复用现有 screen stream 和浏览器 codec 协商,当前普通 Web relay 不按桌面、Android 或 iOS UA 拆分 capacity;房间接入默认八名、显式允许 1 至 16,代表性资源/质量门禁仍按八名执行。捕获存在时沿同一标准 WebRTC 路径带上音频。移动真机表现作为非阻断兼容观察;Encoded Transform/DataChannel/WebCodecs 媒体、SVC/simulcast、多树、转码、codec ladder、综合评分、移动端后台 relay 和跨模式 SFU 迁移不在本次 spike 内实现。该范围限制不能解释为拒绝产品所需的自动兜底;验收与失败即放弃条件以 ADR-0004 为准,自动路由由后续独立 ADR 定义。
浏览器不保证多个 `RTCPeerConnection` 共享一次编码。实验允许 host 对一至两个 seed 各自编码。后续 native sender 必须让每个 active representation 只编码一次并跨 direct、peer 与 SFU transport 复用;每条连接仍拥有独立 packetizer、SSRC、pacing、加密、反馈和上传。健康路径选择共享 `HIGH`,最多另有一份弱路径共用的 `LOW`,是否常驻由硬件、游戏和上传门槛决定。该计划不依赖本次浏览器 relay 实验成败,也不在本 spike 中提前抽象或实现。
+2 -1
View File
@@ -9,6 +9,7 @@ import WebSocket from "ws";
import {
DEFAULT_PEER_RELAY_DOWNSTREAM_EDGES,
MAX_PEER_RELAY_DOWNSTREAM_EDGES,
MAX_VIEWERS_PER_ROOM_LIMIT,
type ParticipantRouteAssignment,
} from "../src/shared/protocol";
import {
@@ -202,7 +203,7 @@ interface ManagedProcess {
}
const DEFAULT_VIEWER_COUNTS = [1, 3, 5, 8];
const MAX_VIEWERS = 8;
const MAX_VIEWERS = MAX_VIEWERS_PER_ROOM_LIMIT;
const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
class BoundedLog {
-7
View File
@@ -16,7 +16,6 @@ const MIN_SELECTED_EDGE_TURN_SECRET_BYTES = 32;
const MAX_SELECTED_EDGE_TURN_SECRET_BYTES = 128;
const MIN_SELECTED_EDGE_TURN_TTL_SECONDS = 60;
const MAX_SELECTED_EDGE_TURN_TTL_SECONDS = 10 * 60;
const MAX_PEER_ASSISTED_VIEWERS = 8;
const DEFAULT_MAX_VIEWERS_PER_ROOM = 8;
const DEFAULT_MAX_SFU_ROOTS_PER_ROOM = 2;
const MAX_SFU_ROOTS_PER_ROOM = 2;
@@ -390,12 +389,6 @@ export function loadConfig(
if (nodeEnv === "production" && stunUrls.length === 0) {
throw new Error("STUN is required in production");
}
if (peerAssistedMedia && maxViewersPerRoom > MAX_PEER_ASSISTED_VIEWERS) {
throw new Error(
"PEER_ASSISTED_MEDIA currently supports at most 8 viewers per room",
);
}
return {
nodeEnv,
port,
+5 -1
View File
@@ -219,7 +219,11 @@ describe("peer topology loopback configuration", () => {
it("deduplicates configured viewer counts without changing their order", () => {
expect(parseViewerCounts("8, 3,3,1")).toEqual([8, 3, 1]);
expect(() => parseViewerCounts("9")).toThrow(/1 to 8/);
});
it("accepts the room viewer ceiling and rejects values above it", () => {
expect(parseViewerCounts("16")).toEqual([16]);
expect(() => parseViewerCounts("17")).toThrow(/1 to 16/);
});
it("requires recovery to target one selected case", () => {
+14 -4
View File
@@ -207,14 +207,24 @@ describe("server configuration", () => {
expect(() => loadConfig({ PEER_ASSISTED_MEDIA: "1" })).toThrow(
"PEER_ASSISTED_MEDIA must be true or false",
);
});
it("keeps the peer-assisted room default at 8 and accepts explicit 16", () => {
expect(
loadConfig({ PEER_ASSISTED_MEDIA: "true" }).maxViewersPerRoom,
).toBe(8);
expect(
loadConfig({
PEER_ASSISTED_MEDIA: "true",
MAX_VIEWERS_PER_ROOM: "16",
}).maxViewersPerRoom,
).toBe(16);
expect(() =>
loadConfig({
PEER_ASSISTED_MEDIA: "true",
MAX_VIEWERS_PER_ROOM: "9",
MAX_VIEWERS_PER_ROOM: "17",
}),
).toThrow(
"PEER_ASSISTED_MEDIA currently supports at most 8 viewers per room",
);
).toThrow("MAX_VIEWERS_PER_ROOM");
});
it.each(["1", "2", "3"])(