Fix virtual mic audio linking (#62)

* Fix virtual mic audio linking by managing it in AudioPlayer lifecycle

- Moved `link_player_to_virtual_mic` to `src/utils/pipewire.rs` and updated it to return a termination sender.
- Added `player_link_sender` to `AudioPlayer` to manage the PipeWire link between the daemon and the virtual mic.
- Integrated linking logic into `AudioPlayer::play` and `AudioPlayer::update` to ensure the link is established when audio starts playing.
- Ensured the link is terminated in `AudioPlayer::drop_stream` when the audio sink is closed.
- Removed redundant and potentially failing startup linking loop from the daemon.
- Fixed log spam by ensuring `link_player` is only attempted when necessary and errors are handled gracefully.
- Maintained compatibility with stable Rust by avoiding unstable features.

Co-authored-by: arabianq <55220741+arabianq@users.noreply.github.com>

* small refactor

* refactor

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
This commit is contained in:
Tarasov Aleksandr
2026-04-17 14:24:58 +03:00
committed by GitHub
parent 5c4b8f4b45
commit 2a8fcca06b
5 changed files with 97 additions and 76 deletions
+45 -11
View File
@@ -2,7 +2,7 @@ use crate::{
types::pipewire::{DeviceType, Terminate},
utils::{
daemon::get_daemon_config,
pipewire::{create_link, get_device},
pipewire::{create_link, get_device, link_player_to_virtual_mic},
},
};
use rodio::{Decoder, DeviceSinkBuilder, MixerDeviceSink, Player, Source};
@@ -58,6 +58,7 @@ pub struct AudioPlayer {
pub next_id: u32,
input_link_sender: Option<pipewire::channel::Sender<Terminate>>,
player_link_sender: Option<pipewire::channel::Sender<Terminate>>,
pub input_device_name: Option<String>,
pub volume: f32, // Master volume
@@ -74,6 +75,7 @@ impl AudioPlayer {
next_id: 1,
input_link_sender: None,
player_link_sender: None,
input_device_name: daemon_config.default_input_name.clone(),
volume: default_volume,
@@ -98,21 +100,46 @@ impl AudioPlayer {
fn drop_stream(&mut self) {
if self.stream_handle.is_some() {
self.stream_handle = None;
self.abort_player_link_thread();
}
}
fn abort_link_thread(&mut self) {
if let Some(sender) = &self.input_link_sender {
match sender.send(Terminate {}) {
Ok(_) => {
println!("Sent terminate signal to link thread");
self.input_link_sender = None;
}
Err(_) => eprintln!("Failed to send terminate signal to link thread"),
if let Ok(_) = sender.send(Terminate {}) {
println!("Sent terminate signal to input link thread");
self.input_link_sender = None;
} else {
eprintln!("Failed to send terminate signal to input link thread");
}
}
}
fn abort_player_link_thread(&mut self) {
if let Some(sender) = &self.player_link_sender {
if let Ok(_) = sender.send(Terminate {}) {
println!("Sent terminate signal to player link thread");
self.player_link_sender = None;
} else {
eprintln!("Failed to send terminate signal to player link thread");
}
}
}
async fn link_player(&mut self) -> Result<(), Box<dyn Error>> {
if self.player_link_sender.is_some() {
return Ok(());
}
match link_player_to_virtual_mic().await {
Ok(sender) => {
self.player_link_sender = Some(sender);
Ok(())
}
Err(_) => Ok(()),
}
}
async fn link_devices(&mut self) -> Result<(), Box<dyn Error>> {
self.abort_link_thread();
@@ -316,6 +343,7 @@ impl AudioPlayer {
}
self.ensure_stream()?;
self.link_player().await.ok();
let id = self.next_id;
self.next_id += 1;
@@ -394,6 +422,10 @@ impl AudioPlayer {
self.link_devices().await.ok();
}
}
if self.stream_handle.is_some() && self.player_link_sender.is_none() {
self.link_player().await.ok();
}
}
// Handle looped sounds
@@ -423,10 +455,12 @@ impl AudioPlayer {
}
for handle in restart_futures {
if let Ok(Some((id, source))) = handle.await {
if let Some(sound) = self.tracks.get_mut(&id) {
sound.sink.append(source);
sound.sink.play();
if let Ok(res) = handle.await {
if let Some((id, source)) = res {
if let Some(sound) = self.tracks.get_mut(&id) {
sound.sink.append(source);
sound.sink.play();
}
}
}
}