From 4e1797f2d03ceed581a31ee080776813e3ef6136 Mon Sep 17 00:00:00 2001 From: AAA <35992542+TNTcraftHIM@users.noreply.github.com> Date: Wed, 19 Aug 2026 21:09:07 +0800 Subject: [PATCH] docs(product): schedule room presence diagnostics --- docs/project-memory.md | 2 +- docs/research/realtime-quality-adaptation.md | 11 +++++++++++ docs/status.md | 4 ++-- docs/方案设计.md | 12 +++++++----- docs/需求理解.md | 4 +++- 5 files changed, 24 insertions(+), 9 deletions(-) diff --git a/docs/project-memory.md b/docs/project-memory.md index 781d526..cb2bdee 100644 --- a/docs/project-memory.md +++ b/docs/project-memory.md @@ -27,7 +27,7 @@ Last updated: 2026-08-19 - Flagship media defaults to UDP; HTTPS/WSS remains TLS/TCP. Current production still requires coturn UDP/TCP. Future TURN absence is normal, partial config fails, and any media-TCP/port compatibility mode is chosen only by canary. - Treat settings as ceilings and deployed degradation as unclassified. Use correlated Host A+B/Viewer C to compare exact `769de201f7cc` against current `main` for Host-only recovery, game FPS/load, preview cost, actual codec/encoder and one-variable rebuilds; then test standard simulcast/LiveKit/SVC before custom `LOW`. Never use UA or a composite score. - Production reports poor film audio and self-echo when system capture includes voice software. Diagnose audio A/B/C and sync; Web cannot isolate arbitrary processes and `maxBitrate` is not quality-up. A Windows 11 native candidate defaults to game-process-tree audio and never widens silently; Windows 10 remains unresolved/unsupported. See `docs/research/browser-screen-audio-quality.md`. -- Queue a Viewer-only `0-100%` volume slider after the current quality/SFU gates; it is page-local playback state, not a media, signaling, or persistence control. +- Post-gate UI: local volume/mute, display name, transient roster, local-only candidate endpoints, and RTP loss rate; no accounts, database, telemetry, media, or routing effects. - Do not add scene detection, dynamic-FPS control, or forced AV1 until stats and target hardware prove the need. Codec acceptance requires actual negotiation, power-efficient candidate evidence, interval encode cost, game FPS, CPU/GPU and sender count; Discord's native capture/hardware tuning is comparison evidence, not proof of server re-encoding or a reusable preset. - Keep room policy deployment-driven: public/password-only rooms are random and temporary; password plus SQLite enables sequential persistent rooms and reusable stopped links. - Deferred architecture audit: `docs/maintenance.md`. diff --git a/docs/research/realtime-quality-adaptation.md b/docs/research/realtime-quality-adaptation.md index b7bdd52..99fc727 100644 --- a/docs/research/realtime-quality-adaptation.md +++ b/docs/research/realtime-quality-adaptation.md @@ -142,6 +142,17 @@ dimensions/FPS, byte/loss/jitter counters, decoded/dropped frames, and freeze counters when implemented. Stats members may be absent, and cumulative values must be compared across two samples rather than treated as interval values. +The user-facing loss value should therefore be an RTP interval loss rate, not +the cumulative `packetsLost` counter and not a claim about UDP itself. For one +unchanged RTP stats object, compute `lostDelta / (receivedDelta + lostDelta)`; +an initial sample, reset, negative delta, zero denominator, or absent field is +unknown. `RTCReceivedRtpStreamStats` defines both counters and notes that +`packetsLost` can be negative. `RTCIceCandidateStats.address` is privacy-sensitive +and remote addresses are null by default unless the application supplied the +candidate. Screener may show a selected address locally in collapsed details, +but it must never enter Viewer C, signaling, logs, exports, persistence, route +selection, or NAT/geography inference. + Correlate one time interval and media generation across: | Evidence | Fields | diff --git a/docs/status.md b/docs/status.md index cdc2225..13aba9c 100644 --- a/docs/status.md +++ b/docs/status.md @@ -57,8 +57,8 @@ first fit before custom `LOW`, independently of ADR-0006. Diagnose audio A/B/C, sync, and voice-source leakage in parallel when it does not displace that P0; `maxBitrate` is not a quality fix. WebRTC/LiveKit owns congestion/layers; the app owns two-state policy. An exact room later gates peer/SFU UDP and coturn changes. -The Viewer-local volume slider is a later bounded UI batch and does not block -these media gates. +After media gates, UI adds local volume/mute, names/roster, local endpoint +details, and RTP loss rate. ADR-0004 still requires a full-resolution 30-minute `1/3/5/8` network, resource, quality, latency, recovery, and browser/mobile-leaf matrix. diff --git a/docs/方案设计.md b/docs/方案设计.md index 0b31f97..b9fe6a4 100644 --- a/docs/方案设计.md +++ b/docs/方案设计.md @@ -88,6 +88,8 @@ tests/ RoomStore、TURN、协议和 WebSocket 集成测试 客户端用 `sessionStorage` 保存随机的 128-bit `clientId`,作为当前标签页内稳定的参与者 key。同一 role/clientId 的新 WebSocket 会原子替换旧 socket;观看者断线后保留五秒 grace,期间不发送 `peer-left` 且 `peerId` 不变,重新鉴权时重发幂等的 `peer-joined`,超时才释放名额。host 鉴权或重连后,`authenticated` 携带包含 grace 成员的权威 viewer ID 列表供客户端删除已离开的 peer,服务端再重放当前在线观看者的 `peer-joined`,因此观看者可以先于 host 打开邀请,host 离线期间的离开也不会遗留幽灵连接。 +显示名是独立的本地偏好:一个同源 `localStorage` 值供 Host 与 Viewer 共用,未设置时使用角色默认标签。客户端鉴权时发送经同一严格 schema 校验的纯文本名称,服务端只在当前内存会话中保存;每次加入、离开或会话替换后向房间内所有参与者广播不超过一名 Host 加配置观看上限的完整权威 roster,而不增加增量事件状态机。房间标题取当前 Host 显示名;名称允许重复、只按文本渲染,并且不替代 role、host token、peer/session ID 或任何授权判断。SQLite schema 保持不变。 + ## 信令协议 WebSocket 使用同源 `/signal`。启用全站密码时,upgrade 必须先携带有效 access cookie;连接后五秒内第一条消息仍必须是房间级 `authenticate`。host 消息携带内部 host token,viewer 只携带房间码、role 与 clientId,不存在 viewer token,也不把凭据放 query string。服务端设置 64 KiB `maxPayload`、关闭 `perMessageDeflate`,并以 ping/pong 清理失效连接。单进程固定限制 2048 条总连接和 256 条未鉴权连接,容量在 WebSocket upgrade 前预留,超限直接返回 HTTP 503 并销毁 socket;这只是 PoC 的资源边界,不是容量承诺。 @@ -203,7 +205,7 @@ SFU 是主要中央兜底,但正常只作为 virtual parent 向一至两个必 ## 可观测性 -每两秒在本地采集一次 `getStats()`。首版不上传原始统计,也不展示候选地址。 +每两秒在本地采集一次 `getStats()`。首版不上传原始统计。折叠详情可在产生该连接的本机显示 selected local/remote candidate 的 `address`/`port`,但浏览器未暴露时保持未知;地址不得进入信令、Viewer C、日志、诊断导出、持久化或路由/质量决策。在 peer 边中它只是当前连接端点,在 SFU 边中通常是 SFU 端点,不能标成参与者的权威公网 IP。 质量诊断必须能按顺序区分:捕获源设置、sender 实际参数、CPU 或带宽 `qualityLimitationReason`、selected candidate/TURN 路径、outbound 与 inbound 差异,以及接收端 dropped/freeze。当前有界实现显示 sender 参数的 requested/applied 读回;同一个非 `none` 原生 `qualityLimitationReason` 连续出现三个采样周期后显示单条原因提示,原因变化、缺失或恢复为 `none` 即重新计数。这只是去抖后的观察,不组合评分、不触发调档或迁移。完整跨路径分类仍由后续真实矩阵验收;首轮使用浏览器 WebRTC internals 导出和一份脱敏 run manifest,不新增服务端遥测。 @@ -214,22 +216,22 @@ SFU 是主要中央兜底,但正常只作为 virtual parent 向一至两个必 - 本地 relay candidate 的 `relayProtocol`;规范禁止远端 candidate 暴露该字段,因此不推断远端使用 TURN/UDP、TCP 还是 TLS; - 后续 exact-room SFU/UDP 与可选兼容 transport 只按[低服务器成本媒体路由](./research/low-server-media-routes.md)的 generation-baselined、privacy-safe ICE manifest 记录;先以本地脱敏 `webrtc-internals` 建立 ground truth,不新增后台遥测; - `currentRoundTripTime`、发送码率和 `availableOutgoingBitrate`; -- 实际帧率、分辨率和丢包; +- 实际帧率、分辨率和相邻有效采样窗的 RTP 丢包率; - codec、编码实现、最近采样区间内的每帧编解码耗时和 `qualityLimitationReason`;同一连接不并发采样,首个样本、无新帧、统计对象换代或计数器重置时显示未知并重新建立基线,不用连接生命周期均值掩盖后续过载。 -观看者显示接收码率、帧率、分辨率、丢包、jitter、解码/丢帧和当前路径。字段不可用时显示“未知”。RTT 只标为网络往返时间,不能冒充玻璃到玻璃延迟。 +观看者显示接收码率、帧率、分辨率、RTP 丢包率、jitter、解码/丢帧和当前路径。丢包率只在同一 RTP generation 的相邻样本中以 `lostDelta / (receivedDelta + lostDelta)` 计算;首样本、对象/计数器重置、负 delta、零分母或字段缺失时显示“未知”,不把累计 `packetsLost` 伪装成百分比。RTT 只标为网络往返时间,不能冒充玻璃到玻璃延迟。 ## 界面结构 首屏就是工具,不做营销页。视觉采用中性深灰工作台,绿色表示 direct、琥珀色表示 relay/警告、红色表示失败,避免单一蓝紫色主题。 - 全站 gate:仅在配置 `ACCESS_PASSWORD` 且 cookie 无效时显示一个密码输入窗口;验证后返回原目标路径。 -- 分享者根页面:顶部房间状态;主区域保持稳定的 16:9 舞台。未发布、已停止或可恢复错误状态时,舞台内以同等尺寸和权重显示“开始分享”和“加入房间”,中间只用“或”与分隔线表达发送/接收;前者直接开始现有分享流程,后者直接进入 `/join`,不增加首页、向导或拓扑选择。直播时舞台显示本地预览,底部保留停止、暂停、质量档位和复制邀请。 +- 分享者根页面:顶部显示“{分享者显示名} 的房间”和房间状态;主区域保持稳定的 16:9 舞台。未发布、已停止或可恢复错误状态时,舞台内以同等尺寸和权重显示“开始分享”和“加入房间”,中间只用“或”与分隔线表达发送/接收;前者直接开始现有分享流程,后者直接进入 `/join`,不增加首页、向导或拓扑选择。直播时舞台显示本地预览,底部保留停止、暂停、质量档位和复制邀请。 - `/join`:只输入正整数形式的数字房间码,成功后进入 `/r/{code}`。 - `/r/{code}` 观看者:主区域最大化远端视频;顶部显示房间与连接状态;提供本地音量、静音、全屏和重试控制,不要求额外邀请码或 viewer token。 - 移动端:视频保持稳定宽高比,控制与状态移到视频下方,任何文字和按钮不得溢出或遮挡。 -两端默认只显示房间、媒体、核心连接状态和常用操作。一个不持久化的“显示连接详情”复选框按页展开信令、ICE/TURN、codec、RTT、丢包、编解码耗时及逐边统计;真实连接失败、音频缺失、持续画质受限和需要用户动作的告警始终可见,不能被该开关隐藏。分享者默认观看人数必须来自房间 roster,而不是只统计当前由 host 直连的媒体 child。 +两端默认显示紧凑的在线 roster;Host 与 Viewer 均可修改本机显示名,更新只影响之后由服务端广播的纯文本 roster。一个不持久化的“显示连接详情”复选框按页展开信令、ICE/TURN、浏览器实际暴露的本地/远端候选地址、codec、RTT、RTP 丢包率、编解码耗时及逐边统计;真实连接失败、音频缺失、持续画质受限和需要用户动作的告警始终可见,不能被该开关隐藏。分享者默认观看人数必须来自房间 roster,而不是只统计当前由 host 直连的媒体 child。 用户可见的非直播状态只使用“已停止分享”(分享端已结束当前发布)、“等待开始分享”(观看端等待发布)、“正在连接”与“正在恢复连接”。`room-closed` 显示“房间已关闭”或“房间已过期”,不得误写成“分享已结束”。房间寿命、媒体代次、换父和恢复等内部状态不得作为额外产品文案暴露。 diff --git a/docs/需求理解.md b/docs/需求理解.md index 092a160..910e940 100644 --- a/docs/需求理解.md +++ b/docs/需求理解.md @@ -18,6 +18,7 @@ - [ ] 屏幕采集:分享者可主动选择显示器或应用窗口,开始、停止、临时暂停画面和切换分享来源。Host 本地预览必须直接使用 capture stream,不得为预览建立 viewer/媒体边或消耗 TURN、SFU、应用服务器 media egress;预览仍可能产生本地合成、缩放和 GPU 显示成本,不能把隐藏预览称为停止采集或编码。 - [ ] 游戏声音:Windows Chrome/Edge 目标环境可请求系统音频;没有返回音轨时必须明确提示,而不是静默无声。 - [ ] 观看端本地音量:播放控制栏提供 `0%` 至 `100%` 音量条和静音按钮,只改变当前浏览器的媒体元素,不修改 Host 音轨、编码、房间状态或其他观看者。切流、换父和 ICE 恢复保留当前页面内的值;刷新后不持久化。静音保留上次非零音量,拖动到非零值自动取消静音。 +- [ ] 本地显示名与房间成员:用户可设置一个保存在当前浏览器 `localStorage` 的显示名;信令只在当前在线会话内保存和广播经严格长度/字符校验的纯文本标签,不写 SQLite。Host 页面显示“{分享者显示名} 的房间”,Host 与 Viewer 都看到包含分享者和当前观看者的权威 roster。显示名不是账号、授权身份或可信唯一标识,重复名称允许且不得参与路由或质量决策。 - [ ] 一对多观看:一名分享者可向配置允许的多名观看者发送画面;默认上限为 8,可配置范围为 1 至 16。当前 PoC 的逐观看者连接是测量基线,不再是超过两名观看者时的目标拓扑。 - [ ] 端点 fanout 硬上限:任一非服务器端点任何时刻最多维护两条 active downstream media edges,包括加入、断线、换父和恢复过程;上游接收不计入这项上传预算。当前 browser relay 因逐跳重编码仍保守限制为一个 child,只有资源矩阵通过后才可升到二。第三名及后续观看者由系统自动分配给通过验收的 peer-assisted 拓扑,不能隐藏地回退为 Host 第三条连接。没有可行 peer path 时,SFU 作为主要中央兜底 virtual parent 正常向一至两个必要 roots 送流;媒体到达 root 后其余可行观看者仍优先沿受限 peer descendants 扩散。多个无法挂到健康 root 的极端 viewer 可成为额外 server-fed 例外,但必须受显式中央 egress/admission 上限约束,不能无界整房 fanout。TURN 只作为可选的极端网络 transport 兼容层,不定义房间拓扑。 - [ ] P2P 优先:每条逻辑媒体边的 ICE 必须先尝试直接 UDP 路径,服务器不得默认中转可直连的媒体。一个或两个观看者优先直接连接分享端;更多观看者的 peer-assisted 行为须先通过 ADR-0004 的实验门槛。 @@ -26,7 +27,7 @@ - [ ] 按需双表示:健康房间只运行一套 active `HIGH` representation;第一条观看路径被同一时段的 sender 与 viewer 统计持续证明无法承受时,系统必须启动唯一一套由全部弱路径共用的 `LOW` 并迁移弱路径。健康路径继续使用 `HIGH`,全部弱路径稳定恢复后关闭 `LOW`。active representation 硬上限为二,禁止按观看者增长为 N 套,也禁止把最差路径的目标应用到整个房间。实现必须依次验证标准 WebRTC simulcast、LiveKit layer selection/Dynacast 和 SVC;任一方案满足按需停启、独立选层与资源门槛即停止,不继续自研媒体层。标准浏览器不保证跨 PeerConnection 共用一个物理编码器,inactive 层也不保证释放 GPU;物理 encoder instance、CPU/GPU 和释放行为必须实测。没有合格 hardware/power-efficient 路径或启动 `LOW` 造成不可接受负载时,当次必须 fail closed:保护 `HIGH` 并明确显示弱路径降级或不可用;这只是异常止损,supported sender cohort 若不能可靠按需启动 `LOW`,则自动质量控制验收失败,必须优化编码路径或明确标记不支持。 - [ ] 路径级质量状态:每条观看路径只使用 `HIGH` 与 `FALLBACK` 两态。进入 `FALLBACK` 需要多个连续窗口的带宽不足、冻结或解码跟不上证据,回到 `HIGH` 需要更长的稳定窗口;进入与退出使用不同门槛且不计算综合分数。Viewer 的低档请求必须经当前会话鉴权、限频和去重,并由 sender 的 GCC/Transport-CC、实际码率、RTT、丢包、重传、编码限制、派生后的实际协商视频 codec/profile/parameters(适用时含 `scalabilityMode`)与 viewer 的实际解码、丢帧、冻结证据交叉验证,不能仅凭自报生效;上报不得包含 raw SDP、candidate 地址或原始设备/网络标识,鉴权与关联只使用服务端签发的不透明路径/连接代次 ID。 - [ ] 断线恢复:临时网络变化时尝试 ICE restart 和重连,并向用户显示当前连接状态。 -- [ ] 可观测性:按目标 RTP transport 与不透明连接/ICE 代次关联 selected path、`iceGatheringState`/ICE/connection state、事件推导的建立时长、generation-baselined pair-change delta,以及 RTT、丢包、抖动、码率、帧率、编码耗时和受限原因。候选与错误仅使用[低服务器成本媒体路由](./research/low-server-media-routes.md)的脱敏字段和 code bucket,禁止 raw SDP、candidate、地址、URL、`errorText` 或网络标识。 +- [ ] 可观测性:按目标 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 推断,缺失或被隐私保护时显示未知。 - [ ] 渐进式诊断:分享者和观看者默认只看到房间、画面、核心状态和常用控制;候选路径、RTT、丢包、编解码器、编码耗时等技术详情由一个明确的“显示连接详情”开关展开。隐藏诊断不能隐藏失败原因、音频缺失、持续画质受限或需要用户操作的告警。 - [ ] 访问控制:部署者可配置一个全站 `ACCESS_PASSWORD`;为空时网站公开,非空值必须为 1 至 128 个可见 ASCII 字符。配置时分享者和观看者都必须先登录。登录成功签发 12 小时、无服务端会话状态的 HttpOnly、`SameSite=Strict` cookie,建房 HTTP 和 WebSocket 都只使用该 cookie,不提供建房 Bearer 旁路;不引入账号、JWT、服务端 session Map 或 logout。 - [ ] 持久受保护房间:`ROOM_DATABASE_PATH` 可选且必须与 `ACCESS_PASSWORD` 同时配置。两者都存在时,SQLite 只持久保存从 `1` 开始自增的房间 ID 与 host token 摘要;房间及链接不设过期时间,停止后分享端显示“已停止分享”,观看页显示“等待开始分享”。密码模式不配置数据库时仍使用临时房间;公开模式始终使用随机临时房间。 @@ -103,6 +104,7 @@ - Given 观看者只有受支持的手机浏览器,When 打开有效邀请链接并执行浏览器要求的播放手势,Then 无需安装应用即可进入房间、看到首帧并听到可用音频。 - Given 观看者调节音量或静音,When 媒体切流、换父或恢复连接,Then 当前页面的本地播放音量保持不变,其他观看者和发送端不受影响;刷新页面后恢复默认音量,不产生信令、持久化或媒体重协商。 +- Given 用户在本机设置显示名,When 以 Host 或 Viewer 加入房间,Then 所有在线参与者看到同一份服务端权威 roster 和对应房间标题;刷新后本机名称仍在,清除站点数据后恢复未命名默认值,数据库中不存在显示名或成员记录。 - Given 手机从 Wi-Fi 切换到蜂窝网络或页面从后台恢复,When 原候选对失效,Then 客户端执行 ICE restart 或重建连接,并显示恢复进度而不是永久黑屏。 ### 一对多与带宽保护