feat(audio): release/refresh output-device button (#263)
After bit-perfect ALSA Direct grabs a DAC exclusively (hw: + D-Bus reservation), the device stays sequestered and is invisible to PipeWire, so switching back to the PipeWire backend can't see it without restarting the app. Tauri had a refresh affordance for hot-plugged DACs; this is the refresh plus an explicit release, in one button. Backend: Player::release_device() / AudioCommand::ReleaseDevice tears the stream down immediately (cancelling the deferred drop), freeing the hw: grab + its reservation, then un-suspends the parked sink and resets any forced graph clock (both self-gating). It does NOT reopen, unlike reinit_device which would re-grab the device. UI: a refresh-cw icon button beside the Output device select, propagated through the settings callback chain to handle_release_device, which releases, waits for PipeWire to reclaim, then re-enumerates so a freed or hot-plugged DAC reappears without an app restart. cc
This commit is contained in:
7 files changed
+150
-15
No files matched your search
@@ -937,6 +937,36 @@ pub async fn handle_reset(
|
||||
apply_audio(&ctx, &runtime, Apply::Reinit);
|
||||
}
|
||||
|
||||
/// Release the held output device, then re-enumerate. Frees a device QBZ is
|
||||
/// holding exclusively (ALSA Direct, which leaves the DAC invisible to
|
||||
/// PipeWire/other apps) and rebuilds the snapshot so a freed or hot-plugged
|
||||
/// DAC shows up in the list without restarting the app — the Tauri
|
||||
/// "refresh" affordance plus an explicit release, in one action.
|
||||
pub async fn handle_release_device(
|
||||
ctx: Arc<SettingsCtx>,
|
||||
runtime: Arc<AppRuntime<SlintAdapter>>,
|
||||
weak: slint::Weak<AppWindow>,
|
||||
) {
|
||||
if let Err(e) = runtime.core().player().release_device() {
|
||||
log::error!("[qbz-slint] player.release_device failed: {e}");
|
||||
}
|
||||
// Let PipeWire/WirePlumber reclaim the just-freed device before we list.
|
||||
tokio::time::sleep(std::time::Duration::from_millis(300)).await;
|
||||
let snap = {
|
||||
let ctx = ctx.clone();
|
||||
match tokio::task::spawn_blocking(move || load_snapshot(&ctx)).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
log::error!("[qbz-slint] release-device rebuild task failed: {e}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
let _ = weak.upgrade_in_event_loop(move |w| {
|
||||
apply_snapshot(&w, snap);
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Reference in new issue
Block a user