diff --git a/docs/adr/0005-automatic-hybrid-media-routing.md b/docs/adr/0005-automatic-hybrid-media-routing.md index 49b5336..dd423db 100644 --- a/docs/adr/0005-automatic-hybrid-media-routing.md +++ b/docs/adr/0005-automatic-hybrid-media-routing.md @@ -132,10 +132,17 @@ 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. - A Viewer that receives the publication may relay to ordinary children only - after independent outbound RTP, decode, resource, and sender-slot proof; - a strict SFU Viewer remains a leaf. Viewer republishing into a second SFU - publication is outside the current product. + An SFU-fed Viewer is not a committed relay parent merely because it advertises + capacity or proves its SFU ingress. The controller may reserve one formal + sender slot and prepare one exact provisional ordinary-child edge as the proof + transaction. Connected transport, a live video track, and positive current- + window RTP-receive and decoded-frame deltas at that child prove the outbound + path end to end; exact sender ownership and controller admission prove resource + and slot availability. Only atomic commit makes the Viewer relay-eligible. + Failure, timeout, stale authority, pause, or identity change destroys the + candidate and leaves the Viewer a leaf. Every later child still requires its + own exact media proof. Viewer republishing into a second SFU publication is + outside the current product. - TURN is a selected transport for an existing authorized logical edge or the Host-to-SFU ingress. It is not a topology node, a second source, or a global room lease. Existing direct/STUN edges remain preferred; a failed selected diff --git a/docs/project-memory.md b/docs/project-memory.md index e6808f9..547b0cc 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. 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. +- 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 stays a leaf until one controller-owned formal provisional child edge proves connected transport, live video, and positive current-window RTP receive and decode under exact sender-slot admission; only atomic commit makes it a relay parent, and every later child proves its own edge. 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 4566506..78e35fb 100644 --- a/docs/status.md +++ b/docs/status.md @@ -18,7 +18,7 @@ This is the current execution index. Git history owns completed timelines; [veri - The v6 release boundary rejects v5 before room authority and carries the exact deployment cap in every authenticated snapshot so ordinary Host authorization, Native admission, peer-assisted child assignments, and sender slots share the server authority. It leaves an SFU-fed Viewer childless without independent outbound proof and keeps quality events child-scoped without a fixed two-child parent drain. - Current source also implements explicit no-default SFU ingress/egress capacities and exact `reserved | committed | draining` accounting. The listener owner reconciles its dedicated non-auto-create LiveKit namespace before serving, creates each generation before token issuance, releases only after delete-plus-absence proof, and reclaims Host-offline generations through bounded exact-participant checks. Selected TURN has an independent explicit no-default deployment capacity; one process-local ledger charges every exact peer-selected edge and Host-SFU ingress through reserve, commit, drain, and release. - Automatic P2P-first routing, one active upstream, an acyclic graph, exact identity/generation authority, media-proven make-before-break, and bounded failure remain accepted. -- The holistic assisted-route model is accepted: one controller performs allocation, child reparenting, and relay abdication/drain. One Host publication serves SFU subscriptions; TURN is an exact edge transport, not a topology node. Endpoint accounting, the wire boundary, SFU lifecycle, and TURN admission are implemented; controller convergence remains pending. +- The holistic assisted-route model is accepted: one controller performs allocation, child reparenting, and relay abdication/drain. One Host publication serves SFU subscriptions; TURN is an exact edge transport, not a topology node. Controller convergence includes one formally admitted provisional child proof before an SFU-fed Viewer can atomically become a relay parent; advertisement or ingress readiness alone leaves it a leaf, and every later child proves its own edge. Endpoint accounting, the wire boundary, SFU lifecycle, and TURN admission are implemented; controller convergence remains pending. - Parent selection is deterministic rather than random or score-based: hard eligibility filters, then shallowest result, remaining sender capacity, stable join order, and peer identity; one provisional edge uses standard ICE and media proof at a time. Viewer connection feedback and the strict-NAT emulator gate are accepted follow-up work, not deployed behavior. - Live 64/128/256 audio-ceiling mutation and paused-share video codec switching are accepted but not yet implemented on canonical `main`. H.264 startup blur/low-FPS reports remain an evidence-led diagnosis, not a presumed codec defect. Public-server and fully local distribution packages are later work after the functional, NAT, and real-network ledger is complete. diff --git a/docs/todo.md b/docs/todo.md index a6c981e..d25ecb1 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.** 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. +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. Close the SFU-fed relay bootstrap with one controller-owned formal provisional child edge: capacity advertisement and SFU ingress do not authorize relay, while exact sender-slot admission plus child-side connected/live/positive RTP receive and decode proof permits only the atomic committed parent; every later child still proves its own edge. 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 71407c1..5ed9c4e 100644 --- a/docs/方案设计.md +++ b/docs/方案设计.md @@ -253,14 +253,18 @@ identity 或授权撤销销毁 candidate,保留或恢复最后一个有效 rou SFU 与 TURN 是可选、有界的 server-assisted resources。一个 room controller 统一负责 allocate/distribute、child reparent 和 relay abdicate/drain:Host -拥有唯一 authoritative publication,所有 SFU-fed Viewer 订阅它;具备独立出站 -证明的 SFU-fed Viewer 可以继续 relay,严格受限 Viewer 保持 leaf。TURN 只替换 -授权 logical edge 或 Host-SFU ingress 的 transport,不是拓扑节点。现有 edge -优先保持 direct/STUN,必要时使用 exact selected TURN,再按需要切换到 SFU; -Host 无 direct first-level path 时优先一条可经 TURN 的 Host-SFU publication。 -端点容量、SFU/TURN ingress/egress、临时 overlap、media binding generation、 -server admission 和 bounded failure 在同一 reserve/proof/commit/rollback -事务中记账。 +拥有唯一 authoritative publication,所有 SFU-fed Viewer 订阅它。SFU-fed +Viewer 的容量广告或 ingress ready 不授予 relay eligibility;controller 只可预留 +一个正式 sender slot,并以一条 exact provisional ordinary-child edge 作为证明事务。 +该 child 同时证明 connected transport、live video、当前窗口正 RTP receive 与 +decoded-frame progress,加上 exact sender ownership 和 controller admission 后才 +atomic commit;失败、超时、暂停或 identity 变化销毁 candidate 并保持 leaf,后续 +每个 child 仍分别证明自己的 exact edge。TURN 只替换授权 logical edge 或 +Host-SFU ingress 的 transport,不是拓扑节点。现有 edge 优先保持 direct/STUN, +必要时使用 exact selected TURN,再按需要切换到 SFU;Host 无 direct first-level +path 时优先一条可经 TURN 的 Host-SFU publication。端点容量、SFU/TURN +ingress/egress、临时 overlap、media binding generation、server admission 和 +bounded failure 在同一 reserve/proof/commit/rollback 事务中记账。 短期 server-assisted credential 必须严格绑定当前 route identity、只存内存、受 TTL 与 quota 限界,并且不进入 URL、日志、browser storage、room row 或 SQLite。Coturn diff --git a/docs/需求理解.md b/docs/需求理解.md index db06b9e..d1add6c 100644 --- a/docs/需求理解.md +++ b/docs/需求理解.md @@ -25,7 +25,7 @@ SFU/TURN 的 publication、subscription、transport、server-resource accounting - 本地昵称与房间成员:用户可设置一个只保存在当前浏览器同源 `localStorage` 的显示名,界面统一称为“昵称”;默认以只读文本显示,铅笔编辑按钮紧贴昵称,点击后才进入编辑。Viewer 顶部标题直接显示“{Host 名称} 的屏幕”,不得在下方重复一行 Host 名称。信令只在当前在线 socket 会话内保存和广播 NFC 规范化、最多 24 个 Unicode code points、拒绝控制/换行/双向覆盖符与孤立 surrogate 的纯文本标签,不写 SQLite 或日志。空值使用通用角色 fallback。Web Host 与 Viewer 都必须看到全部当前在线 Viewer 的权威总数与 roster,不能拿最多两条 Host 媒体直连数冒充在线人数;presence 更新也不得创建媒体连接。展示身份默认只显示名称;仅当房内出现同名时追加当前房间 `peerId` 的至少六字符短后缀,并只在同名项后缀碰撞时有界延长。显示名、后缀和 `clientId` 都不是账号、授权身份或全局 UUID,重复名称允许且不得参与授权、路由或质量决策。Web Host 与 Viewer 通过同一 opt-in participant roster 看到 Host 名称;Native 使用同一版本化鉴权 wire,但不发送 presence capability,也不接收 Web roster。 - 一对多观看:房间 admission 与端点下游容量分别配置,超额加入必须明确拒绝。Production 当前默认 8、显式接受 1 至 16;未来默认值和上限是独立产品决策,必须经过相应资源、质量、深度和恢复验收,不能从端点 cap 或一次短跑推导。 - 端点发送容量:所有非服务器端点使用同一个服务端权威稳态 outbound media-copy capacity,由唯一部署项 `ENDPOINT_MEDIA_COPY_CAPACITY` 配置,默认 `2` 且只接受 `1`、`2` 或 `3`;不读取旧键或保留第二套 tier。peer child 或 Host 的唯一 SFU publication 各消耗一份,上游接收不消耗该预算,committed selected TURN 只替换同一份 logical edge 的 transport。客户端只能声明不高于部署值的可用容量,角色、浏览器、UA、设备类型和页面可见性不产生不同上限。服务端在每次 `authenticated` 权威快照中返回最终 `endpointMediaCopyCapacity`;普通非 peer-assisted 模式也只授权稳定加入顺序中的至多 `C` 个 Host child,超额 Viewer 保持入房等待并在槽位释放后提升,信令不能绕过该集合。Web 与 Native 严格校验该值,Native Host 的普通 Viewer admission、peer-assisted child assignment 和实际发送槽位都使用同一值,且不得从独立的房间 `maxViewers` 推导。并行产生媒体的 candidate 必须持有唯一的 `TransitionOverlapSlot`,并在 commit 后回到稳态上限;SFU subscriber egress 与 TURN allocation 由独立 server admission 记账。该不兼容能力变化与单一 signaling wire 原子升级;缺失或非法 cap 与旧客户端都在取得房间或 route authority 前 fail closed。 -- P2P 优先:每条普通逻辑媒体边先尝试 direct/peer UDP,服务器不得默认中转可直连媒体。更多观看者优先沿满足统一端点容量的无环 peer 图分布;每名 Viewer 同时至多一个 active upstream,失败时只影响需要重选的分支。 +- P2P 优先:每条普通逻辑媒体边先尝试 direct/peer UDP,服务器不得默认中转可直连媒体。更多观看者优先沿满足统一端点容量的无环 peer 图分布;每名 Viewer 同时至多一个 active upstream,失败时只影响需要重选的分支。SFU-fed Viewer 不能仅凭容量广告或 SFU ingress ready 成为 relay parent;controller 只能为它预留一个正式 sender slot,并用一条 exact provisional ordinary-child edge 验证 connected transport、live video、当前窗口正 RTP receive 与 decoded-frame progress。该 child 的端到端证明与 controller admission、精确 sender ownership 同时成立后,atomic commit 才授予 relay eligibility;失败、超时、暂停或 identity 变化都销毁 candidate 并让它保持 leaf,以后的每个 child 仍分别证明自己的 exact edge。 - 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:保护健康路径并明确显示弱路径降级或不可用。