feat(audio): add --strict-audio + app_audio route-status events
With --app, pixelpass mirrors the default-sink monitor (whole desktop) until the
chosen app's streams route, and restores that loopback if the app's audio later
stops. That fallback captures everything playing — including a voice call the
sharer is in — so a caller watching the share can hear themselves echoed back
(peerspeak bug A23: the per-app pick alone is best-effort, not a guarantee).
- New --strict-audio flag (HostOpts.strict_audio): with --app, never load the
default-sink loopback (not at startup, not on LastRoutedStreamGone). The viewer
hears only the chosen app, and silence when it's quiet — never the rest of the
desktop. No effect without --app; standalone best-effort behavior is unchanged.
- New app_audio JSON event ({"event":"app_audio","state":"routed"|"lost"}),
emitted whenever --app is set, so a front-end (peerspeak) can tell when the
chosen app's audio is actually live vs. dropped and warn accordingly.
- Banner capture summary shows "(strict)" when active.
Unknown-event-tolerant: pixelpass's own --gui child parser skips lines it can't
deserialize, so app_audio doesn't disturb it. 10 tests (+2: wire-shape + banner),
clippy --all-targets clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+43
-1
@@ -488,9 +488,51 @@ fn copy_to_clipboard(text: &str) -> bool {
|
||||
fn capture_summary(opts: &HostOpts) -> String {
|
||||
let mut bits = vec![if opts.window { "window" } else { "fullscreen" }.to_string()];
|
||||
if let Some(app) = &opts.app {
|
||||
bits.push(format!("app-audio={app}"));
|
||||
if opts.strict_audio {
|
||||
bits.push(format!("app-audio={app} (strict)"));
|
||||
} else {
|
||||
bits.push(format!("app-audio={app}"));
|
||||
}
|
||||
} else {
|
||||
bits.push("system-audio".to_string());
|
||||
}
|
||||
bits.join(" + ")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::cli::Quality;
|
||||
|
||||
fn opts(app: Option<&str>, strict_audio: bool) -> HostOpts {
|
||||
HostOpts {
|
||||
window: false,
|
||||
app: app.map(str::to_string),
|
||||
strict_audio,
|
||||
display_server: None,
|
||||
quality: Quality::Auto,
|
||||
bitrate: None,
|
||||
framerate: None,
|
||||
max_height: None,
|
||||
no_hwencode: false,
|
||||
max_viewers: None,
|
||||
interactive: false,
|
||||
relay: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn capture_summary_reflects_audio_mode() {
|
||||
assert_eq!(capture_summary(&opts(None, false)), "fullscreen + system-audio");
|
||||
assert_eq!(
|
||||
capture_summary(&opts(Some("Firefox"), false)),
|
||||
"fullscreen + app-audio=Firefox"
|
||||
);
|
||||
// strict only shows when an app is selected.
|
||||
assert_eq!(
|
||||
capture_summary(&opts(Some("Firefox"), true)),
|
||||
"fullscreen + app-audio=Firefox (strict)"
|
||||
);
|
||||
assert_eq!(capture_summary(&opts(None, true)), "fullscreen + system-audio");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user