From aa657382fa723c9d72f7d5dc3402659ceef0d459 Mon Sep 17 00:00:00 2001 From: Mark <> Date: Sun, 14 Jul 2024 18:01:01 +0200 Subject: [PATCH] change player backend from rodio to playback-rs because rodio struggled to load some (possibly broken, but still playable) audio files. this is not perfect and sometimes suffers from small lags, and it uses song_finished_polling instead of being event-driven. --- musicdb-client/rust-toolchain.toml | 2 + musicdb-client/src/main.rs | 4 +- musicdb-lib/Cargo.toml | 7 +- musicdb-lib/rust-toolchain.toml | 2 + musicdb-lib/src/player/mod.rs | 3 + musicdb-lib/src/player/playback_rs.rs | 162 ++++++++++++++++++++++++++ musicdb-lib/src/server/mod.rs | 15 ++- musicdb-server/rust-toolchain.toml | 2 + 8 files changed, 189 insertions(+), 8 deletions(-) create mode 100644 musicdb-client/rust-toolchain.toml create mode 100644 musicdb-lib/rust-toolchain.toml create mode 100644 musicdb-lib/src/player/playback_rs.rs create mode 100644 musicdb-server/rust-toolchain.toml diff --git a/musicdb-client/rust-toolchain.toml b/musicdb-client/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/musicdb-client/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/musicdb-client/src/main.rs b/musicdb-client/src/main.rs index 2e96791..ba7e878 100755 --- a/musicdb-client/src/main.rs +++ b/musicdb-client/src/main.rs @@ -12,7 +12,7 @@ use gui::GuiEvent; #[cfg(feature = "playback")] use musicdb_lib::data::cache_manager::CacheManager; #[cfg(feature = "playback")] -use musicdb_lib::player::{rodio::PlayerBackendRodio, Player}; +use musicdb_lib::player::{playback_rs::PlayerBackendPlaybackRs, Player}; use musicdb_lib::{ data::{ database::{ClientIo, Database}, @@ -152,7 +152,7 @@ fn main() { cm.set_cache_songs_count(20); cache_manager = Some(cm); Some(Player::new_client( - PlayerBackendRodio::new_without_command_sending().unwrap(), + PlayerBackendPlaybackRs::new_without_command_sending().unwrap(), )) } else { None diff --git a/musicdb-lib/Cargo.toml b/musicdb-lib/Cargo.toml index 9f34510..236521e 100644 --- a/musicdb-lib/Cargo.toml +++ b/musicdb-lib/Cargo.toml @@ -6,11 +6,14 @@ edition = "2021" [dependencies] base64 = "0.22.1" colorize = "0.1.0" +playback-rs = { version = "0.4.3", optional = true } rand = "0.8.5" rc-u8-reader = "2.0.16" rodio = { version = "0.18.0", optional = true } sysinfo = "0.30.12" [features] -# default = ["playback"] -playback = ["dep:rodio"] +default = ["playback"] +playback = ["playback-via-playback-rs"] +playback-via-playback-rs = ["dep:playback-rs"] +playback-via-rodio = ["dep:rodio"] diff --git a/musicdb-lib/rust-toolchain.toml b/musicdb-lib/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/musicdb-lib/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/musicdb-lib/src/player/mod.rs b/musicdb-lib/src/player/mod.rs index 33db334..7a01c2b 100755 --- a/musicdb-lib/src/player/mod.rs +++ b/musicdb-lib/src/player/mod.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "playback-via-playback-rs")] +pub mod playback_rs; +#[cfg(feature = "playback-via-rodio")] pub mod rodio; use std::{collections::HashMap, ffi::OsStr, sync::Arc}; diff --git a/musicdb-lib/src/player/playback_rs.rs b/musicdb-lib/src/player/playback_rs.rs new file mode 100644 index 0000000..b127056 --- /dev/null +++ b/musicdb-lib/src/player/playback_rs.rs @@ -0,0 +1,162 @@ +use std::{ffi::OsStr, io::Cursor, path::Path, sync::Arc, time::Duration}; + +use playback_rs::Hint; + +use crate::{data::SongId, server::Command}; + +use super::PlayerBackend; + +pub struct PlayerBackendPlaybackRs { + player: playback_rs::Player, + current: Option<(SongId, Option, T)>, + next: Option<(SongId, Option, T)>, + command_sender: Option>, +} + +impl PlayerBackendPlaybackRs { + pub fn new( + command_sender: std::sync::mpsc::Sender, + ) -> Result> { + Self::new_with_optional_command_sending(Some(command_sender)) + } + pub fn new_without_command_sending() -> Result> { + Self::new_with_optional_command_sending(None) + } + pub fn new_with_optional_command_sending( + command_sender: Option>, + ) -> Result> { + Ok(Self { + player: playback_rs::Player::new(None)?, + current: None, + next: None, + command_sender, + }) + } +} + +impl PlayerBackend for PlayerBackendPlaybackRs { + fn load_next_song( + &mut self, + id: SongId, + filename: &OsStr, + bytes: Arc>, + _load_duration: bool, + custom_data: T, + ) { + let mut hint = Hint::new(); + if let Some(ext) = Path::new(filename).extension().and_then(OsStr::to_str) { + hint.with_extension(ext); + } + let reader = Box::new(Cursor::new(ArcVec(bytes))); + let loaded_song = match playback_rs::Song::new(reader, &hint, None) { + Ok(v) => Some(v), + Err(e) => { + if let Some(s) = &self.command_sender { + s.send(Command::ErrorInfo( + format!("Couldn't decode song #{id}!"), + format!("Error: {e}"), + )) + .unwrap(); + } + None + } + }; + if let Some(song) = &loaded_song { + if self.player.has_current_song() { + if let Err(e) = self.player.play_song_next(song, None) { + if let Some(s) = &self.command_sender { + s.send(Command::ErrorInfo( + format!("Couldn't preload song #{id}!"), + format!("Error: {e}"), + )) + .unwrap(); + } + } + } + } + self.next = Some((id, loaded_song, custom_data)); + } + fn pause(&mut self) { + self.player.set_playing(false); + } + fn stop(&mut self) { + self.pause(); + self.player.seek(Duration::ZERO); + } + fn resume(&mut self) { + self.player.set_playing(true); + } + fn next(&mut self, play: bool, _load_duration: bool) { + self.pause(); + self.player.skip(); + self.current = self.next.take(); + if self.player.has_current_song() { + self.player.set_playing(play); + } else { + if let Some((id, song, _)) = &self.current { + if let Some(song) = song { + if let Err(e) = self.player.play_song_now(song, None) { + if let Some(s) = &self.command_sender { + s.send(Command::ErrorInfo( + format!("Couldn't play song #{id}!"), + format!("Error: {e}"), + )) + .unwrap(); + s.send(Command::NextSong).unwrap(); + } + } else { + self.player.set_playing(play); + } + } else if let Some(s) = &self.command_sender { + s.send(Command::NextSong).unwrap(); + } + } + } + } + fn clear(&mut self) { + // remove next song + let _ = self.player.force_remove_next_song(); + // remove current song + let _ = self.player.force_remove_next_song(); + self.current = None; + self.next = None; + } + fn playing(&self) -> bool { + self.player.is_playing() + } + fn current_song(&self) -> Option<(SongId, bool, &T)> { + self.current.as_ref().map(|v| (v.0, true, &v.2)) + } + fn next_song(&self) -> Option<(SongId, bool, &T)> { + self.next.as_ref().map(|v| (v.0, true, &v.2)) + } + fn gen_data_mut(&mut self) -> (Option<&mut T>, Option<&mut T>) { + ( + self.current.as_mut().map(|v| &mut v.2), + self.next.as_mut().map(|v| &mut v.2), + ) + } + fn song_finished_polling(&self) -> bool { + true + } + fn song_finished(&self) -> bool { + self.current.is_some() && !self.player.has_current_song() + } + fn current_song_duration(&self) -> Option { + self.player + .get_playback_position() + .map(|v| v.1.as_millis() as _) + } + fn current_song_playback_position(&self) -> Option { + self.player + .get_playback_position() + .map(|v| v.0.as_millis() as _) + } +} + +pub struct ArcVec(pub Arc>); +impl AsRef<[u8]> for ArcVec { + fn as_ref(&self) -> &[u8] { + self.0.as_ref().as_ref() + } +} diff --git a/musicdb-lib/src/server/mod.rs b/musicdb-lib/src/server/mod.rs index d90b099..fb923d9 100755 --- a/musicdb-lib/src/server/mod.rs +++ b/musicdb-lib/src/server/mod.rs @@ -137,18 +137,25 @@ pub fn run_server_caching_thread_opt( use std::time::Instant; use crate::data::cache_manager::CacheManager; + #[cfg(feature = "playback-via-playback-rs")] + use crate::player::playback_rs::PlayerBackendPlaybackRs; + #[cfg(feature = "playback-via-rodio")] + use crate::player::rodio::PlayerBackendRodio; #[cfg(feature = "playback")] - use crate::player::{rodio::PlayerBackendRodio, PlayerBackend}; + use crate::player::PlayerBackend; // commands sent to this will be handeled later in this function in an infinite loop. // these commands are sent to the database asap. let (command_sender, command_receiver) = mpsc::channel(); + #[cfg(feature = "playback-via-playback-rs")] + let backend = PlayerBackendPlaybackRs::new(command_sender.clone()).unwrap(); + #[cfg(feature = "playback-via-rodio")] + let backend = PlayerBackendRodio::new(command_sender.clone()).unwrap(); + #[cfg(feature = "playback")] let mut player = if play_audio { - Some(Player::new( - PlayerBackendRodio::new(command_sender.clone()).unwrap(), - )) + Some(Player::new(backend)) } else { None }; diff --git a/musicdb-server/rust-toolchain.toml b/musicdb-server/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/musicdb-server/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly"