diff --git a/docs/adr/0005-automatic-hybrid-media-routing.md b/docs/adr/0005-automatic-hybrid-media-routing.md index b5fcf5b..c8c2569 100644 --- a/docs/adr/0005-automatic-hybrid-media-routing.md +++ b/docs/adr/0005-automatic-hybrid-media-routing.md @@ -104,12 +104,13 @@ There is no independent maximum-depth policy. Acyclicity and room admission bound the graph, while shallowest-first ordering minimizes depth. Depth remains an observed acceptance metric. -A candidate identity is one logical upstream plus one transport. For an invalid -ordinary child edge, the operation may create a fresh direct/STUN connection to -the same parent, another eligible peer parent, an admitted selected-TURN -transport, or an SFU subscription. All eligible direct/STUN tuples use -deterministic parent order before admitted selected-TURN tuples, followed by SFU -fallback. If the Host has no usable direct first-level peer path, the operation +A candidate identity is one logical upstream plus one transport. An operation +opened by a failed edge seeds that exact current tuple as already tried, so the +same parent and transport cannot immediately repeat. Other eligible direct/STUN +parents use deterministic order first; a different transport on the old parent, +such as admitted selected TURN after direct failure, may appear with the later +transport candidates, followed by SFU fallback. A new external fact starts a new +operation and may make the old tuple eligible again. If the Host has no usable direct first-level peer path, the operation prefers its single Host publication, using selected TURN for that ingress when needed, rather than enumerating Host-TURN Viewer edges. The operation records only exact tuples already tried; failure does not globally exclude that parent diff --git a/docs/research/advanced-peer-distribution.md b/docs/research/advanced-peer-distribution.md index 45443c9..baf5f59 100644 --- a/docs/research/advanced-peer-distribution.md +++ b/docs/research/advanced-peer-distribution.md @@ -240,10 +240,11 @@ reference boundaries, not universal prescriptions. Screener's separately owned 15-second initial deadline remains a candidate pending mobile-network evidence. WebRTC/LiveKit first owns transient reconnect. A hard failure or non-paused -decoded-frame stall wakes ADR-0005 once. The same room transaction may try a -fresh connection to the same parent, another parent, exact selected TURN, or a -Host-publication SFU subscription as ordinary bounded candidates under current -capacity and server admission. There is no separate same-edge repair ladder. +decoded-frame stall wakes ADR-0005 once and seeds the exact failed +`parent + transport` tuple as already tried. The same room operation tries other +direct parents, then different admitted transports and Host-publication SFU +under current capacity and server admission. There is no separate same-edge +repair ladder or persistent parent blacklist. Every completion is guarded by the current authenticated sessions, share generation, pending route revision, and candidate connection. Sustained bitrate, FPS, resolution, and blur remain diagnostic. diff --git a/docs/方案设计.md b/docs/方案设计.md index 1561fcf..38b354b 100644 --- a/docs/方案设计.md +++ b/docs/方案设计.md @@ -373,7 +373,7 @@ Token-free SFU standby prewarm 保持可选且不创建 participant、publicatio 服务端维护每个房间唯一的版本化 route owner,它独占 committed graph、一个 pending child operation 和 transition lifecycle。拓扑 helper 只保存 participant metadata 并纯计算 candidate list,不持有第二份 graph。operation 内聚 list/cursor、一个 current candidate/预留和一个总 deadline。committed media graph 必须无环且 source-reachable;Host 没有 upstream,每名 Viewer 同时只有零或一个 active upstream。加入、容量变化、节点离开、当前 edge `failed/closed`,以及非暂停时的 decoded-frame stall 都只唤醒同一个 `reconcileRoom(roomId)`;未受影响分支保持 sticky。 -`reconcileRoom` 在没有 pending operation 时先删除 disconnected childless participant,再找一个 waiting Viewer,或 parent offline/effective-capacity 不足、exact edge 已失效的 child。Candidate planning 拒绝 stale session、不可 source-reachable、成环、无 sender/overlap slot或 server resource 未准入的 tuple;对剩余 upstream 依次比较结果深度、剩余 sender slots、稳定加入顺序与 peer identity,并建立 direct/STUN、selected TURN、SFU 的有界顺序。每次只 reserve/prepare cursor 指向的 candidate。WebRTC 负责 ICE/DTLS/consent;exact candidate child 解码出第一帧新画面后发送一次 ready,controller 才 commit。失败先幂等清理并推进 cursor,operation 总 deadline 或耗尽进入 waiting/failure。没有上述离散事件时不改变拓扑。无环和房间 admission 已约束最大深度,不增加独立 depth hard cap。 +`reconcileRoom` 在没有 pending operation 时先删除 disconnected childless participant,再找一个 waiting Viewer,或 parent offline/effective-capacity 不足、exact edge 已失效的 child。Candidate planning 拒绝 stale session、不可 source-reachable、成环、无 sender/overlap slot或 server resource 未准入的 tuple;由 failed edge 启动时还把当前 `parent + transport` tuple 预先记为已尝试。对剩余 upstream 依次比较结果深度、剩余 sender slots、稳定加入顺序与 peer identity,先建立其他 direct/STUN,再建立 selected TURN 与 SFU 的有界顺序;同一 parent 的不同 transport 仍是不同 tuple。每次只 reserve/prepare cursor 指向的 candidate。WebRTC 负责 ICE/DTLS/consent;exact candidate child 解码出第一帧新画面后发送一次 ready,controller 才 commit。失败先幂等清理并推进 cursor,operation 总 deadline 或耗尽进入 waiting/failure。新的外部事实才开启新 operation 并重新计算候选。无环和房间 admission 已约束最大深度,不增加独立 depth hard cap。 所有非服务器端点共享一个 server-authoritative steady outbound media-copy cap `C`:唯一配置 `ENDPOINT_MEDIA_COPY_CAPACITY` 默认 `2`,只接受 `1`、`2` 或 `3`,不兼容旧键。服务端将 endpoint 当前广告的 `0..C` 可用值 clamp 为唯一 `effectiveDownstreamCapacity`;该值只表示当前可用 sender slots,不建立角色、浏览器、UA、设备或页面可见性 tier。peer child 或 Host publication 各占一份,upstream receive 免费,committed selected TURN 只替换同一份 edge transport;SFU subscriber egress 与 TURN allocation 使用独立 server admission。服务端通过 v6 `authenticated.endpointMediaCopyCapacity` 把部署 `C` 交给 Web 与 Native;Native 不从 `maxViewers` 推导发送预算。代码通过一个 shared accounting guard 执行,不重复 magic number;client/server/native wire 随该能力原子升级。Selected TURN 使用显式无默认值的正 safe-integer deployment capacity;同一个进程内 ledger 对每条 exact peer-selected edge 和 Host-SFU ingress 在 credential 签发前 reserve `1`,并让 reserved、committed、draining authorization 持续计费至 exact logical release。`C=3` 满载时只阻止会产生第四份 endpoint media copy 的 token/grant;同一 edge 改用 TURN 不增加 endpoint copy,但仍需独立 TURN admission。 diff --git a/docs/需求理解.md b/docs/需求理解.md index 222b156..cbc858b 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。所有恢复和候选授权必须绑定 authenticated room/endpoint session、share generation、base/pending route revision 与 candidate 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 的标准两层 screen-share simulcast,让内建 per-subscriber BWE 独立降层和恢复,Screener 不实现媒体层 selector。应用只诊断实际接收层、硬件编码、游戏性能、资源和上传;SFU 自动降层不改变拓扑。`LOW` 停编只是资源优化,active representation/layer 硬上限为二且不随 viewer 增长。当前 Web P2P simulcast 与 Web/LiveKit SVC 不能证明跨 direct/peer 共享编码或可移植硬件路径;全部标准路径失败后才考虑 custom/native dual encode。 -- 路由失效输入:应用不推断 parent-wide 质量,也不计算综合分数。当前 authenticated child 只在 PeerConnection `failed/closed`,或非权威暂停时一个统一 deadline 内没有新解码帧时,使自己的 exact edge 失效并唤醒同一个 reconcile。低码率、低 FPS、模糊、sender/viewer stats 与 SFU layer 只用于 stock WebRTC/LiveKit adaptation 和诊断。relay 自身 ingress 失效时,它作为 child 换父并保留 subtree;disconnect 或 effective downstream capacity 降低只让 direct/overflow children 逐个走同一事务。 +- 路由失效输入:应用不推断 parent-wide 质量,也不计算综合分数。当前 authenticated child 只在 PeerConnection `failed/closed`,或非权威暂停时一个统一 deadline 内没有新解码帧时,使自己的 exact edge 失效并唤醒同一个 reconcile。该 operation 把失败的 `parent + transport` tuple 直接视为已尝试,先选择其他 direct parent;同一 parent 只有换用另一 transport 时才可在本次继续,新的外部事实才允许原 tuple 重新进入。低码率、低 FPS、模糊、sender/viewer stats 与 SFU layer 只用于 stock WebRTC/LiveKit adaptation 和诊断。relay 自身 ingress 失效时,它作为 child 换父并保留 subtree;disconnect 或 effective downstream capacity 降低只让 direct/overflow children 逐个走同一事务。 - 断线恢复:临时网络变化先交给 WebRTC/LiveKit 的 ICE、consent、重连和流状态。应用只在上述 exact edge 失效后运行一次有界 reconcile;任一步成功即停止,失败进入明确等待或有界失败。所有异步结果受 authenticated session、share generation、base/pending route revision 与 candidate connection identity 约束,未受影响分支保持 sticky。权威暂停 abort 未提交 candidate、保留 active graph、抑制 stall,并在 resume 后重新 reconcile。 - 可观测性:按目标 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 音源或实际立体声。隐藏诊断不能隐藏失败原因、音频缺失、持续画质受限或需要用户操作的告警。