From b8741a4a673a20758e02d2f80e7782d555141022 Mon Sep 17 00:00:00 2001 From: Tzu-Ching Yang Date: Mon, 15 Jul 2024 02:30:01 +0900 Subject: [PATCH] Using multi-threading to avoid possible deadlock (`v0.0.7`) --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/lib.rs | 16 ++++++ src/player.rs | 24 ++++---- src/shared_player.rs | 130 ++++++++++++++++++++++++++----------------- 5 files changed, 109 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 18c9056..0593177 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -601,7 +601,7 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "super-rodio" -version = "0.0.6" +version = "0.0.7" dependencies = [ "limited-queue", "rodio", diff --git a/Cargo.toml b/Cargo.toml index aa97f5b..e31cbe1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "super-rodio" -version = "0.0.6" +version = "0.0.7" edition = "2021" authors = ["Tzu-Ching Yang"] license = "MIT" diff --git a/src/lib.rs b/src/lib.rs index fcff6e9..bca7d66 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -160,4 +160,20 @@ mod tests { } } } + + #[test] + fn test_add_play_pause_set_auto() { + let player = SharedPlayer::make(); + for _ in 0..10 { + player.add(Song::from("Music".into(), "audio/music".into())); + } + player.play(); + sleep(Duration::from_secs(1)); + player.toggle(); + sleep(Duration::from_secs(1)); + player.use_auto_play(); + sleep(Duration::from_secs(2)); + player.stop(); + // should not be dead if is dead, that is a bug + } } diff --git a/src/player.rs b/src/player.rs index a660d9b..c9875d3 100644 --- a/src/player.rs +++ b/src/player.rs @@ -6,31 +6,31 @@ use crate::song::{ActiveSong, Song}; pub trait Player { /// Add a song to the player - fn add(&self, song: Song); + fn add(&self, song: Song) -> JoinHandle<()>; /// Get current waiting list - fn waiting_list(&self) -> Vec; + fn waiting_list(&self) -> JoinHandle>; /// Get current played history - fn played_list(&self) -> Vec; + fn played_list(&self) -> JoinHandle>; /// Get current active song - fn current_song(&self) -> ActiveSong; + fn current_song(&self) -> JoinHandle; /// Play the song in waiting list fn play(&self) -> JoinHandle<()>; /// Use normal play mode: playing a single song and stop - fn use_normal_play(&self); + fn use_normal_play(&self) -> JoinHandle<()>; /// Use auto play mode: playing all the songs one-by-one in the playlist - fn use_auto_play(&self); + fn use_auto_play(&self) -> JoinHandle<()>; /// Toggle play/pause - fn toggle(&self); - /// Stop current music and clear all songs in waiting/played list - fn stop(&self); + fn toggle(&self) -> JoinHandle<()>; + /// Stop current music + fn stop(&self) -> JoinHandle<()>; /// Clear all songs in waiting/played list - fn clear(&self); + fn clear(&self) -> JoinHandle<()>; /// Check whether the current song is playing - fn is_playing(&self) -> bool; + fn is_playing(&self) -> JoinHandle; /// Set output device generator, the default /// generator is based on `OutputStream::try_default` fn set_device_maker( &self, with_generator: Box (OutputStream, OutputStreamHandle) + Send + Sync>, - ); + ) -> JoinHandle<()>; } diff --git a/src/shared_player.rs b/src/shared_player.rs index 6a9a0de..24333fc 100644 --- a/src/shared_player.rs +++ b/src/shared_player.rs @@ -23,34 +23,50 @@ impl Make for SharedPlayer { } impl Player for SharedPlayer { - fn add(&self, song: Song) { - self.write().unwrap().waiting_q.push(song); + fn add(&self, song: Song) -> JoinHandle<()> { + // acquire an arc for this thread + let state = Arc::clone(&self); + spawn(move || { + state.write().unwrap().waiting_q.push(song); + }) } - fn waiting_list(&self) -> Vec { - self.read() - .unwrap() - .waiting_q - .iter() - .map(Clone::clone) - .collect() + fn waiting_list(&self) -> JoinHandle> { + // acquire an arc for this thread + let state = Arc::clone(&self); + spawn(move || { + state + .read() + .unwrap() + .waiting_q + .iter() + .map(Clone::clone) + .collect() + }) } - fn played_list(&self) -> Vec { - self.read() - .unwrap() - .played_q - .iter() - .map(Clone::clone) - .collect() + fn played_list(&self) -> JoinHandle> { + // acquire an arc for this thread + let state = Arc::clone(&self); + spawn(move || { + state + .read() + .unwrap() + .played_q + .iter() + .map(Clone::clone) + .collect() + }) } - fn current_song(&self) -> ActiveSong { - self.read().unwrap().current.clone() + fn current_song(&self) -> JoinHandle { + // acquire an arc for this thread + let state = Arc::clone(&self); + spawn(move || state.read().unwrap().current.clone()) } fn play(&self) -> JoinHandle<()> { - if self.is_playing() { + if self.is_playing().join().unwrap() { return spawn(|| {}); }; // acquire an arc for child thread @@ -110,56 +126,66 @@ impl Player for SharedPlayer { }) } - fn toggle(&self) { + fn toggle(&self) -> JoinHandle<()> { // acquire an arc for this thread let state = Arc::clone(&self); - // check if old sink exists and - // play/pause it by acquiring read lock - if let Some(sink) = &state.read().unwrap().sink { - if sink.is_paused() { - sink.play(); - } else { - sink.pause(); - } - }; + spawn(move || { + // check if old sink exists and + // play/pause it by acquiring read lock + if let Some(sink) = &state.read().unwrap().sink { + if sink.is_paused() { + sink.play(); + } else { + sink.pause(); + } + }; + }) } - fn stop(&self) { + fn stop(&self) -> JoinHandle<()> { // acquire an arc for this thread let state = Arc::clone(&self); - // check if old sink exists and - // stop it by acquiring read lock - if let Some(sink) = &state.read().unwrap().sink { - sink.stop(); - }; - - self.clear(); // clear the whole list + spawn(move || { + // check if old sink exists and + // stop it by acquiring read lock + if let Some(sink) = &state.read().unwrap().sink { + sink.stop(); + }; + }) } /// Clear the playlist - fn clear(&self) { + fn clear(&self) -> JoinHandle<()> { // acquire an arc for this thread let state = Arc::clone(&self); - let mut state = state.write().unwrap(); - state.waiting_q.clear(); - state.played_q.clear(); + spawn(move || { + let mut state = state.write().unwrap(); + state.waiting_q.clear(); + state.played_q.clear(); + }) } - fn is_playing(&self) -> bool { - // acquire an arc for this thread + fn is_playing(&self) -> JoinHandle { let state = Arc::clone(&self); - let res = state.read().unwrap().current.state == SongState::PLAY; - res + spawn(move || { + // acquire an arc for this thread + let res = state.read().unwrap().current.state == SongState::PLAY; + res + }) } - fn use_normal_play(&self) { + fn use_normal_play(&self) -> JoinHandle<()> { let state = Arc::clone(&self); - state.write().unwrap().mode = PlaybackMode::NORMAL; + spawn(move || { + state.write().unwrap().mode = PlaybackMode::NORMAL; + }) } - fn use_auto_play(&self) { + fn use_auto_play(&self) -> JoinHandle<()> { let state = Arc::clone(&self); - state.write().unwrap().mode = PlaybackMode::AUTO; + spawn(move || { + state.write().unwrap().mode = PlaybackMode::AUTO; + }) } /// Set output device generator, the default @@ -179,8 +205,10 @@ impl Player for SharedPlayer { with_generator: Box< dyn Fn() -> (rodio::OutputStream, rodio::OutputStreamHandle) + Send + Sync, >, - ) { + ) -> JoinHandle<()> { let state = Arc::clone(&self); - state.write().unwrap().gen_out = with_generator; + spawn(move || { + state.write().unwrap().gen_out = with_generator; + }) } }