From f23259d620addfe470ac7422cf66432f9ab61d67 Mon Sep 17 00:00:00 2001 From: AAA <35992542+TNTcraftHIM@users.noreply.github.com> Date: Wed, 19 Aug 2026 19:47:42 +0800 Subject: [PATCH] docs(media): adopt SFU-root optional-TURN direction --- AGENTS.md | 7 +- README.md | 16 +- docs/README.md | 6 +- docs/adr/0001-p2p-first-media-topology.md | 14 +- .../0004-peer-assisted-media-experiment.md | 4 +- .../0005-automatic-hybrid-media-routing.md | 204 +++++++++++------- ...mand-driven-dual-representation-quality.md | 40 ++-- docs/deployment.md | 43 +++- docs/maintenance.md | 6 +- docs/project-memory.md | 24 +-- docs/research/advanced-peer-distribution.md | 23 +- docs/research/low-server-media-routes.md | 183 +++++++++++----- docs/research/native-shared-encode-sender.md | 4 +- docs/research/peer-assisted-media.md | 15 +- docs/research/realtime-quality-adaptation.md | 18 +- docs/research/webrtc-p2p-screen-sharing.md | 28 +-- docs/status.md | 24 +-- docs/方案设计.md | 39 ++-- docs/需求理解.md | 50 ++--- 19 files changed, 473 insertions(+), 275 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 09a57cc..2805ec0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,11 +4,12 @@ - Build private, low-latency game screen sharing for one broadcaster and a small group of trusted friends. - This is not a public or large-scale streaming product; users can use OBS/Twitch-class services for that workload. -- Keep the media path P2P-first. Use centralized services for rooms, authentication, signaling, STUN, observability, and TURN fallback. +- Keep the media path P2P-first. Use centralized services for rooms, authentication, signaling, STUN, observability, and bounded SFU-root capacity; TURN is an optional transport compatibility fallback. - Let viewers join from a normal desktop or mobile browser without installing the sender application. -- STUN and authenticated TURN fallback are required. "P2P-first" must never mean "direct-only"; fallback is decided independently for every viewer. +- STUN is required. The target automatic ladder is direct/peer ICE over UDP, then an SFU virtual parent feeding at most two roots, then optional authenticated TURN for deployments that explicitly cover restrictive networks, followed by a clear bounded failure. Ordinary peer edges must not receive TURN candidates by default merely to maximize rare-network coverage. HTTPS/WSS remains TLS/TCP and is independent of this media-transport policy. +- Every non-server endpoint has at most two active downstream media edges; an upstream receive edge does not consume this upload budget. Browser relays remain stricter at one downstream edge until their re-encode/resource gates pass. Central media normally serves at most two roots; extra server-fed exceptional viewers require an explicit deployment egress/admission cap and must never create unbounded fanout. - Treat a web client as the initial delivery target. A packaged desktop sender or native capture helper is a later optimization, not an assumed requirement. -- Do not silently change the product into an always-SFU conferencing system. Record any topology change as an ADR with measured justification. +- Do not silently change the product into an always-SFU conferencing system. An SFU is a virtual parent for one or two necessary roots whose peer descendants remain distributed; record any broader topology change as an ADR with measured justification. ## Repository Knowledge diff --git a/README.md b/README.md index ce52984..d959f13 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Screener -Screener is a private, low-latency screen-sharing project for one game player and a small group of friends. Its media path is WebRTC P2P-first, with authenticated TURN fallback for network pairs that cannot connect directly. Viewers should be able to join from a desktop or mobile browser without installing the sender application. +Screener is a private, low-latency screen-sharing project for one game player and a small group of friends. Its accepted media target is WebRTC P2P-first, with an SFU virtual parent feeding only the necessary roots and optional authenticated TURN for explicitly supported restrictive networks. Roots continue distributing to bounded peer descendants; this is not an always-SFU conferencing design. Viewers should be able to join from a desktop or mobile browser without installing the sender application. The first measurable Web proof of concept is implemented. A host captures a screen, window, or browser tab and shares a numeric room code; friends can open `/r/{code}` directly or enter the code at `/join`, with no separate viewer token. During a share, the host can change between three ceiling profiles without reopening the source picker: 1080p60 at 8 Mbps, 1080p30 at 5 Mbps, and 720p30 at 3 Mbps. The host can also temporarily pause the picture or switch sources. Rooms are random and temporary by default. A password-protected deployment can optionally use SQLite-backed room numbers starting at `1`, reusable links, and a waiting state after sharing stops. Each viewer currently receives an independent WebRTC connection, so direct and TURN-relayed paths can coexist in one room. The default room capacity is eight viewers and deployments may configure 1 through 16, but that admission limit is not a performance promise. @@ -26,11 +26,13 @@ must remain on `localhost` or HTTPS because screen capture requires a secure context. The default has no STUN or TURN, so cross-network use still requires HTTPS and the production ICE configuration documented separately. -Production startup requires STUN plus separate TURN/UDP and TURN/TCP URLs. -TURN/TLS is an optional restrictive-network enhancement: use its standard TCP -port 5349 by default, or port 443 only when the deployment has a dedicated -public IP or a validated layer-4/SNI route. This configuration preflight is not -evidence that any external relay path works. +The current production release still requires STUN plus separate TURN/UDP and +TURN/TCP URLs. Do not remove them from an existing deployment yet. The accepted +but unimplemented flagship target is direct/peer UDP, then one SFU publication +feeding normally one or two roots, then optional generation-bound TURN only for +a selected exceptional edge, followed by a clear bounded failure. The exact +optional media-TCP/TLS transport and port remain subject to the documented +exact-room canary; HTTPS/WSS continues to use TLS/TCP independently. Run the complete automated validation with: @@ -42,7 +44,7 @@ Start here: - [Requirements](./docs/需求理解.md) - [WebRTC PoC design](./docs/方案设计.md) -- [Deployment and TURN setup](./docs/deployment.md) +- [Deployment and media transport](./docs/deployment.md) - [Feasibility research](./docs/research/webrtc-p2p-screen-sharing.md) - [Current project memory](./docs/project-memory.md) - [Current status](./docs/status.md) diff --git a/docs/README.md b/docs/README.md index c628b81..47dedd1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,11 +4,11 @@ - [方案设计](./方案设计.md): accepted, code-ready design for the first measurable WebRTC proof of concept. - [Project memory](./project-memory.md): durable decisions and unresolved questions to read at the start of future work. - [Current status](./status.md): bounded snapshot of the current phase, completed baseline, next step, and blockers. -- [Deployment](./deployment.md): minimal Node, HTTPS/WSS, coturn, firewall, secret, optional TURN/TLS, and network-verification procedure. +- [Deployment](./deployment.md): current Node/nginx/coturn/LiveKit contract plus the gated SFU/UDP-first, optional-TURN flagship migration. - [Maintenance guide](./maintenance.md): Git workflow, context hygiene, document lifecycle, research policy, and automation. - [P2P WebRTC research](./research/webrtc-p2p-screen-sharing.md): evidence, bandwidth model, browser constraints, reference implementations, and feasibility assessment. - [Peer-assisted media research](./research/peer-assisted-media.md): browser shared-encode limits, standard relay re-encoding, deterministic sticky topology, runtime relay-capacity extension, and abandon gates. -- [Low-server-cost media routes](./research/low-server-media-routes.md): per-edge TURN versus SFU-root topology, hop-level traffic accounting, privacy-safe ICE evidence, dual-TURN shadow gates, and failure-only final fallback. +- [Low-server-cost media routes](./research/low-server-media-routes.md): SFU-root/peer distribution, optional selected-edge TURN, hop-level accounting, privacy-safe ICE evidence, and the bounded transport canary. - [Advanced peer distribution](./research/advanced-peer-distribution.md): multi-tree/SVC, encoded-object relay, native RTP forwarding, FEC/network-coding, MoQ, and measurable go/no-go gates. - [Native shared-encode sender](./research/native-shared-encode-sender.md): consolidated Draft #16/#18/#22/#23/#25/#28 ladder, unclassified product-gate failure, staged revalidation, and stop line. - [Realtime quality adaptation](./research/realtime-quality-adaptation.md): correlated capture/send/receive diagnosis, production generation-rebuild gate, demand-driven dual representations, and bounded simulcast/Dynacast/SVC checks. @@ -17,7 +17,7 @@ - [ADR-0001](./adr/0001-p2p-first-media-topology.md): accepted P2P-first media topology and its consequences. - [ADR-0002](./adr/0002-persistent-protected-rooms.md): optional SQLite persistence, protected sequential room IDs, and room lifecycle. - [ADR-0004](./adr/0004-peer-assisted-media-experiment.md): proposed, experiment-only standard-WebRTC peer-assisted spike with hard fanout and failure gates. -- [ADR-0005](./adr/0005-automatic-hybrid-media-routing.md): proposed, default-off automatic P2P/peer/SFU preparation, token-free standby prewarm, commit, failback, and rollback with a hard host-edge budget. +- [ADR-0005](./adr/0005-automatic-hybrid-media-routing.md): accepted direct/peer UDP -> SFU-root -> optional exceptional-edge TURN direction, with the current default-off controller and migration gates. - [ADR-0006](./adr/0006-fixed-high-native-sender-canary.md): proposed fixed-`HIGH` native canary, current no-go result, and staged revalidation boundary. - [ADR-0007](./adr/0007-demand-driven-dual-representation-quality.md): accepted demand-driven `HIGH + at most one on-demand LOW` quality policy, path-level hysteresis, evidence contract, and SVC boundary. diff --git a/docs/adr/0001-p2p-first-media-topology.md b/docs/adr/0001-p2p-first-media-topology.md index 07f333f..d5e936d 100644 --- a/docs/adr/0001-p2p-first-media-topology.md +++ b/docs/adr/0001-p2p-first-media-topology.md @@ -7,12 +7,14 @@ Current clarification, 2026-08-19: the MVP remains unchanged, but statements below that exclude automatic topology migration do not describe the product target. Automatic, viewer-transparent routing through direct P2P, peer-assisted media, and an enabled SFU fallback is a standing requirement and -will be governed by a later ADR. +is governed by ADR-0005. Historical ADR-0003's explicit whole-room SFU mode is rejected/superseded. -Proposed ADR-0004 and ADR-0005 do not supersede this production baseline; they -govern default-off peer-assisted and automatic optional-SFU experiments. A -later Accepted ADR is required before either changes the production topology. +Proposed ADR-0004 and the default-off ADR-0005 implementation do not yet +supersede this production baseline. ADR-0005 now accepts the target direct/peer +UDP -> SFU-root UDP -> optional exceptional-edge TURN ladder, but its config +migration and exact-room acceptance gates must pass before production topology +changes. ## Context @@ -25,10 +27,10 @@ Use separate control and media planes: - A small HTTPS/WSS service owns identity, rooms, invitations, presence, and WebRTC signaling. - A viewer can join through a numeric room code on desktop or mobile without installing the sharing client. The viewer link has no separate token or fragment; the host publication token remains internal. A deployment may place one site-wide password gate in front of both hosting and viewing; access policy remains control-plane state and does not change the media topology. ADR-0002 owns the current room-ID and lifetime policy. - ICE attempts a direct UDP path for every current broadcaster-viewer edge, using STUN to discover candidates. A later accepted peer-assisted topology must apply the same rule independently to each assigned parent-child edge. -- Only pairs that cannot connect directly use authenticated TURN. Production requires STUN plus TURN over UDP and TCP; TURN/TLS is an optional restrictive-network enhancement, using its standard TCP port 5349 by default. Port 443 is optional and requires a dedicated public IP or a validated layer-4/SNI route when HTTPS already owns that address and port. +- For the deployed MVP, only pairs that cannot connect directly use authenticated TURN. Its current production contract requires STUN plus TURN over UDP and TCP; TURN/TLS is an optional restrictive-network enhancement. ADR-0005 owns the accepted future change to optional, controller-selected exceptional-edge TURN. - Candidate selection is independent per pair. A room may simultaneously contain direct and relayed viewers without moving working peers onto the server. - The deployed MVP broadcaster creates one peer connection per viewer. Rooms default to eight viewers and deployments may configure a limit from 1 through 16. Eight is an admission default, not a validated media-performance promise. A newer product target caps host media fanout at two; the current implementation does not satisfy that target above two viewers, and Proposed ADR-0004 owns the isolated experiment rather than silently changing this accepted baseline. -- An SFU is not the default path for the normal product envelope. Closed PR #12's explicit whole-room mode is superseded by merged PR #17's default-off automatic fallback. ADR-0005 permits selecting configured SFU capacity only after cheaper endpoint routes fail. The code ships in production but remains unconfigured, inactive, and unaccepted for production enablement. +- An SFU is not the default whole-room path. Closed PR #12's explicit whole-room mode is superseded by merged PR #17's default-off automatic fallback. Its current code selects SFU only after peer recovery fails and remains unconfigured in production. ADR-0005 accepts SFU roots as the primary central fallback after direct/peer UDP while retaining distributed descendants; rollout remains gated. ## Consequences diff --git a/docs/adr/0004-peer-assisted-media-experiment.md b/docs/adr/0004-peer-assisted-media-experiment.md index 5a2f074..623bcdf 100644 --- a/docs/adr/0004-peer-assisted-media-experiment.md +++ b/docs/adr/0004-peer-assisted-media-experiment.md @@ -34,8 +34,8 @@ Preserve the product priority in this order: 1. direct P2P for one or two viewers; 2. peer-assisted forwarding for later viewers when this experiment is active and every required capability is present; and -3. an enabled optional SFU as the final automatic fallback in a later route - controller. +3. the ADR-0005 SFU virtual parent as the flagship central fallback while + retaining bounded peer descendants. This bounded experiment implements only the first two levels. That scope must not be read as requiring users to select a topology or rejecting automatic diff --git a/docs/adr/0005-automatic-hybrid-media-routing.md b/docs/adr/0005-automatic-hybrid-media-routing.md index 8aaecc4..38ab401 100644 --- a/docs/adr/0005-automatic-hybrid-media-routing.md +++ b/docs/adr/0005-automatic-hybrid-media-routing.md @@ -1,6 +1,6 @@ # ADR-0005: Automatic Hybrid Media Routing -- Status: Proposed - Default-Off Implementation, Experiment Only +- Status: Accepted Direction - Default-Off Migration Unverified - Date: 2026-08-19 ## Context @@ -8,25 +8,33 @@ The product has always required route selection and recovery to be automatic and invisible to the broadcaster and viewers. Earlier repository wording that prohibited silent migration was an incorrect interpretation and is superseded -by this proposal. Simplicity constrains the controller and user experience; it +by this decision. Simplicity constrains the controller and user experience; it does not require users to choose a media topology. The ordered preference remains: 1. direct P2P edges; 2. peer-assisted forwarding; -3. an SFU supplied by the deployment; and +3. an SFU virtual parent supplied by the flagship deployment; and 4. bounded waiting or failure when no route can satisfy the budgets. -Every P2P edge independently tries direct ICE and authenticated TURN fallback. -TURN is not a separate room topology. The host and every future native/encoded -relay have a downstream budget of at most two active media edges. The current -browser relay remains stricter at one child. +STUN is required. The accepted target keeps ordinary direct and peer edges on +ICE/UDP, then assigns one or two SFU roots when no peer path can satisfy the +route and fanout budgets. Authenticated TURN is optional and, when configured, +is a final transport compatibility layer for a selected exceptional edge; it +is not advertised to every ordinary peer edge by default. HTTPS/WSS remains TLS/TCP and +is outside this media policy. Every non-server endpoint has at most two active +downstream media edges; its upstream receive edge does not consume that upload +budget. The current browser relay remains stricter at one child until its +re-encode and resource gates pass. -TURN and SFU occupy different layers. TURN replaces transport for only the ICE -edge that selected a relay pair. An SFU is a virtual topology parent and may -feed only one or two necessary roots; after media reaches a reliable root, the -existing bounded peer subtree remains the preferred distribution path. A +TURN and SFU occupy different layers. A selected TURN candidate continuously +relays media for that ICE edge; it is not a handshake helper or a topology +decision. LiveKit TURN cannot rescue an unavailable SFU. Independent coturn may +transport a separately selected ordinary peer edge around that outage, but only +after the controller authorizes that peer topology. An SFU is a virtual topology parent and may feed only one +or two necessary roots; after media reaches a reliable root, the existing +bounded peer subtree remains the preferred distribution path. A zero-descendant SFU root is allowed only when no relay root satisfies capability, depth, path, and recovery gates; it still counts against the same one-or-two-root room budget. @@ -36,7 +44,7 @@ Closed PR #12 proposed a mutually exclusive process-wide `p2p|sfu` mode; this ADR and merged PR #17 supersede that model because it cannot satisfy the route priority or minimize server egress. -## Proposed Experiment +## Accepted Target And Bounded Migration Treat the SFU as a virtual parent for selected fallback roots, not as an all-room replacement. Keep the existing peer-assisted topology as the active @@ -56,10 +64,11 @@ exists, a necessary viewer may be an SFU root with no descendants under that same total; this is the last central-fanout boundary, not a default whole-room topology. The cost model is owned by the linked low-server research. -Deployment decides whether SFU capacity exists by configuring the complete -LiveKit endpoint/key/secret tuple. There is no user-facing topology selector and -no process-wide `MEDIA_MODE` union. Without the tuple, the same controller ends -at peer assistance and bounded waiting/failure. +The flagship deployment target supplies SFU capacity with a complete LiveKit +endpoint/key/secret tuple. There is no user-facing topology selector and no +process-wide `MEDIA_MODE` union. A deployment without that tuple ends at peer +assistance and bounded waiting/failure and is not the final flagship route +configuration. Optional TURN is a separate selected-edge transport choice. ## Implementation Status @@ -69,6 +78,15 @@ production release `769de201f7cc`, but the deployment has neither `PEER_ASSISTED_MEDIA` nor a LiveKit tuple, so its active path remains ordinary one-host-peer-per-viewer P2P/TURN. +That implementation is not yet the accepted transport target. It advertises +coturn per P2P edge, production configuration requires TURN/UDP plus TURN/TCP, +and the SFU activates only after a peer edge exhausts recovery. The scheduled +change must make the complete TURN tuple optional, omit it from ordinary peer +ICE, make SFU/UDP the primary central fallback, and issue short-lived TURN only +for a controller-selected exceptional edge. Until its tests and exact-room +canary pass, the current config contract and production deployment remain +unchanged. + The repository also supports an optional strict `PEER_ASSISTED_ROOM_IDS` deployment allowlist. When non-empty, only exact listed room IDs enter the controller or receive optional LiveKit standby and grants. All other rooms @@ -83,10 +101,14 @@ and API secret tuple is present together with `PEER_ASSISTED_MEDIA=true`. It issues short-lived room-, role-, peer-, and publication-generation-bound grants, and only allowlisted branch roots may subscribe. A necessary viewer with no descendants is still a root under the same authorization and total cap. The -server first retries a failed edge through the deterministic peer topology; -only an exhausted peer route can request an SFU branch root. A -healthy route does not migrate merely because one or both selected edge -transports use TURN. +server currently retries a failed edge through the deterministic peer topology; +only an exhausted peer route can request an SFU branch root. The target keeps +healthy UDP routes sticky but may select SFU directly when admission has no +eligible peer path. Optional TURN has two distinct future boundaries: an +exceptional ordinary host-to-root or root-to-viewer edge needs a new +generation-bound coturn grant, while a LiveKit publisher/subscriber may receive +the pinned participant-wide LiveKit TURN config. Neither is implemented as a +controller-selected TURN edge today; other routes remain unchanged. Every viewer starts with zero relay capacity for each authenticated session. A peer-assisted Web client explicitly advertises either zero or one downstream @@ -102,8 +124,9 @@ standby warming. The corrected cold Chrome 151/LiveKit 1.13.5 localhost run took 1.481 seconds from failure report to active and 2.257 seconds to a new rendered frame. With authenticated standby warming, the same physical-leaf/two-root scenario took 200 ms to active and 319.7 ms to render while the host edge peak -remained two. Public transport/audio/load/browser checks remain pending, so this -ADR stays Proposed. +remained two. Public transport/audio/load/browser checks remain pending. The +direction is accepted, while implementation migration and deployment +acceptance remain unverified. ## State And Wire @@ -148,6 +171,12 @@ Minimal protocol additions: - `route-failed { revision, phase, connectionId }`; and - `refresh-sfu { revision }`. +The optional selected-edge TURN target is not present in this protocol. A +future migration PR must add strict, session/assignment/generation-bound coturn +grant and refresh messages for ordinary peer edges. Pinned LiveKit embedded +TURN remains participant-wide ICE configuration for a LiveKit publisher or +subscriber and is not a Screener per-edge grant. + Authentication carries the current participant assignment and room revision, plus the non-secret standby URL when fallback is configured. It never carries a standby JWT. An authenticated snapshot is the sole authority allowed to replace @@ -163,9 +192,14 @@ complete LiveKit tuple. ## Transition -1. Keep two ordinary peer roots while those routes work. -2. A failed edge first exhausts its current ICE restart/rebuild and per-edge - direct/TURN options. +1. Keep direct/peer UDP roots while those routes work. +2. A failed edge first exhausts its bounded ICE/UDP restart/rebuild and peer + reparent options. When admission has no eligible peer path, or recovery is + exhausted, select the SFU root plan without first advertising TURN to every + peer edge. If ordinary coturn is configured, its grant is issued only to a + selected exceptional peer edge after UDP is exhausted. Stock LiveKit + participant-wide TURN remains a canary candidate and does not satisfy this + selected-edge issuance target unless the integration isolates or extends it. 3. The server computes revision `R+1` without mutating the active topology. It sends a prepare plan only to the host and required fallback roots. 4. When fallback is configured, host and viewer clients have already made one @@ -197,13 +231,13 @@ complete LiveKit tuple. baseline revision and disables SFU for the rest of the current share. Stop or a new sharing generation clears that one-shot circuit breaker. -SFU fallback is sticky until the current share stops. A new sharing generation -starts from the cheapest available route. The first experiment does not -continuously fail back during a live share, avoiding oscillation without adding -a score or hysteresis framework. +An activated SFU-root route is sticky until a discrete route event requires a +change or the current share stops. A new sharing generation starts from the +cheapest available UDP route. The first target does not continuously rebalance +healthy media, avoiding oscillation without adding a score or optimizer. The authenticated standby is not a transport: it has no grant and never joins a -room. During route prepare the Draft warms only an unpublishing/unsubscribed +room. During route prepare the current implementation warms only an unpublishing/unsubscribed transport. Its media transition remains break-before-make: the host releases the replaced peer or SFU media edge before activating the new one, and a viewer retires its SFU subscriber before returning to peer media. @@ -255,36 +289,55 @@ Measurements may add a new explicit trigger only through a reviewed change. ADR-0007's `HIGH`/`FALLBACK` quality state is separate and does not become a topology trigger or a room-wide health score. -`MAX_SFU_ROOTS_PER_ROOM` bounds server egress. Exhausting that budget waits or -fails explicitly; it never creates a third host edge or silently fans the SFU -out to the whole room. +`MAX_SFU_ROOTS_PER_ROOM` currently bounds server egress at one or two roots. +The target retains two as the normal distributed limit. If several exceptional +viewers cannot attach behind any healthy root, a later reviewed config may +admit additional direct server-fed edges under a separate explicit egress cap; +it never creates a third endpoint edge or unbounded whole-room fanout. Sub-second failure recovery is a target after failure detection. The current 30-second control heartbeat cannot meet it for silent partitions, so the media plane needs a small 100-200 ms liveness/queue signal or an equivalent native transport event. This signal must be measured before its interval is fixed. -## Shadow-Only Dual-TURN Candidate +## SFU-First UDP Transport Migration -Four cases define the decision boundary: retain direct/direct and direct/TURN -peer roots; treat stable TURN/TURN roots only as a shadow SFU-root candidate; -and use the current failure-only SFU path only after no reliable relay root -remains. A necessary zero-descendant viewer still consumes one of the same one -or two SFU-root slots. No stability threshold or new trigger is accepted. +The accepted product direction supersedes the earlier dual-TURN shadow +candidate as the preferred central comparison. Healthy direct/peer UDP stays distributed. When no such path +can satisfy admission or recovery, one SFU publication feeds one or two roots, +which keep their bounded peer descendants. Optional TURN is not room-wide: the +server may grant independent coturn only to a selected exceptional ordinary +host-to-root or root-to-viewer edge. A LiveKit publisher/subscriber connection +may instead receive LiveKit's participant-wide embedded/external TURN config; +that path is not assignment-level issuance. Absence of TURN is normal; +exhausted configured paths fail clearly. -The bounded cost model, privacy-safe ICE fields, four-case measurement matrix, -and exact-room shadow/A/B sequence live in -[Low-Server-Cost Media Routes](../research/low-server-media-routes.md). One -important accounting invariant is retained here: host-to-SFU publisher, -SFU-to-root subscriber, and peer-descendant edges are independent ICE -connections and each may use TURN. Every host NIC, TURN ingress/egress, SFU -ingress/egress, and root NIC hop is real traffic and remains in service and -billing totals, even when the same logical payload traverses consecutive hops. +The bounded cost model, privacy-safe ICE fields, and exact-room A/B sequence +live in [Low-Server-Cost Media Routes](../research/low-server-media-routes.md). +For the normal-root baseline with no exceptional server-fed viewer, one equal +representation of bitrate `B` and `R` roots gives TURN seed host upload `R*B` +and central ingress plus egress `2R*B`; one SFU publisher gives host upload `B` +and central ingress plus egress `(R+1)*B`. Mixed representations use measured +`B_pub` and `sum(B_i)`. If `E` separately admitted exceptional viewers receive +bitrate `B_exc,j`, SFU egress adds `sum(B_exc,j)`; every TURN leg additionally +adds its own ingress and egress. Every host NIC, TURN ingress/egress, SFU +ingress/egress, root NIC, and exception NIC hop remains real traffic. -Only measured benefit plus an amended ADR-0005 may authorize an automatic -exact-room canary. Until then, shadow observation changes no route, wire, -controller, or production trigger, and the implementation remains -failure-only. +Migration is gated, not optional design debate: first retain the current path +as a baseline, then use one exact room to verify direct/peer UDP, SFU/UDP, +one selected-edge compatibility transport when configured, and bounded failure with all UDP +blocked. The matrix covers CGNAT, double NAT, mobile hotspot, ordinary home +networks, root departure, reconnect, SFU unavailable, and rollback. It records +CPU seconds/GiB, NIC bytes/pps, RSS, host upload, p95/p99 forwarding latency, +loss/recovery, final quality, host edges at most two, SFU roots at most two, and +unchanged healthy subtrees. Only after that gate may config parsing stop +requiring coturn and may production enable the new ladder. + +A peer root re-publishing its received stream to the SFU while also feeding +peer descendants remains a separate bounded candidate. The current browser +path would decode and re-encode, and it adds publication ownership and load; +it is not silently added to this migration. No composite score, continuous +optimizer, or geographic/UA inference is introduced. ## Security And Privacy @@ -295,7 +348,7 @@ the current fallback-root identities, including any zero-descendant roots. Ordinary SFU WebRTC transport encryption terminates at the SFU, so its operator can access media. LiveKit supports application E2EE in which its server cannot access media content, but signaling/API data remains visible and Screener has -not implemented the required key distribution. The shadow comparison must +not implemented the required key distribution. The exact-room comparison must record which boundary is actually configured and the UI must not claim E2EE. ## Implementation Order @@ -308,31 +361,34 @@ record which boundary is actually configured and the UI must not claim E2EE. 3. Add versioned route state and pure invariant/property tests. 4. Implement server prepare/commit/abort and strict authorization. 5. Integrate host/viewer first-frame switching while retaining relay children. -6. Extend the tracked `1/3/5/8` benchmark with automatic fallback, direct/TURN, - source/profile/pause/stop, and server-egress measurements. +6. Extend the tracked `1/3/5/8` benchmark with automatic fallback, peer/SFU UDP, + optional selected-edge TURN, source/profile/pause/stop, and server-egress measurements. ## Acceptance Gates -- Host active media edges never exceed two across every prepare, commit, - rollback, reconnect, and stale-message sequence. -- Current browser relays never exceed one child; future capacity two is exposed - only by a proven native/encoded capability. -- Only necessary fallback roots receive SFU media and the configured root/egress - budget is never exceeded. -- A reliable SFU root continues to serve bounded peer descendants; only when no - reliable relay root exists may a necessary viewer be a zero-descendant root, - still within the same one-or-two-root cap. +- Every non-server endpoint stays at or below two active downstream media edges + across prepare, commit, rollback, reconnect, and stale-message sequences; + current browser relays stay at one child until separately accepted. +- Only necessary roots or explicitly admitted exceptional viewers receive SFU + media and the configured central egress budget is never exceeded. +- A reliable SFU root continues to serve bounded peer descendants. Normal + central fanout stays at one or two roots; only multiple exceptional viewers + that cannot attach to a healthy root may consume separately capped direct + server edges. - Prepare failure leaves the old active route unchanged. - First new decodable picture arrives within one second after a route failure is detected in the reference regional network. - Existing descendants, source selection, quality profile, pause, audio, and persistent-room stop/restart semantics survive the transition. -- No LiveKit configuration preserves the existing P2P/peer behavior and wire. +- No LiveKit configuration preserves the existing P2P/peer behavior and wire, + but is not the final flagship deployment target. - In one process, non-allowlisted rooms preserve the legacy P2P wire, directed signaling, quality rejection, stop/reconnect/delete semantics, and remain isolated from allowlisted peer/SFU state. -- LiveKit UDP, TCP, and TURN fallback are independently verified before the SFU - can be called a reliable final route. +- LiveKit/SFU UDP must pass CGNAT, double-NAT, hotspot, home-network, loss, + rollback, and SFU-unavailable gates. With optional TURN absent, blocked UDP + fails clearly within a bounded window. Any configured selected-edge TURN or + media TCP mode is verified separately and never becomes a quality claim. ## Consequences @@ -340,16 +396,17 @@ Positive: - Users do not select or understand a topology. - Host fanout remains bounded while server media egress is paid only for - fallback roots. -- The ordinary peer path and its direct/TURN behavior remain reusable. + fallback roots and separately admitted exceptional viewers. +- The ordinary peer path stays direct/UDP by default; optional TURN is paid only + for a selected exceptional edge. - Revisioned prepare/commit isolates stale asynchronous results without a continuous optimizer. Negative: - Strict two-edge migration can include a bounded interruption. -- LiveKit adds an optional operational dependency and can inspect ordinary SFU - media. +- LiveKit becomes an operational dependency for the flagship target and can + inspect ordinary SFU media. - A configured peer-assisted client downloads/parses the current build's roughly 137.5 kB gzip (531 kB minified) LiveKit chunk and sends one `HEAD` even if it never needs SFU, shifting that small one-time client/static-egress cost earlier. @@ -359,10 +416,10 @@ Negative: ## Relationship To Existing ADRs -- This proposal corrects the automatic-migration interpretation in ADR-0001 +- This decision corrects the automatic-migration interpretation in ADR-0001 without changing the currently deployed MVP. - ADR-0004 remains the bounded full-stream browser-relay experiment. -- This experiment and merged PR #17 supersede ADR-0003/closed PR #12's +- This decision and merged PR #17 supersede ADR-0003/closed PR #12's process-wide explicit media mode with default-off automatic hybrid fallback. ADR-0003 remains historical rejected/superseded context. - Native shared encoding and two-tree striped distribution remain orthogonal @@ -378,5 +435,8 @@ Negative: - [LiveKit JavaScript client usage](https://github.com/livekit/client-sdk-js#usage) - [LiveKit end-to-end encryption](https://docs.livekit.io/transport/encryption/) - [TURN, RFC 8656](https://www.rfc-editor.org/rfc/rfc8656.html) +- [WebRTC transports, RFC 8835](https://www.rfc-editor.org/rfc/rfc8835.html) +- [LiveKit ports and firewall](https://docs.livekit.io/transport/self-hosting/ports-firewall/) +- [LiveKit 1.13.5 configuration](https://github.com/livekit/livekit/blob/v1.13.5/config-sample.yaml) - [WebRTC statistics](https://www.w3.org/TR/webrtc-stats/) - [WebRTC](https://w3c.github.io/webrtc-pc/) diff --git a/docs/adr/0007-demand-driven-dual-representation-quality.md b/docs/adr/0007-demand-driven-dual-representation-quality.md index 0f8be49..fa0d845 100644 --- a/docs/adr/0007-demand-driven-dual-representation-quality.md +++ b/docs/adr/0007-demand-driven-dual-representation-quality.md @@ -31,14 +31,14 @@ Use demand-driven dual representations: representation. Every verified weak path moves to that same representation. Healthy paths remain on `HIGH`. 4. A fallback path returns to `HIGH` only after a longer, independently defined - stable-recovery window. When no path needs `LOW`, stop its encoder and free - its resources. + stable-recovery window. When no path needs `LOW`, deactivate the + representation and verify its bytes, frames, and resource cost stop. 5. The representation count is a hard `HIGH + at most one on-demand LOW <= 2`; it never grows with viewer count. -Starting `LOW` is conditional on a qualified hardware/power-efficient encoder +Starting `LOW` is conditional on a qualified hardware/power-efficient media path and measured spare game-performance budget. If that path is absent or the -second encoder causes unacceptable CPU/GPU/game load, fail closed for that +additional representation causes unacceptable CPU/GPU/game load, fail closed for that attempt: preserve `HIGH` and show the weak path an explicit degraded/unavailable state. This is exceptional damage containment, not permission to ignore a weak path indefinitely. A supported sender cohort that cannot reliably start `LOW` @@ -106,8 +106,10 @@ but runtime encode/decode behavior is authoritative. ## Deferred Capability Spikes -These spikes start only after the A+B/C evidence contract and the native -fixed-`HIGH` path pass. They do not change the accepted two-state controller: +These spikes start only after the A+B/C evidence contract is trustworthy. They +may proceed independently of ADR-0006 native-sender revalidation and must run +before any custom dual-representation media implementation. They do not change +the accepted two-state controller: 1. Negotiate exactly two `HIGH`/`LOW` simulcast encodings on one sender in its initial envelope, with `LOW` inactive. Verify requested/applied parameters, @@ -115,7 +117,7 @@ fixed-`HIGH` path pass. They do not change the accepted two-state controller: `LOW` is inactive. W3C `active=false` stops that encoding from being sent; it does not guarantee that a physical encoder or GPU resource is released. Separate direct PeerConnections have no portable shared-encode contract. -2. On the optional SFU path, publish at most two LiveKit simulcast +2. On the SFU path, publish at most two LiveKit simulcast representations and let each one or two roots select independently. Treat Dynacast as a bounded rejection/verification spike, not an assumed fit. The pinned server 1.13.5 aggregates the maximum quality requested across all @@ -126,17 +128,19 @@ fixed-`HIGH` path pass. They do not change the accepted two-state controller: actual per-RID bytes/frames plus host CPU/GPU/encoder evidence prove that `LOW` stops while `HIGH` continues; otherwise reject it for the exact on-demand-`LOW` requirement. -3. Only for the strict-one-output requirement, request SVC and compare the - applied codec/`scalabilityMode` with Media Capabilities `powerEfficient` and - the game-performance matrix. Reject silent software fallback. +3. Run a bounded SVC viability spike and compare the applied + codec/`scalabilityMode` with Media Capabilities `powerEfficient` and the + game-performance matrix. This avoids prematurely building a custom second + representation; product adoption still requires a future strict-one-output + requirement. Reject silent software fallback. None may reuse PR #28's minimum-of-two target, bypass a per-edge stock WebRTC congestion controller, or expand the representation limit beyond two. ## SVC Boundary -Consider SVC only when a later accepted requirement demands one encoded output -at all times. A path may then receive only the base layer or the base plus +The bounded spike may measure SVC now, but adopt it only when a later accepted +requirement demands one encoded output at all times. A path may then receive only the base layer or the base plus enhancement layers. Enable it only when the exact negotiated codec and `scalabilityMode` are read back, a power-efficient/hardware path is positively established on the supported sender cohort, and game-performance and latency @@ -155,15 +159,16 @@ particular physical hardware encoder. Positive: - Healthy viewers are not reduced to the worst path. -- Normal operation pays for one encoder; a second is paid only while at least - one verified weak path needs it. +- Normal operation sends one active representation; one additional + representation is paid only while a verified weak path needs it. Physical + encoder instances and resource release remain measured outcomes. - Encode cost is bounded independently of viewer count. - The state machine and its evidence remain inspectable. Negative: -- A weak-path interval can temporarily require two encoder instances and the - bandwidth for both representations on the affected distribution edges. +- A weak-path interval adds representation bandwidth and may require another + physical encoder; the exact instance/resource cost is implementation-specific. - Starting/stopping `LOW` and switching paths need a keyframe-safe transition. - Browser capability reporting cannot by itself prove hardware or sustained decode performance, so the real-device matrix remains mandatory. @@ -186,7 +191,8 @@ Negative: - One sustained weak path starts exactly one `LOW`; healthy viewers remain on `HIGH`, and additional weak viewers reuse `LOW`. - After all weak paths sustain the longer recovery window, every path returns - to `HIGH` and the `LOW` encoder stops. + to `HIGH`; `LOW` bytes/frames stop and its physical resource behavior is + recorded rather than assumed. - Representation count never exceeds two and host media edges never exceed two. - An unavailable/over-budget `LOW` fails visibly for weak paths while healthy paths and `HIGH` remain unchanged. diff --git a/docs/deployment.md b/docs/deployment.md index 7f49e77..632116b 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -1,15 +1,15 @@ # Minimal Deployment -Last verified against upstream documentation: 2026-08-18. +Last verified against upstream documentation: 2026-08-19. -This is the first production-shaped deployment for the WebRTC PoC: one Node.js +This section documents the currently deployable release contract: one Node.js process provides the built Web client, room API, and WebSocket signaling behind Caddy or nginx; coturn is the separate STUN/TURN service. Normal media remains browser-to-browser. Only an ICE pair that cannot connect directly consumes TURN bandwidth. A deployment may additionally provide one single-node LiveKit process as the -automatic final media fallback. This capacity is dormant unless the complete +current controller's automatic final media fallback. This capacity is dormant unless the complete `LIVEKIT_URL`/key/secret tuple is configured. It does not replace the P2P path, the peer-assisted experiment, or the required application STUN/TURN settings. @@ -63,6 +63,43 @@ endpoint and paths below `/rtc/` to LiveKit. Its HTTP/WebSocket listener on TCP 7880 is private to nginx, while WebRTC media reaches LiveKit directly on TCP 7881 or UDP 7882. +## Accepted flagship target after transport migration + +The accepted target for `share.bonfire.icu` remains distributed and automatic: +direct/peer UDP first, then an SFU virtual parent feeding normally one or two +roots, whose peer descendants continue carrying media. TURN becomes an optional +extreme-network transport for a controller-selected edge rather than a room-wide +ICE default. Multiple exceptional viewers that cannot attach behind a healthy +root may consume additional server egress only under a separate explicit cap. + +This is not deployable from the current configuration contract. Release +`769de201f7cc` still refuses production startup without STUN, coturn credentials, +and explicit TURN/UDP plus TURN/TCP URLs; it sends that ICE config to ordinary +P2P peers. The tracked LiveKit example enables ICE/TCP 7881 and ICE/UDP mux 7882, +omits embedded TURN, and pinned LiveKit 1.13.5 permits TCP fallback when the +corresponding transport is configured. Keep the current environment, firewall, +and services until a code PR changes validation/wire behavior and an exact-room +canary passes. + +The migration must make an entirely absent ordinary-peer coturn tuple a normal +no-warning state, reject partial configuration, and add short-lived +assignment/connection-generation grants only for an authorized exceptional +ordinary edge. LiveKit participant transport is separate: the pinned +integration issues/advertises embedded or external TURN only to LiveKit +publishers/subscribers and does so participant-wide, not through Screener's +per-edge grant. It remains a canary candidate and must be isolated/extended to +the accepted exceptional-edge scope or rejected. The target default media path is UDP. Which optional TURN or +media-TCP compatibility transport and port, if any, remains enabled is decided +by the canary; HTTPS/WSS always remains TLS/TCP. + +Do not collapse the public listener plan to `443/tcp` plus one UDP port yet. +LiveKit documents a single ICE/UDP mux port but its pinned sample recommends a +multi-port mux range at least as wide as the CPU count for performance. Embedded +TURN/UDP defaults to 3478 and recommends 443 only when HTTP/3/QUIC does not use +that UDP port. The current nginx template has no HTTP/3 listener, but port choice, +privileged binding, firewall, and one-port performance still require the bounded +network/load gate in the low-server route research. + ## Production application environment Keep the real values in the process secret store or an untracked, access-restricted diff --git a/docs/maintenance.md b/docs/maintenance.md index c9022d8..b1f4b0c 100644 --- a/docs/maintenance.md +++ b/docs/maintenance.md @@ -67,7 +67,7 @@ 这些总量、分类口径和“产品源码可能净减少 7%-14%”都只是当日待重测的审查假设,不是删除指标、承诺区间或 LOC KPI。文件长度也不单独证明职责错误。正式审查必须先重新读取 `AGENTS.md`、当前 memory/status、需求、相关研究及 [ADR-0004](adr/0004-peer-assisted-media-experiment.md)/[ADR-0005](adr/0005-automatic-hybrid-media-routing.md),重新统计届时 `main`,明确计数口径,并按消费者和测试把代码分为: 1. 当前生产路径必需; -2. 自动 `direct P2P -> peer-assisted -> optional SFU` 目标必需; +2. 自动 `direct/peer UDP -> SFU roots -> optional exceptional-edge TURN` 目标必需; 3. 有明确下一阶段消费者和退出条件的实验能力; 4. 已被替代、重复、无消费者或仅服务一次性定位的代码。 @@ -85,8 +85,8 @@ 任何候选改动都必须保持以下行为不变: -- 用户无感的自动 `direct P2P -> peer-assisted -> optional SFU` 梯级; -- 每条 P2P 边独立执行 ICE,并可使用短期凭据回退到鉴权 TURN; +- 用户无感的自动 `direct/peer UDP -> SFU roots -> optional exceptional-edge TURN` 梯级; +- 每条媒体边独立执行 ICE,普通 peer 默认不收到 TURN;启用兼容层时只有授权的异常 edge 可使用短期凭据; - host 下游活跃媒体边不超过两个; - 普通桌面或移动浏览器 Viewer 无需安装应用; - break-before-make、安全授权、重连、回滚和陈旧消息隔离语义; diff --git a/docs/project-memory.md b/docs/project-memory.md index 385c617..d1f2a89 100644 --- a/docs/project-memory.md +++ b/docs/project-memory.md @@ -6,25 +6,25 @@ Last updated: 2026-08-19 - Build low-latency game screen sharing for one broadcaster and a small trusted group; public or large broadcasts belong on OBS/Twitch-class services. - Viewers join from normal desktop/mobile browsers. A later native sender may share encoding and improve capture/audio without changing the Web viewer requirement. -- Use a small central service for access, rooms, signaling, deterministic topology, STUN/TURN, and observability while clients carry media whenever practical. -- Minimize server bandwidth. Automatic, viewer-transparent routing follows direct P2P, measured peer assistance, then enabled user-operated/central SFU fallback under deterministic budgets; TURN is transport chosen per edge, not a topology. -- Host/native roots have at most two downstream edges; browser relays have one. An SFU is only a virtual parent for one or two necessary roots, which retain bounded peer descendants; a zero-descendant viewer consumes the same root cap only when no reliable relay root exists. A long-term native/object path should reuse one encode, while browsers re-encode. +- Use a small central service for access, rooms, signaling, deterministic topology, STUN, observability, and bounded SFU-root capacity; TURN is optional extreme-network transport. +- Minimize server bandwidth. The accepted invisible ladder is direct/peer UDP, then SFU/UDP roots, then optional TURN for a selected exceptional edge, then bounded failure. TURN is transport, not topology. +- Non-server nodes have at most two downstream edges; browser relays stay at one until resource gates pass. SFU normally feeds one or two roots that retain peer descendants; separately capped server edges may serve multiple exceptional viewers that cannot attach behind a healthy root. Native/object paths should reuse one encode while browsers re-encode. - Two-tree packet/layer striping may reduce endpoint upload toward one stream bitrate, but needs a bounded multi-parent, loss, sync, churn, and latency experiment; it is not in the current full-stream chains. -- Shared encoding reduces compute, not each viewer's last-hop copy. Automatic quality uses shared `HIGH` plus exactly one on-demand `LOW` whenever verified weak paths exist; all weak paths share it and recovery stops it. An unavailable efficient second encoder fails closed for that attempt while protecting `HIGH`, but a supported sender cohort that cannot start `LOW` reliably fails controller acceptance and must be optimized or marked unsupported. SVC has no software fallback. +- Shared encoding reduces compute, not each viewer's last-hop copy. Automatic quality uses one active `HIGH` representation plus at most one on-demand `LOW`; all verified weak paths share `LOW` and recovery stops its bytes/frames. This does not prove one/two physical encoders or GPU release. An over-budget media path fails closed for that attempt while protecting `HIGH`, but a supported cohort that cannot start `LOW` reliably fails acceptance. SVC has no software fallback. - Keep decisions, snapshots, research, code, `AGENTS.md`, and `.codex/` in Git; rewrite memory/status in place. Research current primary sources before material work and reject speculative machinery. ## Current Recommendation - Keep desktop Chrome/Edge and the responsive Web viewer as the baseline. Validate Android Chrome and iOS Safari as leaves. -- Use direct host P2P for one or two viewers. Keep ADR-0004 off for broad production traffic: two mobile leaves can consume both host roots. Use the exact-room allowlist only on an isolated canary until its resource, quality, recovery, TURN, and browser/mobile gates pass. +- Use direct host P2P for one or two viewers. Keep ADR-0004 off broadly; use an exact-room canary until resource, quality, recovery, SFU/UDP, optional-TURN, and browser/mobile gates pass. - Browser relays resend remote `MediaStreamTrack` values and re-encode at each hop; WebRTC does not guarantee a shared encoder across peer connections, so measure the cost. - Keep browser experiments bounded; spike native RTP relay, encoded-object striping, SVC, and FEC separately. - ADR-0006 retains a fixed-`HIGH` native canary boundary, but its sole product-wiring run reached only host setup and one encoder output before the first-viewer decoded/rendered gate timed out. Downstream checkpoints were not retained, so the result is no-go-unclassified, not a diagnosed product bug. No native product code is accepted; revisit only through the staged evidence gate. -- Optional single-node SFU capacity is failure-only final fallback; it never defaults to whole-room fanout. Stable dual-TURN host roots are only a future exact-room shadow comparison against the same SFU roots, with real hop/egress and E2EE/operator accounting, until evidence amends ADR-0005. PR #12's whole-room SFU mode is superseded. -- Keep ADR-0005 routing default-off: discrete failures drive peer/SFU recovery, full host fanout uses break-before-make, and mobile/iPad clients stay leaves. PR #20 prewarm passes locally; public transport/load gates remain. +- ADR-0005 accepts SFU roots as the primary central fallback after direct/peer UDP, never default whole-room fanout. Optional TURN is issued only to an exceptional assigned edge; PR #12 is superseded. +- Keep the current failure-only controller default-off until config migration and exact-room gates pass. Preserve break-before-make, sticky healthy subtrees, mobile leaves, and PR #20 prewarm; public transport/load remains open. - Local reparenting is a later candidate: start with unassigned relay admission rescue; do not block current work. -- Prefer direct UDP, then TURN/UDP, with TURN/TCP required; optional TURN/TLS defaults to 5349, while 443 needs a dedicated address or validated L4/SNI routing. -- Treat video settings as ceilings and degradation as unclassified. Local host A+B now covers same-tick capture/outbound identity, windows, deltas, remote linkage, path, and nullable negotiated codec/profile token/allowlisted parameters/current stream `scalabilityMode`. Use it first to reproduce the reported Host-only recovery, then add minimal authenticated C, a two-state controller, and shared `LOW`. Never use UA or a composite score. +- 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 video settings as ceilings and degradation as unclassified. Local host A+B now covers same-tick capture/outbound identity, windows, deltas, remote linkage, path, and nullable negotiated codec/profile token/allowlisted parameters/current stream `scalabilityMode`. Use it first to reproduce the reported Host-only recovery, then add minimal authenticated C and test standard simulcast/LiveKit/SVC before custom `LOW`; this need not wait for ADR-0006. Never use UA or a composite score. - Reported poor movie/video audio is unclassified. After video A+B, separately diagnose audio A/B/C and A/V sync across capture settings, codec/fmtp, actual bitrate, loss, jitter, concealment, and jitter buffer. `maxBitrate` is not quality-up; do not expand runtime before evidence. See `docs/research/browser-screen-audio-quality.md`. - Do not add custom scene detection or dynamic-FPS control until WebRTC statistics and host resource measurements prove a material gap. Keep browser codec order until target hardware proves a more efficient common codec. - Keep room policy deployment-driven: public/password-only rooms are random and temporary; password plus SQLite enables sequential persistent rooms and reusable stopped links. @@ -52,7 +52,7 @@ Last updated: 2026-08-19 ## Provisional Quality Targets - Controlled direct path at RTT no more than 40 ms and loss no more than 1%: glass-to-glass p50 no more than 150 ms and p95 no more than 250 ms. -- Regional TURN/UDP under the same endpoint conditions: glass-to-glass p95 no more than 350 ms. +- Regional SFU/UDP roots under the same endpoint conditions: glass-to-glass p95 no more than 350 ms; optional TURN is measured separately. - First picture within 3 seconds after a watch request, excluding explicit permission time. - A 60 fps profile must not remain below 50 decoded fps for more than 5 seconds without a visible downgrade or limiting reason. @@ -62,7 +62,7 @@ These are measurement gates, not performance claims. - Sustainable viewer count by broadcaster and relay hardware, quality profile, network class, and media route; use instrumented 1/3/5/8 comparisons rather than a 1:8 claim. - Whether ADR-0004 passes fanout, re-encoding, depth-four latency, reparenting, silent-partition, and mobile-leaf gates. -- Whether ADR-0005 standby gains persist across public LiveKit UDP/ICE-TCP/TURN, rollback, reconnect, restart, egress, load, and browser-matrix gates. +- Which optional TURN/media-TCP transport and port, if any, survives ADR-0005's public UDP-first canary; current LiveKit ICE/TCP/coturn stays until migration passes. - Exact mobile lifecycle behavior and whether Windows per-application audio is required for the first release. - Project license and distribution model, which determines whether GPL/AGPL sources can move beyond study-only use. - Initial deployment regions and expected mainland China, Hong Kong, and overseas network mix. @@ -74,5 +74,5 @@ These are measurement gates, not performance claims. - Documentation index and current phase: `docs/README.md` and `docs/status.md` - Requirements and design: `docs/需求理解.md` and `docs/方案设计.md` - Media research: `docs/research/`, indexed by `docs/README.md` -- Architecture: ADR-0001/0002, proposed ADR-0004/0005, no-go proposed ADR-0006, accepted ADR-0007, and rejected/superseded ADR-0003 +- Architecture: ADR-0001/0002, proposed ADR-0004, accepted-but-unverified ADR-0005, no-go proposed ADR-0006, accepted ADR-0007, and rejected/superseded ADR-0003 - Deployment and maintenance: `docs/deployment.md`, `docs/maintenance.md`, `AGENTS.md`, and `.codex/` diff --git a/docs/research/advanced-peer-distribution.md b/docs/research/advanced-peer-distribution.md index 542fb25..7279bdd 100644 --- a/docs/research/advanced-peer-distribution.md +++ b/docs/research/advanced-peer-distribution.md @@ -35,7 +35,7 @@ overhead. Protocol headers, retransmission, and TURN overhead are additional. | Current full-stream browser chains | at most `2B` | zero except TURN edges | host up to two encoders; every relay encodes again | | Native full-stream RTP relay | at most `2B` | zero except TURN edges | relay zero encode; native host can share one encode | | Two encoded-object stripe trees | about `(1+r)B` | zero except TURN edges | host one encode; relay zero encode | -| SFU virtual parent to bounded roots | about `B` | root-only; see low-server model | host one publication; roots keep peer descendants | +| SFU virtual parent to bounded roots | measured `B_pub` | root-only; see low-server model | one publication may carry at most two active representations; physical encoder count remains measured evidence | | Full central SFU/MoQ fanout | about `B` | ingress `B`, egress `N*B` | comparison class, not the accepted fallback shape | Useful last-hop traffic remains approximately `N*B`; these routes only decide @@ -196,7 +196,8 @@ host -> other root This admits the waiting viewer while keeping host fanout two and browser relay fanout one. It must pass the existing depth, codec, authorization, and recovery -gates; otherwise the controller proceeds to the optional SFU or visible wait. +gates; otherwise the controller proceeds to the ADR-0005 SFU-root fallback or +visible wait. Eligibility is discrete: an active authenticated session, explicit relay capacity, compatible representation, a free downstream slot, acyclicity, and @@ -237,19 +238,21 @@ healthy TURN-to-direct optimization. The accepted product order is: -`direct host P2P -> accepted peer-assisted capability -> enabled SFU roots -> wait/fail` +`direct/peer UDP -> SFU roots -> optional exceptional-edge TURN -> wait/fail` Native full-stream relay and striped-object routes remain the bounded candidates above; passing their own gates may change capability, but does not insert them into the current product ladder. -The enabled SFU is a virtual parent for at most `R=2` roots. Roots retain -bounded peer descendants; only if no reliable relay root exists may a necessary -viewer consume one of the same root slots with zero descendants. There is no -second whole-room central-fanout budget. The detailed direct/direct, -direct/TURN, TURN/TURN shadow, and no-root matrix lives in -[Low-Server-Cost Media Routes](./low-server-media-routes.md); it does not alter -the sticky local-reparenting candidate above or ADR-0005's failure-only trigger. +The enabled SFU is normally a virtual parent for at most `R=2` roots, which +retain bounded peer descendants. A viewer that cannot attach behind any healthy +root may be separately admitted only under the explicit `E`/central-egress cap; +this is an exceptional compatibility budget, not unbounded whole-room fanout. +The SFU/UDP and optional selected-edge transport accounting lives in +[Low-Server-Cost Media Routes](./low-server-media-routes.md). ADR-0005's current +controller remains failure-only, while its accepted target also handles +admission with no eligible peer path; neither changes the sticky local +reparenting candidate above. The controller must be automatic and invisible. It uses explicit capability bits, route revisions, media generations, edge budgets, and discrete failures; diff --git a/docs/research/low-server-media-routes.md b/docs/research/low-server-media-routes.md index 7f7444f..82d043c 100644 --- a/docs/research/low-server-media-routes.md +++ b/docs/research/low-server-media-routes.md @@ -3,9 +3,9 @@ - Research date: 2026-08-19 - Scope: one broadcaster, at most eight trusted viewers, low latency, and host media fanout at most two -- Status: route screening for ADR-0004/ADR-0005; automatic failure-only hybrid - routing has a Draft implementation, while dual-TURN optimization and every - non-browser data-plane candidate remain separate, unimplemented experiments +- Status: ADR-0005 accepts SFU/UDP roots as the primary central fallback and + optional TURN only for exceptional edges; the current failure-only, + TURN-required controller and every non-browser data plane remain unverified ## Current Route Ladder @@ -23,7 +23,7 @@ The smallest current plan is: 4. Only if browser relay re-encoding is the isolated failure should another experiment add opt-in native volunteer encoded-RTP relays. 5. Keep a user-operated mini-SFU and a centrally operated single-node SFU as - optional capacity. The current ADR-0005 Draft controller uses that capacity + optional capacity. The current default-off ADR-0005 controller uses that capacity automatically only after deterministic peer recovery is exhausted. The SFU is a virtual parent for only one or two necessary roots; those roots continue bounded peer descendants. A necessary viewer may be a zero-descendant root @@ -72,20 +72,23 @@ still require measurement. This is an engineering accounting identity, not a named theorem. Let `B` be the measured useful media bitrate for one same-representation branch, `B_pub` the actual SFU publication bitrate, `B_i` root `i`'s selected bitrate, `N` the -viewer count, `R` the one or two seed/root viewers, and `B_edge` a descendant -edge's actual bitrate. -Useful last-hop delivery is approximately `N*B`. Ignoring protocol overhead: +viewer count, `R` the one or two normal roots, `D` their peer descendants, `E` +the separately admitted exceptional server-fed viewers, `B_exc,j` exception +`j`'s selected bitrate, and `B_edge` a descendant edge's actual bitrate. Thus +`N = R + D + E`. For the equal-representation normal-root baseline `E=0`, +useful last-hop delivery is approximately `N*B`. Ignoring protocol overhead: `host last-hop copies + peer last-hop copies + server last-hop copies = N * B` | Full-stream shape | Host upload | Viewer-relay upload | Central media traffic | | --- | ---: | ---: | ---: | -| Peer roots, direct edges | `R*B` | `(N-R)*B` | none | -| Peer roots, all `R` seed edges through TURN | `R*B` | `(N-R)*B` | TURN ingress `R*B` + egress `R*B` | -| SFU virtual parent to the same roots | `B_pub` | `(N-R)*B` | SFU ingress `B_pub` + egress `sum(B_i)` | +| Peer roots, direct edges | `R*B` | `D*B` | none | +| Peer roots, all `R` seed edges through TURN | `R*B` | `D*B` | TURN ingress `R*B` + egress `R*B` | +| SFU virtual parent to the same roots | `B_pub` | `D*B` | SFU ingress `B_pub` + egress `sum(B_i)` | | Same-representation full-room SFU | `B` | `0` | SFU ingress `B` + egress `N*B` | -The table's `R*B` and `(N-R)*B` rows are equal-representation screening cases. +The table's `R*B` and `D*B` rows are equal-representation screening cases with +`E=0`. With mixed root representations, peer host/seed-TURN traffic is `sum(B_i)` and descendant upload is `sum(B_edge)`. SFU host upload and ingress are `B_pub`, the sum of all actively published representations; if simulcast `HIGH` and `LOW` @@ -101,6 +104,14 @@ ingress and egress `B_i`; a relayed descendant edge adds both directions for that edge's actual bitrate. They are one logical useful payload copy but real physical hops, so NIC, service, and billing counters must never be folded. +If `E>0`, SFU root/exception egress is +`sum(B_i) + sum(B_exc,j)` and central SFU traffic is +`B_pub + sum(B_i) + sum(B_exc,j)`. Peer descendant upload remains +`sum(B_edge)`. Any TURN-relayed root, exception, or descendant leg adds TURN +ingress and egress equal to that leg's measured bitrate. The separately capped +exceptions are therefore never hidden inside the normal `R<=2` root budget or +the equal-representation formulas. + RTP/RTCP/SRTP, DTLS, ICE/TURN and IP headers, retransmission, FEC, and redundant paths only add traffic. W3C candidate-pair byte counters exclude some transport overhead, and a provider may bill ingress and egress differently, so formulas @@ -143,50 +154,109 @@ Never retain raw `errorText`, address, port, URL, candidate strings, SDP, or device/network identifiers. Stats members may be absent, and the pair-change counter is transport-lifetime state, so events and generation baselines prevent old connection or ICE-restart history from being attributed to a new route. -Use opaque generations only. This is a shadow diagnostic manifest, not a +Use opaque generations only. This is a bounded diagnostic manifest, not a backend telemetry schema or controller input. -## Dual-TURN Shadow Candidate +## SFU-First UDP And Optional Selected-Edge TURN -Use four explicit network scenarios; no threshold or optimizer is selected: +The accepted target is `direct/peer UDP -> SFU/UDP roots -> optional +selected-edge TURN -> bounded failure`. It keeps distribution after central +fallback: the SFU normally emits only one or two root copies, and roots keep +their peer descendants. Every endpoint has at most two downstream edges; +browser relays remain at one until re-encode/resource gates pass. Multiple +exceptional viewers that cannot attach behind any healthy root may require +additional server-fed edges, but only under a separate explicit central egress +and admission cap. -1. **Direct/direct roots:** retain peer distribution. -2. **Direct/TURN roots:** retain the mixed peer distribution; one relayed edge - does not justify an SFU. -3. **TURN/TURN roots:** after both host seed edges remain selected relay pairs - across a future reviewed stability window, emit only a shadow candidate. - The active route does not move under ADR-0005. A necessary useful-payload - condition for any host-upload claim is measured `B_pub < sum(B_i)`; real - hop counters must still prove the total benefit. -4. **No reliable relay root:** after deterministic recovery is exhausted, use - the existing failure-only SFU route; necessary viewers may consume the same - one-or-two root slots with zero descendants, never a second fanout budget. +STUN/ICE discovers and checks paths. RFC 8656 TURN allocates a relayed address +and continuously carries media; it is not a handshake helper. The target +therefore does not advertise coturn to every ordinary peer edge. If that +compatibility service is configured, a future controller may issue short-lived, +assignment/generation-bound credentials only for an exceptional ordinary +host-to-root or root-to-viewer transport after UDP is exhausted. Other healthy +UDP paths stay unchanged. -The evidence sequence is deliberately narrow: +Pinned LiveKit 1.13.5 includes authenticated embedded TURN, but those +credentials and relays belong to LiveKit participants and are advertised +participant-wide when configured. They can cover a publisher or subscriber +connection to the SFU; they are not Screener assignment/generation grants and +do not transparently serve an ordinary peer `RTCPeerConnection`. A selected +host/root-to-viewer TURN edge still needs independent coturn plus a new per-edge +grant/wire path. +Participant-wide LiveKit TURN is therefore only an isolated canary candidate; +unless the integration can isolate or extend it to satisfy the accepted +exceptional-edge scope, reject it rather than silently broadening the target. +LiveKit TURN cannot rescue an unavailable SFU. Independent coturn can carry a +separately authorized ordinary peer edge that bypasses the SFU, but TURN itself +does not choose or create that topology. -1. Use local `webrtc-internals` as manual ground truth for selected pairs and - redact SDP, addresses, candidates, and identifiers before retention. -2. Enable privacy-safe diagnostics for one exact `PEER_ASSISTED_ROOM_IDS` room - in shadow mode. It records a candidate but changes no media or protocol. -3. Run matched, non-simultaneous exact-room A/B sessions for two TURN roots and - one SFU publisher feeding the same one or two roots. This avoids exceeding - the live host-edge budget merely to benchmark both routes concurrently. -4. Compare real host upload, TURN/SFU ingress and egress, selected protocols, - RTT/loss/bitrate, first picture, steady latency, recovery, host/root load, - and the actual E2EE/operator boundary. The host-to-SFU publisher is an - independent ICE connection that may use ICE/UDP, ICE/TCP, or deployed - TURN/TLS. Retain every transport and service hop separately, including TURN - on publisher, subscriber-root, or peer-descendant edges; distinguish only - physical traffic totals from logical useful-payload copies. Thresholds - remain unknown. -5. Only a measured win plus an amended ADR-0005 may authorize an automatic - exact-room canary; broad rollout remains default-off. +The tracked deployment is intentionally still the old baseline. It exposes +LiveKit ICE/TCP 7881 and ICE/UDP mux 7882, omits embedded TURN, configures +coturn UDP/TCP, and the application refuses production startup without both +TURN transports. LiveKit 1.13.5 also defaults `allow_tcp_fallback` to true when +TCP/TURN-TLS is configured. Do not remove those listeners or variables until +the config migration and exact-room gate land. The target disables media TCP +by default; HTTPS/WSS remains TLS/TCP. -Ordinary SFU transport encryption lets the SFU operator access media. LiveKit -offers application E2EE for media/data, but signaling and APIs remain visible -and Screener does not yet implement key distribution. Compare the configured -boundary instead of treating E2EE as a checkbox. This candidate adds no route -score, controller trigger, wire field, backend telemetry system, or code. +No public port is selected by this decision. LiveKit documents ICE/UDP mux as +optional and its pinned sample recommends a multi-port UDP mux range at least +as wide as the CPU count for performance. Embedded TURN/UDP defaults to 3478 +and recommends 443 only when it does not conflict with HTTP/3/QUIC; TURN/TLS +has different certificate and 443 constraints. The current nginx template has +no HTTP/3 listener, but that fact alone does not prove one UDP port or UDP 443 +is the best production layout. + +For one equal representation of measured bitrate `B` and `R` roots: + +- `R` TURN seed edges: host upload `R*B`, server ingress `R*B`, server egress + `R*B`, total central traffic `2R*B`; +- one SFU publisher to `R` roots: host upload `B`, server ingress `B`, server + egress `R*B`, total central traffic `(R+1)*B`. + +At `R=1` useful-payload traffic is equal, so the identity alone gives neither +route a preference; CPU and allocation cost require the same-host benchmark. +At `R=2`, the equal-representation SFU case halves host upload and +central ingress and reduces total central traffic from `4B` to `3B`, with the +same `2B` egress. Mixed representations replace `B` with measured `B_pub` and +`sum(B_i)`; a HIGH+LOW publication may erase that advantage. Public LiveKit +benchmarks and the Jitsi profiling breakdown establish capacity and component +categories only. Different machines and implementations cannot support a +universal coturn/SFU CPU ratio or a claimed fixed percentage saving. + +One bounded exact-room gate owns rollout evidence: + +1. On the same host and NIC, compare coturn UDP and LiveKit SFU UDP at measured + 8 and 12 Mbps with one and two roots. Record CPU seconds/GiB, RX/TX bytes, + packets/s, RSS, host upload, p95/p99 forwarding latency, loss/recovery, and + final decoded quality. +2. Cover representative consumer networks with a temporary canary config that + removes `rtc.tcp_port`, closes public 7881, and configures no TURN/TCP or + TURN/TLS listener. Verify host and roots use SFU/UDP. In one separately + chosen restrictive-network case, prove an ordinary coturn grant affects only + its authorized generation. Test LiveKit participant-wide TURN separately and + do not label it selected-edge issuance. +3. Block all UDP. With the optional compatibility layer absent, show a bounded + explicit failure rather than a long pseudo-connection. Any later media TCP + mode is measured separately and cannot be marketed as a quality path. +4. Exercise root departure, reconnect, SFU unavailable, and rollback. Endpoint + downstream edges stay at most two, normal SFU roots at most two, separately + capped exceptional server edges stay bounded, and unaffected peer subtrees + do not migrate. +5. Correlate only selected candidate type/protocol/relayProtocol and opaque + generations; never upload raw SDP, candidate/address/IP, credentials, or + device identifiers. + +Ordinary TURN retains endpoint-to-endpoint DTLS-SRTP. Ordinary SFU transport +terminates DTLS-SRTP on both sides, so its operator can access media unless +Screener later implements application E2EE and key distribution. That accepted +tradeoff remains visible in deployment and UI claims. + +A peer root simultaneously re-publishing its received stream to the SFU and +serving peer children is only a later bounded experiment. The current browser +relay would decode and re-encode, and publication ownership adds another +failure domain. Do not add it to the controller until measurements prove a +specific consumer. No global score, continuous optimizer, geography, IP, UA, +or self-reported capability chooses these routes. On today's unicast Internet, a design cannot maximize all three of these for more than one viewer: @@ -208,8 +278,8 @@ lost. | Fixed two-chain browser relay | Host emits at most two copies; each relay emits at most one | Ordinary browser, but every relay decodes and re-encodes and adds a hop | Current default-off, maximum-eight-viewer spike | | Native shared-encode host | Host targets one encode for at most two standard WebRTC edges | libwebrtc public-API proxy risk spike, with Pion as fallback | Planned separate sender phase; still pays per-edge upload | | Native volunteer encoded-RTP relay | Each volunteer forwards one encoded copy | Native install, RTP/RTCP forwarding, packaging, and opt-in relay policy | Conditional experiment only if relay re-encoding is the sole browser-spike failure | -| SFU virtual parent | SFU emits only one or two root copies; roots keep peer descendants | Service pays measured root egress; host sends one publication | Default retained SFU shape; automatic only after peer failure under ADR-0005 | -| Zero-descendant SFU roots | SFU emits necessary viewer copies | Same root budget; service egress is at most `sum(B_i)` | Last resort only when no reliable relay root exists | +| SFU virtual parent | SFU normally emits one or two root copies; roots keep peer descendants | Service pays measured root egress; host sends one publication | Accepted primary central fallback after direct/peer UDP; current code is failure-only | +| Exceptional server-fed viewers | SFU/TURN emits necessary copies that no healthy root can distribute | Additional capped central egress | Explicit compatibility exception only; never unbounded whole-room fanout | | SVC plus multiple trees | Peers emit striped layer copies across several trees | Layer scheduling, reassembly, redundancy, and more churn state | Separate conditional spike; target endpoint upload near `B` | | Network coding | Peers or servers emit coded blocks | Generations, buffering, decoding, integrity, and a custom media plane | Trace/FEC spike only; optimize loss recovery, not clean bandwidth | | MoQ | Publishers and MoQ relays emit object copies | New transport, packaging, player, relay, and auth stack | Optional central-fallback benchmark; still pays server egress | @@ -289,6 +359,9 @@ Primary sources checked on 2026-08-19: under IETF Trust terms. - [TURN, RFC 8656](https://www.rfc-editor.org/rfc/rfc8656.html) - the relay is transport for client/peer traffic, not a room distribution topology. +- [WebRTC transports, RFC 8835](https://www.rfc-editor.org/rfc/rfc8835.html) - + browser transport capability requirements do not require an application to + advertise every supported fallback on every connection. - [WebRTC](https://www.w3.org/TR/webrtc/), [WebRTC Statistics](https://www.w3.org/TR/webrtc-stats/), [WebRTC Encoded Transform](https://w3c.github.io/webrtc-encoded-transform/#stream-processing), @@ -310,14 +383,22 @@ Primary sources checked on 2026-08-19: - [IP multicast, RFC 1112](https://www.rfc-editor.org/rfc/rfc1112.html) and [Source-Specific Multicast, RFC 4607](https://www.rfc-editor.org/rfc/rfc4607.html) - IETF standards under IETF Trust terms. -- [LiveKit](https://github.com/livekit/livekit) - Apache-2.0; the optional SFU +- [LiveKit](https://github.com/livekit/livekit) - Apache-2.0; the SFU reference, with no code copied into this research change. +- [LiveKit 1.13.5 configuration sample](https://github.com/livekit/livekit/blob/v1.13.5/config-sample.yaml), + [pinned configuration source](https://github.com/livekit/livekit/blob/v1.13.5/pkg/config/config.go), + [ports/firewall](https://docs.livekit.io/transport/self-hosting/ports-firewall/), + [deployment/embedded TURN](https://docs.livekit.io/transport/self-hosting/deployment/), + and [benchmark guidance](https://docs.livekit.io/transport/self-hosting/benchmark/) + - transport, port, authentication, and capacity boundaries; no source copied. - [LiveKit selective subscription](https://docs.livekit.io/transport/media/subscribe/) and [end-to-end encryption](https://docs.livekit.io/transport/encryption/) - official behavior and operator-boundary references. - [LiveKit client 2.22.0 `Room.prepareConnection`](https://github.com/livekit/client-sdk-js/blob/v2.22.0/src/room/Room.ts) and [official usage](https://github.com/livekit/client-sdk-js#usage) - Apache-2.0; API behavior was inspected, with no source copied. +- [Grozev, *Towards a Scalable Video Conferencing System*](https://publication-theses.unistra.fr/public/theses_doctorat/2019/Grozev_Boris_2019_ED269.pdf) + - one Jitsi profiling breakdown, not a coturn/LiveKit cross-system CPU ratio. No GPL/AGPL code was copied. Published papers and specifications support design analysis only; their presence here is not an implementation license. diff --git a/docs/research/native-shared-encode-sender.md b/docs/research/native-shared-encode-sender.md index b1acca8..f30f65c 100644 --- a/docs/research/native-shared-encode-sender.md +++ b/docs/research/native-shared-encode-sender.md @@ -187,8 +187,8 @@ paths on `HIGH`; and stop `LOW` after every weak path sustains the longer recovery window. The representation limit is two, never one per viewer. The independent host budget remains at most two downstream media edges. -If a qualified hardware/power-efficient second encoder is unavailable or its -measured game load is unacceptable, `LOW` fails closed for weak paths while +If a qualified hardware/power-efficient `LOW` path is unavailable or its +measured encoder/CPU/GPU game load is unacceptable, `LOW` fails closed for weak paths while healthy paths keep `HIGH`. Each path has only `HIGH` and `FALLBACK` state with asymmetric consecutive diff --git a/docs/research/peer-assisted-media.md b/docs/research/peer-assisted-media.md index 1696296..ff2b0df 100644 --- a/docs/research/peer-assisted-media.md +++ b/docs/research/peer-assisted-media.md @@ -23,11 +23,12 @@ cost is unavoidable in ordinary browsers: every relay decodes and re-encodes the screen stream. The first spike measures whether that cost is acceptable; it does not hide it or claim shared encoding. -The product preference is `direct P2P -> peer-assisted -> optional SFU`. Direct -P2P remains the simplest path for one or two viewers. The experiment assigns the -third and later viewers to peers automatically. Deployment decides whether SFU -capacity exists; when it does, the current ADR-0005 Draft controller uses it as -an automatic last fallback without asking host or viewers to select a topology. +The accepted flagship preference is `direct/peer UDP -> SFU-root fallback -> +optional exceptional-edge TURN -> bounded failure`. Direct P2P remains the +simplest path for one or two viewers. The experiment assigns the third and later +viewers to peers automatically. ADR-0005 makes SFU capacity part of the flagship +target while retaining peer descendants; its current default-off controller is +still failure-only and unconfigured in production. ## What Browsers Can Share @@ -212,7 +213,7 @@ current local senders still apply the setting but the UI reports that room sync is waiting for reconnect. The next successful host authentication reasserts the latest object; no acknowledgement state machine is added. -The optional SFU publisher follows the same boundary: first publication, +The configured SFU publisher follows the same boundary: first publication, successful track replacement, and live setting changes all configure the real `RTCRtpSender`, then retain requested/applied bitrate, frame rate, scale, and preference. Configuration rejection uses the existing rollback/fail-closed @@ -281,7 +282,7 @@ slot: Peer assistance distributes traffic; it does not eliminate it. Relay eligibility must therefore be visible and voluntary in any production design. The standalone ADR-0004 spike has no runtime capability flag and relies on -controlled join order. The stacked ADR-0005 Draft starts every viewer as a leaf, +controlled join order. The accepted ADR-0005 direction's current default-off controller starts every viewer as a leaf, then accepts an explicit per-session capacity of zero or one; its Web client reports detected mobile/iPad clients as leaves and desktop-class browsers as one-child relays. That conservative heuristic is still unverified on the real diff --git a/docs/research/realtime-quality-adaptation.md b/docs/research/realtime-quality-adaptation.md index c62854d..b166a25 100644 --- a/docs/research/realtime-quality-adaptation.md +++ b/docs/research/realtime-quality-adaptation.md @@ -192,8 +192,8 @@ starts one shared `LOW`; all weak paths reuse it while healthy paths remain on path recovers, stop `LOW`. The hard representation limit is two, never one per viewer. -`LOW` itself is conditional: if no qualified hardware/power-efficient encoder -path exists or the second encoder exceeds the measured CPU/GPU/game budget, the +`LOW` itself is conditional: if no qualified hardware/power-efficient media +path exists or the additional representation exceeds the measured CPU/GPU/game budget, the controller preserves `HIGH` and fails visibly for the weak path. It never buys weak-path recovery by degrading healthy paths. @@ -211,9 +211,10 @@ capacity. An ordinary non-scalable stream cannot yield a second independent quality by packet forwarding alone. The alternatives are a second representation, -scalable layers, or relay/SFU transcoding. Screener chooses the temporary second -representation first. SVC is conditional on a future strict-one-output need, -an exact negotiated mode, a positively established hardware or power-efficient +scalable layers, or relay/SFU transcoding. Screener tests standard capabilities +before custom media. The bounded SVC spike is scheduled now, but product +adoption remains conditional on a future strict-one-output need, an exact +negotiated mode, a positively established hardware or power-efficient path, and measured game performance. WebRTC-SVC permits the browser to return a different configured `scalabilityMode`; Media Capabilities reports support and expected smoothness/power efficiency for a specified configuration; WebCodecs @@ -221,8 +222,9 @@ defines `hardwareAcceleration` only as a hint the user agent may ignore. Therefore none is, by itself, proof of a particular hardware encoder, and a software SVC fallback must not be silent. -After trustworthy A+B/C and the native fixed-`HIGH` path, run three bounded -capability spikes. First, negotiate `HIGH`/`LOW` simulcast in one sender's +After trustworthy A+B/C, run three bounded capability spikes before custom +dual-representation media work; they do not wait for ADR-0006 native-sender +product acceptance. First, negotiate `HIGH`/`LOW` simulcast in one sender's initial envelope with `LOW` inactive and prove applied parameters, per-RID bytes/frames, and CPU/GPU/encoder release; separate PeerConnections have no portable shared-encode contract. Second, test at most two independently @@ -288,7 +290,7 @@ resolution, frame rate, or bitrate. then reads requested/applied bitrate, frame rate, scale, and preference. Rejection or browser rewriting is visible rather than console-only. - One strict room setting is last-wins for current/future peer relays and the - optional SFU publisher. Ordinary P2P keeps that state local and does not add + configured SFU publisher. Ordinary P2P keeps that state local and does not add it to the authenticated wire. - Pausing the picture disables the existing video track, producing black video without closing the room or media connection. Audio remains enabled. diff --git a/docs/research/webrtc-p2p-screen-sharing.md b/docs/research/webrtc-p2p-screen-sharing.md index 2f4da60..8196083 100644 --- a/docs/research/webrtc-p2p-screen-sharing.md +++ b/docs/research/webrtc-p2p-screen-sharing.md @@ -2,7 +2,7 @@ - 调研日期:2026-08-18 - 目标场景:一名玩家向少量熟人私密分享,观看者可用手机/桌面浏览器加入,低延迟,尽量不消耗媒体服务器带宽 -- 结论状态:标准 P2P 原型基线已采用;多人退化已触发独立的 peer-assisted 调研与 Draft SFU 对照,二者都未成为默认路径 +- 结论状态:本文记录已部署 PoC 的 P2P/coturn 基线。ADR-0005 与[低服务器成本媒体路由](./low-server-media-routes.md)已取代本文早期“每条 peer edge 必带 TURN”的旗舰建议;生产代码尚未迁移 ## 结论 @@ -16,7 +16,7 @@ | +---- 仅直连失败的观看者 ---- TURN relay -------+ -首选顺序:一至两名观看者直接 P2P -> 通过门槛后的 peer-assisted -> 部署已提供的可选 SFU。 +旗舰目标顺序:一至两名观看者 direct P2P -> 通过门槛后的 peer-assisted -> SFU virtual parent 服务必要 roots;当前生产仍未配置 peer assistance/LiveKit。 系统必须无感完成拓扑分配、恢复和必要迁移;大规模公开分享仍直接使用现有直播服务。 ``` @@ -32,7 +32,7 @@ | 产品 | 已公开的可靠事实 | 对本项目的含义 | | --- | --- | --- | | Discord Go Live | 使用 WebRTC,但媒体发往 Discord RTC Worker 后再转发给观看者;官方明确这是为了路由控制和隐藏用户 IP。桌面端有原生 C++ media engine、自研捕获/编码、多后端回退和硬件编码。 | Discord 的稳定性并不是纯 P2P 或纯浏览器免费获得的。它是未来质量上限参考,不是首版拓扑参考。 | -| TeamSpeak 6 | 官方技术回复说明用 WebRTC/ICE 做 P2P 屏幕分享;Windows 在 DX、Windows Game Capture 与传统捕获路径间选择。2026-04 官方称其 `turn.*` 主机实际仅启用 STUN,server-side SFU 仍在开发。 | 与当前目标最接近,也解释了 direct-only/STUN-only 在严格 NAT 下可能出现“同房间部分人能看、部分人不能看”;本项目必须从首版部署 TURN relay。 | +| TeamSpeak 6 | 官方技术回复说明用 WebRTC/ICE 做 P2P 屏幕分享;Windows 在 DX、Windows Game Capture 与传统捕获路径间选择。2026-04 官方称其 `turn.*` 主机实际仅启用 STUN,server-side SFU 仍在开发。 | 与当前目标接近,也解释了 direct-only/STUN-only 的部分可达风险;Screener 已部署 coturn 基线,旗舰迁移改由 SFU/UDP roots 兜底并把 TURN 设为可选兼容层。 | | KOOK / Oopz | 官方 SDK 清单只足以证明屏幕分享接入声网相关能力,没有公开具体媒体拓扑。 | 可以参考交互,不能把它们写成已经证实的 P2P 或 SFU 案例。 | 来源: @@ -53,7 +53,7 @@ - STUN 让客户端发现公网映射并产生 server-reflexive candidate。它不承载媒体,也不能保证穿过所有 NAT。 - ICE 测试 host、server-reflexive、peer-reflexive 和 relay candidates,并选择可工作的候选对。Trickle ICE 可以减少建连等待。 - TURN 在无法直连时转发完整媒体流,是 NAT 组件中真正产生高带宽成本的部分。 -- 生产最小候选集合是 STUN、TURN/UDP 和 TURN/TCP;可选再加入 TURN/TLS。整体偏好应表达 direct UDP -> TURN/UDP -> TURN/TCP -> optional TURN/TLS,但 ICE 可能交错或并发进行候选检查,应用不应手写严格串行计时器。TCP 在丢包时可能产生队头阻塞。 +- 当前 release `769de201f7cc` 的生产候选集合是 STUN、TURN/UDP 和 TURN/TCP,可选再加入 TURN/TLS;这是部署事实,不是新旗舰目标。ADR-0005 的迁移目标是 ordinary peer ICE/UDP -> SFU/UDP roots -> 可选兼容 transport -> bounded failure。ICE 仍可能交错或并发检查候选,应用不手写严格串行计时器;媒体 TCP 在丢包时可能产生队头阻塞。 不存在适用于所有用户的权威“P2P 直连率”。CGNAT、endpoint-dependent mapping、校园/企业防火墙、移动网络、IPv6 和地区运营商都会改变结果。首版必须通过 `getStats()` 统计自己的 `host/srflx/prflx/relay` 比例,而不是引用未经验证的行业百分比。 @@ -61,11 +61,11 @@ ICE 是按分享者与每一名观看者的网络组合独立选路,而不是整个房间只做一次连接。一个家庭宽带观看者可能成功 UDP 打洞,另一个处于 CGNAT、对称 NAT、校园网、企业代理或蜂窝网络的观看者却没有可用直连候选。因此同一房间天然可能出现不同结果。 -解决方式不是强迫所有人走服务器,也不是继续增加 STUN 地址,而是同时提供有效的 TURN 凭据和多种传输: +已部署 PoC 的处理方式是同时提供有效 TURN 凭据和多种传输;旗舰迁移后,下列 2/3 项只描述现行基线: 1. 将 direct UDP 设为最高优先级,成功者保持零媒体服务器路径。 -2. 同时提供有效的 TURN/UDP 与 TURN/TCP candidates,由 ICE 连通性检查和优先级选择路径;需要额外兼容性时再加入 `turns`,默认使用标准 TCP 5349。 -3. 通过统计确认最终选中的 candidate pair,而不是从应用计时顺序推断路径。 +2. 当前生产同时提供 TURN/UDP 与 TURN/TCP candidates;目标 ordinary peer edge 不再默认收到它们,UDP 失败时优先使用 SFU root,可选 coturn 只由新 per-edge grant/wire 授予异常连接。 +3. 通过统计确认最终选中的 candidate pair;LiveKit participant TURN 与 ordinary peer coturn 分别记录,不能从应用计时顺序推断路径。 4. 网络切换或候选对失效时执行 ICE restart,超时后重建该 peer connection。 5. 在 UI 和诊断中区分“直连”“服务器中继”“正在恢复”和明确失败原因。 @@ -90,7 +90,7 @@ ICE 是按分享者与每一名观看者的网络组合独立选路,而不是 | P2P 星型 | `N * B` | 直连时接近 0 | 最符合成本目标;适合少量朋友 | | P2P + TURN | 仍为 `N * B` | 每条中继约 `B` 入 + `B` 出 | 只解决不可达,不解决分享者上行 | | Peer-assisted 两链 | 分享端最多约 `2 * B` | 直连边接近 0;TURN 仍按边计费 | 每名 viewer 最多转发一份;标准 browser relay 逐跳解码/重编码 | -| SFU | 约 `B` | `B` 入 + `N * B` 出 | 分享者轻松,但服务器承担全部观看流量 | +| 整房 SFU(仅对照) | 约 `B` | `B` 入 + `N * B` 出 | 分享者轻松,但服务器承担全部观看流量;当前 root-shape 目标另按 `B_pub`、`B_i` 和 `B_exc,j` 计量 | | MCU | 约 `B` | 另有解码、合成、重编码 | 本场景没有合成需求,不采用 | 以 `8 Mbps` 的 1080p60 目标流为例: @@ -120,10 +120,10 @@ IETF 对 mesh/SFU 的拓扑说明见 [RFC 7667](https://www.rfc-editor.org/rfc/r - 若一开始就有多条 `relay`,应提示服务器带宽正在增加。 - 桌面和手机观看者使用同一个 Web 播放端;分享者不要求朋友安装完整客户端。 -新的产品目标要求分享端 fanout 永远不超过二。Proposed ADR-0004 用一个可整体删除的实验验证第三名及后续 viewer 能否由客户端转发;`main` 中已合并 PR #17 按 ADR-0005 提供默认关闭的自动可选 SFU 兜底。已关闭 PR #12 的显式整房 SFU 模式已被取代,不再作为当前方案;自动路由通过真实门槛前仍不可部署,超过小房间上限时仍建议使用外部直播服务。观察项包括: +新的产品目标要求分享端 fanout 永远不超过二。Proposed ADR-0004 用一个可整体删除的实验验证第三名及后续 viewer 能否由客户端转发;`main` 中已合并 PR #17 按 ADR-0005 提供默认关闭、failure-only 的 SFU-root 控制器。旗舰目标要求 SFU 只服务必要 roots 并保留 peer descendants,但配置迁移与真实门槛尚未通过。已关闭 PR #12 的显式整房 SFU 模式已被取代,不再作为当前方案;自动路由通过真实门槛前仍不可部署,超过小房间上限时仍建议使用外部直播服务。观察项包括: - 正常工作负载持续超过实测可承载的 P2P 人数。 -- 第一名或多名观看者已使用 TURN,继续 P2P 会重复占用服务器上行。 +- 第一名或多名观看者已使用 TURN;这只进入成本观测,不直接触发自动迁移。 - 分享者上行安全余量不足。 - 分享者因 CPU/encoder 限制降质。 - 产品开始要求隐藏好友之间的 IP。 @@ -244,7 +244,7 @@ WebRTC 媒体本身使用 DTLS-SRTP 加密;但 direct P2P 仍会让这组可 WebRTC 标准没有承诺固定毫秒延迟。工程目标必须带网络条件,并使用画面时间码或高速摄像机测量玻璃到玻璃延迟。60 fps 的单帧周期是 16.7 ms,端到端延迟还包含采集等待、编码、单程网络、jitter buffer、解码和显示。 -建议原型目标:受控 direct/RTT <= 40 ms/丢包 <= 1% 时 p50 <= 150 ms、p95 <= 250 ms;区域 TURN/UDP p95 <= 350 ms。先测量,再决定是否需要原生或 SFU。 +建议目标:受控 direct/RTT <= 40 ms/丢包 <= 1% 时 p50 <= 150 ms、p95 <= 250 ms;区域 SFU/UDP root p95 <= 350 ms,可选 TURN 单独测量。先测量,再决定原生优化。 ## 安全与成本防护 @@ -263,7 +263,7 @@ WebRTC 标准没有承诺固定毫秒延迟。工程目标必须带网络条件 | [Tailchat Meeting](https://github.com/msgbyte/tailchat-meeting) | React 捕获生命周期与会议产品交互参考 | Apache-2.0,但媒体基于 mediasoup/SFU,不能作为当前 P2P 拓扑底座 | [ScreenShare.ts](https://github.com/msgbyte/tailchat-meeting/blob/master/app/src/features/ScreenShare.ts)、[media.ts](https://github.com/msgbyte/tailchat-meeting/blob/master/packages/sdk/src/client/media.ts) | | [WebRTC samples](https://github.com/webrtc/samples) | 官方浏览器 API 最小示例 | BSD 风格。用于理解 API,不是产品框架 | [getDisplayMedia](https://github.com/webrtc/samples/tree/gh-pages/src/content/getusermedia/getdisplaymedia)、[peer connection examples](https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection) | | [PeerJS](https://github.com/peers/peerjs) | 快速 P2P 原型与简单信令抽象 | MIT。原型快,但产品最终可能需要直接控制 RTCPeerConnection 和统计 | [PeerJS server](https://github.com/peers/peerjs-server) | -| [coturn](https://github.com/coturn/coturn) | 生产 STUN/TURN fallback | BSD-3-Clause。P2P-first 必需基础设施 | [turnserver 文档](https://github.com/coturn/coturn/blob/master/README.turnserver)、[Docker](https://github.com/coturn/coturn/blob/master/docker/coturn/README.md) | +| [coturn](https://github.com/coturn/coturn) | 当前生产 STUN/TURN 与未来可选 ordinary-peer fallback | BSD-3-Clause。当前 release 必需,旗舰目标按异常 edge 可选 | [turnserver 文档](https://github.com/coturn/coturn/blob/master/README.turnserver)、[Docker](https://github.com/coturn/coturn/blob/master/docker/coturn/README.md) | | [Peer Calls](https://github.com/peer-calls/peer-calls) | 同一应用中的 mesh/SFU 双模式 | Apache-2.0;维护速度较慢,适合参考而非首选底座 | [mesh.go](https://github.com/peer-calls/peer-calls/blob/master/server/mesh.go)、[sfu.go](https://github.com/peer-calls/peer-calls/blob/master/server/sfu.go)、[iceauth.go](https://github.com/peer-calls/peer-calls/blob/master/server/iceauth.go) | | [Broadcast Box](https://github.com/Glimesh/broadcast-box) | 未来专用一对多 SFU,WHIP 推流/WHEP 播放 | MIT。比会议型 SFU 更贴近单路广播 | [Broadcast.tsx](https://github.com/Glimesh/broadcast-box/blob/main/web/src/components/broadcast/Broadcast.tsx)、[simple watcher](https://github.com/Glimesh/broadcast-box/blob/main/examples/simple-watcher.html) | | [LiveKit](https://github.com/livekit/livekit) | 生产级区域 SFU、SDK、内置 TURN、鉴权 | Apache-2.0。未来需要稳定 SFU 时的首选完整底座 | [屏幕共享](https://docs.livekit.io/transport/media/screenshare/)、[turn.go](https://github.com/livekit/livekit/blob/master/pkg/service/turn.go) | @@ -320,8 +320,8 @@ WebRTC 标准没有承诺固定毫秒延迟。工程目标必须带网络条件 1. 首版坚持 P2P-first,但明确只服务小房间;默认接入上限为八名、可配置 1 至 16,真实 1:8 测量完成前不把它写成性能承诺。 2. Web 先行,目标 Windows Chrome/Edge;把 1080p60 写成 best effort,同时提供降档。 -3. 从第一天部署 coturn,并验证 direct、TURN/UDP、TURN/TCP 以及移动网络切换;这是避免“部分好友永远看不了”的必要条件。TURN/TLS 按部署需要选配,默认 5349,443 只作为受限网络增强。 +3. 保留当前 coturn 部署直到 ADR-0005 迁移 gate 通过。旗舰默认验证 direct/peer UDP 与 SFU/UDP roots;ordinary peer coturn 不再向房间常驻广告。LiveKit participant TURN 单独 canary,pinned participant-wide 形态不满足 selected-edge 目标,无法隔离或扩展时即拒绝。 4. 观看端优先做成免安装响应式 Web;分享端先 Web 验证,再按捕获/音频实测升级 Electron。 5. 产品代码优先直接使用浏览器 WebRTC API;借鉴 MiroTalk BRO 和 Screego,不在许可证未定前直接 fork GPL/AGPL 代码。 6. 分享端 hard fanout 为二;一至两名 viewer 走直接 P2P,第三名及以后只通过 ADR-0004 的 bounded peer-assisted spike 验证,不得隐藏回退为更多 host 连接。 -7. 可选 SFU 保持可逆且非默认;部署提供后可被后续路由控制器自动用作最后兜底。peer-assisted 若未通过逐跳重编码负载、延迟、兼容和换父门槛就整体放弃,不通过 encoded custom media、评分器、多树或自研 RTP 来挽救。 +7. SFU 保持 root-only、可逆且非整房默认;旗舰部署通过 canary 后由 ADR-0005 控制器自动用作中央兜底。peer-assisted 若未通过逐跳重编码负载、延迟、兼容和换父门槛就整体放弃,不通过 encoded custom media、评分器、多树或自研 RTP 来挽救。 diff --git a/docs/status.md b/docs/status.md index 3a3eced..c30f6ea 100644 --- a/docs/status.md +++ b/docs/status.md @@ -13,8 +13,8 @@ unconfigured, so production remains one host connection per viewer and P2P/TURN. - Capture precedes room creation; live source/quality changes preserve peers and picture pause keeps audio/connections. Quality defaults to `maintain-resolution` with balanced/fluid options. - Optional `ACCESS_PASSWORD` uses a stateless 12-hour HMAC HttpOnly Strict cookie; host auth stays internal and signaling role-bound. - Without `ROOM_DATABASE_PATH`, rooms are temporary. With it and the password, sequential links persist after stop; SQLite stores only room ID and host-token digest. -- Direct ICE is preferred independently per media edge. Authenticated TURN/UDP and TURN/TCP are required production fallbacks; TURN/TLS is optional. -- Hidden routing is `direct P2P -> peer-assisted -> optional SFU roots`; TURN is selected per edge, and SFU roots retain bounded peer descendants. Exact-room canaries use `PEER_ASSISTED_ROOM_IDS`; production is off. +- Production still requires coturn UDP/TCP. Target media is peer UDP -> SFU/UDP roots -> optional edge TURN -> failure; HTTPS/WSS stays TLS/TCP. +- SFU normally feeds one or two roots. Downstream cap is two, browser relay one; exceptional central edges need a separate cap. Production routing is off. - Configured fallback adds a non-secret standby URL and token-free DNS/TLS warmup; absent configuration adds no field, import, request, participant, or edge. - Each viewer advertises relay capacity 0/1; detected mobile/iPad clients are leaves. Withdrawal does not move a healthy edge. Browser relays stay one-child and host two-child. - Production has memory-only video settings and manual readback. Automatic `HIGH`/`FALLBACK` plus one shared on-demand `LOW` is accepted but not deployed; host A+B exists, while C/actions do not. Browser audio is request/presence-only. @@ -26,7 +26,7 @@ unconfigured, so production remains one host connection per viewer and P2P/TURN. - Synthetic Chrome 151 720p30 propagated balanced/clarity to one host and three viewers without peer changes; decoding and 2/1 fanout continued. This proves control continuity only. - Chrome 151/LiveKit localhost A/B measured cold active/render at 1.481/2.257 seconds versus standby 0.200/0.320 with host edges at two. It includes SDK/prewarm and is not public-network evidence. - Atomic activation passed health/access, exact bundle, SQLite preservation, services, and logs; the old release is rollback-ready. Responsive 320/375/390 px checks passed without live media. -- Host A+B aligns capture with one unique outbound RTP, interval/identity/deltas, `remoteId`, and path. Its same-transport codec exposes nullable MIME/self-describing profile token/allowlisted fmtp/`scalabilityMode`; tokens imply no quality or capability conclusion, and no raw fmtp/SDP/stats are added. Stale, ambiguous, reset, or cross-transport evidence stays unknown. Independent review found no P1/P2 issues; the final local check passed 22 files/299 tests and both builds. +- Host A+B aligns capture with one outbound RTP, interval/deltas, `remoteId`, path, and nullable transport-bound codec/profile/fmtp/`scalabilityMode`. It infers no quality/capability, retains no raw fmtp/SDP/stats, and treats stale, ambiguous, reset, or cross-transport data as unknown. Review found no P1/P2; 22 files/299 tests and both builds passed. - HTTPS/WSS, access and room/WebSocket auth, renewal, public STUN, and authenticated TURN/UDP/TCP relay-only paths pass; TURN/TLS is off. - Draft #16/#18/#22/#23/#25/#28 passed isolated experiments only. ADR-0006 reached host setup/one encoder output, then timed out before viewer 1 decoded/rendered. @@ -38,9 +38,9 @@ unconfigured, so production remains one host connection per viewer and P2P/TURN. - Real audio, heterogeneous clients, mobile lifecycle, quality/pause, room `1` republish, and sustained profiles are unverified. Poor movie/video audio is user-reported but unclassified; after video A+B, diagnose capture settings, codec/fmtp, actual bitrate, loss, jitter, concealment, jitter buffer, and A/V sync. - Production `769de201f7cc` reportedly sustains native `qualityLimitationReason=bandwidth` and severe blur with one capable LAN/direct viewer; only Host refresh restores quality. Within the five-second grace, stable-clientId Viewer refresh retains its `peerId`/`HostPeer`, while Host refresh rebuilds peers/capture; churn persistence is also reported. This prioritizes but does not prove sender/PC/GCC/capture generation over network or lifecycle causes. - ADR-0007 remains incomplete. Host A+B codec fields exist, but browser support and the reported case are unverified; authenticated C, the controller, and `LOW` are absent. Weak paths must share one `LOW`; fail closed protects `HIGH` only exceptionally, and an unreliable supported cohort fails acceptance. -- Browser relays re-encode. ADR-0006 is no-go-unclassified: downstream checkpoints were not retained; two-edge/FIFO/TURN/product gates remain unrun. Spikes prove one WebCodecs object, not hardware; GCC+RTX is no-go and no-RTX weakens stats. -- The endpoint budget remains host/relay at most two downstream edges; the browser path is host two/viewer one. -- ADR-0005 permits SFU only after peer failure. Stable dual-TURN roots are shadow-only; exact-room TURN-root/SFU-root cost, recovery, and trust evidence is absent. +- Browser relays re-encode. ADR-0006 is no-go-unclassified: downstream checkpoints and two-edge/FIFO/TURN gates are absent. Spikes prove one WebCodecs object, not hardware; GCC+RTX is no-go and no-RTX weakens stats. +- Browser fanout is host two/viewer one; any accepted endpoint relay stays capped at two downstream edges. +- ADR-0005 accepts SFU-root fallback, but code is failure-only/TURN-required; UDP-first config, edge grants, bounded failure, cost/trust, and public canary are absent. - Pinned LiveKit 1.13.5 Dynacast enables all qualities at or below the room's maximum request, so a `HIGH` root is expected to keep `LOW` enabled. It remains a bounded rejection/verification spike, not evidence that on-demand `LOW` can stop. - The corrected standby smoke is localhost/headless/video-only; public DNS/TLS reuse, transport, audio, shaping, load, mobile, endurance, and sub-25 ms overlap remain open. @@ -54,11 +54,11 @@ moving healthy peers or breaking the host-edge cap. Only proven stuck generations justify guarded/cooldown recovery; never reconnect periodically or raise ceilings blindly. -After video A+B, separately run the movie/video audio A/B/C and sync diagnosis -in `docs/research/browser-screen-audio-quality.md`; `maxBitrate` is not a quality -fix. Then add minimal authenticated video C, the two-state controller, and -on-demand shared `LOW`. Later route work starts with manual ICE ground truth and -shadow-only exact-room TURN-root/SFU-root comparisons, not automatic migration. +After video A+B, run the audio A/B/C and sync diagnosis; `maxBitrate` is not a +quality fix. Then add authenticated video C and test standard +simulcast/LiveKit/SVC before custom `LOW`, independently of ADR-0006. The app +owns only the two-state policy; WebRTC/LiveKit owns congestion/layers. Later one exact room must pass peer/SFU UDP, optional +compatibility, and bounded-failure gates before changing coturn/TCP. ADR-0004 still requires a full-resolution 30-minute `1/3/5/8` network, resource, quality, latency, recovery, and browser/mobile-leaf matrix. @@ -75,7 +75,7 @@ decode/render, 2/3-viewer, and TURN evidence. ## Blockers And Decisions -Peer assistance and automatic routing remain No-Go until ADR-0004/0005 pass. +Peer assistance and automatic routing remain No-Go until ADR-0004/0005 migration gates pass. Without LiveKit, two mobile leaves can fill both host roots; use an isolated exact-room canary only. Native shared encode and striping remain separate experiments. diff --git a/docs/方案设计.md b/docs/方案设计.md index 6efc14e..3fe30cf 100644 --- a/docs/方案设计.md +++ b/docs/方案设计.md @@ -151,7 +151,7 @@ Host 本地预览直接绑定现有 capture stream,不创建 viewer、`RTCPeer `frameRate` 约束和 sender 的 `maxFramerate` 都是目标或上限;浏览器仍控制实际捕获、重复帧处理、编码和拥塞降级。静态画面可能由浏览器减少实际编码数据,但标准没有保证每个实现以同一种方式节省采集或 GPU 开销。本 PoC 不添加画面差分、定时改约束或自定义动态 FPS 状态机,先用 `framesEncoded`、实际发送码率、`totalEncodeTime`、质量受限原因和主机资源占用测量真实问题。 -自动质量控制采用 ADR-0007 的按需双表示,而不是全房间按最差链路降档。正常状态只运行一套共享 `HIGH` 编码;第一条被真实统计持续证明无法承受的观看路径出现时,必须启动唯一一套共享 `LOW` 并迁移所有已验证弱路径,健康路径继续接收 `HIGH`。最后一条弱路径经过更长的稳定恢复窗口回到 `HIGH` 后关闭 `LOW`。表示数量硬限制为 `HIGH + at most one on-demand LOW <= 2`,不能按 viewer 增长为 N 套编码。若没有合格硬件/power-efficient 编码路径或第二编码实测破坏游戏 CPU/GPU 预算,则当次 fail closed,保留 `HIGH` 并明确显示弱路径降级或失败;这只是异常止损。supported sender cohort 若不能可靠按需启动 `LOW`,则自动质量控制验收失败,必须优化编码路径或明确标记不支持,不能长期忽略弱路径。 +自动质量控制采用 ADR-0007 的按需双表示,而不是全房间按最差链路降档。正常状态只运行一套 active `HIGH` representation;第一条被真实统计持续证明无法承受的观看路径出现时,必须启动唯一一套共享 `LOW` 并迁移所有已验证弱路径,健康路径继续接收 `HIGH`。最后一条弱路径经过更长的稳定恢复窗口回到 `HIGH` 后关闭 `LOW`。active representation 数量硬限制为 `HIGH + at most one on-demand LOW <= 2`,不能按 viewer 增长为 N 套。标准浏览器不保证跨 PeerConnection 共用一个物理编码器,inactive 层也不保证释放 GPU;实例数、CPU/GPU 与释放行为必须实测。若没有合格 hardware/power-efficient 路径或启动 `LOW` 实测破坏游戏预算,则当次 fail closed,保留 `HIGH` 并明确显示弱路径降级或失败;这只是异常止损。supported sender cohort 若不能可靠按需启动 `LOW`,则自动质量控制验收失败,必须优化编码路径或明确标记不支持,不能长期忽略弱路径。 每条观看路径只维护 `HIGH`/`FALLBACK` 两态。`HIGH -> FALLBACK` 需要多个连续采样窗口中的带宽不足、冻结或解码跟不上;`FALLBACK -> HIGH` 使用不同门槛和更长的稳定窗口,避免来回抖动。不把 RTT、CPU、码率、设备信息等压成一个综合分数。具体窗口和阈值必须由真实质量矩阵确定,而不是先写死一个猜测。 @@ -169,15 +169,15 @@ Host 本地预览直接绑定现有 capture stream,不创建 viewer、`RTCPeer Viewer 可通过已鉴权、绑定当前房间/路径代次的控制面请求 `LOW`,但请求只是一条观测输入,必须限频和去重,并与 sender 的 Transport-CC/GCC、实际码率、RTT、丢包、重传及编码限制、派生协商 codec/profile/parameters(适用时含 `scalabilityMode`)和 viewer 的实际解码/丢帧/冻结表现交叉验证。UA、平台和设备型号不参与质量决策;现有 mobile/iPad 检测只用于保守设置 relay capacity。 -若以后要求任何时刻严格只有一个编码输出,才单独评估 SVC:每条连接可独立选择基础层或增强层,但必须读回实际 codec/`scalabilityMode`,确认目标硬件或 power-efficient 编码路径,并通过游戏性能和亚秒延迟验收。浏览器的硬件偏好不是保证,不能静默回退为软件 SVC;常驻 SVC 不是默认方案。普通非分层编码包不能只靠转发生成另一档画质,实现路径差异只能用第二表示、SVC 或 relay/SFU 转码,当前优先第二表示。 +固定排期的 SVC spike 只验证标准能力和资源边界;只有以后要求任何时刻严格一个编码输出时才可采用 SVC 产品路径。每条连接可独立选择基础层或增强层,但必须读回实际 codec/`scalabilityMode`,确认目标 hardware/power-efficient 路径,并通过游戏性能和亚秒延迟验收。浏览器的硬件偏好不是保证,不能静默回退为软件 SVC;常驻 SVC 不是默认方案。普通非分层编码包不能只靠转发生成另一档画质,实现路径差异只能用第二表示、SVC 或 relay/SFU 转码,当前优先验证标准表示能力。 质量控制不放宽媒体拓扑预算:host 仍最多两条下游媒体边;native relay 只转发选定表示的编码包,不解码重编码。后续双树或条带化可把 host 上传从约两份完整码率推向一份加必要冗余,但不阻塞按需双表示的实现。 -标准表示能力的顺序固定在完整 A+B/C 证据和原生 fixed-`HIGH` 通过之后:再依次做同 sender simulcast、最多两个 LiveKit root 的 Dynacast 验证/否决和 SVC spike。具体语义与资源门槛由 ADR-0007 和[实时质量调研](./research/realtime-quality-adaptation.md)持有,任何方案都不得复用 PR #28 的 minimum-of-two 或绕过 stock WebRTC congestion control。 +标准表示能力排在完整 A+B/C 证据之后、任何自定义双表示媒体实现之前,并可与 ADR-0006 native sender 复核并行:依次做同 sender simulcast、最多两个 LiveKit root 的 Dynacast 验证/否决和 SVC spike。具体语义与资源门槛由 ADR-0007 和[实时质量调研](./research/realtime-quality-adaptation.md)持有,任何方案都不得复用 PR #28 的 minimum-of-two 或绕过 stock WebRTC congestion control。 -## TURN 配置与轮换 +## 媒体传输配置与可选 TURN -应用服务在房间鉴权后生成 coturn REST 凭据: +当前已部署版本仍在房间鉴权后生成 coturn REST 凭据: ```text expiry = floor(unix_seconds) + ttl @@ -185,15 +185,15 @@ username = expiry + ":" + opaque_room_and_peer_subject credential = base64(HMAC-SHA1(shared_secret, username)) ``` -`TURN_SHARED_SECRET` 只存在于应用服务和 coturn。浏览器只收到短期 username/credential、STUN/TURN URLs 和 `expiresAt`。生产环境缺少 STUN、TURN secret、显式 TURN/UDP URL 或显式 TURN/TCP URL 时应用启动失败;开发环境允许 STUN-only,但界面必须标记 TURN 未配置。 +`TURN_SHARED_SECRET` 只存在于应用服务和 coturn。浏览器只收到短期 username/credential、STUN/TURN URLs 和 `expiresAt`。这是 release `769de201f7cc` 的现行实现和部署契约:生产缺少 STUN、TURN secret、显式 TURN/UDP URL 或显式 TURN/TCP URL 时启动失败。在新的传输策略代码和 exact-room canary 合入前,部署人员不得仅依据目标文档删除这些变量。 -TURN/TLS 是可选能力,不是生产启动门槛。启用时默认使用 RFC 7065 的标准 TCP 5349;若为了受限网络改用 443,则 TURN 主机必须有独立公网 IP,或通过明确支持 TURN 的 L4/SNI 入口分流,不能与普通 HTTPS 进程直接争用同一 IP:443。普通 Cloudflare HTTP 代理只处理 HTTP/HTTPS,不转发 TURN;需要 Cloudflare 四层代理时必须单独评估 Spectrum 等支持该协议的服务。 +已接受的旗舰目标把 STUN 和 SFU/UDP 作为基线,把 TURN 改为默认未配置的极端网络兼容层。完整 TURN 配置缺失表示正常禁用且不显示提醒;任一部分存在但不完整必须启动即失败。ordinary Host-to-root 或 root-to-viewer 例外需要新 coturn per-edge grant/wire,短期凭据绑定 assignment/connection generation,不能在房间鉴权时广播给普通 peer edges。LiveKit publisher/subscriber 到 SFU 属于另一 ICE 域;pinned 1.13.5 embedded/external TURN 当前向 LiveKit participant 广告,不是 Screener selected-edge grant,也不能在 SFU 宕机时恢复媒体。它仅是 canary candidate,若不能隔离/扩展到 accepted exceptional-edge scope 就拒绝。两条兼容路径必须分开配置、鉴权和 canary,不能把 embedded TURN 写成 ordinary peer coturn 的替代。 -凭据过期前,客户端通过已认证 WebSocket 请求新配置,并对现有连接调用 `setConfiguration()`。已有 TURN allocation 通常不会因凭据时间点到达而立即终止,但后续 ICE restart 必须使用新凭据。 +目标媒体梯级是 `direct/peer UDP -> SFU ICE/UDP -> optional selected-edge compatibility transport -> failure`。媒体 TCP 默认不启用;具体 ICE/TCP、TURN/UDP、TURN/TCP 或 TURN/TLS 组合只能由目标网络 canary 选择。HTTPS/WSS 始终使用 TLS/TCP,不属于媒体 TCP。clarity/balanced/fluid 只改变编码策略,不能选择 UDP/TCP;TCP 队头阻塞可能放大实时视频 RTT、冻结和 GCC 带宽限制,因此不得把媒体 TCP 描述为高清路径。当前 tracked LiveKit 样例仍配置 ICE/TCP 7881 且 pinned 1.13.5 允许 TCP fallback;在迁移 PR 前这仍是未验收的现行候选,不是假装已经关闭。 -候选由 ICE 按优先级和连通性检查选择,不承诺 JavaScript 严格串行执行 UDP、TCP 或可选 TLS 尝试。界面报告最终选中的 candidate type、ICE protocol,以及本地为 relay 时的 TURN protocol,而不是推测回退步骤。`turn:` over TCP 没有客户端至 TURN 的 TLS 封装,但被中继的 WebRTC 媒体仍由 DTLS-SRTP 保护。TURN 是单条 ICE 边的 transport,peer-assisted 与 SFU 才是拓扑;一个 selected relay pair 不改变其他边的父子关系。 +候选由 ICE 按优先级和连通性检查选择。界面只报告实际 selected candidate type、ICE protocol,以及本地为 relay 时的 TURN protocol,不推测 NAT 类型或回退步骤。TURN 是单条 ICE 边的 transport,peer-assisted 与 SFU 才是拓扑;一个 selected relay pair 不改变其他边的父子关系。普通 TURN 保留 WebRTC endpoints 间的 DTLS-SRTP,而普通 SFU 终止两侧 DTLS-SRTP;没有应用层 E2EE 时 operator 可接触 SFU 媒体,这一产品取舍必须保持可见。 -可选 SFU 只作为虚拟 parent 向总计一至两个必要 roots 发布;每个可靠 root 收到媒体后继续服务既有 sticky descendants。host/native root 最多两条下游媒体边,browser root/relay 最多一个 child;没有可靠 relay root 时,必要 viewer 只作为同一上限内的零 descendants root。host→SFU publisher 是独立 ICE 连接,所有 TURN/SFU/endpoint 物理 hop 都按[低服务器成本媒体路由](./research/low-server-media-routes.md)逐项计量。双 host seed 稳定 TURN 仍只允许精确房间 shadow;ADR-0005 保持 failure-only。 +SFU 是主要中央兜底,但正常只作为 virtual parent 向一至两个必要 roots 发布;每个可靠 root 收到媒体后继续服务既有 sticky descendants。host/native root 最多两条下游媒体边,browser root/relay 最多一个 child。无法挂到健康 root 的多个极端 viewers 只能通过独立 `E`/central-egress cap 准入,不能藏进 normal-root 预算。host→SFU publisher 是独立 ICE 连接,所有 TURN/SFU/endpoint 物理 hop 都按[低服务器成本媒体路由](./research/low-server-media-routes.md)逐项计量。当前控制器仍是 default-off、failure-only;新的 UDP-first 梯级必须先通过 ADR-0005 exact-room canary,不能直接翻转生产开关。 ## 可观测性 @@ -206,7 +206,7 @@ TURN/TLS 是可选能力,不是生产启动门槛。启用时默认使用 RFC - `connectionState` / `iceConnectionState`; - 选中候选对的 local/remote candidate type 与 ICE `protocol`,任一端为 `relay` 即标记中继; - 本地 relay candidate 的 `relayProtocol`;规范禁止远端 candidate 暴露该字段,因此不推断远端使用 TURN/UDP、TCP 还是 TLS; -- 后续双 TURN shadow 只按[低服务器成本媒体路由](./research/low-server-media-routes.md)的 generation-baselined、privacy-safe ICE manifest 记录;先以本地脱敏 `webrtc-internals` 建立 ground truth,不新增后台遥测; +- 后续 exact-room SFU/UDP 与可选兼容 transport 只按[低服务器成本媒体路由](./research/low-server-media-routes.md)的 generation-baselined、privacy-safe ICE manifest 记录;先以本地脱敏 `webrtc-internals` 建立 ground truth,不新增后台遥测; - `currentRoundTripTime`、发送码率和 `availableOutgoingBitrate`; - 实际帧率、分辨率和丢包; - codec、编码实现、最近采样区间内的每帧编解码耗时和 `qualityLimitationReason`;同一连接不并发采样,首个样本、无新帧、统计对象换代或计数器重置时显示未知并重新建立基线,不用连接生命周期均值掩盖后续过载。 @@ -235,7 +235,7 @@ TURN/TLS 是可选能力,不是生产启动门槛。启用时默认使用 RFC - 全站密码以定长摘要配合常量时间比较;access cookie 由服务端 HMAC 签名且不含账号或可查询的 session ID。host token 只保存摘要并以常量时间验证;TURN secret 仅作为 HMAC key 存在于服务端;认证和建房响应设置 `Cache-Control: no-store`。 - HTTP body、WebSocket payload、总连接数、未鉴权连接数、输出缓冲、房间总数、观看人数和 candidate 缓存都有硬上限;无法解析的 upgrade request-target 在未鉴权边界返回 HTTP 400,不允许异常逃逸到进程顶层。 - 应用日志不得主动记录 IP;反向代理和 coturn 为提供网络服务必然可见源地址,部署时需设置最短必要留存与访问控制。 -- TURN 应配置 `use-auth-secret`、`realm`、`fingerprint`、`no-multicast-peers`、私网/loopback peer 拒绝、配额和出口监控;禁止匿名 relay。 +- 若部署启用独立 coturn,必须配置 `use-auth-secret`、`realm`、`fingerprint`、`no-multicast-peers`、私网/loopback peer 拒绝、配额和出口监控;禁止匿名 relay。未启用 TURN 是目标配置的正常状态。 - 分享者直连会向可信观看者暴露网络地址。需要隐藏 IP 时才提供强制 relay 诊断选项,并明确其服务器带宽成本。 ## 验证计划 @@ -245,9 +245,9 @@ TURN/TLS 是可选能力,不是生产启动门槛。启用时默认使用 RFC - AccessSession 的密码开关、12 小时签名 cookie、过期、篡改、cookie 属性和无服务端 session 状态; - RoomStore 的随机临时房间、TTL、host token 摘要验证、停止分享等待、默认八人上限、自定义 1 至 16 边界和超额拒绝; - SQLite 房间库从 `1` 自增、重启恢复、持久房间无过期时间、数据最小化、数据库路径无全站密码时启动失败; -- TURN HMAC 固定向量、TTL、生产环境缺少 STUN 或 TURN/UDP+TCP 覆盖时启动失败,并接受可选 TURN/TLS; +- 现行 TURN HMAC 固定向量、TTL 与生产 TURN/UDP+TCP 强校验保持回归覆盖;目标配置迁移另测 STUN 必需、TURN 全空正常禁用、部分配置 fail-fast、ordinary peer 的 per-edge coturn grant/wire,以及 LiveKit participant TURN 独立边界; - 协议非法 JSON、未知消息、错误方向、跨房间目标和 payload 限制; -- 质量协议的 host 授权、严格字段与数值范围、last-wins、当前/未来 relay、可选 SFU 和普通 P2P wire 兼容;sender 的四字段 requested/applied 读回、拒绝与持续受限提示; +- 质量协议的 host 授权、严格字段与数值范围、last-wins、当前/未来 relay、已配置 SFU 和普通 P2P wire 兼容;sender 的四字段 requested/applied 读回、拒绝与持续受限提示; - HTTP/WS 集成:密码模式下 host/viewer gate、建房和 upgrade 只认 cookie 且无建房 Bearer 旁路;一名 host、多名 viewer、定向 offer/answer/candidate、连接上限、grace 内恢复、显式 restart/rebuild、离开和停止分享后的等待状态; - 客户端策略与 stats 解析:会话替换不重连,本地 TURN protocol 与 ICE protocol 分离,远端 TURN transport 保持未知; - TypeScript 类型检查、Vite production build 和仓库 hygiene。 @@ -255,8 +255,8 @@ TURN/TLS 是可选能力,不是生产启动门槛。启用时默认使用 RFC 人工矩阵: - 同机双标签页与同 LAN direct; -- 真实跨网络、强制 relay、同房间 direct + relay; -- 封 UDP 后验证必需的 TURN/TCP;若部署启用 TURN/TLS,再分别验证默认 5349 或可选 443; +- 真实跨网络、同房间 direct/peer UDP + SFU/UDP root; +- CGNAT、双 NAT、移动热点和普通家宽下使用临时 canary 配置移除 `rtc.tcp_port`、关闭公网 7881 且不配置 TURN/TCP/TLS,验证 host 与 roots 使用 SFU/UDP;人为封锁 UDP 时必须有界明确失败。只在部署选择兼容层时另测 LiveKit participant TURN 或 ordinary peer coturn; - Windows Chrome/Edge 的音轨有/无; - Android Chrome 与 iOS Safari 的播放手势、旋转、前后台和网络切换; - 一名分享者、八名异构观看者连续 30 分钟,同时记录较低人数基线、实际上行、编码负载和首帧时间。真实 1:8 未完成前不得把默认上限写成性能承诺。 @@ -269,8 +269,9 @@ TURN/TLS 是可选能力,不是生产启动门槛。启用时默认使用 RFC 1. 建立单 package 工程、共享 schema、配置与 CI。 2. 完成 RoomStore、建房 API、WebSocket 鉴权和定向信令。 3. 完成屏幕捕获、逐观看者 PeerConnection、观看播放和手动质量档。 -4. 完成 TURN 短期凭据、ICE 配置刷新、连接恢复和 stats 面板。 -5. 完成自动测试、真实浏览器烟测、部署说明、状态与记忆更新。 +4. 完成现行 TURN 短期凭据、ICE 配置刷新、连接恢复和 stats 面板。 +5. 完成 automatic peer/SFU controller 与 exact-room canary 边界。 +6. 用有界网络矩阵把默认媒体梯级迁移到 direct/peer UDP、SFU/UDP、可选异常 edge 兼容 transport,再更新配置与部署。 任何一步只实现当前消费者需要的接口。不得为未来账号、第三种媒体拓扑或原生 sender 预建抽象层。 @@ -285,7 +286,7 @@ room code 规则且不重复的 ID;非法配置启动即失败。未配置或 `HybridMediaRouter`、房间质量同步或 LiveKit 兜底,鉴权 wire、定向信令、停止、 重连和删除必须逐项保持普通 P2P 行为。该边界不增加 UI、用户选择或百分比框架。 -产品硬目标是分享端任何时刻最多两条下游媒体边。系统按一个或两个观看者直接 P2P、更多观看者 peer-assisted、部署已提供的 SFU 最后兜底的顺序自动分配和恢复,分享者与观看者不选择拓扑。TURN 只在每条逻辑边内独立兜底;SFU 作为虚拟 parent 只送总计一至两个必要 roots,roots 之后仍优先沿受限 peer descendants 扩散;没有可靠 relay root 时,必要 viewer 只作为同一总上限内的零 descendants root。`main` 已包含 default-off 的跨模式控制器,但生产未启用且 ADR-0004/0005 尚未验收。 +产品硬目标是任一非服务器节点任何时刻最多两条下游媒体边。系统按一个或两个观看者直接 P2P、更多观看者 peer-assisted、部署 SFU 中央兜底的顺序自动分配和恢复,分享者与观看者不选择拓扑。默认媒体 transport 只使用 UDP;SFU 作为 virtual parent 正常只送一至两个 roots,roots 之后仍优先沿受限 peer descendants 扩散;多个无法挂到健康 root 的极端 viewers 只可使用另设上限的中央 edge。可选 TURN 只按需给异常 assignment/connection generation,不默认发给 ordinary peer edges。`main` 已包含 default-off、failure-only 的跨模式控制器,生产也仍使用现行 coturn 强配置;两者尚未迁移到目标梯级,ADR-0004/0005 尚未验收。 实验只实现一个由服务端确定的广度优先拓扑:host 最多两个 child,每名 viewer 最多一个 child,因此形成两条按加入顺序平衡的 sticky 链。父节点只按深度和 `serverJoinSequence` 选择第一个空 slot。新 viewer 加入时不搬动已有节点;parent 离开时只把失去 parent 的 subtree root 重新分配到第一个空 slot,其 descendants 保持连接。该规则不采集或组合 RTT、带宽、CPU、地理位置与质量评分,也不主动重平衡。没有 parent 时明确等待或拒绝,不能创建分享端第三条边。 diff --git a/docs/需求理解.md b/docs/需求理解.md index 08e68ff..749f621 100644 --- a/docs/需求理解.md +++ b/docs/需求理解.md @@ -4,7 +4,7 @@ ## 需求概述 -构建一个面向熟人游戏场景的低延迟屏幕共享工具。分享者可以在私密房间内分享游戏、应用窗口或显示器画面及声音,多名朋友可以即时观看。系统采用类似 Photon Host Mode 的产品抽象:中心服务负责房间、身份、确定性拓扑和建立连接,媒体优先级依次为小规模直接 P2P、peer-assisted 转发、部署已提供的可选 SFU;STUN 协助打洞,直连失败的单条边才使用 TURN。系统必须自动、无感地完成分配、故障恢复和必要的路线迁移,分享者与观看者不手动选择拓扑。分享端 Web 优先并允许后续 Electron/原生增强;观看端必须可通过桌面或手机浏览器打开邀请链接,不绑定专用客户端。 +构建一个面向熟人游戏场景的低延迟屏幕共享工具。分享者可以在私密房间内分享游戏、应用窗口或显示器画面及声音,多名朋友可以即时观看。系统采用类似 Photon Host Mode 的产品抽象:中心服务负责房间、身份、确定性拓扑和建立连接,媒体优先级依次为小规模直接 P2P、peer-assisted 转发、部署提供的 SFU virtual parent;STUN 协助打洞,SFU 只向一至两个必要 roots 送流,roots 继续分布式服务其 peer descendants。TURN 是部署可选的极端网络 transport 兼容层,不是普通 peer 边的默认候选或独立拓扑。系统必须自动、无感地完成分配、故障恢复和必要的路线迁移,分享者与观看者不手动选择拓扑。分享端 Web 优先并允许后续 Electron/原生增强;观看端必须可通过桌面或手机浏览器打开邀请链接,不绑定专用客户端。 当前 PoC 默认允许一名分享者和最多八名观看者,部署者可在 1 至 16 之间配置上限,超额观看者会被明确拒绝。八人只是当前容量默认值,不是已验证的性能承诺;真实 1:8 上行、编码和稳定性仍须测量。产品不承担公开直播或大规模观看,相关需求交给 OBS/Twitch 等成熟直播服务。 @@ -18,11 +18,11 @@ - [ ] 屏幕采集:分享者可主动选择显示器或应用窗口,开始、停止、临时暂停画面和切换分享来源。Host 本地预览必须直接使用 capture stream,不得为预览建立 viewer/媒体边或消耗 TURN、SFU、应用服务器 media egress;预览仍可能产生本地合成、缩放和 GPU 显示成本,不能把隐藏预览称为停止采集或编码。 - [ ] 游戏声音:Windows Chrome/Edge 目标环境可请求系统音频;没有返回音轨时必须明确提示,而不是静默无声。 - [ ] 一对多观看:一名分享者可向配置允许的多名观看者发送画面;默认上限为 8,可配置范围为 1 至 16。当前 PoC 的逐观看者连接是测量基线,不再是超过两名观看者时的目标拓扑。 -- [ ] 分享端 fanout 硬上限:分享端任何时刻最多维护两条下游媒体边,包括加入、断线、换父和恢复过程。第三名及后续观看者由系统自动分配给通过验收的 peer-assisted 拓扑,必要时自动回退到部署已提供的可选 SFU;两者都不可用时才明确拒绝,不能隐藏地回退为分享端第三条连接。TURN 只替代无法直连的单条 ICE 边,不定义房间拓扑;SFU 只作为虚拟 parent 向一至两个必要 root 送流,媒体到达 root 后其余可行观看者仍优先沿受限 peer descendants 扩散。只有没有可靠 relay root 时,中央服务才可把必要 viewer 作为零 descendants 的 SFU root,仍计入同一个一至二 root 总上限,不能把 SFU fallback 默认为整房 fanout。 +- [ ] 端点 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 的实验门槛。 -- [ ] NAT 兜底:每条 P2P 边以及 SFU publisher/subscriber connection 独立进行 ICE;生产环境必须提供 STUN、TURN/UDP 与 TURN/TCP candidates,通过优先级偏好直连和 UDP,同时允许无法使用 UDP 的网络选择 TCP。某条边选择 TURN 只改变该边的 transport,不能把其他边或整房静默改成服务器拓扑。 -- [ ] 质量控制:保留 1080p60(8 Mbps)、1080p30(5 Mbps)、720p30(3 Mbps)三档推荐上限,默认采用清晰优先;分享期间可直接换档或应用有界高级设置,不重新选择来源或重建健康连接。Peer-assisted relay 与可选 SFU 必须接收同一个严格校验的房间级权威设置,覆盖当前和以后建立的 sender;普通 P2P 信令结构保持不变。任何设置都只是浏览器上限和偏好,必须读回实际 sender 参数并显示参数拒绝、改写和持续受限原因。不做自定义动态 FPS、综合评分或周期性全房间调档。 -- [ ] 按需双表示:健康房间只运行一套共享 `HIGH` 编码;第一条观看路径被同一时段的 sender 与 viewer 统计持续证明无法承受时,系统必须启动唯一一套由全部弱路径共用的 `LOW` 并迁移弱路径。健康路径继续使用 `HIGH`,全部弱路径稳定恢复后关闭 `LOW`。编码表示硬上限为二,禁止按观看者增长为 N 套,也禁止把最差路径的目标应用到整个房间。没有合格硬件/power-efficient 路径或第二编码造成不可接受负载时,当次必须 fail closed:保护 `HIGH` 并明确显示弱路径降级或不可用;这只是异常止损,supported sender cohort 若不能可靠按需启动 `LOW`,则自动质量控制验收失败,必须优化编码路径或明确标记不支持。 +- [ ] NAT 与传输兜底:STUN 必须可用。普通 direct/peer 边默认只使用 ICE/UDP;其路径不可用时改由 SFU virtual parent 承担必要 root,而不是给每条 peer edge 常驻 TURN candidates。旗舰默认媒体路径为 `direct/peer UDP -> SFU ICE/UDP -> 明确失败`。部署者可为已验证的受限网络额外启用短期鉴权 TURN。普通 Host 到 peer root 或 root 到 viewer 的异常边需要新增、绑定当前 assignment/connection generation 的独立 coturn grant/wire;一个 TURN seed root 收到媒体后仍优先服务 bounded peer descendants。LiveKit publisher/subscriber 到 SFU 是另一 ICE 域,pinned 1.13.5 的 embedded/external TURN 当前按 LiveKit participant 广告,不是 Screener selected-edge grant。两者必须分开配置、鉴权和验收。媒体 ICE/TCP、TURN/TCP 和 TURN/TLS 不是默认质量路径。HTTPS/WSS 仍使用 TLS/TCP。全部可选 TURN 配置缺失表示正常禁用且不提示,部分配置必须启动即失败。 +- [ ] 质量控制:保留 1080p60(8 Mbps)、1080p30(5 Mbps)、720p30(3 Mbps)三档推荐上限,默认采用清晰优先;分享期间可直接换档或应用有界高级设置,不重新选择来源或重建健康连接。Peer-assisted relay 与已配置的 SFU 必须接收同一个严格校验的房间级权威设置,覆盖当前和以后建立的 sender;普通 P2P 信令结构保持不变。任何设置都只是浏览器上限和偏好,必须读回实际 sender 参数并显示参数拒绝、改写和持续受限原因。不做自定义动态 FPS、综合评分或周期性全房间调档。 +- [ ] 按需双表示:健康房间只运行一套 active `HIGH` representation;第一条观看路径被同一时段的 sender 与 viewer 统计持续证明无法承受时,系统必须启动唯一一套由全部弱路径共用的 `LOW` 并迁移弱路径。健康路径继续使用 `HIGH`,全部弱路径稳定恢复后关闭 `LOW`。active representation 硬上限为二,禁止按观看者增长为 N 套,也禁止把最差路径的目标应用到整个房间。标准浏览器不保证跨 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` 或网络标识。 @@ -32,34 +32,34 @@ ### P1 - 重要功能 -- [ ] 实验路由按房间灰度:部署开启 `PEER_ASSISTED_MEDIA` 时可用严格的房间 ID allowlist 只让指定房间进入 peer-assisted 与可选 SFU 路径;同一进程内未命中房间的鉴权 wire、P2P 信令、质量拒绝、停止、重连和删除行为必须保持不变。缺失或空 allowlist 为兼容已有部署而表示全部房间,因此隔离灰度必须显式填写至少一个合法 ID;非法或重复配置启动即失败。该能力仅由部署控制,不增加 UI、用户选择、百分比发布或第二套路由器。 +- [ ] 实验路由按房间灰度:部署开启 `PEER_ASSISTED_MEDIA` 时可用严格的房间 ID allowlist 只让指定房间进入 peer-assisted 与已配置的 SFU 路径;同一进程内未命中房间的鉴权 wire、P2P 信令、质量拒绝、停止、重连和删除行为必须保持不变。缺失或空 allowlist 为兼容已有部署而表示全部房间,因此隔离灰度必须显式填写至少一个合法 ID;非法或重复配置启动即失败。该能力仅由部署控制,不增加 UI、用户选择、百分比发布或第二套路由器。 - [ ] 质量控制扩展:推荐档位仍是默认入口;折叠高级区只允许 720p/1080p/1440p 分辨率 ceiling、15 至 60 的整数 FPS ceiling、2 至 12 Mbps 视频码率 ceiling,以及清晰/平衡/流畅三种偏好。直播中显式应用后才生效,避免滑杆产生连续控制流量。当前 Web 发送端不提供音频声道、采样率、Opus 码率、stereo、DTX 或 FEC 控件:display capture 未把声道和采样率定义为可移植控制,标准音频 `maxBitrate` 只是可能导致更低质量或停播的发送上限,其他项依赖 codec/fmtp 协商而没有可移植 sender setter。除非真实音频矩阵证明用户问题、标准 API 能应用并读回且实际结果可独立观测,否则不得为这些控件新增应用层 SDP munging 或用默认值制造伪控制。实际质量持续低于所选上限时必须显示可解释的限制原因;在真实拥塞、编码器和接收端证据完成前,不把任一组合称为“完美平衡”。 - [ ] 本地预览性能候选:在相同动态场景中 A/B 测量 Host 动态预览开/关的 CPU、GPU、encode time 和实际 FPS。只有稳定收益且捕获、编码和远端画面不受影响时,才允许直播开始后默认显示静止缩略图或隐藏动态预览并提供按需展开;未测前不宣称收益。 - [ ] Peer-assisted 媒体实验:分享端最多两个 child、每名 viewer 最多一个 child;信令服务只按深度和服务端加入序号确定性地形成两条 sticky 平衡链,不加入综合评分或主动重平衡。每个 viewer 将收到的标准 remote track 通过新的 `RTCPeerConnection` 转发,因此会逐跳解码和重编码;必须测量而不能称为 shared encode。服务端只在受房间数限制的内存状态中保存当前严格校验的质量设置,不写入 SQLite;host 鉴权后先重报设置,在线 viewers 接收更新,后加入者从鉴权快照取得当前值。该功能在 ADR-0004 的实验门槛通过并由后续 ADR 正式接受前不得成为产品路径。 - [ ] 原生 shared encode 分享端:后续独立的 Electron/原生分享端阶段必须保证一份兼容编码输出扇出到最多两套独立 WebRTC packetizer,并由单独 ADR 和测量约束。它降低分享端重复编码负载,但不消除每条出边各自的上传带宽。浏览器不保证跨 `RTCPeerConnection` 复用编码器,因此当前 Web spike 不实现、也不能宣称满足 shared encode 保证。 -- [ ] 原生 shared encode relay:具备原生 relay 能力的 audience 同样最多维护两条下游媒体边,并应让一份兼容编码输出供两条边复用。普通浏览器 audience 继续作为 leaf 或使用当前会逐跳重编码的实验路径,不伪称 shared encode。 -- [ ] 条件式 SVC:仅当后续需求要求任何时刻严格只有一个编码输出时才评估可伸缩编码,让各连接独立选择基础层或增强层。该 spike 只能排在完整 A+B/C 证据与原生 fixed-`HIGH` 之后,必须读回 requested/applied codec/`scalabilityMode`,由 Media Capabilities 提供 power-efficient 候选证据并通过真实游戏性能验证;不得静默回退为高负载软件 SVC,常驻 SVC 不是默认方案。 +- [ ] 原生 shared encode relay:具备原生 relay 能力的 audience 同样最多维护两条下游媒体边,并应复用兼容编码输出而不再次编码。普通浏览器 audience 继续作为 leaf 或使用当前会逐跳重编码的实验路径,不伪称 shared encode。 +- [ ] 条件式 SVC:在完整 A+B/C 证据之后、任何自定义双表示媒体实现之前执行有界 viability spike,并可与 ADR-0006 native sender 复核并行。只有后续需求要求任何时刻严格一个编码输出时才可采用 SVC 产品路径;必须读回 requested/applied codec/`scalabilityMode`,由 Media Capabilities 提供 power-efficient 候选证据并通过真实游戏性能验证,不得静默回退为高负载软件 SVC,常驻 SVC 不是默认方案。 - [ ] 分包/分层多树实验:把编码包、对象或可独立解码层拆到多个 peer 路径以分散上传,只能在独立 spike 中验证。必须保持亚秒级,处理多父重组、排序、丢包恢复、关键帧、同步和节点退出;未通过门槛前不进入产品路由控制器。 -- [ ] 可选 SFU:部署者可选择是否提供单节点 SFU 资源;配置后由 `main` 中 PR #17 的路由控制器把它作为 P2P 与 peer-assisted 故障恢复穷尽后的自动兜底,而不要求用户选模式。它保持默认关闭、可逆,只向总计一至两个必要 root 出口;root 优先承担受限 peer descendants,没有可靠 relay root 时必要 viewer 只作为同一总上限内的零 descendants root。已关闭 PR #12 的显式整房 SFU 模式已被取代,不是当前选项;两条 host seed 都稳定选择 TURN 时只进入脱敏 shadow 评估,不改变 ADR-0005 的 failure-only 触发语义。 +- [ ] SFU-root 中央兜底:产品部署目标必须提供经 canary 验收的单节点 SFU 资源;系统在 direct/peer UDP 不可用或明确拓扑预算无法接纳时自动把它作为主要中央兜底,不要求用户选模式。它只向总计一至两个必要 roots 出口;root 优先承担受限 peer descendants,没有可靠 relay root 时必要 viewer 只作为同一总上限内的零 descendants root。已关闭 PR #12 的显式整房 SFU 模式已被取代。当前 PR #17 实现仍是 default-off、failure-only 且生产未配置;按 ADR-0005 的 exact-room canary 验收并修改配置契约后,才能启用新的默认梯级。 - [ ] 分享端桌面封装:用 Electron 固定 Chromium 版本、改善选源/热键/系统音频体验,同时保留 Web 观看端。 - [ ] Windows 应用音频:仅捕获所选游戏及其子进程的音频,避免把语音聊天或通知声一并分享。 -- [ ] 连接自检:在开始分享前验证信令、STUN、TURN/UDP、TURN/TCP 及部署实际配置的可选 TURN/TLS 可达性,并给出不暴露凭据的诊断结果。 -- [ ] 受限网络增强:部署者可选配 TURN/TLS;默认使用标准 TCP 5349,只有独立公网 IP 或经过验证的 L4/SNI 分流可用时才占用 TCP 443。 +- [ ] 连接自检:在开始分享前验证信令、STUN、SFU ICE/UDP 及部署实际启用的可选 TURN 或媒体 TCP 可达性,并给出不暴露凭据的诊断结果。未配置可选兼容层不是告警。 +- [ ] 受限网络增强:部署者仅在明确选择覆盖 UDP 受限网络且 canary 证明收益时,才可启用短期鉴权 TURN 或媒体 TCP;它只能用于控制器选中的异常 transport,不能成为所有普通 peer 边的默认候选,也不能被描述为更高清的传输方式。 - [ ] 运行时编解码选择:根据端点能力和统计数据选择实际硬件编码的 H.264、VP8、VP9 或 AV1,并始终保留兼容回退。 - [ ] 诊断页面:用户可导出不含敏感地址和凭据的连接诊断信息。 ### P2 - 可选功能 - [ ] 原生 Windows 捕获辅助程序,使用 WGC/DDA、WASAPI 和硬件编码器降低复制与编码开销。 -- [ ] 多地区 TURN 调度与按地区、运营商统计直连率。 +- [ ] 多地区 SFU-root 调度,以及可选 TURN 兼容层的按地区、运营商可达率。 ## 非功能需求 -- 性能:在 RTT 不高于 40 ms、丢包不高于 1% 的受控 P2P 路径中,玻璃到玻璃延迟目标为 p50 不高于 150 ms、p95 不高于 250 ms;区域 TURN/UDP 路径的 p95 目标不高于 350 ms。 +- 性能:在 RTT 不高于 40 ms、丢包不高于 1% 的受控 P2P 路径中,玻璃到玻璃延迟目标为 p50 不高于 150 ms、p95 不高于 250 ms;区域 SFU/UDP root 路径的 p95 目标不高于 350 ms。可选 TURN 单独测量,不能借用该目标宣称通过。 - 启播:观看者请求观看后,除用户授权耗时外,3 秒内出现首帧。 - 流畅性:选择 60 fps 档位时,不能在没有降档或提示的情况下连续 5 秒低于 50 个解码帧每秒。 -- 成本:除按边选择的 TURN 中继外,直连和 peer-assisted 媒体不经过应用服务器;STUN 与信令仅承担小流量。候选筛选使用[低服务器成本媒体路由](./research/low-server-media-routes.md)的 `B`/`B_pub`/`B_i`、`N`、`R` 模型;验收必须保留 host/root NIC、每条 TURN ingress/egress、SFU ingress/egress 和供应商计费的真实 hop bytes,不得用有效载荷近似折叠物理流量。 +- 成本:direct/peer UDP 不经过应用服务器;SFU 正常只承担 publisher ingress 与一至两个 root egress,另行准入的极端 viewer 必须受独立中央 egress cap 约束。候选筛选使用[低服务器成本媒体路由](./research/low-server-media-routes.md)的 `B`/`B_pub`/`B_i`、`B_exc,j`、`N=R+D+E` 模型;验收必须保留 host/root/exception NIC、每条可选 TURN ingress/egress、SFU ingress/egress 和供应商计费的真实 hop bytes,不得用有效载荷近似折叠物理流量。 - 安全:应用控制面全程 HTTPS/WSS;P2P/TURN 边在 WebRTC 端点间使用 DTLS-SRTP,普通 `turn:` over TCP 不额外套 TLS,但不移除该媒体层加密。普通 SFU 是逐跳 DTLS-SRTP,只有另行实现并验收应用层 E2EE 才能对 SFU operator 保持媒体端到端机密。TURN 使用短期凭据、配额和速率限制;仓库不得包含真实密钥。 - 隐私:P2P 会向房间内好友暴露网络地址,首版威胁模型仅覆盖可信熟人;若要求隐藏 IP,必须切换为服务器转发。 - 可维护性:代码、需求、调研、项目记忆、`AGENTS.md` 和 `.codex/` 均在 Git 中跟踪;开发配置不得依赖单一操作系统的绝对路径。 @@ -73,7 +73,7 @@ - 默认八名观看者和可配置的 1 至 16 范围是接入容量边界,不承诺任意机器、档位或网络都能稳定承载该人数,也不承诺跨洲网络下的固定低延迟。 - 浏览器规范要求每次由用户主动选择分享源,权限不能永久保存;纯网页不能保证指定游戏窗口或始终获得系统音频。 - 1080p60 是能力目标,实际分辨率、帧率和硬件编码由浏览器、GPU、驱动、画面复杂度及网络共同决定。 -- 已部署首版仍是标准 WebRTC P2P。Peer-assisted spike 只复用标准 remote-track WebRTC,不加入 Encoded Transform/DataChannel/WebCodecs 媒体协议,也不自研 UDP、编解码器、拥塞控制、FEC/RTX、多树、转码、codec ladder 或综合评分器;需要其中任一项即放弃本次浏览器 relay 实验。独立计划中的 native shared encode sender 不在本 spike 实现,也不能挽救其他拓扑门槛失败。跨 P2P、peer-assisted 与可选 SFU 的自动路由是产品要求,但由单独 ADR 约束;当前 spike 尚未实现跨模式迁移或短时双发。 +- 已部署首版仍是标准 WebRTC P2P。Peer-assisted spike 只复用标准 remote-track WebRTC,不加入 Encoded Transform/DataChannel/WebCodecs 媒体协议,也不自研 UDP、编解码器、拥塞控制、FEC/RTX、多树、转码、codec ladder 或综合评分器;需要其中任一项即放弃本次浏览器 relay 实验。独立计划中的 native shared encode sender 不在本 spike 实现,也不能挽救其他拓扑门槛失败。跨 P2P、peer-assisted 与 SFU-root 的自动路由是产品要求,但由单独 ADR 约束;当前 spike 尚未实现跨模式迁移或短时双发。 - GPL/AGPL 项目在许可证策略确认前仅用于研究,不能直接复制到计划闭源的产品中。 ## 验收标准 @@ -86,13 +86,15 @@ - Given `ROOM_DATABASE_PATH` 已配置但 `ACCESS_PASSWORD` 为空,When 应用启动,Then 配置校验明确失败,不能在公开网站暴露可枚举的持久房间。 - Given `ROOM_DATABASE_PATH` 为空,When 创建房间,Then 无论是否启用全站密码都使用随机临时房间码并遵守 `ROOM_TTL_SECONDS`;停止分享本身不立即销毁房间。 -### P2P 建立与 TURN 兜底 +### P2P、SFU 与可选 TURN 兜底 - Given 两端网络允许 UDP 打洞,When 观看者加入,Then `getStats()` 显示选中的候选对不是 `relay`,且服务器没有承载媒体流量。 -- Given 模拟对称 NAT 或封锁直连,When 观看者加入,Then 系统自动选择 TURN 并在界面标识“中继连接”,无需用户配置端口。 -- Given UDP 被封锁但 TURN/TCP 可达,When ICE 检查发现 direct 与 TURN/UDP candidates 不可用,Then TURN/TCP candidate 能建立连接或返回可诊断的明确失败原因。 -- Given 部署者启用了可选 TURN/TLS,When 单独测试其标准 5349 端口或经过验证的 443 入口,Then 对应 `turns:` candidate 能建立 relay;未启用该能力不阻塞生产验收。 -- Given 同一房间中一名观看者可以直连、另一名处于严格 NAT 或移动网络,When 两者同时观看,Then 前者保持 P2P,后者单独使用 TURN,双方都能看到同一分享且互不影响。 +- Given direct/peer UDP 无法建立但 LiveKit SFU/UDP 可达,When 观看者加入或现有 edge 恢复耗尽,Then 系统只把必要 root 切到 SFU,保留可行 peer descendants 和未受影响路径,无需用户选择模式。 +- Given 未配置 TURN 且所有媒体 UDP 路径均不可用,When 有界连接窗口耗尽,Then 界面快速显示通用连接失败,不静默黑屏、不长时间假连接,也不自动尝试媒体 TCP。 +- Given 部署者为 ordinary peer edge 明确启用了经 canary 选择的 coturn transport,When 控制器选中的 Host-to-root 或 root-to-viewer UDP 路径耗尽,Then 只有该 assignment/connection generation 通过新增 per-edge grant/wire 收到短期凭据;其他普通 peer edges 不收到 TURN candidates,健康 root 仍继续 bounded peer 分发。 +- Given 部署者为 LiveKit 启用了 embedded/external TURN,When publisher 或 subscriber 的 SFU/UDP 路径耗尽,Then 只把它作为 LiveKit participant-to-SFU 兼容 transport 验收;pinned participant-wide ICE 广告不满足 Screener selected-edge issuance,除非集成可隔离/扩展到该边界,否则拒绝而非放宽目标。 +- Given 部署者另行启用并验收媒体 TCP/TLS 兼容模式,When UDP 被封锁,Then 对应路径建立或返回可诊断失败;未启用该能力不阻塞面向消费网络的旗舰验收,HTTPS/WSS 仍正常使用 TLS/TCP。 +- Given 同一房间中一名观看者可以 direct/peer UDP、另一名只能通过 SFU root 接收,When 两者同时观看,Then 前者保持分布式 P2P,后者使用 SFU,双方互不迁移且 host/SFU root 上限不变。 ### 浏览器与移动端观看 @@ -106,17 +108,17 @@ - Given 一名分享者和八名观看者,When 在受控网络中运行已记录的质量档位,Then 记录每条边的拓扑深度、候选类型、码率、编码/解码负载、分享端与 relay 上行、首帧和玻璃到玻璃延迟;该条在真实矩阵完成前保持未验收。 - Given peer-assisted host 鉴权或直播中连续修改质量设置,When 服务端校验其分辨率、FPS、码率和偏好,Then 只有 host 的完整有界设置被接受,last-wins 地应用到当前和以后建立的 relay child sender 以及启用的 SFU publisher;越界、缺字段和多余字段被拒绝,普通 P2P 的鉴权 wire 不新增该字段且不接受同步消息。 - Given 标准浏览器把收到的 remote track 直接加入下游 `RTCPeerConnection`,When 检查 relay 统计和 libwebrtc 路径,Then 明确记录新的 outbound encoder、编码耗时和 CPU/GPU 限制,不把它当作 encoded forwarding 或 shared encode 成果。 -- Given ADR-0004 的任一硬门槛只有在引入 Encoded Transform/DataChannel/WebCodecs 媒体、自定义拥塞控制、FEC/RTX、多树、综合评分、转码、codec ladder、浏览器 RTP 注入 hack 或放宽 host fanout 后才能满足,When 结束 bounded spike,Then 放弃并删除浏览器 relay 实验运行时代码,保留标准 P2P 和可选 SFU;后续 native shared encode sender 仍按独立 ADR 推进,但不能据此把失败的 relay 拓扑判为通过。 +- Given ADR-0004 的任一硬门槛只有在引入 Encoded Transform/DataChannel/WebCodecs 媒体、自定义拥塞控制、FEC/RTX、多树、综合评分、转码、codec ladder、浏览器 RTP 注入 hack 或放宽 host fanout 后才能满足,When 结束 bounded spike,Then 放弃并删除浏览器 relay 实验运行时代码,保留标准 P2P 和 SFU-root fallback;后续 native shared encode sender 仍按独立 ADR 推进,但不能据此把失败的 relay 拓扑判为通过。 - Given 新观看者会使预计总上行超过安全预算,When 其请求加入,Then 系统先遵守两边 fanout 预算选择 peer-assisted 或已配置的 SFU;没有合格路线时明确等待或拒绝,而不是创建第三条 host 边或持续堆积发送队列。质量不足只对经证据确认的观看路径使用共享 `LOW`,不能降低健康路径。 - Given TURN 或 SFU 被某条路线使用,When 媒体已抵达一至两个可靠 root,Then 其余可行 viewers 仍由 sticky、受深度与故障半径限制的 peer descendants 服务;host/root fanout 不超过二,当前 browser relay 不超过一个 child,中央服务和任一 endpoint 都不得无界集中 fanout。 -- Given 没有任何满足能力、深度、路径和恢复门槛的可靠 relay root,When 控制器穷尽 peer recovery,Then 已配置 SFU 才可把必要 viewers 作为零 descendants roots,且总数仍不超过同一个一至二 root 上限,否则明确等待或失败;不得把该异常边界解释为默认整房 SFU。 +- Given 没有任何满足能力、深度、路径和恢复门槛的可靠 relay root,When 控制器穷尽 peer recovery,Then SFU 可把必要 viewers 作为零-descendant roots;正常仍限一至二 roots,多个异常 viewers 只能使用另行配置的中央 egress cap,否则明确等待或失败,不得变成无界整房 SFU。 ### 质量和恢复 - Given 用户选择任一推荐档位或高级质量参数,When 接收端实际分辨率、帧率或码率持续显著低于该目标,Then 诊断数据能区分采集约束、发送编码、带宽/拥塞、TURN 路径和接收端限制;产品不得只改变档位标签却继续隐藏实际降级。 -- Given 房间所有观看路径持续健康,When 自动质量控制运行,Then 只存在一套共享 `HIGH` 表示,且任何单路径短暂抖动或未经验证的低档请求都不启动第二套编码或降低其他路径。 +- Given 房间所有观看路径持续健康,When 自动质量控制运行,Then 只存在一套 active `HIGH` representation,且任何单路径短暂抖动或未经验证的低档请求都不启动 `LOW` 或降低其他路径;物理编码器实例数另行实测。 - Given 一个或多个路径在连续窗口中被 sender 与 viewer 证据共同确认无法承受 `HIGH`,When 它们进入 `FALLBACK`,Then 系统必须启动且最多只启动一套共享 `LOW`,只迁移这些弱路径,其他路径继续 `HIGH`;所有弱路径经过更长稳定恢复窗口后返回 `HIGH` 并关闭 `LOW`。 -- Given 启动第二套编码会超过游戏 CPU/GPU 预算或没有合格硬件/power-efficient 路径,When 弱路径请求 `LOW`,Then 当次保留健康路径的 `HIGH` 并向弱路径明确显示降级或不可用,不得退回全房间最低目标或静默软件编码;若 supported sender cohort 无法可靠按需启动 `LOW`,Then 自动质量控制验收失败,必须优化编码路径或明确标记不支持,不能把 fail closed 当作长期忽略弱路径的许可。 +- Given 启动 `LOW` representation 的实测 encoder/CPU/GPU 成本超过游戏预算或没有合格 hardware/power-efficient 路径,When 弱路径请求 `LOW`,Then 当次保留健康路径的 `HIGH` 并向弱路径明确显示降级或不可用,不得退回全房间最低目标或静默软件编码;若 supported sender cohort 无法可靠按需启动 `LOW`,Then 自动质量控制验收失败,必须优化编码路径或明确标记不支持,不能把 fail closed 当作长期忽略弱路径的许可。 - 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 请求不改变表示或路径状态;有效请求也只能作为交叉验证输入。 - Given 受控 RTT/丢包测试条件,When 连续分享高动态游戏画面 30 分钟,Then 延迟、帧率与丢帧达到上述目标,且没有不可恢复的冻结。