From e63d0b9a4fb586a821761113fe0e7ebdc8f2f962 Mon Sep 17 00:00:00 2001 From: AAA <35992542+TNTcraftHIM@users.noreply.github.com> Date: Mon, 24 Aug 2026 05:57:13 +0800 Subject: [PATCH] docs(media): close resume authority gaps --- docs/project-memory.md | 2 +- docs/todo.md | 2 +- docs/方案设计.md | 2 +- docs/需求理解.md | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/project-memory.md b/docs/project-memory.md index 00585c7..2edd9b0 100644 --- a/docs/project-memory.md +++ b/docs/project-memory.md @@ -18,7 +18,7 @@ Last updated: 2026-08-24 - Ordinary peer ICE is STUN-only, and current LiveKit media is UDP-only. Credentials, room secrets, candidate details, and diagnostic data remain private and narrowly scoped. A future strict-firewall transport requires separate evidence and must remain inside LiveKit rather than becoming another Screener route candidate. - Browser peers use existing periodic WebRTC/LiveKit stats to maintain one exact-route decoded-progress timestamp. New frames or route/connection changes reset it; a non-paused deadline reports the edge once. Track availability, bitrate, FPS, resolution, freeze, RTT, jitter, loss, sender stats, and SFU layer describe only the current path: they remain framework or diagnostic inputs and cannot prove another parent would be better. - Browser capture already requests available share audio by default on initial capture and source switch, with an explicit video-only warning when no audio track is returned. The accepted v9 screen-audio control 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. -- Video codec selection remains `automatic | H.264 | VP8`. The accepted first switching boundary is a generation-fenced renegotiation/republish transaction while sharing is explicitly paused. Preparation never resumes a user-paused share: it stops its deadline but retains the single room-serial codec owner as one bounded prepared record with no timer. The user's normal Resume atomically revalidates those exact bindings inside that owner, allocates the next server-monotonic proof attempt, starts the proof deadline, and only then enables the source. Proof request and response echo that exact attempt, and Pause invalidates it before another Resume can allocate the next attempt. Proof failure first restores authoritative pause and immediately runs bounded rollback preparation; the resulting prepared rollback has no timer and waits for the user's next normal Resume before revalidation, proof, and source enable. Unpaused hot switching is later reuse of that transaction, not an independent mechanism. +- Video codec selection remains `automatic | H.264 | VP8`. The accepted first switching boundary is a generation-fenced renegotiation/republish transaction while sharing is explicitly paused. Preparation never resumes a user-paused share: it stops its deadline but retains the single room-serial codec owner as one bounded prepared record with no timer. The user's normal Resume atomically revalidates those exact bindings, allocates the next server-monotonic resume attempt, and starts one bounded resume/proof deadline. The server keeps authoritative pause until the Host enables its source and returns the exact attempt acknowledgement; only then does it broadcast Resume and issue proof requests. Authorization, source acknowledgement, proof request, and proof response echo that attempt, and Pause invalidates it before another Resume can allocate the next one. Proof failure first restores authoritative pause and immediately runs bounded rollback preparation; the resulting prepared rollback has no timer and waits for the user's next normal Resume before revalidation, a fresh resume attempt, proof, and source enable. Unpaused hot switching is later reuse of that transaction, not an independent mechanism. - Recommended quality remains exactly `1080p60`, `1080p30`, and `720p30`, with the middle `1080p30` as the accepted default. `480p` means only an advanced `854x480` resolution choice whose FPS and bitrate remain independently selected; it is not a fourth recommended profile. - Two distribution artifacts are accepted only after the current feature, NAT, and real-network ledger is complete. A public-server one-click deployment package installs the current application plus STUN/SFU and proxy components on a user-owned server. A Windows/macOS/Linux local package runs the Host, application server, and local state on the broadcaster's machine without source, Node, or any external Screener/network service; it keeps workable direct P2P but reports the reachability loss imposed by public ingress, TLS, gateway, NAT, or firewall limits rather than promising universal connectivity. - Current UI, presence, route labels, and diagnostics describe observed state only; they do not create route authority. diff --git a/docs/todo.md b/docs/todo.md index 6de2ee1..1c5cede 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -7,7 +7,7 @@ Only items in **Now** are executable after their stated decision gate. A branch ## Now 1. **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. -2. **Switch video codec while paused.** Serialize the change with route mutation, freeze exact current route/media bindings, hold new joins, and boundedly renegotiate P2P/relay plus republish SFU while paused. Preparation must not resume a share the user explicitly paused: after it settles, stop the deadline but retain the single room-serial codec owner as one bounded prepared record with no timer. The user's normal Resume first atomically revalidates those bindings in that owner, allocates the next server-monotonic proof attempt, starts the decoded-frame proof deadline, and only then enables the source. Proof request and response carry that exact attempt; Pause invalidates it, so a delayed proof from an earlier Resume cannot settle a later proof window. A binding invalidated by leave or hard route failure exits this proof set and waits for routing to rebuild after the transaction settles. Preparation failure remains paused; proof failure first restores authoritative pause and performs bounded rollback preparation, whose proof waits for the user's next Resume. Rollback proves the prior preference was reapplied plus a newly negotiated actual codec and decoded progress, not literal equality with the old actual codec. No path auto-resumes. 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. +2. **Switch video codec while paused.** Serialize the change with route mutation, freeze exact current route/media bindings, hold new joins, and boundedly renegotiate P2P/relay plus republish SFU while paused. Preparation must not resume a share the user explicitly paused: after it settles, stop the deadline but retain the single room-serial codec owner as one bounded prepared record with no timer. The user's normal Resume first atomically revalidates those bindings, allocates the next server-monotonic resume attempt, and starts one bounded resume/proof deadline. The server keeps the share authoritatively paused until the Host enables its source and returns the exact source-enabled acknowledgement; only then does it announce Resume and issue proof requests. Authorization, acknowledgement, proof request, and proof response carry that exact attempt; Pause invalidates it, so a delayed acknowledgement or proof from an earlier Resume cannot settle a later window. A binding invalidated by leave or hard route failure exits this proof set and waits for routing to rebuild after the transaction settles. Preparation failure remains paused; proof failure first restores authoritative pause and performs bounded rollback preparation, whose proof waits for the user's next Resume. Rollback proves the prior preference was reapplied plus a newly negotiated actual codec and decoded progress, not literal equality with the old actual codec. No path auto-resumes. 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. 3. **Diagnose the dated H.264/blur report.** Reproduce the 2026-08-19 then-production 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. 4. **Diagnose desktop Host background/minimized performance.** On the same machine, dynamic scene, direct Viewer, browser, and fresh share, compare foreground, occluded, minimized, and hidden states while separately verifying actual negotiated VP8/H.264. Correlate capture settings/FPS, frames captured/encoded/sent/decoded, bitrate, `qualityLimitationReason`, encode time, browser/game CPU and GPU, and the existing local-preview pause boundary. Treat browser scheduling/throttling and codec effects as hypotheses until measured. Do not spoof page activity, add keepalive timers, or misuse Wake Lock; if current Chrome/Edge cannot preserve the accepted Host behavior through standard Web APIs, record that capability boundary before considering the packaged/native Host. 5. **Remove the unused parent-edge quality-proof surface.** Delete the client reporter, protocol message, server dispatch/ack flag, benchmark sender, and tests whose only purpose is the unconsumed `parent-edge-quality-evidence` message. Keep the existing exact Viewer evidence generation/session gate used by diagnostics. Do not replace the deleted magic packet/loss thresholds with another score, probe, blacklist, or route input. diff --git a/docs/方案设计.md b/docs/方案设计.md index c2cc8cb..002fabe 100644 --- a/docs/方案设计.md +++ b/docs/方案设计.md @@ -160,7 +160,7 @@ Zod 在服务边界拒绝未知类型、超长字符串、非法 SDP/candidate 每次 `setParameters()` 都必须从当次 `getParameters()` 派生更新,然后读回 requested/applied `maxBitrate`、`maxFramerate`、`scaleResolutionDownBy` 和 degradation preference。浏览器负责具体降级,实际结果由 stats 观测;拒绝或浏览器改写必须进入可见状态,不能只写 `console.warn`。 -Web 视频端不做视频 SDP munging。`automatic` 以空 `setCodecPreferences([])` 恢复 P2P 浏览器默认且不覆盖 LiveKit 默认 codec;显式 H.264/VP8 在首个 offer/publication 前应用,P2P 与 browser relay 仍保留完整 browser codec/repair 回退列表,SFU 始终保持 `backupCodec: false`。暂停时的 codec 切换使用单调 codec-negotiation generation,并和 route mutation 串行:冻结 exact current route/media binding target,新 join 按 pause 语义等待、leave 从 target 移除;P2P/relay 在暂停中以独立总 deadline 显式重新 offer/answer,SFU 以新 publication generation republish。Preparation 成功后只停止 preparation deadline;同一个 room-serial codec mutation owner 保留一个内存有界的 prepared generation,暂停期间无 timer并排斥 route mutation,绝不擅自恢复用户主动暂停的分享。Host 的正常 Resume 动作先在该 owner 内原子重验 frozen bindings,由服务端为本次证明窗口分配该事务内严格递增的 `proofAttempt`,启动 proof deadline 后才启用 source。每名 still-current target 从实际 inbound RTP stats 与 decoded-frame counter 生成专用 codec-transition proof;proof 精确绑定 room/share、Viewer session、assignment/connection/media-binding/publication generation、codec-negotiation generation 与当前 `proofAttempt`,request 和 response 必须回显同一 attempt。Pause 清空当前 proof batch并使该 attempt 失效;下一次 Resume 分配新 attempt,因此任何较早窗口延迟到达的 proof 都不能提交当前窗口。不复用 Viewer quality evidence,也不改变 route authority。leave、hard failure 或 binding generation 变化会移除该 target并把仍在线 Viewer置为 `route-wait`;事务以剩余 target settle 后释放 owner,controller 再使用最终 committed preference 恢复该 Viewer。服务端只在全部 still-current target 的 actual codec 与 decoded progress 成立后提交 desired codec。Preparation failure 保持暂停并运行有界 old-preference rollback preparation;forward proof failure 先重新进入权威 paused,再运行 rollback preparation。Rollback prepared state 保留同一 owner且无 timer,等待用户下一次正常 Resume 后重验、分配新 proof attempt、启动 rollback proof deadline并启用 source;proof 证明 P2P 空/有序 preference 或 SFU fresh publish options 已恢复,并从新协商代次取得 actual inbound codec 与 decoded progress,不要求 actual codec 与旧观测字面相同。回滚失败保持暂停和明确错误,任何路径都不自动 Resume。stop/share replacement/room delete 清除 prepared state;零 target 只更新 future-edge desired codec。该 slice 是 paused bounded-gap mutation,不声称 make-before-break;`replaceTrack()` 或暂停本身不改变 codec,未暂停热切换后续复用同一事务。实际 outbound codec/profile、encoder implementation 与 stats 是结果真相;浏览器支持 H.264 不证明命中硬编。AV1 仍不得仅凭压缩率推断默认启用。唯一获准的 Web SDP 变换是下述 Viewer answer 的 Opus stereo receive preference,不得借此扩展视频码率、codec 或通用 munging。 +Web 视频端不做视频 SDP munging。`automatic` 以空 `setCodecPreferences([])` 恢复 P2P 浏览器默认且不覆盖 LiveKit 默认 codec;显式 H.264/VP8 在首个 offer/publication 前应用,P2P 与 browser relay 仍保留完整 browser codec/repair 回退列表,SFU 始终保持 `backupCodec: false`。暂停时的 codec 切换使用单调 codec-negotiation generation,并和 route mutation 串行:冻结 exact current route/media binding target,新 join 按 pause 语义等待、leave 从 target 移除;P2P/relay 在暂停中以独立总 deadline 显式重新 offer/answer,SFU 以新 publication generation republish。SFU bounded-gap 先以 active revision 权威退休旧 generation,并等待该 managed room delete-plus-absence 完成,之后才创建 room、签发 token 或连接新 generation;全局 SFU 尚有余量也不能形成未获准的双 publication overlap。Preparation 成功后只停止 preparation deadline;同一个 room-serial codec mutation owner 保留一个内存有界的 prepared generation,暂停期间无 timer并排斥 route mutation,绝不擅自恢复用户主动暂停的分享。Host 的正常 Resume 动作先在该 owner 内原子重验 frozen bindings,由服务端为本次窗口分配进程内严格递增的 `resumeAttempt` 并启动同一个有界 resume/proof deadline;服务端向 Host 发恢复授权,但继续保持 authoritative pause。Host 成功启用 source 后回发精确绑定 room/share、Host session、codec generation(可空)与 `resumeAttempt` 的 source-enabled acknowledgement;只有服务端接受该 ack 后才清除 authoritative pause、先向 Viewer 广播 Resume,再下发 codec proof request。每名 still-current target 从 ack 后的实际 inbound RTP stats 与 decoded-frame counter 生成专用 codec-transition proof;proof 精确绑定 room/share、Viewer session、assignment/connection/media-binding/publication generation、codec-negotiation generation 与当前 `resumeAttempt`,request 和 response 必须回显同一 attempt。Pause 清空当前 proof batch并使该 attempt 失效;下一次 Resume 分配新 attempt,因此任何较早窗口延迟到达的 ack 或 proof 都不能提交当前窗口。不复用 Viewer quality evidence,也不改变 route authority。leave、hard failure 或 binding generation 变化会立即使 target 退出 proof 并把仍在线 Viewer置为 `route-wait`;在 codec owner settle 前,其旧物理 edge 与资源仍按实际占用保守计数,commit/abort 用同一个下一 active revision 统一 teardown 和释放,不能以未广播的逻辑失效低估 endpoint 或 SFU 用量。事务以剩余 target settle 后释放 owner,controller 再使用最终 committed preference 恢复该 Viewer。服务端只在全部 still-current target 的 actual codec 与 decoded progress 成立后提交 desired codec。Preparation failure 保持暂停并运行有界 old-preference rollback preparation;forward proof failure 先重新进入权威 paused,再运行 rollback preparation。Rollback prepared state 保留同一 owner且无 timer,等待用户下一次正常 Resume 后重验、分配新 resume attempt、完成 source-enabled ack、启动 rollback proof并启用 source;proof 证明 P2P 空/有序 preference 或 SFU fresh publish options 已恢复,并从新协商代次取得 actual inbound codec 与 decoded progress,不要求 actual codec 与旧观测字面相同。回滚失败保持暂停和明确错误,任何路径都不自动 Resume。stop/share replacement/room delete 清除 prepared state;零 target 只更新 future-edge desired codec。普通无 codec 的 Resume 也使用同一个 bounded `resumeAttempt` authorization/ack boundary,ack 前不向 Viewer宣告恢复,attempt timeout、Pause、stop、share replacement 或 Host session replacement 都保持 authoritative pause并允许用户重试。该 slice 是 paused bounded-gap mutation,不声称 make-before-break;`replaceTrack()` 或暂停本身不改变 codec,未暂停热切换后续复用同一事务。实际 outbound codec/profile、encoder implementation 与 stats 是结果真相;浏览器支持 H.264 不证明命中硬编。AV1 仍不得仅凭压缩率推断默认启用。唯一获准的 Web SDP 变换是下述 Viewer answer 的 Opus stereo receive preference,不得借此扩展视频码率、codec 或通用 munging。 Web display-audio 请求显式关闭 `echoCancellation`、`noiseSuppression`、`autoGainControl` 和 `voiceIsolation`,并以 `channelCount: { ideal: 2 }` 偏好双声道;这是当前 Chrome/Edge Host 基线的实现契约,用于避开网页 `getDisplayMedia()` 默认的语音处理和单声道 APM 候选,不宣称跨浏览器保证,也不固定采样率。所有值都是非强制 post-selection preference;不使用 `exact`、`min` 或 `advanced`。若 `getAudioTracks()` 为空,分享者界面必须显示“当前来源没有可共享音频”。Web 端同时传入 `windowAudio: "window"` 与 `systemAudio: "include"`:窗口选项请求该窗口/应用音频,整个屏幕选项允许用户勾选系统音频;标签页使用浏览器自身的标签页音频选择。浏览器可以忽略这些约束或 picker hints,且没有对应的标准来源类型读回,因此 UI 不额外推断或标注来源。系统音频由用户显式选择,可能包含语音或通知;video-only 始终可用。 diff --git a/docs/需求理解.md b/docs/需求理解.md index 81ce4cc..8bdf775 100644 --- a/docs/需求理解.md +++ b/docs/需求理解.md @@ -153,7 +153,7 @@ SFU 的 publication、subscription、server-resource accounting、临时 overlap - Given 当前 Viewer 的 exact edge 进入 `failed/closed`,或在非权威暂停时超过统一 deadline 没有新 decoded frame,When 服务端确认 authenticated child、share generation、active revision 与 connection identity 仍匹配,Then 只把该 child 送入同一个 reserve/prepare/first-frame/commit-or-abort 事务;低码率、低 FPS、模糊和 SFU layer 只进入诊断。 - Given 当前 edge 仍在解码但分辨率、FPS、码率、freeze ratio、RTT、jitter、loss 或 limitation evidence 持续影响观感,When 评估是否自动换父,Then 当前产品只诊断而不迁移:现有路径证据不能证明未连接 parent 的反事实质量,WebRTC/LiveKit 继续拥有拥塞适应,应用不建立 alternative-parent probe、hysteresis/cooldown、加权分数或周期性重排。 - Given 同一 Viewer 同时是某人的 child 和其他人的 parent,When 自身 ingress 失效,Then 它作为 child 换父、在新 ingress 首帧提交前保留可用旧 edge,并保持 subtree。Given endpoint disconnect 或 `effectiveDownstreamCapacity` 降低,Then controller 只保留稳定顺序内可容纳的 children,并将 deterministic overflow children 逐个送入同一事务;childless 后删除 disconnected relay。 -- Given 当前分享权威 paused,When 新 Viewer 加入、edge 失效或已有 child operation 尚未提交,Then abort 整个 pending route operation 及其 current candidate/预留、保留 active graph、让新 Viewer waiting,并且不因没有 decoded frame 触发恢复。普通 resume 从当前 graph 重新 reconcile;若存在 prepared codec owner,则 Resume 必须先在该唯一 room-serial owner 内重验 frozen bindings、启动 proof deadline 并启用 source,codec settle 后才处理积压 route reconcile。 +- Given 当前分享权威 paused,When 新 Viewer 加入、edge 失效或已有 child operation 尚未提交,Then abort 整个 pending route operation 及其 current candidate/预留、保留 active graph、让新 Viewer waiting,并且不因没有 decoded frame 触发恢复。普通 Resume 和 prepared codec Resume 都先分配一个服务端单调 `resumeAttempt` 并保持 authoritative pause,直到当前 Host 启用 source 后回发 exact acknowledgement;attempt 超时、再次 Pause、stop、share replacement 或 Host session replacement 都使其失效并保持暂停。普通 ack 后从当前 graph 重新 reconcile;若存在 prepared codec owner,则 ack 后才开始该唯一 room-serial owner 的 frame proof,codec settle 后才处理积压 route reconcile。 - 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。 - Given Viewer 发送低档请求,When 请求未鉴权、过频、重复、过期或没有匹配的 sender/viewer 统计,Then 请求不改变表示或路径状态;有效请求也只能作为交叉验证输入。 @@ -168,7 +168,7 @@ SFU 的 publication、subscription、server-resource accounting、临时 overlap - Given Web Host 或浏览器 relay 建立或替换分享音轨,When 对实际音频 sender 应用发送参数,Then 从当次 `getParameters()` 派生所选 `encodings[0].maxBitrate = 64000 | 128000 | 256000`、调用 `setParameters()` 并立即读回,旧配置缺失时按 128000 处理;Given Web Viewer 为含唯一有效 audio/Opus payload 的 offer 生成 answer,Then 用结构化 SDP parser 只 upsert 该 payload 的 `stereo=1` 与 `maxaveragebitrate=256000`,并把同一个结果用于 `setLocalDescription()` 和 signaling。首次连接、ICE restart、同 parent rebuild 和 relay child 都适用;空 SDP、无/歧义 Opus、多个有效 audio、重复目标参数、畸形 fmtp 或 parse/write 异常必须按对象 identity 返回浏览器原 answer 并继续,不能发送部分修改或静默冒充 stereo/实际码率。只有实际 `setLocalDescription()` 失败才进入既有连接错误路径。`sprop-stereo` 与 `usedtx` 不新增,其他 fmtp/FEC/session/media 字段逐项保留。 - Given 通过 SFU 发布分享音轨,When 构建 LiveKit 2.22.0 publish options,Then 64 kbps 使用 `AudioPresets.musicStereo`、128 kbps 使用 `AudioPresets.musicHighQualityStereo`、256 kbps 使用同等结构的显式 sender ceiling,三档都设置 `forceStereo = true` 与 `dtx = false`,且不覆盖 pinned room default `red = true`;验收同时核对 SDK 选项、applied ceiling、当前 RTP codec/fmtp 与 stats 实际码率,不能从 preset 名称或 `opus/48000/2` 单独推断实际声道内容。 - Given 分享正在直播或暂停且 Host 选择新的 64/128/256 kbps 音频档位,When 当前 profile mutation 串行执行,Then 服务端以 last-wins 保存并广播最新 desired profile,每个当前 Host、relay 与 SFU audio sender 从 fresh `getParameters()` 派生 `setParameters()` 并立即 readback,过期 completion 不得覆盖新值;以后创建的 sender 使用最新 desired profile。单个端点应用失败时保留媒体与其旧 applied 值、显示可见告警并允许后续更新或重建重试,不得谎称全房已收敛。当前 applied readback 保持 endpoint-local;没有显式回执 wire 就不宣称跨端点原子提交。该操作不改变 Opus、route revision、media-binding generation 或 publication generation,也不创建第二个音频控制环。当前 screen-audio Host 基线是 Chrome/Edge;其他浏览器只有实际 sender readback 成功时才算支持。 -- Given 分享已显式暂停且 Host 选择 `自动 | H.264 | VP8` 的不同值,When 执行首个视频 codec 切换闭环,Then 为该请求分配单调 codec-negotiation generation,并与 room route mutation 串行;事务开始时冻结 exact current route/media binding target,离开的 Viewer 从 target 移除,新加入者按既有 pause 语义等待。暂停期间在独立有界 preparation deadline 内对 target 的 P2P/relay path 完成显式 offer/answer 重协商,并对 SFU 路径完成新的 publication generation/republish;选择 `automatic` 时以空 codec preference 恢复浏览器默认。Preparation 完成不得擅自恢复用户主动暂停的分享:停止 preparation deadline,但继续由同一个 room-serial codec mutation owner 持有一个 bounded prepared generation,暂停期间不运行 timer并排斥 route mutation。Host 的正常 Resume 必须先在该 owner 内原子重验全部 frozen binding,由服务端分配该事务内严格递增的 `proofAttempt`,启动 proof deadline,然后才启用 source。暂停帧不能充当媒体证明:用户恢复 source 后,每名 still-current target 发送一个只属于 codec transaction 的 proof,精确绑定 room/share、Viewer session、assignment/connection/media-binding/publication generation、codec-negotiation generation、当前 `proofAttempt`、实际 inbound codec 与 decoded-frame baseline/delta;request 和 response 必须回显同一 attempt,Pause 立即使当前 attempt 失效,因此旧 Resume 窗口的延迟 proof 不能提交新窗口。该 proof 不进入路由失效判断。若 leave、hard failure 或任何 exact binding 变化使 target 失效,服务端把它移出本事务并标为 `route-wait`,codec 事务以剩余 target settle 后释放 owner,controller 再用最终 committed preference 为仍在线 Viewer 恢复路径。只有服务端校验全部 still-current proof 后才提交 desired codec。Preparation failure 保持暂停并开启有界 old-preference rollback preparation;forward proof failure 则先重新进入服务端权威 paused,再执行同一 rollback preparation。Rollback prepared state继续占有同一 owner、无 timer,并等待用户下一次正常 Resume 后重验 binding、分配新 proof attempt、启动 rollback proof deadline、再启用 source;rollback 证明旧 preference 已重新应用(P2P 空/有序 preference 或 SFU fresh publish options)以及新协商代次的 actual inbound codec 与 decoded progress,不要求 actual codec 字面等于历史值。回滚失败保持暂停并显示明确失败;任何路径都不自动 Resume,不允许 stale generation 或 stale proof attempt 恢复播放。停止分享、替换 share generation 或删除房间清除 prepared state。零 current Viewer 或事务中全部离开时直接提交 future-edge desired codec,无需伪造 frame proof。`pause`、`replaceTrack()` 或仅调用 `setCodecPreferences()` 均不能冒充已完成切换;本 slice 是明确的 paused bounded-gap 事务,未暂停 MBB 热切换留作后续扩展。 +- Given 分享已显式暂停且 Host 选择 `自动 | H.264 | VP8` 的不同值,When 执行首个视频 codec 切换闭环,Then 为该请求分配单调 codec-negotiation generation,并与 room route mutation 串行;事务开始时冻结 exact current route/media binding target,离开的 Viewer 从 target 移除,新加入者按既有 pause 语义等待。暂停期间在独立有界 preparation deadline 内对 target 的 P2P/relay path 完成显式 offer/answer 重协商,并对 SFU 路径完成新的 publication generation/republish;选择 `automatic` 时以空 codec preference 恢复浏览器默认。SFU republish 必须先用 active revision 权威退休旧 generation并完成 managed room delete-plus-absence,之后才创建、签发或连接 fresh generation;不得因全局 ingress 尚有余量形成双 publication。Preparation 完成不得擅自恢复用户主动暂停的分享:停止 preparation deadline,但继续由同一个 room-serial codec mutation owner 持有一个 bounded prepared generation,暂停期间不运行 timer并排斥 route mutation。Host 的正常 Resume 必须先在该 owner 内原子重验全部 frozen binding,由服务端分配进程内严格递增的 `resumeAttempt`、启动同一个有界 resume/proof deadline并发送恢复授权,同时保持 authoritative pause。Host 成功启用 source 后回发精确绑定 room/share、Host session、codec generation 与 `resumeAttempt` 的 acknowledgement;服务端接受后才清除 authoritative pause、先向 Viewer 广播 Resume、再发送 codec proof request。暂停帧不能充当媒体证明:ack 后每名 still-current target 发送一个只属于 codec transaction 的 proof,精确绑定 room/share、Viewer session、assignment/connection/media-binding/publication generation、codec-negotiation generation、当前 `resumeAttempt`、实际 inbound codec 与 decoded-frame baseline/delta;request 和 response 必须回显同一 attempt,Pause 立即使当前 attempt 失效,因此旧 Resume 窗口的延迟 ack 或 proof 不能提交新窗口。该 proof 不进入路由失效判断。若 leave、hard failure 或任何 exact binding 变化使 target 失效,服务端立即把它移出本事务并标为 `route-wait`;codec owner settle 前仍按物理 edge 和资源的实际占用保守计数,并在 commit/abort 的下一 active revision统一 teardown和释放。codec 事务以剩余 target settle 后释放 owner,controller 再用最终 committed preference 为仍在线 Viewer 恢复路径。只有服务端校验全部 still-current proof 后才提交 desired codec。Preparation failure 保持暂停并开启有界 old-preference rollback preparation;forward proof failure 则先重新进入服务端权威 paused,再执行同一 rollback preparation。Rollback prepared state继续占有同一 owner、无 timer,并等待用户下一次正常 Resume 后重验 binding、分配新 resume attempt、完成 source-enabled ack并启动 rollback proof;rollback 证明旧 preference 已重新应用(P2P 空/有序 preference 或 SFU fresh publish options)以及新协商代次的 actual inbound codec 与 decoded progress,不要求 actual codec 字面等于历史值。回滚失败保持暂停并显示明确失败;任何路径都不自动 Resume,不允许 stale generation 或 stale resume attempt 恢复播放。停止分享、替换 share generation 或删除房间清除 prepared state。零 current Viewer 或事务中全部离开时直接提交 future-edge desired codec,无需伪造 frame proof。`pause`、`replaceTrack()` 或仅调用 `setCodecPreferences()` 均不能冒充已完成切换;本 slice 是明确的 paused bounded-gap 事务,未暂停 MBB 热切换留作后续扩展。 - Given 浏览器返回屏幕音轨,When 产品展示音频状态或未来连接详情,Then 只能把存在性、浏览器实际返回的 track settings、实际协商 codec/fmtp 和实际流量分别标为捕获、协商或观测事实;字段缺失必须显示为未知,且不得从 Opus 的 `48000/2` RTP 声明推断源采样率或实际 stereo。 - Given Web 或后续 native sender 分享电影/视频音频,When 进行产品验收,Then 必须用独立 A/B/C 证据关联 capture audio track/settings、实际 negotiated codec/fmtp、outbound/inbound 实际码率、loss、jitter、concealment、jitter-buffer 与 A/V sync;P2P、浏览器 relay 与 SFU route switch 保持同一持久 Viewer `MediaStream`/媒体元素和 stereo 协商,不新增独立 audio element 或 Web Audio 时钟边界。native sender 必须通过该音质和同步矩阵,且不得把 audio `maxBitrate` ceiling 当作提质证明。 - Given 分享者同时运行语音软件,When 选择仅分享游戏声音,Then supported Windows 11 native sender 只保留目标游戏及其子进程的渲染音频,观众、通知和其他应用声音不得进入分享;浏览器 system audio 无法满足时必须允许 video-only 或明确告知其可能包含语音/通知,不能用 AEC、降噪或来源标签伪称已隔离。