mirror of
https://github.com/arabianq/pipewire-soundpad.git
synced 2026-04-28 06:21:23 +00:00
fix: drop audio stream when idle to allow system suspend (#54)
* fix: drop audio stream when idle to allow system suspend The daemon kept its ALSA playback stream open permanently, which PipeWire reported as a running Stream/Output/Audio node even with no tracks playing. This prevented desktop environments from detecting idle state and entering suspend. - Make the audio sink on-demand: created when playback starts, dropped when all tracks finish - Reduce player loop polling from 100ms to 2s when idle - Throttle PipeWire device enumeration to every ~5s while playing - Log only first and last link retry attempt instead of all 60
This commit is contained in:
+20
-10
@@ -40,7 +40,11 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
println!("Successfully linked player to virtual mic.");
|
||||
break;
|
||||
}
|
||||
Err(e) => println!("{e}\t{i}/{max_retries}"),
|
||||
Err(e) => {
|
||||
if i == 0 || i == max_retries {
|
||||
eprintln!("{e} (attempt {i}/{max_retries})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sleep(Duration::from_millis(1000)).await;
|
||||
@@ -174,19 +178,25 @@ async fn commands_loop(listener: UnixListener) -> Result<(), Box<dyn Error>> {
|
||||
}
|
||||
|
||||
async fn player_loop() {
|
||||
let mut device_check_counter: u32 = 0;
|
||||
loop {
|
||||
match get_audio_player().await {
|
||||
let is_idle = match get_audio_player().await {
|
||||
Ok(player_mutex) => {
|
||||
let mut audio_player = player_mutex.lock().await;
|
||||
audio_player.update().await;
|
||||
let check_devices = device_check_counter == 0;
|
||||
audio_player.update(check_devices).await;
|
||||
audio_player.tracks.is_empty()
|
||||
}
|
||||
Err(_err) => {
|
||||
// To avoid spamming logs every 100ms when audio player fails to init
|
||||
// we can just sleep, or you might prefer to print the error.
|
||||
// Assuming it failed to initialize, no player update is possible.
|
||||
}
|
||||
}
|
||||
Err(_err) => true,
|
||||
};
|
||||
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
if is_idle {
|
||||
device_check_counter = 0;
|
||||
sleep(Duration::from_secs(2)).await;
|
||||
} else {
|
||||
// Check devices every ~5 seconds (50 * 100ms) while playing
|
||||
device_check_counter = (device_check_counter + 1) % 50;
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+45
-19
@@ -53,7 +53,7 @@ pub struct PlayingSound {
|
||||
}
|
||||
|
||||
pub struct AudioPlayer {
|
||||
pub stream_handle: MixerDeviceSink,
|
||||
stream_handle: Option<MixerDeviceSink>,
|
||||
pub tracks: HashMap<u32, PlayingSound>,
|
||||
pub next_id: u32,
|
||||
|
||||
@@ -68,10 +68,8 @@ impl AudioPlayer {
|
||||
let daemon_config = get_daemon_config();
|
||||
let default_volume = daemon_config.default_volume.unwrap_or(1.0);
|
||||
|
||||
let stream_handle = DeviceSinkBuilder::open_default_sink()?;
|
||||
|
||||
let mut audio_player = AudioPlayer {
|
||||
stream_handle,
|
||||
stream_handle: None,
|
||||
tracks: HashMap::new(),
|
||||
next_id: 1,
|
||||
|
||||
@@ -88,6 +86,21 @@ impl AudioPlayer {
|
||||
Ok(audio_player)
|
||||
}
|
||||
|
||||
fn ensure_stream(&mut self) -> Result<&MixerDeviceSink, Box<dyn Error>> {
|
||||
if self.stream_handle.is_none() {
|
||||
let mut sink = DeviceSinkBuilder::open_default_sink()?;
|
||||
sink.log_on_drop(false);
|
||||
self.stream_handle = Some(sink);
|
||||
}
|
||||
Ok(self.stream_handle.as_ref().unwrap())
|
||||
}
|
||||
|
||||
fn drop_stream(&mut self) {
|
||||
if self.stream_handle.is_some() {
|
||||
self.stream_handle = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn abort_link_thread(&mut self) {
|
||||
if let Some(sender) = &self.input_link_sender {
|
||||
match sender.send(Terminate {}) {
|
||||
@@ -179,6 +192,9 @@ impl AudioPlayer {
|
||||
} else {
|
||||
self.tracks.clear();
|
||||
}
|
||||
if self.tracks.is_empty() {
|
||||
self.drop_stream();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_paused(&self) -> bool {
|
||||
@@ -299,12 +315,15 @@ impl AudioPlayer {
|
||||
self.tracks.clear();
|
||||
}
|
||||
|
||||
self.ensure_stream()?;
|
||||
|
||||
let id = self.next_id;
|
||||
self.next_id += 1;
|
||||
|
||||
let duration = source.total_duration().map(|d| d.as_secs_f32());
|
||||
|
||||
let sink = Player::connect_new(self.stream_handle.mixer());
|
||||
let mixer = self.stream_handle.as_ref().unwrap().mixer();
|
||||
let sink = Player::connect_new(mixer);
|
||||
sink.set_volume(self.volume); // Default volume is 1.0 * master
|
||||
sink.append(source);
|
||||
sink.play();
|
||||
@@ -358,20 +377,23 @@ impl AudioPlayer {
|
||||
tracks
|
||||
}
|
||||
|
||||
pub async fn update(&mut self) {
|
||||
if let Some(input_device_name) = &self.input_device_name {
|
||||
// Unlink devices if selected input device was removed
|
||||
if self.input_link_sender.is_some() && get_device(input_device_name).await.is_err() {
|
||||
// Selected input device was removed
|
||||
eprintln!(
|
||||
"Selected input device {} was removed, unlinking devices",
|
||||
input_device_name
|
||||
);
|
||||
self.abort_link_thread();
|
||||
}
|
||||
// Link devices if not linked
|
||||
else if self.input_link_sender.is_none() {
|
||||
self.link_devices().await.ok();
|
||||
pub async fn update(&mut self, check_devices: bool) {
|
||||
if check_devices {
|
||||
if let Some(input_device_name) = &self.input_device_name {
|
||||
// Unlink devices if selected input device was removed
|
||||
if self.input_link_sender.is_some()
|
||||
&& get_device(input_device_name).await.is_err()
|
||||
{
|
||||
eprintln!(
|
||||
"Selected input device {} was removed, unlinking devices",
|
||||
input_device_name
|
||||
);
|
||||
self.abort_link_thread();
|
||||
}
|
||||
// Link devices if not linked
|
||||
else if self.input_link_sender.is_none() {
|
||||
self.link_devices().await.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,6 +434,10 @@ impl AudioPlayer {
|
||||
|
||||
self.tracks
|
||||
.retain(|_, sound| !sound.sink.empty() || sound.looped);
|
||||
|
||||
if self.tracks.is_empty() {
|
||||
self.drop_stream();
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set_current_input_device(&mut self, name: &str) -> Result<(), Box<dyn Error>> {
|
||||
|
||||
Reference in New Issue
Block a user