fix(linux): capture the default output's monitor for remote audio (#16681)

* fix(linux): capture the default output's monitor for remote audio

With no audio input chosen, the _pa service recorded the first source whose name contains "monitor". Sources are listed in creation order, so on a host with more than one output, such as onboard audio plus HDMI, a USB headset or a Bluetooth speaker, that monitor can belong to an output the desktop does not play to, and the controlling side hears silence. Ask the server for the default sink and record its monitor, keeping the first monitor as the fallback.

* fix(linux): log the lookups that fall back to the first monitor

* fix(linux): fall back to the first monitor when the default sink has no monitor name

* fix(linux): throttle the default monitor lookup warnings
This commit is contained in:
Lukas authored and GitHub committed 2026-10-24 14:56:09 +08:00
1 parent ed6a1acce8
commit c8a4e0680c
1 file changed
+46
+46
View File
@@ -1550,6 +1550,9 @@ where
}
pub fn get_pa_monitor() -> String {
if let Some(monitor) = get_default_pa_monitor() {
return monitor;
}
get_pa_sources()
.drain(..)
.map(|x| x.0)
@@ -1558,6 +1561,49 @@ pub fn get_pa_monitor() -> String {
.unwrap_or("".to_owned())
}
// The first monitor in the source list can belong to an output the desktop does not play to.
fn get_default_pa_monitor() -> Option<String> {
use pulsectl::controllers::*;
// The audio service retries about every second while PulseAudio is unavailable.
const LOG_INTERVAL: std::time::Duration = std::time::Duration::from_secs(60);
let mut handler = SinkController::create()
.map_err(|err| {
hbb_common::throttled_log!(
LOG_INTERVAL,
warn,
"Failed to create SinkController: {:?}",
err
)
})
.ok()?;
let sink = handler
.get_server_info()
.map_err(|err| {
hbb_common::throttled_log!(
LOG_INTERVAL,
warn,
"Failed to get the PulseAudio server info: {:?}",
err
)
})
.ok()?
.default_sink_name?;
handler
.get_device_by_name(&sink)
.map_err(|err| {
hbb_common::throttled_log!(
LOG_INTERVAL,
warn,
"Failed to get the default sink {}: {:?}",
sink,
err
)
})
.ok()?
.monitor_name
.filter(|name| !name.is_empty())
}
pub fn get_pa_source_name(desc: &str) -> String {
get_pa_sources()
.drain(..)