fix(web): explain locked codec selection [skip ci]
* fix(media): isolate forced H.264 regression [skip ci] * feat(media): add explicit Web codec selection [skip ci] * fix(media): raise peer Opus bitrate target [skip ci] * fix(web): explain locked codec selection [skip ci]
This commit is contained in:
1 parent
834579a7fa
commit
7dcb5af9c1
8 files changed
+52
-8
No files matched your search
@@ -43,14 +43,14 @@ Last updated: 2026-08-21
|
||||
## Current Implementation
|
||||
|
||||
- The repository is one npm package using Node.js 24, React, TypeScript, Vite, native WebRTC, `ws`, Zod, Vitest, and separate coturn.
|
||||
- Production is exact `6ccb516a47261054f91dfa2fafa408d39ced59fc`; source/dist artifacts are 619,674/358,621 bytes, with hashes in deployment; cutover 2,048 ms lock/1,272 ms stop-health/888 ms switch-health; rollbacks `1331fbdd`, `261e980`, `691863e`.
|
||||
- Production is exact `7dd38fc39ce8e91b913b1f45e053c00e0520f3b9`, release `7dd38fc39ce8`, wire `screener-v3`; rollback is `f7d667c4d32d`.
|
||||
- Web keeps one SFU stream, clears unavailable video, exposes evidence-backed routes/transports and paused preview, and drops signals to a grace-retained offline current-edge target. Routing is automatic; entry/nickname UI is live; Native remains source-only.
|
||||
- Web relay is cap2 with one upstream; child3 rejects and no UA/visibility split exists.
|
||||
- Four services are active/running at `NRestarts=0`; local/public health are 200. SQLite v3 has five rooms, SHA-256 `aef724ae52c4107be8ec8791e72f8dcaa11b0ec849fb432d022e2cfb9cfe0974`, owner/mode `screener:screener`/0600.
|
||||
- Production keeps ordinary ICE STUN-only, SFU roots <=2, selected-edge UDP TTL 120, and one pending/answered `peer-selected` attempt per room excluding Host ingress. This rollout ran no browser/media/SFU/TURN/performance canary; earlier exact-`16f6eab` evidence proves active Host ingress only.
|
||||
- Production requires independent `SITE_ACCESS_PASSWORD`: its stateless cookie authorizes creation/Host and code-only Viewer attempts; private grants stay direct, while public/private code entry needs site access alone/plus room password. Env/endpoint/cookie/nginx naming migrated atomically; failures remain neutral; there are no accounts/JWT/session rows. Local `gate:access-privacy` proves fragment isolation, site/grant transport-log absence, and raw room-password SQLite absence; room-password WebSocket/log ingress and live/headful evidence remain open.
|
||||
- Production uses nginx, Node.js 24.19.0, coturn 4.17.2, and LiveKit 1.13.5 on UDP 7882 (TCP fallback off). Ordinary peer ICE receives no TURN; the selected-edge tuple is configured only for the current controller edge, with no credential pre-advertised to ordinary peers. Coturn retains the old authenticated-relay ports. Services are healthy with zero restarts.
|
||||
- ADR-0005 rejects `PEER_ASSISTED_ROOM_IDS`; `PEER_ASSISTED_MEDIA=true` enables every room and ordinary peers stay STUN-only. Initial or active Host ingress may consume one bound relay-only grant; ready/abort clears it and failure restores peer baseline. Stale room-ID config fails startup; `screener-v2` is the only deployed wire.
|
||||
- ADR-0005 rejects `PEER_ASSISTED_ROOM_IDS`; `PEER_ASSISTED_MEDIA=true` enables every room and ordinary peers stay STUN-only. Initial or active Host ingress may consume one bound relay-only grant; ready/abort clears it and failure restores peer baseline. Stale room-ID config fails startup; `screener-v3` is the deployed wire.
|
||||
- Defaults are balanced with automatic codec; clarity/fluid and H.264/VP8 remain explicit. Codec is locked during a share. Web SFU uses LiveKit 2.22.0 `q,h`; SFU/UDP and selected-TURN work; BWE, Host A+B and production remain open.
|
||||
- Chrome 151 five-Viewer 720p30 cap2 evidence: Host/relay fanout two; both relay children +80 decoded frames/~1.1 MB; every Viewer decoded; clean stop. Functional only; resource and heterogeneous-network behavior remain open.
|
||||
|
||||
|
||||
@@ -209,6 +209,12 @@ does not prove shared encode: separate browser PeerConnections may construct
|
||||
separate encoders, and the SFU still publishes only the configured `q,h`
|
||||
representations.
|
||||
|
||||
The retained follow-up is one direct-Viewer comparison using existing stats,
|
||||
not a new benchmark: actual codec/profile, encoder implementation, power
|
||||
efficiency, source/send/receive FPS, encode time and limitation reason. It must
|
||||
distinguish software fallback from a hardware queue or driver limit before any
|
||||
H.264-specific policy changes.
|
||||
|
||||
Open-source distribution is not itself a patent-license exemption. This Web
|
||||
change only requests a codec already implemented by the browser/LiveKit path and
|
||||
ships no H.264 codec binary or new codec dependency, so licensing uncertainty is
|
||||
|
||||
+3
-4
@@ -4,8 +4,7 @@ Last updated: 2026-08-21
|
||||
|
||||
## Phase
|
||||
|
||||
Production at `https://share.bonfire.icu` is exact `6ccb516a47261054f91dfa2fafa408d39ced59fc`; immutable source/dist artifacts are 619,674/358,621 bytes, with hashes in deployment.
|
||||
Cutover: 2,048 ms lock/1,272 ms stop-health/888 ms switch-health. SQLite v3/five rooms has SHA-256 `aef724ae52c4107be8ec8791e72f8dcaa11b0ec849fb432d022e2cfb9cfe0974`, owner/mode `screener:screener`/0600. Four services are active/running at `NRestarts=0`; rollbacks are `1331fbdd`, then `261e980` and `691863e`.
|
||||
Production is exact `7dd38fc39ce8e91b913b1f45e053c00e0520f3b9` at `https://share.bonfire.icu`, release `7dd38fc39ce8`, wire `screener-v3`; rollback is `f7d667c4d32d`. SQLite v3/five rooms retains SHA-256 `aef724ae52c4107be8ec8791e72f8dcaa11b0ec849fb432d022e2cfb9cfe0974`, owner/mode `screener:screener`/0600. Four services and local/public health are green.
|
||||
Ordinary ICE is STUN-only; limits are SFU roots <=2, one `peer-selected` attempt excluding Host ingress, and selected UDP TTL 120. This rollout ran no browser/media/SFU/TURN/performance canary; earlier exact-`16f6eab` Host-ingress proof remains separate.
|
||||
Stable SFU, route/transport truth, preview notice, entry/nickname UI and Web relay cap2 are live. Native is source-only. The controlled production A/B uses browser/LiveKit codec defaults; explicit codec controls are source-complete.
|
||||
|
||||
@@ -16,7 +15,7 @@ Parallel flagship work; minimum relevant checks and rollback proof. Research plu
|
||||
## Current Snapshot
|
||||
|
||||
- Capture precedes rooms; source/quality changes and AV pause preserve peers. Defaults are balanced and automatic codec; clarity/fluid plus H.264/VP8 stay explicit, with codec locked during a share. Browser degradation and Host SFU A+B remain open.
|
||||
- Production `screener-v2` admits private fragment grants directly. Code-only Viewers need site access, then public rooms accept the code while private rooms also require their password. `SITE_ACCESS_PASSWORD`, `/api/site-access`, and its cookie are deployed atomically; raw credentials are never stored.
|
||||
- Production `screener-v3` admits private fragment grants directly. Code-only Viewers need site access, then public rooms accept the code while private rooms also require their password. `SITE_ACCESS_PASSWORD`, `/api/site-access`, and its cookie are deployed atomically; raw credentials are never stored.
|
||||
- Source UI uses adjacent nickname editing, distinct site/room prompts, and a shared room-code form. Status and details stay neutral before media proof; current evidence labels green P2P/yellow `SFU fallback`, while relay evidence adds `TURN` plus only a locally observed protocol.
|
||||
- Native v2 is source-only (memory rooms, 300s reclaim); it follows authenticated direct-child assignments up to two and fails unsupported SFU/selected ingress boundedly.
|
||||
- Production is roots/Web relay <=2; every Viewer has one upstream and rejects child3. One-root healthy-SFU MBB is deployed but media-unverified; capacity `0 -> 1`, multi-root and browser gates remain open. Room `1` is historical; HTTPS/WSS is TLS/TCP.
|
||||
@@ -56,7 +55,7 @@ Recovery uses one ICE restart, one same-parent rebuild, one alternate peer, then
|
||||
Native WGC/MF passed one-Viewer hardware; package download, Viewer2/FIFO and game A/V remain. Use representative target-device or production evidence for performance, not an ordinary-PC synthetic gate.
|
||||
Ordinary peers stay STUN-only; initial ingress, `peer-selected`, and healthy-reselection browser gates remain open.
|
||||
ADR-0004 still needs its resource/quality matrix; the 20-viewer gate precedes a default change. Mobile uses the same capacity and is a compatibility observation.
|
||||
Stereo: open.
|
||||
Peer/SFU stereo targets 128 kbps in production; user route comparison remains. Next: codec-lock explanation, then existing-stats H.264 encoder follow-up.
|
||||
|
||||
## Blockers And Decisions
|
||||
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@
|
||||
- [ ] Windows 应用音频:Windows 11 Native sender 使用本地 opaque window target 绑定 HWND、PID 与进程创建时间,只捕获所选游戏及其子进程的音频,避免把语音聊天或通知声一并分享;“全部系统声音”只能由分享者显式选择。普通 Windows 10 不具备当前 process-loopback API 门槛,必须明确标为未解决/不支持而不是伪兼容。浏览器无法证明或隔离任意进程音频时必须如实提示,原生捕获失败、无声或进程退出不得静默扩大为整机捕获。
|
||||
- [ ] 连接自检:在开始分享前验证信令、STUN 和 SFU ICE/UDP 可达性,并给出不暴露地址或凭据的诊断结果。启用 selected-edge TURN canary 时只报告脱敏的授权阶段与 selected candidate type,不输出 URL、username、credential、地址或原始 candidate;自检只覆盖当前 UDP 媒体梯级。
|
||||
- [ ] 受限网络增强:短期鉴权 TURN 只能由完整、默认关闭的部署 tuple 与当前 room controller 启用,并且只能在 direct/peer UDP 与 SFU/UDP 均已按有界梯级失败后,向 controller 选中的当前异常 edge 一次性授权。普通 peer PC 从创建起保持 STUN-only;不得向整个 participant、房间或所有 peer connections 预发 TURN candidates。凭据必须绑定当前授权 session 与 route/connection generation、短期有效、内存态、一次性消费且不进入 URL、日志、浏览器持久化或 SQLite。媒体保持 UDP-only,全部路径耗尽后明确失败;TURN 不得被描述为更高清的传输方式。
|
||||
- [x] Web 编码选择:高级视频设置提供 `自动 | H.264 | VP8`,默认自动使用浏览器/LiveKit 协商;显式选择只在下一次分享前用标准 codec preference 生效,直播中锁定。P2P/浏览器 relay 保留完整 repair 与其他 codec 回退列表,SFU 保持 `backupCodec = false`,不维护第二套应用编码链。实际 codec/profile、encoder implementation 与 stats 是结果真相;H.264 可选不等于浏览器必走硬编,AV1 仍不得仅凭压缩率推断默认启用。
|
||||
- [x] Web 编码选择:高级视频设置提供 `自动 | H.264 | VP8`,默认自动使用浏览器/LiveKit 协商;显式选择只在下一次分享前用标准 codec preference 生效。直播中控件锁定并明确提示停止分享后可修改。P2P/浏览器 relay 保留完整 repair 与其他 codec 回退列表,SFU 保持 `backupCodec = false`,不维护第二套应用编码链。实际 codec/profile、encoder implementation 与 stats 是结果真相;H.264 可选不等于浏览器必走硬编,AV1 仍不得仅凭压缩率推断默认启用。
|
||||
- [ ] 诊断页面:用户可导出不含敏感地址和凭据的连接诊断信息。
|
||||
|
||||
### P2 - 可选功能
|
||||
|
||||
@@ -101,6 +101,7 @@ import {
|
||||
import {
|
||||
shouldPauseLocalPreview,
|
||||
sourceSwitchNotice,
|
||||
videoCodecLockNotice,
|
||||
} from "./host-page-notices";
|
||||
|
||||
type HostPhase = "idle" | "starting" | "live" | "ended" | "error";
|
||||
@@ -1525,6 +1526,7 @@ export function HostPage({ onAuthorizationRequired }: HostPageProps = {}) {
|
||||
}).format(new Date(room.expiresAt))} 过期`
|
||||
: "长期有效"
|
||||
: null;
|
||||
const codecLockNotice = videoCodecLockNotice(phase);
|
||||
|
||||
return (
|
||||
<div className="app-shell">
|
||||
@@ -1940,7 +1942,12 @@ export function HostPage({ onAuthorizationRequired }: HostPageProps = {}) {
|
||||
))}
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset className="control-group quality-priority">
|
||||
<fieldset
|
||||
className="control-group quality-priority"
|
||||
aria-describedby={
|
||||
codecLockNotice ? "video-codec-lock-notice" : undefined
|
||||
}
|
||||
>
|
||||
<legend>视频编码</legend>
|
||||
<div className="segmented-control">
|
||||
{(
|
||||
@@ -1972,6 +1979,11 @@ export function HostPage({ onAuthorizationRequired }: HostPageProps = {}) {
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{codecLockNotice && (
|
||||
<p id="video-codec-lock-notice" className="control-note">
|
||||
{codecLockNotice}
|
||||
</p>
|
||||
)}
|
||||
</fieldset>
|
||||
<button
|
||||
className="button button-secondary"
|
||||
|
||||
@@ -33,3 +33,9 @@ export function shouldPauseLocalPreview(
|
||||
): boolean {
|
||||
return visibilityState !== "visible" || !hasFocus;
|
||||
}
|
||||
|
||||
export function videoCodecLockNotice(phase: string): string | null {
|
||||
return phase === "starting" || phase === "live"
|
||||
? "本次分享的编码已锁定,停止分享后可修改"
|
||||
: null;
|
||||
}
|
||||
@@ -552,6 +552,12 @@ h3 {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.control-note {
|
||||
margin: 8px 0 0;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.advanced-quality-grid > .button {
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
shouldPauseLocalPreview,
|
||||
sourceSwitchNotice,
|
||||
videoCodecLockNotice,
|
||||
} from "../src/client/pages/host-page-notices.ts";
|
||||
|
||||
describe("shouldPauseLocalPreview", () => {
|
||||
@@ -44,3 +45,17 @@ describe("sourceSwitchNotice", () => {
|
||||
).toBe("分享来源已切换");
|
||||
});
|
||||
});
|
||||
|
||||
describe("videoCodecLockNotice", () => {
|
||||
it("explains why codec controls are locked during a share", () => {
|
||||
expect(videoCodecLockNotice("starting")).toBe(
|
||||
"本次分享的编码已锁定,停止分享后可修改",
|
||||
);
|
||||
expect(videoCodecLockNotice("live")).toBe(
|
||||
"本次分享的编码已锁定,停止分享后可修改",
|
||||
);
|
||||
for (const phase of ["idle", "ended", "error"]) {
|
||||
expect(videoCodecLockNotice(phase)).toBeNull();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user