diff --git a/docs/adr/0005-automatic-hybrid-media-routing.md b/docs/adr/0005-automatic-hybrid-media-routing.md index 130c400..49b5336 100644 --- a/docs/adr/0005-automatic-hybrid-media-routing.md +++ b/docs/adr/0005-automatic-hybrid-media-routing.md @@ -124,8 +124,11 @@ server fanout, or an untracked parallel route. ## Accepted Assisted-Route Model One room controller owns three operations: allocate/distribute, child reparent, -and relay abdicate/drain. They share one target snapshot, one pending transition, -one resource ledger, and monotonic room/session/revision fences. +and relay abdicate/drain. It is the only owner of the committed graph, pending +target, and transition lifecycle. Topology helpers retain participant metadata +and purely plan candidates; they do not retain a second mutable route graph. +The operations share one target snapshot, one pending transition, one resource +ledger, and monotonic room/session/revision fences. - The Host is the only source publisher. There is at most one authoritative Host publication per share generation, and every SFU-fed Viewer subscribes to it. @@ -157,24 +160,30 @@ one resource ledger, and monotonic room/session/revision fences. signaling and the PeerConnection remain present, expiry of the bounded parent- proof deadline authorizes hard reparent of that child if the stall remains, whether matching positive non-server-parent outbound-media proof arrived or - not. Parent sending or non-response does not change parent eligibility. A - relay parent enters - `suspect` only for its own ingress/parent-scope evidence, then stops accepting - new children and repairs its own ingress with reparent or branch-preserving - `replaceIngress`; Host source failures instead use publication repair and - child migration. A relay parent is marked - directional-ineligible only after that repair fails, an explicit sender or - resource failure makes forwarding unusable, or independent downstream edges - (not C+B from the same edge) confirm forwarding remains unusable. It then - retains a healthy ingress while draining affected children, and replaces or - removes a failed ingress in the same target. The quality policy accounts for - currently observable edges and endpoint capacity; the route model does not - hard-code a two-child quorum. The quality evidence owner defines finite - windows and independent-edge corroboration without turning them into a global - score. If the new ingress restores downstream media, - the subtree remains unchanged. Hard failure preempts confirmed parent drain, - which preempts a single-edge soft reparent. A feasible child migration is - never blocked by a sibling with no destination. + not. Parent sending or non-response does not change parent eligibility. + Downstream evidence drains a relay only when the trigger has at least one + distinct current sibling edge and every current authoritative child edge has + independently completed a current-generation unusable proof. Positive + progress, unknown evidence, or unfinished proof on any current child blocks + that drain; positive progress clears the exact edge's unusable evidence. A + lone child is reparented without draining its parent. A relay parent enters + `suspect` for its own ingress evidence, stops accepting new children, and + repairs that ingress with reparent or branch-preserving `replaceIngress`. + Parent/session hard failure or failed ingress repair independently authorizes + drain. An endpoint-wide sender/resource failure independently authorizes drain + only when a concrete server-verifiable producer establishes that scope; a + generic per-edge failure remains edge-scoped. Host source failures instead use + publication repair and child migration. Drain retains a healthy ingress and + replaces or removes a failed ingress in the same target. If a new ingress + restores downstream media, the subtree remains unchanged. Hard failure + preempts confirmed parent drain, which preempts a single-edge soft reparent. A + feasible child migration is never blocked by a sibling with no destination. +- Unexpired evidence follows its exact edge identity across unrelated route + revisions and broadcasts; the controller advances its revision guard without + resetting unaffected siblings. Identity change, positive progress, or + successful migration clears only that edge's evidence. An authoritative pause + stops and clears current-generation quality correlation. Resume starts a fresh + baseline, so intentional pause silence cannot trigger reparent or drain. - Healthy edges are sticky. Join, departure, capacity release, hard failure, confirmed ineligibility, server-resource change, and explicit ingress restore are the route-changing events. Quality evidence changes topology eligibility, @@ -257,13 +266,17 @@ Before a revised controller ships: - candidate lists are deterministic under input permutation, preserve a healthy current edge, prefer the shallowest least-loaded eligible parent, and try only the next eligible candidate after exact failure and idempotent cleanup; -- quality tests distinguish child-scoped C+B reparent from parent-scoped - corroborated drain, prove that same-edge C+B cannot drain a parent, require - independent sibling edges for parent corroboration, include a Viewer that is - both child and parent plus the Host-source publication-repair exception, bind - evidence to media-binding generation, prove bounded child-only recovery when - an ordinary peer edge stops RTP while signaling remains present, and exercise - suspect/local-repair before drain while preserving unrelated sibling evidence; +- quality tests distinguish child-scoped C+B reparent from parent-scoped drain; + require at least two current children and independently unusable proof on all + current child edges; prove that same-edge C+B, a healthy/unknown/unresolved + sibling, or a lone child cannot drain a parent; and cover independent + parent/session hard failure and failed-ingress-repair drain; +- quality tests bind evidence to exact media identity, preserve unaffected-edge + evidence across unrelated revisions, clear only changed or recovered edges, + suspend and clear correlation during authoritative pause, and start a fresh + baseline on resume. They include a Viewer that is both child and parent, the + Host-source publication-repair exception, bounded child-only recovery while + signaling remains present, and suspect/local-repair before drain; - real-browser tests cover direct peer media, peer relay, server-assisted media, selected transport when configured, failure, and recovery; - deployment preflight proves the LiveKit instance is dedicated, uses diff --git a/docs/project-memory.md b/docs/project-memory.md index 69599e0..e6808f9 100644 --- a/docs/project-memory.md +++ b/docs/project-memory.md @@ -10,7 +10,7 @@ Last updated: 2026-08-23 - Keep routing automatic and media distributed. Direct/peer UDP is preferred; centralized media is fallback infrastructure, not the default topology. - Every non-server endpoint uses one server-authoritative steady outbound media-copy capacity: default `2`, statically configurable as `1`, `2`, or `3`. A peer child or the Host publication consumes one slot; upstream receive is free, selected TURN replaces the same copy's transport, and SFU subscriber egress is accounted at the server. Browser role/UA/visibility does not create a separate tier, and clients cannot raise the deployment value. - The stable routing invariants are one authoritative upstream per Viewer, an acyclic active graph, exact room/session/share/revision/generation authorization, media-proven make-before-break, and bounded success or failure. -- The accepted route model is one room controller with allocate/distribute, child reparent, and relay abdicate/drain operations. One Host publication may serve all SFU-fed Viewers; an SFU-fed Viewer may relay only with independent outbound proof; selected TURN changes an authorized edge transport and is not a topology node. A confirmed bad edge, or an exact ordinary peer edge whose current-generation receive/decode progress remains absent through a bounded parent-proof challenge, reparents only that child regardless of whether the parent proved sending; neither outcome lets one child drain its parent. A relay parent's own ingress failure first enters suspect/cordon and attempts local repair; only failed repair, independent downstream evidence, or explicit sender/resource failure drains its children. Host source failures use publication repair and child migration. Endpoint capacity, temporary overlap, SFU/TURN admission, exact fallback, media binding generations, and rollback are one bounded transaction model. +- The accepted route model is one room controller with allocate/distribute, child reparent, and relay abdicate/drain operations. The controller is the only committed and pending graph owner; topology helpers hold participant metadata and plan candidates without restoring another mutable baseline. One Host publication may serve all SFU-fed Viewers; an SFU-fed Viewer may relay only with independent outbound proof; selected TURN changes an authorized edge transport and is not a topology node. A confirmed bad edge, or an exact ordinary peer edge whose current-generation receive/decode progress remains absent through a bounded parent-proof challenge, reparents only that child regardless of whether the parent proved sending. Downstream evidence drains a relay only when every current authoritative child edge independently remains unusable and at least one different sibling edge corroborates the trigger; positive progress, unknown evidence, or an unfinished proof on any current child blocks that drain. A lone child is reparented without draining its parent. A relay parent's own ingress failure first enters suspect/cordon and attempts local repair; only failed repair, independently corroborated downstream failure, a parent/session hard failure, or server-verifiable endpoint-wide sender/resource failure drains its children. Generic edge failure is not endpoint-wide evidence. Host source failures use publication repair and child migration. Endpoint capacity, temporary overlap, SFU/TURN admission, exact fallback, media binding generations, and rollback are one bounded transaction model. - Parent choice is sticky and deterministic: hard-filter current authority, reachability, cycles, depth, capacity, eligibility, exact-edge cooldown, and server admission; then order by shallowest result, remaining steady sender capacity, stable join order, and peer identity. Standard ICE and exact media proof test one candidate at a time. Raw IP, claimed NAT type, UA, geography, all-pairs probes, weighted scores, and periodic room rebalancing are not route inputs. - Ordinary peer ICE is STUN-only. Credentials, room secrets, candidate details, and diagnostic data remain private and narrowly scoped. - Screen audio offers live-switchable 64/128/256 kbps sender ceilings and defaults to 128. The existing last-wins quality wire owns desired state; each endpoint serially applies and reads back current Host/relay/SFU audio senders, exposes local failures, and gives future senders the latest desired value. Opus remains fixed, and without an applied-ack wire the Host does not claim room-wide atomic convergence. These are configuration ceilings, not fidelity claims. diff --git a/docs/status.md b/docs/status.md index 2c77ade..4566506 100644 --- a/docs/status.md +++ b/docs/status.md @@ -39,7 +39,7 @@ This is the current execution index. Git history owns completed timelines; [veri ## Next Stop -Implement the independent TURN-admission and controller-convergence waves from -this source. The route release still requires measured SFU capacities, a +Implement the controller-convergence wave from this source. The route release +still requires measured TURN and SFU capacities, a dedicated LiveKit instance with the documented private control-plane contract, and the focused real-network and Native `C=1/2/3` acceptance matrix. diff --git a/docs/todo.md b/docs/todo.md index c6d5a75..a6c981e 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -6,7 +6,7 @@ Only items in **Now** are executable after their stated decision gate. A branch ## Now -1. **Route Wave D: controller convergence.** Converge allocation, child reparent, and relay drain on one committed graph/controller. A signaling-connected relay that stops current-generation RTP cannot require its own report: after the bounded parent-proof deadline, persistent child-side silence reparents only the affected child whether the parent proved sending or not; cordon or drain the relay only after parent/session hard failure, explicit sender/resource failure, failed ingress repair, or independent downstream evidence. Parent candidates use hard eligibility filters followed by deterministic shallowest, remaining-capacity, join-order, and peer-identity ordering; standard ICE plus exact media proof tests one candidate at a time. Do not add raw-IP/NAT ranking, all-pairs probes, a weighted score, UA/visibility tiers, or periodic rebalancing. +1. **Route Wave D: controller convergence.** Make the room controller the only committed/pending graph owner and keep topology helpers to participant metadata plus pure candidate planning. A signaling-connected ordinary peer edge whose current-generation RTP and decode progress remain absent through the bounded parent-proof deadline hard-reparents only that child whether the parent proves sending or stays silent. Downstream-only relay drain requires at least one different current sibling edge and independently confirmed unusable evidence on every current authoritative child; any positive, unknown, paused, or unfinished current-child evidence blocks the drain, while a lone child only reparents. Parent/session hard failure and failed own-ingress repair remain independent drain triggers. Treat endpoint-wide sender/resource failure as held until a concrete server-verifiable producer exists; generic per-edge `route-failed` is not that producer. Carry unchanged exact-edge evidence across unrelated route revisions, clear only changed/stale bindings, and suspend correlation during authoritative pause. Parent candidates use hard filters followed by deterministic shallowest, remaining-capacity, join-order, and peer-identity ordering; standard ICE plus exact media proof tests one candidate at a time. Do not add raw-IP/NAT ranking, all-pairs probes, a weighted score, UA/visibility tiers, periodic rebalancing, or a second graph/controller. 2. **Switch screen-audio ceilings live.** Keep Opus fixed and serialize 64/128/256 kbps desired profile updates across every current Host, relay, and SFU audio sender. Each endpoint uses fresh `getParameters()`/`setParameters()` readback, ignores stale completions, retains media plus its prior applied value on failure, and applies the latest desired value to future edges. Do not add a remote applied-ack protocol or claim room-wide atomic convergence in this slice. 3. **Switch video codec while paused.** After the route/media-generation boundary is available, serialize the change with route mutation, freeze exact current route/media bindings, hold new joins, renegotiate P2P/relay and republish SFU while paused, then verify actual codec plus decoded frames on resume. A binding invalidated by leave or hard route failure exits this proof set and waits for routing to rebuild from the final committed preference after the bounded codec transaction settles. On failure pause again and perform bounded rollback; rollback proves the prior preference was reapplied plus a newly negotiated actual codec and decoded progress, not literal equality with the old actual codec. Zero targets update only the future-edge preference. Reset `automatic` with empty codec preferences. This is a paused bounded-gap transaction; unpaused make-before-break is a later extension. 4. **Diagnose the production H.264/blur report.** Reproduce exact release `769de201f7cc` against current `main` on the same Host/browser/driver/game and one wired direct Viewer, changing one variable at a time. Implement a codec-specific repair only if codec/encoder evidence identifies it; otherwise fix the proven capture, sender, BWE, or lifecycle cause. diff --git a/docs/方案设计.md b/docs/方案设计.md index 6c86ef9..71407c1 100644 --- a/docs/方案设计.md +++ b/docs/方案设计.md @@ -208,7 +208,7 @@ SVC 的标准能力门禁已经静态关闭为 `no-go-web-svc-cross-path-hardwar 质量控制不放宽稳态 outbound media-copy 预算:所有非服务器端点继续受统一静态 capacity 约束;native relay 只转发选定表示的编码包,不解码重编码。后续双树或条带化必须另行验收,不能绕过当前 graph、identity 和 capacity invariant。 -质量证据形成两条正交状态:`EdgeHealth(parent, child, generation)` 控制 child-scoped reparent,`RelayEligibility(parent)` 仅适用于 relay parent,控制 `eligible -> suspect -> draining -> ineligible`。Viewer 的 C 证据和 parent 对同一 edge 的 B 证据必须按 exact route identity(room/share、双方 session、assignment/connection/media-binding generation、route revision,及适用时 publication generation)和 sequence 相关;同一 edge 的 C+B 不直接触发 parent drain。若 exact ordinary peer edge 在信令和 PeerConnection 存活时持续没有当前代 RTP/decoded progress,且有界 parent-proof deadline 到期后仍停滞,`EdgeHealth` 不论是否收到 matching positive non-server-parent outbound-media proof,都只为该 child 进入 hard reparent;parent 正在发送或未回应都不改变 `RelayEligibility`。Relay parent 自身 ingress 失败先触发本地 reparent 或 branch-preserving `replaceIngress` 并进入 `suspect`;质量 owner 根据不同 downstream edges 的当前可观察边和端点容量确认 parent 方向失效,修复失败、明确 sender/resource failure 或独立 downstream edges 确认转发仍失效时才 drain。Host source 使用 publication repair/child migration。drain 停止新 child,仍健康的 ingress 保留,失效 ingress 在同一 target 中替换或移除,迁移有目标的 children,并让无目标者进入有界等待或失败;更长稳定证明和 cooldown 才恢复 `eligible`。同一个 controller 每房间只提交一个 target revision,按 hard failure、confirmed parent drain、single-edge soft reparent 的顺序合并事件;drain 吸收覆盖范围内的 child intent,成功的 reparent 只使同一旧 logical edge 的 pending evidence 失效,其他仍匹配且未过期的 sibling evidence 保留。 +质量证据形成两条正交状态:`EdgeHealth(parent, child, generation)` 控制 child-scoped reparent,`RelayEligibility(parent)` 仅适用于 relay parent,控制 `eligible -> suspect -> draining -> ineligible`。Viewer 的 C 证据和 parent 对同一 edge 的 B 证据必须按 exact route identity(room/share、双方 session、assignment/connection/media-binding generation、route revision,及适用时 publication generation)和 sequence 相关;同一 edge 的 C+B 不直接触发 parent drain。若 exact ordinary peer edge 在信令和 PeerConnection 存活时持续没有当前代 RTP/decoded progress,且有界 parent-proof deadline 到期后仍停滞,`EdgeHealth` 不论是否收到 matching positive non-server-parent outbound-media proof,都只为该 child 进入 hard reparent;parent 正在发送或未回应都不改变 `RelayEligibility`。仅靠 downstream evidence 退位时,必须存在一条不同的 current sibling edge,且每条 current authoritative child edge 都独立完成当前代 unusable 证明;任何正进展、未知或未完成证明都阻止 drain,正进展清除该 exact edge 的失效证据,只有一个 current child 时只迁走该 child。Relay parent 自身 ingress 失败先触发本地 reparent 或 branch-preserving `replaceIngress` 并进入 `suspect`;parent/session hard failure 或 ingress 修复失败可独立 drain。endpoint-wide sender/resource failure 只有存在服务端可验证且作用域明确的 producer 时才成立,普通单 edge `route-failed` 只影响该 edge。Host source 使用 publication repair/child migration。drain 停止新 child,仍健康的 ingress 保留,失效 ingress 在同一 target 中替换或移除,迁移有目标的 children,并让无目标者进入有界等待或失败;更长稳定证明和 cooldown 才恢复 `eligible`。同一个 controller 每房间只提交一个 target revision,按 hard failure、confirmed parent drain、single-edge soft reparent 的顺序合并事件;drain 吸收覆盖范围内的 child intent。exact edge identity 未变化时,未过期证据跨 unrelated revision 与 broadcast 保留并更新 guard;identity 变化、正进展或成功迁走该 edge 时只清对应证据。权威 pause 停止并清空当前 generation 的质量关联,resume 后从 fresh baseline 重建。 标准表示能力排在任何自定义双表示媒体实现之前。当前 Web P2P simulcast 缺少 receiver 选 RID 与跨 PeerConnection shared-encode 契约;当前 Web/LiveKit SVC 也缺少 direct/peer 独立选层和可移植硬件保证。Production 的 exactly-two screen-share simulcast 与 `dynacast: false` 只是实现和质量实验事实,不证明 root topology、publication accounting 或 subscriber model。具体表示门槛由 ADR-0007 和[实时质量调研](./research/realtime-quality-adaptation.md)持有,不得绕过 stock WebRTC congestion control。 @@ -372,7 +372,7 @@ Token-free SFU standby prewarm 保持可选且不创建 participant、publicatio 启动 owner fence 复用配置的应用监听地址:进程必须先完成独占 bind,绑定失败时不得调用 LiveKit;bind 成功但 LiveKit 初始化未完成时,HTTP 只返回 `503` 且不安装 signaling upgrade handler。唯一 owner 完成 foreign-room 拒绝、遗留 managed-room 删除和 empty readback 后,才开放 HTTP 与 signaling。这个顺序使重启或竞争进程不能在失去应用 owner 身份时清理仍在使用的 LiveKit room。 -服务端维护每个房间唯一的版本化 route owner。候选 parent 可以形成图,但 committed media graph 必须无环;Host 没有 upstream,每名 Viewer 同时只有零或一个 active upstream,且每个 active Viewer 必须沿 peer edges 到达 Host 或沿当前 authoritative Host publication 到达 source。加入、容量释放、节点离开、当前边失败和已接受的 path-health evidence 才能触发一次有界重选,未受影响分支保持 sticky。 +服务端维护每个房间唯一的版本化 route owner,它独占 committed graph、pending target 和 transition lifecycle。拓扑 helper 只保存 participant metadata 并纯计算 candidate plan,不持有可回滚的第二份 active/pending graph。候选 parent 可以形成图,但 committed media graph 必须无环;Host 没有 upstream,每名 Viewer 同时只有零或一个 active upstream,且每个 active Viewer 必须沿 peer edges 到达 Host 或沿当前 authoritative Host publication 到达 source。加入、容量释放、节点离开、当前边失败和已接受的 path-health evidence 才能触发一次有界重选,未受影响分支保持 sticky。 Parent selection 使用硬过滤加字典序,不建立综合质量评分器。先拒绝 stale session、不可 source-reachable、成环、超深、无 sender/overlap slot、relay-ineligible、位于 failed-edge cooldown 或 server resource 未准入的候选;对剩余候选依次比较结果深度、分配后剩余稳态 sender slots、稳定加入顺序与 peer identity。controller 每次只 reserve/prepare 一个候选,使用该 `RTCPeerConnection` 自带的 ICE checklist/connectivity checks,再以 current-generation RTP、live track 与 decoded frame 验证;初次加入或 hard failure 采用达到 media-usable floor 的首个候选,soft-quality reparent 还需 quality owner 证明候选达到 recovery threshold 且优于仍健康旧 edge。失败先幂等清理候选再取下一个;受管 SFU room 完成删除证明前仍占 server capacity。应用不做 all-pairs ping、raw-IP/NAT/地理排名、周期探测或全房重平衡。一个 exact edge 的近期失败只进入该 edge 的有界 exclusion/cooldown,不污染其他路径。 @@ -386,7 +386,7 @@ SFU admission 在应用单进程中只有一个注入实例。LiveKit credential SFU/TURN 的 publisher、subscriber、selected transport、central admission、resource accounting、temporary overlap 和 exact fallback sequence 已由 ADR-0005 作为一个不可拆分的设计项接受。普通端点 child count 不能隐式成为 server cap,兜底也不能形成无界 whole-room fanout。一个 Host publication 服务所有 SFU subscriptions;SFU-fed Viewer 只有在独立出站证据成立时才可 relay。TURN 只替换授权 edge 的 transport,Host 无 direct first-level path 时可作为单条 Host-SFU ingress 的 transport。 -质量事件按影响范围分工,而不是按谁先发现分工。Viewer 对自己 upstream 的 C 证据与 parent 对同一 edge 的 B 证据相关后,只触发该 child 的换父;不同 downstream edges 达到质量 owner 的独立 corroboration 门槛,或出现明确 sender/resource failure、修复失败的 relay-parent ingress 或独立 downstream edges 确认转发仍失效,才把 parent 标为 directional-ineligible 并退位排空。同一 edge 的 C+B 不等价于 parent corroboration。Host source 走 publication repair/child migration,不走 relay-parent reparent。节点同时是 child 和 relay parent 时,先用 reparent 或 branch-preserving `replaceIngress` 修复其自身 ingress、停止新 child;新 ingress 恢复下游时保留子树,否则由同一 controller 合并下游 drain。C+B 不产生第二个 controller 或第二个 revision。 +质量事件按影响范围分工,而不是按谁先发现分工。Viewer 对自己 upstream 的 C 证据与 parent 对同一 edge 的 B 证据相关后,只触发该 child 的换父;仅当至少还有一条不同 sibling edge,且所有 current authoritative child edges 都各自确认 unusable,downstream evidence 才把 parent 标为 directional-ineligible 并退位排空。同一 edge 的 C+B、未知 evidence、未完成 proof 或任一 current child 的正进展都不能完成 parent corroboration。parent/session hard failure、修复失败的 relay-parent ingress,以及由服务端可验证 producer 确认的 endpoint-wide sender/resource failure 可独立 drain。Host source 走 publication repair/child migration,不走 relay-parent reparent。节点同时是 child 和 relay parent 时,先用 reparent 或 branch-preserving `replaceIngress` 修复其自身 ingress、停止新 child;新 ingress 恢复下游媒体时保留子树,否则由同一 controller 合并下游 drain。C+B 不产生第二个 controller 或第二个 revision;pause、evidence carry 和 exact-edge invalidation 共用同一 generation fence。 实现迁移以当前 ADR-0005 invariants 为准;生产 release `9461e20` 仅是部署快照,代码迁移只保留仍满足当前 identity、media-proof 和 bounded-recovery 约束的部分。 ## 官方与参考资料 diff --git a/docs/需求理解.md b/docs/需求理解.md index d470b97..db06b9e 100644 --- a/docs/需求理解.md +++ b/docs/需求理解.md @@ -29,7 +29,7 @@ SFU/TURN 的 publication、subscription、transport、server-resource accounting - NAT 与传输边界:STUN 必须可用,所有 ordinary peer `RTCPeerConnection` 只接收 STUN ICE servers,普通边不得预发 TURN candidate。所有恢复和候选授权必须绑定 exact room/share generation、endpoint session、route/assignment generation 与 connection identity;成功即停止,失败、超时或绑定失效即撤销,全部可用路径耗尽后明确等待或失败。已接受的顺序是保持既有 logical edge,必要时 direct/STUN 后使用 exact selected TURN;只有 logical ingress 不可用时才使用当前 Host publication 的 SFU subscription。Host 无 direct first-level path 时优先一条可经 TURN 的 Host-SFU publication,而不是逐 Viewer 建 Host-TURN peer 边。凭据不得进入应用页面 URL、日志、浏览器持久化或 SQLite;TURN 凭据和 Screener route grant 不进入任何 URL,LiveKit join JWT 仅按协议进入不得记录的 WebSocket request target。HTTPS/WSS 继续独立使用 TLS/TCP。 - 质量控制:保留 1080p60(8 Mbps)、1080p30(5 Mbps)、720p30(3 Mbps)三档推荐上限,三档与高级设置初始值统一采用 `balanced`(平衡)与 `automatic`(自动编码),`maintain-resolution`(清晰优先)、`maintain-framerate`(流畅优先)和 H.264/VP8 保留为显式选择。分享期间可直接换视频档位和 64/128/256 kbps 音频 sender ceiling,不重新选择来源或重建健康连接;视频 codec 首个可交付切换边界是在分享已显式暂停时执行有界重协商,未暂停热切换是后续复用同一事务的扩展。Peer-assisted relay 与已配置的 SFU 必须接收同一个严格校验的房间级权威设置,覆盖当前和以后建立的 sender;普通 P2P 信令结构保持不变。任何设置都只是浏览器上限和偏好,具体降级由浏览器决定;必须读回实际 sender 参数并通过 stats 观测结果、显示参数拒绝、改写和持续受限原因。不做自定义动态 FPS、综合评分或周期性全房间调档。 - 路径隔离双表示:direct/peer 继续使用每条 PeerConnection 独立的 stock WebRTC GCC,不把任何 viewer 反馈合成全房目标。每条 SFU path 从 `HIGH` ceiling 开始;优先用 pinned LiveKit 2.22.0/1.13.5 的标准两层 screen-share simulcast,在 Dynacast off 下常驻 exactly `HIGH+LOW`,让内建 per-subscriber BWE 独立降层和恢复。若该 gate 通过,Screener 不实现媒体层 selector。首 gate 使用 zero-descendant SFU leaf;root-with-children 的自动 downshift 与有界迁移是 default-on 前独立硬门。只有内建选择不满足门槛时才用标准 subscriber quality 显式限制,常驻成本不合格时再测标准 sender 手动启停。`LOW` 停编只是资源优化;active representation/layer 硬上限为二,禁止按 viewer 增长。必须实测实际接收层、硬件编码证据、游戏 FPS/p1 low、CPU/GPU、区间 encode cost、上传和分层 bytes。当前 Web P2P simulcast 缺少 receiver 选 RID 和跨 PeerConnection shared-encode 契约;当前 Web/LiveKit SVC 也缺少 direct/peer 独立选层与可移植硬件保证,且 pinned screen share 强制三层 temporal-only `L1T3`,二者按现契约静态 no-go。全部标准路径失败后才考虑 custom/native dual encode。没有合格 hardware/power-efficient 路径或额外表示破坏游戏/上传预算时 fail closed:保护健康路径并明确显示弱路径降级或不可用。 -- 路径级质量状态:应用的 `HIGH`/`FALLBACK` 非对称窗口只决定拓扑资格和诊断,不命令正常的媒体层切换,也不计算综合分数。`EdgeHealth(parent, child)` 只控制该 child 的换父;`RelayEligibility(parent)` 仅适用于 relay parent,按 `eligible -> suspect -> draining -> ineligible` 控制其是否接收新 child 以及是否排空现有 children。质量 owner 根据不同 downstream edges 的当前可观察证据和端点容量要求足够的独立关联;同一 edge 的 C+B 只处理该 child。relay parent 自身 ingress 失败先触发本地修复并进入 `suspect`,只有修复失败、明确 sender/resource failure 或独立 downstream edges 确认转发仍失效时才 drain,不等待不存在的第二个 child;Host source 走 publication repair/child migration,不走 parent reparent。退位时仍健康的 ingress 保留,失效 ingress 在同一 bounded target 中替换或移除;无目标 child 进入有界等待或失败,更长的稳定证明和 cooldown 后才恢复 `eligible`。Viewer 请求必须鉴权、限频、去重,并由 sender 的 GCC/Transport-CC、实际码率、RTT、丢包、重传、编码限制、协商 codec/profile/parameters 与 viewer 解码、丢帧、冻结证据交叉验证;不能仅凭自报生效,也不得上传 raw SDP、candidate 地址或原始设备/网络标识。 +- 路径级质量状态:应用的 `HIGH`/`FALLBACK` 非对称窗口只决定拓扑资格和诊断,不命令正常的媒体层切换,也不计算综合分数。`EdgeHealth(parent, child)` 只控制该 child 的换父;`RelayEligibility(parent)` 仅适用于 relay parent,按 `eligible -> suspect -> draining -> ineligible` 控制其是否接收新 child 以及是否排空现有 children。同一 edge 的 C+B 只处理该 child。仅靠 downstream evidence 退位时,当前 authoritative children 必须不止触发 edge 一条,且每条 current child edge 都各自完成有界、当前代的 unusable 证明;任何 current child 的正进展、未知值或未完成证明都阻止 parent drain,正进展清除该 exact edge 的失效证据。只有一个 current child 时只迁走该 child,parent 不靠该 edge 退位。relay parent 自身 ingress 失败先触发本地修复并进入 `suspect`;parent/session hard failure 或修复失败可独立 drain,不等待另一条 child edge。只有服务端可验证且作用域明确为整个 endpoint 的 sender/resource failure 才能独立 drain;普通单 edge `route-failed` 不具备该作用域,在具体 producer 落地前该触发项 fail closed。Host source 走 publication repair/child migration,不走 parent reparent。退位时仍健康的 ingress 保留,失效 ingress 在同一 bounded target 中替换或移除;无目标 child 进入有界等待或失败,更长的稳定证明和 cooldown 后才恢复 `eligible`。Viewer 请求必须鉴权、限频、去重,并由 sender 的 GCC/Transport-CC、实际码率、RTT、丢包、重传、编码限制、协商 codec/profile/parameters 与 viewer 解码、丢帧、冻结证据交叉验证;不能仅凭自报生效,也不得上传 raw SDP、candidate 地址或原始设备/网络标识。 - 断线恢复:临时网络变化只触发整体路由模型授权的有限恢复动作;任一步成功即停止,失败最终进入明确等待或有界失败。`connecting`/`checking` 先显示不改变拓扑的慢连接状态,硬超时必须由真实移动网络证据确定。所有异步结果受 session、route revision、assignment/connection generation 约束,未受影响分支保持健康,Host 只看到不含 URL、地址、token 或原始错误的阶段信息。质量证据只改变 edge 或 parent eligibility,不创建第二个 route controller。 - 可观测性:按目标 RTP transport 与不透明连接/ICE 代次关联 selected path、`iceGatheringState`/ICE/connection state、事件推导的建立时长、generation-baselined pair-change delta,以及 RTT、RTP 丢包率、抖动、码率、帧率、编码耗时和受限原因。候选与错误的 wire、日志和导出只使用[低服务器成本媒体路由](./research/low-server-media-routes.md)的脱敏字段和 code bucket,禁止 raw SDP、candidate、地址、URL、`errorText` 或网络标识。只有创建该 `RTCPeerConnection` 的本机可在折叠详情中显示浏览器对 selected local/remote candidate 实际暴露的地址与端口;该值不上传、不持久化、不用于地理/NAT 推断,缺失或被隐私保护时显示未知。 - 渐进式诊断:分享者和观看者默认只看到房间、画面、核心状态和常用控制;Host 的 Viewer 卡片只把本机连接或当前接收证据称为“已连接”,其余状态保持中性的“线路分配中”或具体连接状态,不把 active assignment 当作连接成功。展开连接详情也只在当前媒体证据成立后显示媒体方式:`P2P` 使用绿色与直观图标,`SFU` 使用黄色的 `SFU fallback`;实际 relay 时另标 `TURN`,只有本地 relay protocol 可见时才附加 `/UDP` 等实际值。尚未连接时不预告 P2P、SFU 或未知 ICE,只保留中性状态。Host/Peer 上下游仍只在独立的权威拓扑视图中展示。传输协议、候选路径、RTT、视频/音频 RTP 区间丢包率、实际编解码器、音频区间码率/jitter 和视频编码耗时等技术详情由一个明确的“显示连接详情”开关展开。远端 Viewer evidence 的详情 shell 在同一 viewer/parent/connection/route identity 内保持稳定;非空字段从各自最后一次实际观测起精确保留五秒,`connecting`/`disconnected` 只降为非 fresh,`failed`/`closed`、identity/session/share/route 变化或非单调 sequence 则清空/换代。非 fresh evidence 不得继续证明“已连接”或被其他 current-state 消费者使用。音频 codec MIME、RTP clock、channels 与 fmtp 只能来自当前 RTP 对象引用的 `RTCCodecStats`,缺失即未知;`opus/48000/2` 不得写成 48 kHz 音源或实际立体声。隐藏诊断不能隐藏失败原因、音频缺失、持续画质受限或需要用户操作的告警。 @@ -42,7 +42,7 @@ SFU/TURN 的 publication、subscription、transport、server-resource accounting - 本地自包含一键包:提供 Windows、macOS 和 Linux 可分发的桌面包,在分享者电脑上同时运行 Host、当前 Web/信令应用服务器和本地状态,朋友继续通过普通浏览器邀请观看。安装后启动不要求源码 checkout、Node 或任何外部 Screener、STUN、SFU、TURN、隧道或 relay 服务;它尽力使用本机可达入口和 direct P2P,但必须明确报告公网入口、可信 HTTPS/WSS、网关映射或 NAT 路径缺失,不能承诺任意 CGNAT/严格防火墙必通。两种分发包都排在当前功能、路由、NAT 和真实网络 TODO 完成之后。 - 私密房间双入口:private room 接受现有 `/r/{code}#v={grant}` 合法邀请直接进入,或“站点访问 + 房间号 + 该房间观看密码”。没有 grant 的 `/join` 或 `/r/{code}` 先显示明确标为“站点访问”的整站验证,通过后才显示明确标为“加入房间”的当前房间密码输入;有效 site-access cookie 直接跳过第一步,但两类密码不得复用、代填或互相替代。Host 可设置、轮换或移除房间观看密码。密码接受 1 至 64 个可见 ASCII 字符;异步 scrypt 全局最多执行 2 个、等待 16 个派生。站点口令和房间密码都不得进入 URL/query/storage/log/SQLite;不存在、错误密码、队满和正确密码后的满房都返回通用结果。 -- 统一自动路由:旗舰部署开启 `PEER_ASSISTED_MEDIA` 后,所有正常房间使用同一个服务端 route controller,并各自保存 assignment、session、route revision、assignment generation、connection identity、media-binding generation 和容量状态。controller 只执行 allocate/distribute、child reparent 和 relay abdicate/drain;合格父节点可形成候选图,但 committed media graph 必须无环、每名 Viewer 同时至多一个 active upstream,且每个 active Viewer 必须沿 peer edges 到达 Host 或沿当前 authoritative Host publication 到达 source。加入、容量释放、节点离开、当前边失败或已接受的 path-health evidence 只开启一次有界重选;其余健康分支保持 sticky,不做常驻多源、周期评分或全房重平衡。候选先按当前身份、source reachability、无环、深度、sender slot、relay eligibility、failed-edge/cooldown 和 server admission 做硬过滤,再按最小结果深度、最多剩余稳态 sender slots、稳定加入顺序和 peer identity 确定性排序;一次只准备一个候选,以浏览器 ICE 和当前代媒体证明决定是否采用,失败才尝试下一个。初次加入或 hard failure 接受达到媒体可用门槛的首个候选;soft-quality reparent 还必须由 quality owner 证明候选达到恢复门槛并优于仍健康的旧 edge,否则回滚后试下一个。raw IP、地理位置、UA、自报 NAT 类型、未与 current-generation media 相关的单边 RTT 或跨 edge 加权分数均不得进入排序。迁移保留旧边,只有 exact current candidate 取得 live track、正 RTP 和 decoded-frame progress 后才原子 commit;失败、超时、代次变化或授权撤销销毁候选并保留或恢复旧权威。普通 peer 始终 STUN-only。SFU/TURN overlap 和资源准入由同一 typed ledger 与事务约束。该能力不增加用户拓扑选择、百分比发布或第二套路由器。 +- 统一自动路由:旗舰部署开启 `PEER_ASSISTED_MEDIA` 后,所有正常房间使用同一个服务端 route controller,并各自保存 assignment、session、route revision、assignment generation、connection identity、media-binding generation 和容量状态。controller 是 committed 与 pending media graph 的唯一 owner,只执行 allocate/distribute、child reparent 和 relay abdicate/drain;topology helper 只保存参与者 metadata 并纯函数规划候选,不能维护或回灌另一份 mutable failback graph。committed media graph 必须无环、每名 Viewer 同时至多一个 active upstream,且每个 active Viewer 必须沿 peer edges 到达 Host 或沿当前 authoritative Host publication 到达 source。加入、容量释放、节点离开、当前边失败或已接受的 path-health evidence 只开启一次有界重选;其余健康分支保持 sticky,不做常驻多源、周期评分或全房重平衡。候选先按当前身份、source reachability、无环、深度、sender slot、relay eligibility、failed-edge/cooldown 和 server admission 做硬过滤,再按最小结果深度、最多剩余稳态 sender slots、稳定加入顺序和 peer identity 确定性排序;一次只准备一个候选,以浏览器 ICE 和当前代媒体证明决定是否采用,失败才尝试下一个。初次加入或 hard failure 接受达到媒体可用门槛的首个候选;soft-quality reparent 还必须由 quality owner 证明候选达到恢复门槛并优于仍健康的旧 edge,否则回滚后试下一个。raw IP、地理位置、UA、自报 NAT 类型、未与 current-generation media 相关的单边 RTT 或跨 edge 加权分数均不得进入排序。迁移保留旧边,只有 exact current candidate 取得 live track、正 RTP 和 decoded-frame progress 后才原子 commit;失败、超时、代次变化或授权撤销销毁候选并保留或恢复旧权威。普通 peer 始终 STUN-only。SFU 与 TURN 使用各自的 typed admission/ledger,并由同一个有界 route transaction 约束 overlap、commit、drain 和 release。该能力不增加用户拓扑选择、百分比发布或第二套路由器。 - 观看端连接反馈:Viewer 舞台从鉴权到首帧使用一个由现有权威状态推导的稳定阶段反馈,至少区分等待分享、分配线路、建立 P2P、连接备用线路、等待首帧、浏览器阻止自动播放、恢复连接和有界失败。阶段不是进度百分比,也不产生 route authority;只有当前 video element 实际提交首帧后才显示正在播放。播放按钮只在 `play()` 因自动播放策略被拒绝时稳定出现,连接中不得忽隐忽现;明确失败后才提供一次受现有 controller 约束的重试入口。make-before-break 或仍有可见旧帧时保留画面并叠加恢复状态,不为显示反馈主动清空健康 media element。 - 质量控制扩展:推荐档位仍是默认入口;“分享高级设置”只允许 720p/1080p/1440p 分辨率 ceiling、15 至 60 的整数 FPS ceiling、2 至 12 Mbps 视频码率 ceiling、清晰/平衡/流畅三种偏好、`自动 | H.264 | VP8` 编码选择,以及离散的 `64 kbps 省流 | 128 kbps 音乐默认 | 256 kbps 极高音质` 屏幕音频 sender ceiling;初始值采用 `balanced`、`automatic` 与 128 kbps。视频档位和音频档位在直播中显式应用;音频变更只更新固定 Opus 路径的 sender ceiling,不是 codec 切换。所有当前及以后建立的 Web Host、Browser relay 与 SFU sender 都读取最新 desired 档位,并各自在 fresh sender parameters 上应用和读回 endpoint-local applied 值;实际码率仍由浏览器拥塞控制和 stats 决定。Peer 只在唯一明确的 active Opus audio 上做结构化、幂等的 Viewer answer `stereo=1;maxaveragebitrate=256000` 变换,使同一接收上限覆盖三档;非目标形态或 parser/writer 异常必须原样保留浏览器 answer,不得阻断可继续的 mono/video edge。不得用宽泛字符串/正则改 SDP,也不得改写浏览器协商的 FEC。SFU 将三档映射到 pinned SDK 的 music stereo preset 或显式 256 kbps ceiling,统一保留 `forceStereo = true`、`dtx = false` 与 pinned room default `red = true`。不得暴露任意码率、声道、采样率、audio codec、stereo、DTX、RED 或 FEC 控件;256 kbps 只是显式较高上限,不是无损、高保真或最低码率保证。实际质量持续低于所选上限时必须显示可解释的限制原因;在真实拥塞、编码器和接收端证据完成前,不把任一组合称为“完美平衡”。未来 microphone voice 若进入范围,必须是独立 track/处理链,不能把 AEC/降噪/DTX 套到电影或游戏音频。 - SFU 质量设置生命周期:直播中修改档位时,浏览器 sender 的 applied readback、LiveKit `LocalVideoTrack` 保存的 degradation preference、track restart options 与 `LocalTrackPublication` 的 republish options 必须作为同一次串行操作更新;任一步失败恢复旧 sender 与旧 SDK 保存态,旧 publication generation 不得回写当前状态。LiveKit full reconnect/republish 不得恢复初次发布的旧偏好。该修复不改变 codec、码率上限、层数或路由,也不能单独解释即时低帧。 @@ -150,10 +150,11 @@ SFU/TURN 的 publication、subscription、transport、server-resource accounting - Given 用户选择任一推荐档位或高级质量参数,When 接收端实际分辨率、帧率或码率持续显著低于该目标,Then 诊断数据能区分采集约束、发送编码、带宽/拥塞、TURN 路径和接收端限制;产品不得只改变档位标签却继续隐藏实际降级。 - Given 一个健康 SFU leaf 和一个受控弱 SFU leaf 都以 `HIGH` ceiling 订阅,When 内建 LiveKit BWE 运行,Then 弱 leaf 自动使用唯一共享 `LOW` 并在恢复后回到 `HIGH`,健康 leaf 不受影响,且 Screener 没有实现媒体层 selector;direct/peer 继续使用各自的 stock WebRTC GCC。 - Given 一个 Viewer 观察到自己当前 upstream 的接收效果差,When 当前 Viewer 的 `viewer-quality-evidence` 与 parent 对同一 edge 返回的 `parent-edge-quality-evidence` 按 exact route identity(room/share、双方 session、assignment/connection/media-binding generation、route revision,及适用时 publication generation)和 sequence 连续相关,Then 只为该 Viewer 创建 child-scoped reparent intent,排除旧 parent,按 reserve/proof/commit/rollback 换父;不得直接把 parent 全局判坏。 -- Given Viewer 的 exact current ordinary peer upstream 在信令和 PeerConnection 仍存活时停止产生当前代 RTP 与 decoded-frame progress,When 有界 parent-proof deadline 到期后该停滞仍成立,Then controller 不论是否收到 matching positive non-server-parent outbound-media proof,都只把该 logical edge 作为 hard child-scoped reparent 处理,并以 exact generation、一次候选、媒体证明和有界失败约束恢复该 Viewer;parent 的正发送证明或未回应都不改变 `RelayEligibility(parent)`,只有 parent/session 的硬故障、明确 sender/resource failure、修复失败或独立 downstream evidence 才能 cordon/drain parent。 -- Given 一个 relay parent 的不同 downstream edges 达到质量 owner 针对当前可观察边数和端点容量定义的独立关联证据门槛,或该 parent 出现明确的 sender/resource failure、修复失败的自身 ingress 或独立 downstream edges 确认转发失效,When 统一 controller 确认 directional ineligibility,Then parent 进入 `draining`,停止新 child;仍健康的 ingress 保留,失效 ingress 在同一有界 target 中替换或移除,并迁移可行 children;无候选 child 进入 waiting/bounded-failure,不得阻塞其他 child,也不得用固定的“两个 child”替代质量策略。Host source 走 publication repair/child migration,不进入 relay-parent reparent。 +- Given Viewer 的 exact current ordinary peer upstream 在信令和 PeerConnection 仍存活时停止产生当前代 RTP 与 decoded-frame progress,When 有界 parent-proof deadline 到期后该停滞仍成立,Then controller 不论是否收到 matching positive non-server-parent outbound-media proof,都只把该 logical edge 作为 hard child-scoped reparent 处理,并以 exact generation、一次候选、媒体证明和有界失败约束恢复该 Viewer;parent 的正发送证明或未回应都不改变 `RelayEligibility(parent)`,也不得冒充 parent/session hard failure。 +- Given relay parent 至少还有一条不同的 current authoritative sibling edge,When 每条 current child edge 都各自完成当前代 unusable 证明且没有任何 edge 为正进展、未知或未决,Then downstream-only corroboration 才能把 parent 标为 directional-ineligible 并进入 `draining`;成功换父或 current-generation 正进展清除对应 exact edge 的失效证据,只有一个 child 时只迁该 child。Given parent/session hard failure 或自身 ingress 修复失败,Then 可独立 drain;Given 只有普通单 edge `route-failed` 且没有服务端可验证的 endpoint-wide sender/resource producer,Then 不得把它提升为 parent-wide failure。drain 停止新 child、保留健康 ingress、在同一 bounded target 中替换或移除失效 ingress,并迁移可行 children;无候选 child 进入 waiting/bounded-failure,不得阻塞其他 child。Host source 走 publication repair/child migration,不进入 relay-parent reparent。 +- Given exact edge 的 room/share/session/connection/media-binding identity 在新 active revision 中未变化,When unrelated sibling、join、abort 或 route broadcast 发生,Then 其未过期质量证据只更新 revision guard 而不被全房清空;身份变化只清对应 edge。Given 当前分享处于权威 paused generation,Then 停止并清空该 generation 的质量关联,resume 后从 fresh baseline 重新采样,主动暂停的无 RTP 不得触发换父或退位。 - Given 一个路径的 sender/viewer 证据跨连续窗口确认 `FALLBACK`,When 应用更新状态,Then 只改变其 edge/parent 拓扑资格和诊断;不得重复命令内建媒体层。更长稳定恢复窗口和 cooldown 后才可重新声明 relay capacity。 -- Given 同一 Viewer 同时是某人的 child 和其他人的 parent,When 其自身 ingress 发生 hard/confirmed failure,Then child-scoped reparent 或 branch-preserving `replaceIngress` 优先修复它,同时将其 relay eligibility 置为 `suspect` 并停止新分配;新 ingress 恢复下游媒体时保留子树,只有修复失败或不同 downstream edges 仍确认转发失效时才执行 parent drain。C+B 可同时贡献证据,但 controller 只提交一个 target revision;成功的 child reparent 只使同一旧 logical edge 的 pending evidence 失效,其他仍匹配且未过期的 sibling evidence 保留。 +- Given 同一 Viewer 同时是某人的 child 和其他人的 parent,When 其自身 ingress 发生 hard/confirmed failure,Then child-scoped reparent 或 branch-preserving `replaceIngress` 优先修复它,同时将其 relay eligibility 置为 `suspect` 并停止新分配;新 ingress 恢复下游媒体时保留子树,只有修复失败或 all-of-current downstream corroboration 成立时才执行 parent drain。C+B 可同时贡献该 exact edge 的证据,但 controller 只提交一个 target revision;成功的 child reparent 只使同一旧 logical edge 的 pending evidence 失效,其他仍匹配且未过期的 sibling evidence 保留。 - Given SFU BWE 在应用确认前把一个仍有 children 的 root 瞬时降层,When 收到该观测,Then 先标记 `suspect`;随后由 ADR-0005 的 exact scope 选择 local repair、child reparent 或 parent drain。只有进入 parent-drain 分支才迁走 children 并进入 `FALLBACK`,不得稳定保留 confirmed `FALLBACK` parent。默认启用前必须在 root-with-children gate 中记录并限制这段不可避免的 descendants 短暂画质影响,不能声称 packet-level 先迁后降。 - Given exactly-two publication 的实测 encoder/CPU/GPU 或上传成本超过游戏预算,或没有合格 hardware/power-efficient 路径,When 评估该候选,Then 保留健康路径并明确显示弱路径降级或不可用,不得退回全房间最低目标或静默软件编码;若 supported sender cohort 无法以常驻或按需方式提供共享 `LOW`,Then 质量验收失败,必须优化编码路径或明确标记不支持。 - Given 同一时刻采集、host outbound 与 viewer inbound 证据,When 进行根因分类,Then 关联派生后的实际协商视频 codec/profile/parameters、适用时的 `scalabilityMode` 与真实解码表现,依次区分 Capture 低、Capture 高但 outbound 因 CPU 低、Capture 高但 outbound 因 bandwidth 低、outbound 正常但 inbound 低、以及 inbound 正常但肉眼模糊五类;不用 UA 或设备型号代替运行时证据,也不上传 raw SDP。