From 10203e1edb71cf4b3cfe79ede49d281a88d056f6 Mon Sep 17 00:00:00 2001 From: Mollusk Date: Mon, 20 Jul 2026 05:04:25 -0400 Subject: [PATCH] docs: adopt fan-out (Option C) after feasibility spike Ran the direct-link spike on the live graph (PipeWire 1.6.8, WirePlumber 0.5.15). Fan-out carries full-level audio for paplay, mpv and VLC while the application keeps its existing speaker link; WirePlumber does not reap foreign links across default-sink switch, suspend/resume or 100s steady state; and non-lingering links are destroyed automatically when their owning connection is SIGKILLed. The decisive result is that destroying the capture sink mid-share left the application playing to its speakers undisturbed, so capture-side failure degrades to "not captured" rather than breaking the user's audio. That is the property the move-based design had to work hard to approximate. Records what the spike does not prove: fidelity beyond signal presence, daemon restart, quantum perturbation, and exclusive/passthrough streams. The capture null sink is still pactl-owned, so Stop Share continues to leak a module every time and the graceful-stop work is still owed. Eligibility becomes a broad guarded selector rather than a narrow allowlist, since copying no longer risks disturbing the source. Option A and its attendant cleanup, restore and output-switch machinery are retained for the record but are no longer the plan. A v3 rewrite is owed once the AEC playback-leg identity is settled. Co-Authored-By: Claude Opus 4.8 --- docs/screenshare-audio-exclusion-plan.md | 62 ++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/docs/screenshare-audio-exclusion-plan.md b/docs/screenshare-audio-exclusion-plan.md index 83565d7..efb8b44 100644 --- a/docs/screenshare-audio-exclusion-plan.md +++ b/docs/screenshare-audio-exclusion-plan.md @@ -215,7 +215,33 @@ saved-default restoration plus idempotent crash repair. B's genuine advantages, recorded honestly: new apps land in the mix automatically, no registry-add race, and no sweeping up of arbitrary module playback legs. -## 5.1 Option C — copy instead of move (Codex's proposal; now the leading candidate) +## 5.1 Option C — copy instead of move — ✅ **DECIDED: this supersedes Option A** + +> **STATUS 2026-07-20: feasibility spike RUN, Option C ADOPTED by both reviewers.** +> Measurements: `~/Documents/handoff-docs/Claude/peerspeak/fanout-spike-results-2026-07-20.md`. +> Codex ratification: `…/Codex/peerspeak/review-2026-07-20-fanout-spike-round4-ratification.md`. +> Everything below in §5 (Option A/B), §6, §7 and §8.2–8.3 is **retained for the record +> but is no longer the plan** — most of it describes problems Option C does not have. +> **A v3 rewrite is owed once the AEC gate (§10, OPEN-Q 3) is settled.** +> +> Measured on this machine (PipeWire 1.6.8 / WirePlumber 0.5.15): +> fan-out carries full-level audio (−24.1 dB, matching the speaker monitor) for +> **paplay, mpv and VLC**; the app keeps its speaker link; WirePlumber does **not** reap +> foreign links across default-sink switch, switch-back, suspend, resume, or 100 s +> steady state; and non-lingering links are destroyed automatically when their owning +> connection is **SIGKILLed**. +> **Strongest result — destroying the capture sink mid-share left the application +> playing to its speakers undisturbed.** Capture-side failure degrades to +> "not captured," never to "the user's audio is broken." +> +> **Not yet proven** (do not overclaim these): fidelity beyond signal presence — equal +> `mean_volume` on a sine cannot see xruns, drift or dropouts; daemon restart; +> quantum/latency perturbation; sample-rate mismatch, surround, `port.exclusive`, +> IEC958 passthrough; two concurrent capture sinks. +> **Also still true:** the capture null sink is `pactl`-loaded and pipewire-pulse-owned +> (`audio.rs:69`), so **Stop Share still leaks one null-sink module every time**. +> Option C fixes stranded *desktop audio*, not the sink leak — §6's graceful stop is +> still owed, and the capture sink should become connection-owned too. Codex's round-2 review made the strongest architectural point either review has produced: **the design is overbuilt because it *moves* audio that pixelpass only needs @@ -256,8 +282,38 @@ open question is whether **WirePlumber tears down foreign links** it did not cre part of its own policy management. That is the one thing that would kill Option C, and it is cheap to test. -**Recommendation: a direct-link feasibility spike before any further work on Option A.** -If fan-out holds on this graph, Option C supersedes Option A outright. +### 5.2 Eligibility under Option C — a broad guarded selector + +Because copying is cheap and non-destructive, the narrow allowlist of §4.2 is no longer +justified — but "fan out everything and let mis-selection be harmless" is **also wrong**: +selecting peerspeak-owned playback recreates self-echo, selecting the AEC leg recreates +call echo, and a second link still participates in format/buffer negotiation, so it can +perturb the source even though the original link survives. + +Agreed replacement: + +- Start from **all** `Stream/Output/Audio` nodes. +- Exclude `peerspeak.owned` (§4.1). +- Exclude the proven AEC identity (**OPEN-Q 3 — the remaining gate**). +- Exclude pixelpass-owned objects and any graph path with capture ancestry (cycles). +- Skip `port.exclusive`, encoded/passthrough, and already-owned links. +- Create **per-port** links, **retain the Link proxies** for the session, and count a + stream captured only once every required link reaches `ACTIVE`. In the Rust + `pipewire` crate, dropping a proxy destroys the object — that is the ownership + mechanism, and it is also what makes SIGKILL safe. +- On failure: leave the original route untouched, report the stream as unsupported. + +`node.dont-move` drops out of the predicate entirely — fan-out is not a metadata move. + +### 5.3 Next gate + +**Load `module-echo-cancel` on the live graph and establish a stable, pixelpass- +observable identity for its actual playback `Stream/Output/Audio` leg** — not merely the +virtual sink — that survives unload/reload without PID guesswork. Then fan out an +ordinary tone and demonstrate that remote-call audio is absent from the capture monitor. + +If no stable tag or module/group relationship exists, the AEC path must be redesigned +before this feature proceeds. Both reviewers agree nothing belongs ahead of this. ## 6. Graceful stop is a prerequisite, not a test case