diff --git a/docs/README.md b/docs/README.md index b80e3b9..01c64b3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -17,7 +17,7 @@ - [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. - [Native H.264 hardware decision](./research/native-h264-hardware-decision.md): bounded WebCodecs no-go, proved Media Foundation/NVIDIA hardware encode, the default-fmtp `42c01f` stop, and the retained interop boundary. - [Native H.264 opt-in path](./research/native-h264-opt-in-path.md): explicit H.264 sender mode, exact Pion profile registration, and one bounded Host/Viewer loopback with hardware caveats. -- [Realtime quality adaptation](./research/realtime-quality-adaptation.md): fixed Browser VP8/no-video-hint evidence, manual quality ceilings, diagnostic-only capture/send/receive correlation, the single-`HIGH` decision, and rejected dual/SVC evidence. +- [Realtime quality adaptation](./research/realtime-quality-adaptation.md): fixed Browser VP8/no-video-hint evidence, manual quality ceilings, diagnostic-only capture/send/receive correlation, and LiveKit-owned SFU `HIGH+LOW` adaptation evidence. - [Browser background capture diagnostics](./research/browser-background-capture.md): browser lifecycle evidence, a privacy-bounded capture/send/receive experiment, interpretation gates, and non-goal keepalive mechanisms. - [Browser screen-audio quality](./research/browser-screen-audio-quality.md): capture/source compatibility, live sender-ceiling readback, Opus negotiation boundaries, and remaining audible/device evidence. - [Display name and presence boundaries](./research/display-name-presence.md): browser-local storage failure behavior, Unicode normalization, control-character policy, and the Web-only presence capability boundary. @@ -27,7 +27,7 @@ - [ADR-0004](./adr/0004-peer-assisted-media-experiment.md): historical standard-WebRTC peer-assisted experiment and reusable evidence. - [ADR-0005](./adr/0005-automatic-hybrid-media-routing.md): accepted P2P-first single-reconcile route model, including deployed Browser SFU ICE-server isolation; remaining real-network validation is tracked in verification status. - [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-path-isolated-representation-quality.md): accepted path-isolated single-`HIGH` Browser SFU representation, evidence contract, and rejected dual/SVC boundaries. +- [ADR-0007](./adr/0007-path-isolated-representation-quality.md): accepted LiveKit-owned VP8 `HIGH+LOW` SFU adaptation, pinned evidence, and application-controller/SVC boundaries. - [ADR-0008](./adr/0008-window-scoped-audio-capture.md): browser window-audio hint and the Windows WASAPI process-loopback boundary. Documentation and project memory are part of the product source of truth. Update them in the same change that alters the corresponding requirement or architecture. diff --git a/docs/adr/0007-path-isolated-representation-quality.md b/docs/adr/0007-path-isolated-representation-quality.md index 3ceeb42..f23d748 100644 --- a/docs/adr/0007-path-isolated-representation-quality.md +++ b/docs/adr/0007-path-isolated-representation-quality.md @@ -1,6 +1,6 @@ # ADR-0007: LiveKit-Owned SFU Representation Adaptation -- Status: Accepted design target; implementation and deployment held for the pinned physical gate +- Status: Accepted; not implemented or deployed - Date: 2026-08-19 - Last reviewed: 2026-08-25 @@ -19,6 +19,14 @@ competed with `HIGH` on that constrained Host-to-SFU path; it did not test LiveKit-owned demand-driven layer control and does not justify a permanent single-`HIGH` contract. +The earlier application-layer controller discussion started from the false +premise that LiveKit/WebRTC did not already own per-subscriber bandwidth +estimation and layer selection. Built-in media control is the default owner. +Screener supplies the bounded representations and product ceilings but does not +reimplement that controller. A resource-constrained Host can explicitly choose +a lower existing share profile; the product does not protect it by silently +removing the representation required by constrained Viewers. + ## Decision 1. Direct and peer paths retain independent stock WebRTC congestion control. @@ -33,40 +41,41 @@ single-`HIGH` contract. 4. `HIGH` is a subscriber ceiling, not a forced delivery layer. A weak downlink may receive `LOW` while another subscriber remains on `HIGH` when the pinned LiveKit stack can sustain that contract. -5. AdaptiveStream is a display-demand input, not a network detector. It may be - used only when the LiveKit `RemoteVideoTrack` is attached to the actual leaf - Viewer element. It must not derive an SFU-fed relay's subscription from that - relay page's element size or visibility because LiveKit cannot see the - relay's peer children. +5. AdaptiveStream is a display-demand input, not a network detector. Current + Screener subscribers keep it disabled because any Viewer may become a relay + and LiveKit cannot see that Viewer's peer children. Network adaptation uses + the server stream allocator/BWE instead. 6. ADR-0005 remains the only route owner. Layer choice does not change topology, endpoint capacity, SFU admission, or decoded-stall recovery authority. Current source and production remain the deployed single-`HIGH` implementation -until the gate below passes and a separate implementation is integrated. That -is deployment state, not the accepted quality target. +until a separate implementation is integrated and deployed. That is deployment +state, not the accepted quality target. -## Pinned Physical Gate +## Pinned Physical Result -Use LiveKit server `1.13.5`, client `2.22.0`, Chrome 151, one continuously -changing VP8 source, and one `HIGH+LOW` screen-share publication. Record adjacent -publisher outbound-RTP deltas by RID plus subscriber inbound resolution, frame, -and byte deltas for these states: +The 2026-08-25 gate used LiveKit server `1.13.5`, client `2.22.0`, Chrome 151, +a continuously changing VP8 source, and one `HIGH+LOW` screen-share publication. +Manual selection delivered `1280x720` HIGH and `640x360` LOW at about 30 fps, +and the same connection recovered from LOW to HIGH. -1. no subscriber, then one `HIGH` subscriber; -2. the same subscriber requesting `LOW`, then recovering to `HIGH`; -3. a leaf subscriber using AdaptiveStream while its attached element changes - between large, small, hidden, and visible; -4. an SFU-fed relay without a LiveKit-attached display element. +With the server default receiver-side BWE, an approximately 0.96 Mbps subscriber +remained on HIGH for about 21 seconds and ended at zero decoded fps. Enabling +LiveKit `congestion_control.use_send_side_bwe` made the native stream allocator +select LOW; by the third steady window it delivered `640x360` at about 29 fps and +88 KB/s. A fresh unshaped subscriber immediately received HIGH. The constrained +connection's automatic upgrade after removing shaping was not isolated because +Chrome bound that test condition when creating the PeerConnection; manual and +AdaptiveStream same-connection LOW-to-HIGH recovery were independently proven. -The gate must establish which layers actually encode and send, not merely which -options were requested. It passes the product target only if LiveKit owns the -transitions, a low demand can receive a lower spatial representation, recovery -returns to `HIGH`, and an unrelated weak or hidden leaf does not lower a healthy -subscriber. CPU, bitrate, and FPS evidence must distinguish an inactive layer -from one that remains encoded but is not forwarded. - -If the fixed stack cannot provide demand-driven activation, stop at the -dependency decision. Do not compensate with a Screener layer controller. +Dynacast paused layers with no subscribers after about five seconds. LOW-only +demand disabled HIGH, while any HIGH demand kept both LOW and HIGH encoding; the +second VP8 simulcast encode is therefore an accepted bounded cost, not eliminated +by Dynacast. AdaptiveStream selected HIGH for a large attached element, LOW for a +small element, paused a hidden element, and recovered when visible. With no +attached element it received only LOW, confirming that it cannot own relay +ingress. The accepted product combination is VP8 HIGH+LOW simulcast, Dynacast, +server send-side BWE, and `adaptiveStream: false` for Screener subscribers. ## Evidence Boundary @@ -87,7 +96,10 @@ mutation or predict another parent. - A weak SFU Viewer can receive a real lower spatial representation when the framework selects it; the SFU does not transcode. - The Host publishes at most two SFU representations regardless of Viewer - count, and Dynacast is responsible for avoiding unused work where supported. + count. Dynacast pauses layers above aggregate demand and all layers when no + subscriber remains; HIGH demand keeps both VP8 simulcast encodings active. +- Host encode capacity is handled through the existing explicit share profiles, + not by falling back to single HIGH and abandoning constrained subscribers. - An SFU-fed relay keeps a subscription ceiling sufficient for its subtree; local DOM visibility alone cannot pause that ingress. - A configured 60 fps or bitrate remains a ceiling, not a delivery guarantee. diff --git a/docs/project-memory.md b/docs/project-memory.md index a337c49..179f948 100644 --- a/docs/project-memory.md +++ b/docs/project-memory.md @@ -17,7 +17,7 @@ Last updated: 2026-08-25 - One Host publication may serve all SFU subscriptions. Its exact generation owns one ingress handle and exact Viewer subscription handles own egress; later SFU children reuse the publication, while reserved, committed, and draining handles remain charged until the managed room is deleted and proven absent. Actual SFU usage release wakes only rooms that previously lost a candidate to global admission, and SFU-fed and peer-fed endpoints use the same provisional-child transaction. Parent choice hard-filters authority, reachability, cycles, effective capacity, and producer transition, then orders by shallowest result, remaining steady sender capacity, stable join order, and peer identity; server admission is checked at the current cursor. Acyclicity and room admission bound maximum depth; depth is observed but has no separate hard cap. Healthy committed edges remain sticky: the controller repairs waiting, invalid, departed-parent, and overflow children but does not periodically rebalance the graph. Raw IP, claimed NAT type, UA, geography, all-pairs probes, weighted scores, and periodic room rebalancing are not route inputs. - Ordinary peer ICE is STUN-only. Browser LiveKit publisher and subscriber PCs use no external ICE server, retain LiveKit-signaled UDP candidates, and let standard ICE nominate a non-relay pair; deployment STUN remains available to ordinary peers and LiveKit server-side public-IP discovery. This Browser SFU ICE-server isolation avoids the reproduced external-STUN/SFU association conflict without constraining candidate type or count and without adding TURN, another route, a port, or a timeout. Credentials, room secrets, candidate details, and diagnostic data remain private and narrowly scoped. A future strict-firewall transport requires separate evidence and must remain inside LiveKit rather than becoming another Screener route candidate. - Browser peers use existing periodic WebRTC/LiveKit sampling to maintain one exact-route decoded-progress timestamp. New frames or route/connection changes reset it; a non-paused deadline reports once. An active SFU cadence treats an absent track/report or stats failure as no progress without fabricating metrics and stops only on deactivation/teardown. Other current-path stats remain diagnostic and cannot prove another parent better. -- ADR-0007 targets fixed VP8 `HIGH+LOW` SFU simulcast with LiveKit-owned Dynacast/BWE. AdaptiveStream is leaf-display only and cannot govern relay ingress. Source and production stay single-`HIGH` pending the pinned gate. +- ADR-0007 accepts VP8 `HIGH+LOW`, Dynacast, and server send-side BWE. Gate proved LOW for constrained and HIGH for unshaped subscribers; HIGH demand keeps both encodings. AdaptiveStream stays off because any Viewer may relay. Source/prod remain single-`HIGH`. - Browser capture requests available share audio by default on initial capture and source switch, with an explicit video-only warning when no audio track is returned. Its screen-audio control offers live-switchable 64/128/256 kbps sender ceilings and defaults to 128. The last-wins quality wire owns desired state; each endpoint serially applies and reads back current Host/relay/SFU audio senders, exposes local failures, and gives future senders the latest desired value. Opus remains fixed, and without an applied-ack wire the Host does not claim room-wide atomic convergence. These are configuration ceilings, not fidelity claims. - Browser display video leaves `contentHint` unset so Chromium retains its screen-capture classification; returned audio tracks keep `contentHint = "music"`. Browser video is fixed to VP8 across direct, browser-relay, and SFU paths; codec selection, fallback to another media codec, quality-state/wire fields, and share-lifetime codec branches are not product features. Actual codec/profile/encoder stats remain diagnostic only. - Recommended quality remains exactly `1080p60`, `1080p30`, and `720p30`, with the middle `1080p30` as the accepted default. `480p` means only an advanced `854x480` resolution choice whose FPS and bitrate remain independently selected; it is not a fourth recommended profile. @@ -40,7 +40,7 @@ Last updated: 2026-08-25 ## Current Priority -1. Complete the pinned LiveKit native-representation gate and settle its implementation decision; then deploy the active-SFU decoded-progress cadence, repeat controlled recovery, and finish real-network route, screen-audio, and real-game A/V acceptance. VP8 acceleration, mobile lifecycle, Native, distribution, and repository simplification remain later. +1. Implement the accepted LiveKit-native `HIGH+LOW`/Dynacast/send-side-BWE SFU representation model; then deploy the active-SFU decoded-progress cadence, repeat controlled recovery, and finish real-network route, screen-audio, and real-game A/V acceptance. VP8 acceleration, mobile lifecycle, Native, distribution, and repository simplification remain later. The desktop Host background/minimized report is deferred until it reproduces on current production with a real game and synchronized media plus CPU/GPU evidence. The bounded current-Chrome screening found no immediate Host-page lifecycle drop, and removal of the old harmful `contentHint = "motion"` remains a plausible confound rather than a proven background fix. Mobile Viewer playback and assigned-relay survival remain a separate lifecycle gate. diff --git a/docs/research/browser-screen-audio-quality.md b/docs/research/browser-screen-audio-quality.md index c92422e..9a19d61 100644 --- a/docs/research/browser-screen-audio-quality.md +++ b/docs/research/browser-screen-audio-quality.md @@ -20,6 +20,16 @@ sender, merges the SFU sender reports, resets identity-changing baselines, keeps late relay audio changes on exact children, and reacquires exact SFU senders on reconnect. Explicit no-audio remains unknown. +Pinned LiveKit 2.22.0 already republishes screen tracks after a full reconnect +from the saved publication options, including the audio preset; a signal-only +resume does not replace the sender. Screener must still reacquire its own exact +publication/sender references, reset identity-bound stats, fail closed when an +expected republish is missing, and propagate same-stream audio changes to its +downstream peer children. Reapplying unchanged audio sender parameters after +every `Reconnected` event duplicates the SDK on stable reconnects. A later +cleanup may skip that write when the sender identity and desired readback already +match, while retaining saved-option updates and the latest-profile race check. + A Chrome 151 gate against exact production `bf32859` used real `getDisplayMedia()` with generated active audio. Two Host-direct children, one browser-relay child and one SFU Viewer all advanced audio energy and video frames diff --git a/docs/research/native-shared-encode-sender.md b/docs/research/native-shared-encode-sender.md index 80aa206..8290ceb 100644 --- a/docs/research/native-shared-encode-sender.md +++ b/docs/research/native-shared-encode-sender.md @@ -254,10 +254,12 @@ conservative feedback-derived reconfiguration while feeding two transports. It does not establish heterogeneous feedback behavior and must not evolve into a room-wide minimum controller. -ADR-0007 keeps stock WebRTC GCC per direct/peer path and accepts one Browser SFU -`HIGH` representation. The exact-production gate rejected an always-active -`LOW` because it reduced `HIGH` on the shared Host-to-SFU congestion budget. -Application evidence remains diagnostic and does not select routes or layers. +ADR-0007 keeps stock WebRTC GCC per direct/peer path and accepts bounded Browser +SFU `HIGH+LOW` simulcast with Dynacast and LiveKit server send-side BWE. The +exact-production gate proved its dual-encode cost; the later pinned gate proved +native LOW selection for a constrained subscriber, so the product does not +remove LOW to protect Host capacity. Application evidence remains diagnostic +and does not select routes or layers. The Host still follows the configured non-server outbound media-copy capacity; the two-edge result in this experiment is a historical configuration, not a fixed policy. @@ -269,12 +271,13 @@ and do not form a composite score. UA and device identity do not participate. A native relay forwards the selected encoded packets and must not decode or re-encode them. An ordinary non-scalable representation cannot be forwarded into a second quality; doing so would require another encode, scalable layers, -or transcoding. ADR-0007 closes current Web P2P simulcast, Browser dual -representation, and Web/LiveKit SVC. A future native decision may reopen a -second representation only with an explicit hardware contract, bounded layer -count, one encoded output reused across accepted transports, independent path -selection, and the real game/power matrix. Media Capabilities and RTCStats -power-efficiency fields remain diagnostics, not hardware proof. +or transcoding. ADR-0007 closes current Web P2P shared simulcast and Web/LiveKit +SVC, while accepting Browser SFU `HIGH+LOW` through LiveKit. A future native +decision may implement those representations only with an explicit hardware +contract, bounded layer count, one encoded output reused across accepted +transports, independent path selection, and the real game/power matrix. Media +Capabilities and RTCStats power-efficiency fields remain diagnostics, not +hardware proof. Simulcast does not change this native proof boundary: one sender may negotiate multiple representations, but separate direct PeerConnections have no portable diff --git a/docs/research/realtime-quality-adaptation.md b/docs/research/realtime-quality-adaptation.md index 24e0305..4e0659d 100644 --- a/docs/research/realtime-quality-adaptation.md +++ b/docs/research/realtime-quality-adaptation.md @@ -57,9 +57,10 @@ near 60 fps, encode cost was about 4.46 ms per frame, packet loss and retransmission were zero, and the selected pair reported about 4.81 Mbps available outgoing bitrate. The always-active lower representation therefore consumed the same Host-to-SFU congestion budget and materially reduced `HIGH`. -ADR-0007 rejects that candidate and accepts one Browser SFU `HIGH` -representation. The public SFU ingress remained constrained, so this gate does -not claim SFU 60 fps or real-game performance. +This establishes a real dual-encode cost but does not justify removing the lower +representation needed by constrained subscribers. The public SFU ingress +remained constrained, so this gate does not claim SFU 60 fps or real-game +performance. ## 2026-08-25 Exact-Production Single-Representation Gate @@ -70,8 +71,40 @@ PCs retained empty external ICE-server lists and selected UDP. The publisher reported one video sender encoding and one outbound video stats object, both without RID. Over six seconds it encoded 180 frames and the subscriber decoded and rendered 182 frames at 1822x1080, with byte counters advancing on both ends. -This closes ADR-0007's implementation/deployment gate. It does not establish -public SFU 60 fps, real-game quality, heterogeneous networks, or endurance. +This verifies the deployed single-representation baseline. It does not prove +that the baseline serves constrained subscribers and does not close the later +native-adaptation decision. It also does not establish public SFU 60 fps, +real-game quality, heterogeneous networks, or endurance. + +## 2026-08-25 Pinned Native-Adaptation Gate + +LiveKit server `1.13.5`, JS client `2.22.0`, and Chrome 151 exercised a changing +VP8 `HIGH+LOW` screen-share publication. Manual subscriber selection delivered +`1280x720` HIGH and `640x360` LOW at about 30 fps, and the same connection +returned from LOW to HIGH. AdaptiveStream selected HIGH for a large attached +element, LOW for a small one, paused when hidden, and recovered when visible. +Without an attached element it received only LOW, so it cannot govern a Viewer +that may forward the stream to peer children. + +Dynacast stopped layers with no subscribers after about five seconds. LOW-only +demand stopped HIGH, but any HIGH demand kept LOW and HIGH encoding together. +It therefore avoids unused work but cannot remove VP8's dual-encode cost while a +healthy HIGH subscriber exists. + +With LiveKit's default receiver-side BWE, an approximately 0.96 Mbps subscriber +remained on HIGH for about 21 seconds and ended at zero decoded fps. With +`congestion_control.use_send_side_bwe: true`, LiveKit's native stream allocator +selected LOW and by the third steady window delivered `640x360` at about 29 fps +and 88 KB/s. A fresh unshaped subscriber immediately received HIGH. Network +unshaping on the same PeerConnection was not isolated because the Chrome DevTools +condition was bound when that connection was created; manual and AdaptiveStream +same-connection upward transitions independently proved layer recovery. + +The result accepts VP8 HIGH+LOW simulcast, Dynacast, and server send-side BWE. +Screener does not add a bandwidth estimator or layer controller. AdaptiveStream +stays disabled for current subscribers because every Viewer may become a relay. +The earlier dual-encode cost is handled through explicit Host share profiles, +not by silently abandoning low-bandwidth Viewers. ## Route Quality Authority @@ -457,8 +490,9 @@ a mismatch; multiple encodings remain unknown. Inbound stats provide no current standard `scalabilityMode` source, so C does not carry a null-only placeholder. The earlier exact-production gate rejected its ordered `q,h` publication because the active lower representation reduced `HIGH`. Current source and production -now implement ADR-0007's one Browser SFU `HIGH`; the exact deployment gate above -proved one no-RID encoding with progressing publisher/subscriber frames. +still implement one Browser SFU `HIGH`; the exact deployment gate above proved +that no-RID baseline, while the later native-adaptation gate established the +accepted two-representation target. Official W3C text checked 2026-08-19 defines names ending in `Id` as stats-object references. In particular, outbound [`mediaSourceId`](https://www.w3.org/TR/webrtc-stats/#dom-rtcoutboundrtpstreamstats-mediasourceid) @@ -486,22 +520,21 @@ member, which remains null rather than a capability conclusion. ## LiveKit Native Representation Boundary ADR-0007 owns the accepted target: fixed `HIGH+LOW` VP8 simulcast with layer -activation and per-subscriber selection delegated to LiveKit. Current source and -production remain single-`HIGH` until the pinned physical gate measures actual -RID activity and subscriber output. +activation and per-subscriber selection delegated to LiveKit. The pinned gate +passed with explicit server send-side BWE. Current source and production remain +the not-yet-migrated single-`HIGH` baseline. The candidate assumed one shared `HIGH+LOW` SFU publication could preserve healthy `HIGH` while LiveKit selected a lower representation per constrained subscriber. Static review established two limits before the runtime gate: ordinary WebRTC receivers have no portable per-RID selection API across -separate PeerConnections. Source inspection also indicates that LiveKit 1.13.5 +separate PeerConnections. Runtime evidence confirmed that LiveKit 1.13.5 Dynacast cumulatively enables qualities below the highest requested quality, so -a `HIGH` subscriber may keep `LOW` active; this is a gate hypothesis, not a -runtime verdict. The 2026-08-25 exact-production A/B proved only that the prior -always-active lower representation materially reduced `HIGH`. It did not enable -Dynacast or AdaptiveStream and therefore cannot reject native demand-driven -adaptation. Sender/viewer evidence remains diagnostic and does not change -topology or command a representation. +a `HIGH` subscriber keeps `LOW` active. The earlier exact-production A/B proved +that this has a material cost; the later constrained-subscriber gate proved that +the lower representation is necessary and that LiveKit's send-side BWE selects +it without a Screener controller. Sender/viewer evidence remains diagnostic and +does not change topology or command a representation. ### Historical SFU Source Gate @@ -550,8 +583,8 @@ the high layer, so the application does not own the actual layer choice. For this screen-share product the pin has an additional hard mismatch. Client 2.22.0 overwrites SVC screen-share publication to `L1T3`, even when another mode was supplied: one spatial resolution and three temporal layers. It -therefore supplies no low-resolution base and exceeds the accepted -one-representation contract. LiveKit also documents that Dynacast can pause only an entire SVC +therefore supplies no low-resolution spatial base and does not satisfy the +accepted LOW/HIGH contract. LiveKit also documents that Dynacast can pause only an entire SVC stream, not individual SVC layers. Changing those contracts would require a different dependency/native design decision, not a runtime proof of the pinned path. No SVC harness was written and no Chrome run was performed. None of the diff --git a/docs/status.md b/docs/status.md index 23a32a5..50667f4 100644 --- a/docs/status.md +++ b/docs/status.md @@ -29,7 +29,7 @@ This is the current execution index. Git history owns completed timelines; [veri ## Current Milestone -1. Run ADR-0007's pinned LiveKit native `HIGH+LOW`/Dynacast/AdaptiveStream gate and settle the framework-owned representation decision. Then correct and deploy the active-SFU decoded-progress cadence, repeat the controlled recovery transaction, and finish representative ICE/STUN/SFU acceptance including screen-audio continuity and real-game A/V sync on mobile and heterogeneous networks. Application layer control, Browser port prediction, NAT classification, TCP probing, fake page keepalive, and quality-driven reparenting remain outside the accepted model. +1. Implement ADR-0007's accepted VP8 `HIGH+LOW` simulcast with Dynacast and LiveKit server send-side BWE; keep AdaptiveStream disabled for potential relays and add no application layer controller. Then correct and deploy the active-SFU decoded-progress cadence, repeat the controlled recovery transaction, and finish representative ICE/STUN/SFU acceptance including screen-audio continuity and real-game A/V sync on mobile and heterogeneous networks. Browser port prediction, NAT classification, TCP probing, fake page keepalive, and quality-driven reparenting remain outside the accepted model. ## Active Boundaries diff --git a/docs/todo.md b/docs/todo.md index 4f3adce..f416693 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -6,7 +6,7 @@ Only items in **Now** are executable after their stated decision gate. A branch ## Now -1. **Settle native SFU representation adaptation.** Run ADR-0007's pinned LiveKit `1.13.5` / client `2.22.0` Chrome gate for actual `HIGH+LOW` RID activity, subscriber layer selection/recovery, leaf AdaptiveStream sizing/visibility, and the no-attached-element relay boundary. Accept only framework-owned Dynacast/stream-allocation behavior; do not add a Screener quality controller. +1. **Implement native SFU representation adaptation.** Publish fixed VP8 `HIGH+LOW` simulcast, enable Dynacast and LiveKit server send-side BWE, and keep AdaptiveStream disabled for current Viewers because any may become a relay. Preserve LiveKit-owned per-subscriber selection and the existing Host quality profiles; do not add a Screener quality controller or fall back to single HIGH for Host resource protection. 2. **Finish real-network route acceptance.** First restore the existing active-SFU decoded-progress cadence when its track or stats report disappears, deploy it, and repeat the controlled recovery transaction. Then measure standard ICE/STUN direct paths, peer relay, SFU ingress/subscription, relay-ingress repair with subtree retention, disconnect/capacity drain, Pause/Resume, screen-audio continuity and real-game A/V sync across representative IPv4/IPv6 NATs and mobile networks. Include an all-UDP-blocked case to establish bounded explicit failure. Do not add another watchdog, port prediction, NAT classification, TCP probes, guessed candidates, route scores, or quality-driven reparenting. ## Accepted Later Roadmap diff --git a/docs/verification-status.md b/docs/verification-status.md index d5d62b8..fef75d9 100644 --- a/docs/verification-status.md +++ b/docs/verification-status.md @@ -38,7 +38,7 @@ Capacity and routing entries below describe the exact tested source or release o ## Open Proof Boundaries - Routing: exact v10 local direct loopback remains historical evidence for admission-20 initial ingress. Exact v12 production closes one controlled ordinary-direct exhaustion, explicit rollback, and exact SFU first-frame commit transaction; the later subscriber-PC closure injection found the active-SFU cadence gap without proving physical loss recovery. Representative real-network direct/peer/SFU behavior, relay-ingress reparent with subtree retention, disconnect/effective-capacity drain, pause/resume, and bounded all-UDP-blocked failure remain open. Silent control partitions may wait 30 to 60 seconds for heartbeat detection before the default five-second grace; a non-paused decoded-frame stall may invalidate only that exact child edge. -- Quality: exact-production direct and Browser-relay VP8/no-hint cadence plus ADR-0007's single-`HIGH` SFU implementation/deployment gate are closed. Public SFU 60 fps, real-game quality, heterogeneous devices, and endurance remain open. Startup ramp is stock BWE behavior and does not authorize an SDP or application bitrate workaround. +- Quality: exact-production direct and Browser-relay VP8/no-hint cadence plus the deployed single-`HIGH` SFU baseline are closed. ADR-0007's pinned gate separately proved LiveKit-owned `HIGH+LOW` selection with server send-side BWE; its product implementation/deployment, public SFU 60 fps, real-game quality, heterogeneous devices, and endurance remain open. Startup ramp is stock BWE behavior and does not authorize an SDP or application bitrate workaround. - Host background: deferred until the report reproduces on exact production with a continuously changing real game or replay, a separate direct wired Viewer, synchronized capture/outbound/inbound plus CPU/GPU evidence, and repeated focused/unfocused/hidden/minimized windows. The current-Chrome Browser `getDisplayMedia()` WPF-window screening did not observe an immediate Host-page media drop, and the old harmful `contentHint = "motion"` release remains a plausible confound rather than a proven background cause. Current evidence authorizes no keepalive, Wake Lock, timer, codec switch, route change, or Native fallback. - Relay resources: current source and production expose the authenticated `1/2/3` endpoint-cap boundary, and retained v10 local loopback evidence covers 20 Viewers at cap `2` only for that source. CPU/GPU/upload and heterogeneous-network measurements remain diagnostics, not authority to change the contract. - Room scale: current source and production admit 20 Viewers plus one Host, and the retained v10 local Browser convergence gate covers that bound on direct loopback. A real public-network 1:20 session, quality, and sustained resource matrix remain open. diff --git a/docs/方案设计.md b/docs/方案设计.md index 5be095f..2ddd080 100644 --- a/docs/方案设计.md +++ b/docs/方案设计.md @@ -188,7 +188,7 @@ Viewer 本地连接详情只从当前 route 的唯一 inbound screen video/audio `frameRate` 约束和 sender 的 `maxFramerate` 都是目标或上限;浏览器仍控制实际捕获、重复帧处理、编码和拥塞降级。静态画面可能由浏览器减少实际编码数据,但标准没有保证每个实现以同一种方式节省采集或 GPU 开销。本 PoC 不添加画面差分、定时改约束或自定义动态 FPS 状态机,先用 `framesEncoded`、实际发送码率、`totalEncodeTime`、质量受限原因和主机资源占用测量真实问题。 -质量自适应采用 ADR-0007 的路径隔离框架能力,而不是全房间按最差链路降档。Direct/peer 保留每条 PeerConnection 的 stock WebRTC GCC。Browser Host 的唯一 SFU publication 以固定 `HIGH+LOW` VP8 simulcast 提供两种空间表示,LiveKit Dynacast 负责停用未消费层,subscriber 的 stream allocator/BWE 独立选择实际下行层;`HIGH` 只是上限。Screener 不实现质量评分、带宽估计、周期调档或应用媒体层 selector。AdaptiveStream 只适用于 remote track 直接 attach 到实际播放元素且不承担 peer children 的 leaf Viewer;relay ingress 不能跟随本地 DOM 可见性。当前单层 `HIGH` 源码与生产保持不动,直到 pinned LiveKit 物理门禁证明层启停、弱下行选择和恢复行为符合该模型。 +质量自适应采用 ADR-0007 的路径隔离框架能力,而不是全房间按最差链路降档。Direct/peer 保留每条 PeerConnection 的 stock WebRTC GCC。Browser Host 的唯一 SFU publication 以固定 `HIGH+LOW` VP8 simulcast 提供两种空间表示。LiveKit Dynacast 按全房最高需求停用其上的发布层,无订阅时停用全部层;启用 send-side BWE 的 server stream allocator 独立选择每个 subscriber 的实际下行层,`HIGH` 只是上限。Screener 不实现质量评分、带宽估计、周期调档或应用媒体层 selector。当前任一 Viewer 都可能承担 peer children,因此 subscriber 统一保持 `adaptiveStream: false`,relay ingress 不跟随本地 DOM 可见性。Pinned gate 已证明受限下游原生选择 LOW、非受限下游取得 HIGH、手动及元素驱动的同连接恢复和 Dynacast 启停;也证明任何 HIGH 需求会让 LOW+HIGH 同时编码。双编码是支持受限下游的固定成本,不是回退到单层 HIGH 的理由。当前源码与生产尚未迁移,仍是单层 `HIGH`。 质量证据只用于诊断,不形成全房分数或 parent authority。路由只消费 exact child edge 的 `failed/closed` 和非暂停 decoded-frame stall。prepare/commit/abort 绑定 authenticated sessions、share generation、base/pending revision 与 candidate connection;candidate child 的第一帧新 decoded video 是唯一应用层 media-ready,ICE connected 不能单独提交。 @@ -206,13 +206,13 @@ Viewer 本地连接详情只从当前 route 的唯一 inbound screen video/audio Viewer 的已鉴权质量证据只描述当前路径,不能命令 WebRTC/LiveKit 改层、改变 Host publication 或改变拓扑。UA、平台和设备型号不参与质量或 relay capacity 决策。 -SVC 的标准能力门禁已经静态关闭为 `no-go-web-svc-cross-path-hardware-contract`。WebRTC-SVC 只配置单个 outgoing sender,direct/peer receiver 没有对应的按层选择 API,独立 PeerConnection 之间也没有 shared-encode 契约;requested/applied codec/`scalabilityMode`、Media Capabilities `powerEfficient` 与 RTCStats encoder 字段不能证明编码器一定是硬件。Pinned LiveKit 虽能在 SFU 内为每个 subscriber 设置最大空间/时间层请求,但实际选择依 codec/selector 而异:server 1.13.5 只为 VP8 安装 temporal selector,当前 H.264 `q,h` simulcast 只有空间层选择。Client 2.22.0 还会把 screen-share SVC 强制成三层 temporal-only `L1T3`,违反当前 Browser 无 SVC、单表示合同;这种 SFU 选层也不延伸到 direct/peer descendants。当前静态结论不运行 browser harness。未来只能由能明确锁定硬编、最多两层、跨 direct/peer/SFU 复用同一输出且通过游戏性能/延迟门槛的 native sender 以新决策重开。普通非分层编码包不能只靠转发生成另一档画质,实现路径差异只能用第二表示、合格的 scalable layers 或 relay/SFU 转码。 +SVC 的标准能力门禁已经静态关闭为 `no-go-web-svc-cross-path-hardware-contract`。WebRTC-SVC 只配置单个 outgoing sender,direct/peer receiver 没有对应的按层选择 API,独立 PeerConnection 之间也没有 shared-encode 契约;requested/applied codec/`scalabilityMode`、Media Capabilities `powerEfficient` 与 RTCStats encoder 字段不能证明编码器一定是硬件。Pinned LiveKit 虽能在 SFU 内为每个 subscriber 设置最大空间/时间层请求,但实际选择依 codec/selector 而异:server 1.13.5 只为 VP8 安装 temporal selector,当前 H.264 `q,h` simulcast 只有空间层选择。Client 2.22.0 还会把 screen-share SVC 强制成三层 temporal-only `L1T3`,无法提供当前 Browser 合同要求的 LOW/HIGH 两种空间表示;这种 SFU 选层也不延伸到 direct/peer descendants。未来只能由能明确锁定硬编、最多两层、跨 direct/peer/SFU 复用同一输出且通过游戏性能/延迟门槛的 native sender 以新决策重开。普通非分层编码包不能只靠转发生成另一档画质,实现路径差异只能用第二表示、合格的 scalable layers 或 relay/SFU 转码。 质量控制不放宽稳态 outbound media-copy 预算:所有非服务器端点继续受统一静态 capacity 约束;native relay 只转发选定表示的编码包,不解码重编码。后续双树或条带化必须另行验收,不能绕过当前 graph、identity 和 capacity invariant。 路由状态只有 committed graph 和一个 pending child operation;该对象聚合 candidate list/cursor、一个 current candidate/预留和一个总 deadline。relay 自身 ingress 失效时,它作为 child 进入同一 reparent 并保留 subtree;disconnect 或 effective capacity 降低时让 direct/overflow children 逐个回到 reconcile。多个下游 edge 若各自失效,会独立迁移并自然排空 relay。权威 pause abort operation、保持 active graph、抑制 stall;resume 重新 reconcile。 -标准表示能力排在任何自定义媒体实现之前。Web P2P simulcast 缺少 receiver 选 RID 与跨 PeerConnection shared-encode 契约;LiveKit SFU 则原生拥有 per-subscriber 层选择与 Dynacast。Browser SFU 只在 ADR-0007 的 pinned-stack 门禁通过后发布固定 `HIGH+LOW`,并完全委托框架启停和选择;不得把该能力扩散到 direct/peer,也不得绕过 stock WebRTC congestion control。 +标准表示能力排在任何自定义媒体实现之前。Web P2P simulcast 缺少 receiver 选 RID 与跨 PeerConnection shared-encode 契约;LiveKit SFU 则原生拥有 per-subscriber 层选择与 Dynacast。ADR-0007 的 pinned-stack 门禁已通过,Browser SFU 的接受目标是固定 `HIGH+LOW`、Dynacast 和 server send-side BWE,并完全委托框架启停和选择;不得把该能力扩散到 direct/peer,也不得绕过 stock WebRTC congestion control。 ## 移动分享端能力边界与排期 diff --git a/docs/需求理解.md b/docs/需求理解.md index 9e9872b..f72f3be 100644 --- a/docs/需求理解.md +++ b/docs/需求理解.md @@ -27,8 +27,8 @@ SFU 的 publication、subscription、server-resource accounting、临时 overlap - 端点发送容量:所有非服务器端点使用同一个服务端权威稳态 outbound media-copy capacity,由唯一部署项 `ENDPOINT_MEDIA_COPY_CAPACITY` 配置,默认 `2` 且只接受 `1`、`2` 或 `3`;不读取旧键或保留第二套 tier。peer child 或 Host 的唯一 SFU publication 各消耗一份,上游接收不消耗该预算。客户端只能声明不高于部署值的可用容量,角色、浏览器、UA、设备类型和页面可见性不产生不同上限。服务端在每次 `authenticated` 权威快照中返回最终 `endpointMediaCopyCapacity`;普通非 peer-assisted 模式也只授权稳定加入顺序中的至多 `C` 个 Host child,超额 Viewer 保持入房等待并在槽位释放后提升,信令不能绕过该集合。Browser Host 与 Viewer 严格校验该值并用它约束稳态 assignment;并行产生媒体的 candidate 必须持有唯一 `TransitionOverlapSlot`,active+pending 物理 copy 只可达到 `min(C + 1, 3)` 并在 commit/abort 后释放。`C=3` 满载时必须先收到 active bounded-gap retirement 再 prepare,绝不产生第四份 copy。SFU subscriber egress 由独立 server admission 记账。当前不兼容房间合同以单一 release 原子升级;缺失/非法 cap、旧 wire 与 executable sender都在取得room/route authority前fail closed。 - P2P 优先:每条普通逻辑媒体边先尝试 direct/peer UDP,服务器不得默认中转可直连媒体。更多观看者优先沿满足统一端点容量的无环 peer 图分布;每名 Viewer 同时至多一个 active upstream,失败时只影响需要重选的 child。任何 source-reachable endpoint 在 effective capacity 有空位时都可作为 parent candidate;Peer-fed 与 SFU-fed endpoint 使用同一个 provisional-child transaction,并在 candidate child 解码出第一帧新画面后独立提交该 edge。Host 已满且首次需要 SFU 时,同一 reconcile 将 affected Host direct child(若无则稳定顺序中最新的 connected Host direct child)作为本次 child operation,创建唯一 Host publication 并把该 child 变为首个 SFU subscriber,subtree 不动;publication 取代释放的 Host peer slot,下一次扫描再处理原受限 Viewer,不建立 bootstrap 状态机。 - NAT 与传输边界:STUN 必须可用,所有 ordinary peer `RTCPeerConnection` 只接收 STUN ICE servers。Browser LiveKit publisher/subscriber PC 必须显式使用空 `iceServers`,保留 LiveKit signaling 交付的授权 SFU UDP candidates,并由标准 ICE 选择 nominated non-relay pair;不得把 ordinary-peer STUN、LiveKit join response 中的 STUN 或默认公共 STUN混入该 PC,也不得把某一种 candidate type 或数量写成成功条件。部署 STUN 仍供 ordinary peers 和 LiveKit server-side 公网地址发现使用。浏览器 ICE connectivity checks 是 direct UDP 可达性的权威;Screener 不合成 remote candidate、不预测端口、不分类 NAT,也不使用 TCP 探测推断 UDP 媒体可达性。所有恢复和候选授权必须绑定 authenticated room/endpoint session、share generation、base/pending route revision 与 candidate connection identity;成功即停止,失败、超时或绑定失效即撤销,direct 阶段耗尽后在同一 operation deadline 内进入 SFU/UDP,全部可用路径耗尽后明确等待或失败。已接受的完整顺序只有保持或尝试 direct/STUN peers,再复用或建立 direct-ingress Host publication/SFU subscription。Screener 不配置 TURN、ICE/TCP、media TCP 或 TLS-relayed media;当前 LiveKit 也保持 UDP-only。未来若真实严格防火墙矩阵证明需要另一种传输,必须把它作为 LiveKit 内部能力重新决策,不能增加第三种应用 route candidate。LiveKit join JWT 仅按协议进入不得记录的 WebSocket request target;HTTPS/WSS 继续独立使用 TLS/TCP。 -- 质量控制:推荐档位始终只有 1080p60(8 Mbps)、1080p30(5 Mbps)、720p30(3 Mbps)三档,默认选择中档 `1080p30`。高级设置的分辨率选项增加 `480p`(854x480),其 FPS 与码率仍由高级控件独立选择;不增加 `480p` 推荐档、preset ID 或固定的 480p FPS/码率组合。三档与高级设置初始值统一采用 `balanced`(平衡),`maintain-resolution`(清晰优先)、`maintain-framerate`(流畅优先)保留为显式选择。Browser 视频固定使用 VP8;界面、质量状态和 wire 不提供 codec 选择或切换。分享期间可直接换视频档位和 64/128/256 kbps 音频 sender ceiling,不重新选择来源或重建健康连接。Peer-assisted relay 与已配置的 SFU 必须接收同一个严格校验的房间级权威设置,覆盖当前和以后建立的 sender;普通 P2P 信令结构保持不变。任何设置都只是浏览器上限和偏好,具体降级由浏览器决定;必须读回实际 sender 参数并通过 stats 观测结果、显示参数拒绝、改写和持续受限原因。不做自定义动态 FPS、综合评分或周期性全房间调档。 -- 路径隔离表示:direct/peer 继续使用每条 PeerConnection 独立的 stock WebRTC GCC,不把 Viewer 反馈合成全房目标。Browser Host 的唯一 SFU publication 提供固定两层 VP8 `HIGH+LOW` simulcast;LiveKit Dynacast 与每个 subscriber 的 stream allocator/BWE 负责启停发布层和选择实际下行层,Screener 不实现质量评分、带宽估计、周期调档或应用媒体层 selector。`adaptiveStream` 只可用于将 LiveKit remote track 直接 attach 到实际播放元素的 leaf Viewer;SFU-fed relay 的订阅不得由本地元素尺寸或可见性决定,因为其 peer children 的需求不在 LiveKit 内。表示选择不改变拓扑、端点容量或路由失效权威。当前源码和生产仍是单层 `HIGH`,必须先通过 ADR-0007 的 pinned-stack 物理门禁才可实现和部署上述目标。 +- 质量控制:推荐档位始终只有 1080p60(8 Mbps)、1080p30(5 Mbps)、720p30(3 Mbps)三档,默认选择中档 `1080p30`。高级设置的分辨率选项增加 `480p`(854x480),其 FPS 与码率仍由高级控件独立选择;不增加 `480p` 推荐档、preset ID 或固定的 480p FPS/码率组合。三档与高级设置初始值统一采用 `balanced`(平衡),`maintain-resolution`(清晰优先)、`maintain-framerate`(流畅优先)保留为显式选择。Browser 视频固定使用 VP8;界面、质量状态和 wire 不提供 codec 选择或切换。分享期间可直接换视频档位和 64/128/256 kbps 音频 sender ceiling,不重新选择来源或重建健康连接。Peer-assisted relay 与已配置的 SFU 必须接收同一个严格校验的房间级权威设置,覆盖当前和以后建立的 sender;普通 P2P 信令结构保持不变。任何设置都只是浏览器上限和偏好,具体降级由浏览器决定;必须读回实际 sender 参数并通过 stats 观测结果、显示参数拒绝、改写和持续受限原因。Host 资源不足时由 Host 主动选择较低的现有档位,不得以删除 SFU LOW 表示来静默牺牲受限下游。不做自定义动态 FPS、综合评分或周期性全房间调档。 +- 路径隔离表示:direct/peer 继续使用每条 PeerConnection 独立的 stock WebRTC GCC,不把 Viewer 反馈合成全房目标。Browser Host 的唯一 SFU publication 提供固定两层 VP8 `HIGH+LOW` simulcast;LiveKit Dynacast 按全房最高需求停用其上的发布层,无订阅时停用全部层,任何 `HIGH` 需求则保留两层编码;启用 send-side BWE 的 server stream allocator 为每个 subscriber 选择实际下行层。Screener 不实现质量评分、带宽估计、周期调档或应用媒体层 selector。所有当前 Viewer 都可能承担 peer children,因此 subscriber 保持 `adaptiveStream: false`,不得让本地元素尺寸或可见性控制 relay ingress。表示选择不改变拓扑、端点容量或路由失效权威。ADR-0007 的 pinned-stack 物理门禁已经通过;当前源码和生产仍是尚未迁移的单层 `HIGH`。 - 路由失效输入:应用不推断 parent-wide 质量,也不计算综合分数。当前 authenticated child 只在 PeerConnection `failed/closed`,或非权威暂停时现有 periodic stats 在统一 deadline 内没有新 decoded frame 时,使自己的 exact edge 失效并唤醒同一个 reconcile。decoded progress 只保存 route-keyed `lastDecodedAt`,route/connection变化或新帧重置、暂停抑制,同一 edge只报告一次,不增加轮询器。Active SFU route 暂时没有 video track、stats report 为空或 stats 读取失败时,现有采样 cadence 仍把该次作为无 decoded progress 推进 deadline;它不得停表、伪造连接指标或立即判定 edge 失效,只有 route 停用或 teardown 才停止该 cadence。该 operation 把失败的 exact direct parent 在当前 fact version 直接视为已尝试,先选择其他 direct parent,再进入有界 SFU suffix;新的外部事实才允许原 direct tuple 重新进入。Track availability、低码率、低 FPS、模糊、sender/viewer stats 与 SFU layer只用于stock WebRTC/LiveKit恢复和诊断。relay自身ingress失效时,它作为child换父并保留subtree;disconnect或effective downstream capacity降低只让direct/overflow children逐个走同一事务。 - 断线恢复:临时网络变化先交给 WebRTC/LiveKit 的 ICE、consent、重连和流状态。Direct edge 可在 exact current parent/child sessions 内采用 framework rebuild 的新 connection ID而不改变topology。应用只在上述exact edge失效后运行一次有界reconcile;任一步成功即停止。候选耗尽时hard-invalid、confirmed-departed或overflow edge被退休并进入明确waiting,healthy bootstrap edge保留。所有异步结果受authenticated session、share generation、base/pending route revision与candidate connection identity约束,未受影响分支保持sticky。权威暂停abort未提交candidate、保留active graph、抑制stall,并在resume后重新reconcile。 - 同一 operation 的一个总 deadline 只按实际存在的 `direct | sfu` 语义阶段等分,不配置每个 candidate 的 magic timeout。阶段内的硬失败继续推进 cursor;silent ICE 到达 direct 阶段边界时跳过剩余 direct tuple,必须把派生份额留给 SFU。20 名 Viewer 同时加入时,每名 child 的 operation deadline 不会被人数除分,但单一 room-serial operation 可能形成线性排队尾延迟;在接受另一种并发模型前,只测量 queue wait、candidate start、first decoded frame 与 final route 的 p50/p95/max,不用 NAT 猜测、TCP probe 或 magic timeout 绕过排队。 @@ -50,7 +50,7 @@ SFU 的 publication、subscription、server-resource accounting、临时 overlap - SFU 质量设置生命周期:直播中修改档位时,浏览器 sender 的 applied readback、LiveKit `LocalVideoTrack` 保存的 degradation preference、track restart options 与 `LocalTrackPublication` 的 republish options 必须作为同一次串行操作更新;任一步失败恢复旧 sender 与旧 SDK 保存态,旧 publication generation 不得回写当前状态。LiveKit full reconnect/republish 不得恢复初次发布的旧偏好。该修复不改变 codec、码率上限、层数或路由,也不能单独解释即时低帧。 - 本地预览性能候选:在相同动态场景中 A/B 测量 Host 动态预览开/关的 CPU、GPU、encode time 和实际 FPS。只有稳定收益且捕获、编码和远端画面不受影响时,才允许直播开始后默认显示静止缩略图或隐藏动态预览并提供按需展开;未测前不宣称收益。 - Peer-assisted 媒体路径:所有非服务器 endpoint 都受同一个服务端权威稳态 outbound media-copy capacity 约束,默认 `2` 且可静态配置为 `1`、`2` 或 `3`;peer child 与 Host publication 都按实际发送副本计数。每名 Viewer 任一时刻只有一个 active upstream,活动媒体图保持无环;只在离散事件中做有界分配或换父。浏览器把收到的标准 remote track 加入独立下游 `RTCPeerConnection` 时会逐跳解码并分别重编码,必须测量而不能称为 shared encode。服务端只在有界房间内存中保存严格校验的质量设置,不写入持久存储。 -- 原生 shared encode 分享端:后续独立的 Electron/原生分享端若获新决策,必须让每个 active representation 只产生一份兼容编码结果,并在获准的 direct、peer 与 SFU transport 间复用。第二表示不属于当前合同,只有明确硬件路径和真实游戏、编码、上传门槛通过后才可重开。每条连接仍保留独立 WebRTC packetization、SSRC、pacing、加密和反馈,且各自消耗上传带宽。浏览器不保证跨 `RTCPeerConnection` 复用编码器,因此当前 Web spike 不能宣称满足该保证。 +- 原生 shared encode 分享端:后续独立的 Electron/原生分享端若获新决策,必须让每个 active representation 只产生一份兼容编码结果,并在获准的 direct、peer 与 SFU transport 间复用。Native 如何提供已接受的 SFU LOW/HIGH 表示仍须通过明确硬件路径和真实游戏、编码、上传门槛。每条连接仍保留独立 WebRTC packetization、SSRC、pacing、加密和反馈,且各自消耗上传带宽。浏览器不保证跨 `RTCPeerConnection` 复用编码器,因此当前 Web spike 不能宣称满足该保证。 - 原生 shared encode relay:具备原生 relay 能力的非服务器端点同样受统一服务端权威下游容量约束,并应复用兼容编码输出而不再次编码。当前普通浏览器 relay 使用逐边重编码的标准 WebRTC 路径,不伪称 shared encode。 - SVC 重开条件:当前标准 Web/LiveKit SVC 已静态拒绝为 `no-go-web-svc-cross-path-hardware-contract`。sender `scalabilityMode` 没有 direct/peer receiver 对应的按层选择或跨 PeerConnection shared-encode 契约;requested/applied mode、Media Capabilities `powerEfficient` 和 RTCStats encoder 字段也不能证明没有静默软件 fallback。Pinned client 2.22.0 还会把 screen-share SVC 覆盖为三层 temporal-only `L1T3`,违反当前 Browser 无 SVC、单表示合同。只有未来 native sender 能明确选择硬件编码器、最多发布两层、将同一编码输出复用于 direct/peer/SFU、保持每条路径独立选择并通过真实游戏和亚秒延迟门槛时,才可用新的 capability decision 重开 SVC;不能把浏览器 API 支持或 UA 推断当成采用理由。 - 分包/分层多树实验:把编码包、对象或可独立解码层拆到多个 peer 路径以分散上传,只能在独立 spike 中验证。必须保持亚秒级,处理多父重组、排序、丢包恢复、关键帧、同步和节点退出;未通过门槛前不进入产品路由控制器。 @@ -151,12 +151,12 @@ SFU 的 publication、subscription、server-resource accounting、临时 overlap ### 质量和恢复 - Given 用户选择任一推荐档位或高级质量参数,When 接收端实际分辨率、帧率或码率持续显著低于该目标,Then 诊断数据能区分采集约束、发送编码、带宽/拥塞、当前 peer/SFU 路径和接收端限制;产品不得只改变档位标签却继续隐藏实际降级。 -- Given Browser Host 建立 SFU publication,When sender 参数和 RTCStats 可读,Then 只存在一个 VP8 outbound video encoding,不出现 `q,h` RID simulcast 或第二表示;弱下行只由 stock LiveKit/WebRTC 传输适应,不能改变 Host publication、健康路径或拓扑,direct/peer 继续使用各自的 stock WebRTC GCC。 +- Given Browser Host 建立 SFU publication,When sender 参数和 RTCStats 可读,Then 最多存在固定的 VP8 `LOW` 与 `HIGH` 两个 outbound video encoding,表示数不随 Viewer 数增长;LiveKit Dynacast 按聚合层需求停用其上的层,启用 send-side BWE 的 server stream allocator 为每个 SFU subscriber 选择实际下行层,弱下行不能改变健康路径、拓扑或 direct/peer 各自的 stock WebRTC GCC。 - Given 当前 Viewer 的 exact edge 进入 `failed/closed`,或在非权威暂停时超过统一 deadline 没有新 decoded frame,When 服务端确认 authenticated child、share generation、active revision 与 connection identity 仍匹配,Then 只把该 child 送入同一个 reserve/prepare/first-frame/commit-or-abort 事务;低码率、低 FPS、模糊和 SFU layer 只进入诊断。 - Given 当前 edge 仍在解码但分辨率、FPS、码率、freeze ratio、RTT、jitter、loss 或 limitation evidence 持续影响观感,When 评估是否自动换父,Then 当前产品只诊断而不迁移:现有路径证据不能证明未连接 parent 的反事实质量,WebRTC/LiveKit 继续拥有拥塞适应,应用不建立 alternative-parent probe、hysteresis/cooldown、加权分数或周期性重排。 - Given 同一 Viewer 同时是某人的 child 和其他人的 parent,When 自身 ingress 失效,Then 它作为 child 换父、在新 ingress 首帧提交前保留可用旧 edge,并保持 subtree。Given endpoint disconnect 或 `effectiveDownstreamCapacity` 降低,Then controller 只保留稳定顺序内可容纳的 children,并将 deterministic overflow children 逐个送入同一事务;childless 后删除 disconnected relay。 - Given 当前分享权威 paused,When 新 Viewer 加入、edge 失效或已有 child operation 尚未提交,Then abort 整个 pending route operation 及其 current candidate/预留、保留 active graph、让新 Viewer waiting,并且不因没有 decoded frame 触发恢复。普通 Pause/Resume 只通过当前 Host session 与 `shareGeneration` 约束的一次 paused-state 更新同步本地 capture tracks、服务端快照、`host-status` 和 route pause;Resume 重新启用同一批 tracks并从当前 graph 唤醒 fresh reconciliation,不建立第二次授权、source acknowledgement、媒体 proof 或回滚状态。较早 share generation、已替换 Host session、stop 或新分享的迟到更新不得改变当前状态。 -- Given 当前 Browser SFU publication,When 进行质量验收,Then 第二表示的 frames/bytes 必须不存在;公开 SFU 路径若受带宽限制,只如实显示 stock transport 的降级或不可用,不得恢复 `LOW`、退回全房间最低目标或增加应用媒体层 controller。 +- Given 当前 Browser SFU publication,When 进行质量验收,Then 受限 SFU subscriber 必须能由 LiveKit 原生选择 `LOW`,非受限 subscriber 仍可取得 `HIGH`;Dynacast 在 LOW-only 或无订阅时停用更高层或全部层,但任何 `HIGH` 需求允许 `LOW+HIGH` 同时编码。不得退回全房间最低目标、启用 AdaptiveStream 控制潜在 relay ingress,或增加应用媒体层 controller。 - 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 报告被丢弃;有效报告也只能用于诊断,不能改变 Host 表示或路径状态。 - Given 受控 RTT/丢包测试条件,When 连续分享高动态游戏画面 30 分钟,Then 延迟、帧率与丢帧达到上述目标,且没有不可恢复的冻结。