From ab174772853c66d15f0deb8ff1ccac143cbf0dec Mon Sep 17 00:00:00 2001 From: Mark <> Date: Wed, 15 Apr 2026 22:07:49 +0200 Subject: [PATCH] feat: playback-via-mpv, rust 2024 --- README.md | 80 +++++++- musicdb-client/Cargo.toml | 4 +- musicdb-client/src/gui_idle_display.rs | 12 +- musicdb-client/src/gui_statusbar.rs | 6 +- musicdb-client/src/textcfg.rs | 48 ++--- musicdb-lib/Cargo.toml | 3 +- musicdb-lib/src/load/mod.rs | 30 ++- musicdb-lib/src/player/mod.rs | 8 +- musicdb-lib/src/player/mpv.rs | 252 +++++++++++++++++++++++++ musicdb-lib/src/player/sleep.rs | 10 +- musicdb-lib/src/server/mod.rs | 98 +++++----- musicdb-server/Cargo.toml | 3 +- 12 files changed, 460 insertions(+), 94 deletions(-) create mode 100644 musicdb-lib/src/player/mpv.rs diff --git a/README.md b/README.md index 1ad660b..b7b6317 100755 --- a/README.md +++ b/README.md @@ -46,6 +46,77 @@ https://github.com/Dummi26/musicdb/assets/67615357/afb0c9fa-3cf0-414a-a59f-7e462 # Setup +## building + +Run `cargo build --release` in `musicdb-filldb`, `musicdb-server`, and `musicdb-client`. + +The executable files will be `musicdb-*/target/release/musicdb-*`. + +### building the server + +You may need to install `libasound2-dev` or similar packages, +as the default audio backend likely requires it. +If this is the case, you will likely see `error: ld returned 1 exit status`. + +Multiple backends are available for playing audio, +but using `default-playback` should be fine most of the time. +If compilation fails or you experience an audio related issue, +try compiling with a different backend and see if the issue persists: + +```sh +# in musicdb-server +cargo build --release --no-default-features --features website,playback-via-$backend +``` + +#### backends + +The `rodio` and `playback-rs` backends are named after the libraries they use. +These pull in dependencies and may try to link to system libraries. + +The `mpv` backend executes the `mpv` media player to play audio. +If you can't compile other backends, use this. +This backend will spawn up to two `mpv` processes at a time +and control them using unix sockets in `/tmp/`. + +The `sleep` backend doesn't play any audio. Instead, +it waits for the duration of the song and then moves +on to the next one. If you want to connect +(audio-playing) servers or (web) clients +to one server where the audio files are stored but which +doesn't play any audio itself, you can use this backend. +If the duration of a song is unknown, the song will be played +for a duration of zero seconds, meaning it will just get skipped +(although connected servers or clients may still load the song +and may even succeed in playing it). + +#### other features + +The `website` feature is required for `--web ` to work. +It allows the server to handle http requests so that it +can be controlled without using `musicdb-client`. +Turning this off improves compile times and stuff, +but leaving the feature on is quite useful +(e.g. to pause/resume playback from a phone). + +### building the client + +The client can play audio in sync with the server. +If you don't need this, you don't need to compile it: + +```sh +# in musicdb-client +cargo build --release --no-default-features --features gui +``` + +Instead of disabling playback, you can also choose +from the backends available to the server. +Read "compiling the server" for more information. + +In the future, it may make sense to disable the `gui` feature too +(e.g. the client may act as a cli tool for scripts), but not yet. + +## setup.sh + Review, then run the `setup.sh` script: ```sh @@ -64,8 +135,11 @@ font = '/usr/share/fonts/...' ... ``` -The script will start a server and client. -After closing the client, the server may still be running, so you may have to `pkill musicdb-server` if you want to stop it. +## manually + +The `setup.sh` script will start a server and client. +After closing the client, the server remains active. +You have to `pkill musicdb-server` if you want to stop it. To open the player again: @@ -73,7 +147,7 @@ To open the player again: musicdb-client 0.0.0.0:26002 gui ``` -To start the server: +To start the server without `setup.sh`: ```sh musicdb-server --tcp 0.0.0.0:26002 --play-audio local ~/my_dbdir ~/music diff --git a/musicdb-client/Cargo.toml b/musicdb-client/Cargo.toml index cedcd23..2e300ab 100644 --- a/musicdb-client/Cargo.toml +++ b/musicdb-client/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "musicdb-client" version = "0.1.0" -edition = "2021" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -32,5 +32,7 @@ merscfg = [] mers = [] playback = [] default-playback = ["playback", "musicdb-lib/default-playback"] +playback-via-sleep = ["playback", "musicdb-lib/playback-via-sleep"] +playback-via-mpv = ["playback", "musicdb-lib/playback-via-mpv"] playback-via-playback-rs = ["playback", "musicdb-lib/playback-via-playback-rs"] playback-via-rodio = ["playback", "musicdb-lib/playback-via-rodio"] diff --git a/musicdb-client/src/gui_idle_display.rs b/musicdb-client/src/gui_idle_display.rs index 1a8a3b0..de2f346 100644 --- a/musicdb-client/src/gui_idle_display.rs +++ b/musicdb-client/src/gui_idle_display.rs @@ -1,13 +1,13 @@ -use std::sync::{atomic::AtomicBool, Arc}; +use std::sync::{Arc, atomic::AtomicBool}; use musicdb_lib::data::ArtistId; use speedy2d::{color::Color, dimen::Vec2, image::ImageHandle, shape::Rectangle}; use crate::{ - gui::{rect_from_rel, DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiServerImage}, + gui::{DrawInfo, GuiAction, GuiElem, GuiElemCfg, GuiServerImage, rect_from_rel}, gui_anim::AnimationController, gui_base::Button, - gui_playback::{get_right_x, image_display, CurrentInfo}, + gui_playback::{CurrentInfo, get_right_x, image_display}, gui_playpause::PlayPause, gui_text::{AdvancedLabel, Label}, }; @@ -126,7 +126,7 @@ impl GuiElem for IdleDisplay { self.c_top_label.content = if let Some(song) = self.current_info.current_song { info.gui_config .idle_top_text - .gen(&info.database, info.database.get_song(&song)) + .gen_new(&info.database, info.database.get_song(&song)) } else { vec![] }; @@ -134,7 +134,7 @@ impl GuiElem for IdleDisplay { self.c_side1_label.content = if let Some(song) = self.current_info.current_song { info.gui_config .idle_side1_text - .gen(&info.database, info.database.get_song(&song)) + .gen_new(&info.database, info.database.get_song(&song)) } else { vec![] }; @@ -142,7 +142,7 @@ impl GuiElem for IdleDisplay { self.c_side2_label.content = if let Some(song) = self.current_info.current_song { info.gui_config .idle_side2_text - .gen(&info.database, info.database.get_song(&song)) + .gen_new(&info.database, info.database.get_song(&song)) } else { vec![] }; diff --git a/musicdb-client/src/gui_statusbar.rs b/musicdb-client/src/gui_statusbar.rs index 5ef9146..72c0435 100644 --- a/musicdb-client/src/gui_statusbar.rs +++ b/musicdb-client/src/gui_statusbar.rs @@ -1,11 +1,11 @@ -use std::sync::{atomic::AtomicBool, Arc}; +use std::sync::{Arc, atomic::AtomicBool}; use speedy2d::{dimen::Vec2, shape::Rectangle}; use crate::{ gui::{DrawInfo, GuiElem, GuiElemCfg}, gui_anim::AnimationController, - gui_playback::{image_display, CurrentInfo}, + gui_playback::{CurrentInfo, image_display}, gui_playpause::PlayPause, gui_text::AdvancedLabel, }; @@ -61,7 +61,7 @@ impl GuiElem for StatusBar { self.c_song_label.content = if let Some(song) = self.current_info.current_song { info.gui_config .status_bar_text - .gen(&info.database, info.database.get_song(&song)) + .gen_new(&info.database, info.database.get_song(&song)) } else { vec![] }; diff --git a/musicdb-client/src/textcfg.rs b/musicdb-client/src/textcfg.rs index 7ba15d5..0bc698f 100755 --- a/musicdb-client/src/textcfg.rs +++ b/musicdb-client/src/textcfg.rs @@ -3,7 +3,7 @@ use std::{ str::{Chars, FromStr}, }; -use musicdb_lib::data::{database::Database, song::Song, CoverId, GeneralData}; +use musicdb_lib::data::{CoverId, GeneralData, database::Database, song::Song}; use speedy2d::color::Color; use crate::gui_text::{AdvancedContent, Content, ImageSource}; @@ -38,7 +38,7 @@ pub enum TextPart { ImgCustom(TextBuilder), } impl TextBuilder { - pub fn gen( + pub fn gen_new( &self, db: &Database, current_song: Option<&Song>, @@ -151,40 +151,39 @@ impl TextBuilder { } } TextPart::TagEq(p) => { - for (i, gen) in all_general(db, ¤t_song).into_iter().enumerate() { - if let Some(_) = gen.and_then(|gen| gen.tags.iter().find(|t| *t == p)) { - push!(match i { - 0 => 's', - 1 => 'a', - 2 => 'A', - _ => unreachable!("array length should be 3"), - } - .to_string()); + for (i, g) in all_general(db, ¤t_song).into_iter().enumerate() { + if let Some(_) = g.and_then(|g| g.tags.iter().find(|t| *t == p)) { + push!( + match i { + 0 => 's', + 1 => 'a', + 2 => 'A', + _ => unreachable!("array length should be 3"), + } + .to_string() + ); break; } } } TextPart::TagEnd(p) => { - for gen in all_general(db, ¤t_song) { - if let Some(t) = - gen.and_then(|gen| gen.tags.iter().find(|t| t.starts_with(p))) - { + for g in all_general(db, ¤t_song) { + if let Some(t) = g.and_then(|g| g.tags.iter().find(|t| t.starts_with(p))) { push!(t[p.len()..].to_owned()); break; } } } TextPart::TagContains(p) => { - for gen in all_general(db, ¤t_song) { - if let Some(t) = gen.and_then(|gen| gen.tags.iter().find(|t| t.contains(p))) - { + for g in all_general(db, ¤t_song) { + if let Some(t) = g.and_then(|g| g.tags.iter().find(|t| t.contains(p))) { push!(t.to_owned()); break; } } } TextPart::If(condition, yes, no) => { - if !condition.gen(db, current_song).is_empty() { + if !condition.gen_new(db, current_song).is_empty() { yes.gen_to(db, current_song, out, line, scale, align, color); } else { no.gen_to(db, current_song, out, line, scale, align, color); @@ -195,7 +194,7 @@ impl TextBuilder { } TextPart::ImgCustom(path) => { push_img!(ImageSource::CustomFile( - path.gen(db, current_song) + path.gen_new(db, current_song) .into_iter() .flat_map(|v| v.into_iter().map(|(v, _, _)| v.to_string())) .collect() @@ -326,7 +325,7 @@ impl TextBuilder { None => { return Err(TextBuilderParseError::InvalidImageSourceName( src, - )) + )); } Some(':') => break, Some(c) => src.push(c), @@ -349,7 +348,7 @@ impl TextBuilder { } "CustomFile" => TextPart::ImgCustom(Self::from_chars(chars)?), _ => { - return Err(TextBuilderParseError::InvalidImageSourceName(src)) + return Err(TextBuilderParseError::InvalidImageSourceName(src)); } }); } @@ -419,7 +418,10 @@ impl Display for TextBuilderParseError { write!(f, "Unknown tag mode '{mode}': Allowed are only _, > or =.") } Self::TooFewCharsForColor => write!(f, "Too few chars for color: Syntax is \\cRRGGBB."), - Self::ColorNotHex => write!(f, "Color value wasn't a hex number! Syntax is \\cRRGGBB, where R, G, and B are values from 0-9 and A-F (hex 0-F)."), + Self::ColorNotHex => write!( + f, + "Color value wasn't a hex number! Syntax is \\cRRGGBB, where R, G, and B are values from 0-9 and A-F (hex 0-F)." + ), Self::CouldntParse(v, t) => write!(f, "Couldn't parse value '{v}' to type '{t}'."), Self::InvalidImageSourceName(name) => write!(f, "Invalid image source name: '{name}'."), Self::InvalidImageCoverId(id) => write!(f, "Invalid image cover id: '{id}'."), diff --git a/musicdb-lib/Cargo.toml b/musicdb-lib/Cargo.toml index ee58f3d..1f8a13e 100644 --- a/musicdb-lib/Cargo.toml +++ b/musicdb-lib/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "musicdb-lib" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] base64 = "0.22.1" @@ -18,5 +18,6 @@ playback = [] default-playback = ["playback-via-playback-rs"] # default-playback = ["playback-via-rodio"] playback-via-sleep = ["playback"] +playback-via-mpv = ["playback"] playback-via-playback-rs = ["playback", "dep:playback-rs"] playback-via-rodio = ["playback", "dep:rodio"] diff --git a/musicdb-lib/src/load/mod.rs b/musicdb-lib/src/load/mod.rs index 2240edf..144a769 100755 --- a/musicdb-lib/src/load/mod.rs +++ b/musicdb-lib/src/load/mod.rs @@ -1,5 +1,5 @@ use std::{ - collections::{HashMap, VecDeque}, + collections::{BTreeMap, HashMap, VecDeque}, io::{Read, Write}, path::PathBuf, }; @@ -164,6 +164,34 @@ where Ok(o) } } +impl ToFromBytes for BTreeMap +where + K: ToFromBytes + std::cmp::Ord, + V: ToFromBytes, +{ + fn to_bytes(&self, s: &mut T) -> Result<(), std::io::Error> + where + T: Write, + { + self.len().to_bytes(s)?; + for (key, val) in self.iter() { + key.to_bytes(s)?; + val.to_bytes(s)?; + } + Ok(()) + } + fn from_bytes(s: &mut T) -> Result + where + T: Read, + { + let len = ToFromBytes::from_bytes(s)?; + let mut o = Self::new(); + for _ in 0..len { + o.insert(ToFromBytes::from_bytes(s)?, ToFromBytes::from_bytes(s)?); + } + Ok(o) + } +} // - for (i/u)(size/8/16/32/64/128) diff --git a/musicdb-lib/src/player/mod.rs b/musicdb-lib/src/player/mod.rs index 1051669..fd0093e 100755 --- a/musicdb-lib/src/player/mod.rs +++ b/musicdb-lib/src/player/mod.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "playback-via-mpv")] +pub mod mpv; #[cfg(feature = "playback-via-playback-rs")] pub mod playback_rs; #[cfg(feature = "playback-via-rodio")] @@ -8,14 +10,18 @@ pub mod sleep; pub type PlayerBackendFeat = playback_rs::PlayerBackendPlaybackRs; #[cfg(feature = "playback-via-rodio")] pub type PlayerBackendFeat = rodio::PlayerBackendRodio; +#[cfg(feature = "playback-via-sleep")] +pub type PlayerBackendFeat = sleep::PlayerBackendSleep; +#[cfg(feature = "playback-via-mpv")] +pub type PlayerBackendFeat = mpv::PlayerBackendMpv; use std::{collections::HashMap, ffi::OsStr, sync::Arc}; use crate::{ data::{ + SongId, database::Database, song::{CachedData, Song}, - SongId, }, server::Action, }; diff --git a/musicdb-lib/src/player/mpv.rs b/musicdb-lib/src/player/mpv.rs new file mode 100644 index 0000000..ef838f8 --- /dev/null +++ b/musicdb-lib/src/player/mpv.rs @@ -0,0 +1,252 @@ +use std::{ + ffi::OsStr, + io::Write, + os::unix::net::UnixStream, + process::Stdio, + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + }, + time::Duration, +}; + +use crate::{ + data::{SongId, song::Song}, + server::{Action, Command}, +}; + +use super::PlayerBackend; + +const IPC_QUIT: &[u8] = b"{\"command\":[\"quit\"]}\n"; +const IPC_PAUSE: &[u8] = b"{\"command\":[\"set_property\",\"pause\",true]}\n"; +const IPC_RESUME: &[u8] = b"{\"command\":[\"set_property\",\"pause\",false]}\n"; +const IPC_STOP: &[u8] = + b"{\"command\":[\"set_property\",\"pause\",true]}\n{\"command\":[\"seek\",0,\"absolute\"]}\n"; + +pub struct PlayerBackendMpv { + id: (u32, u8), + current: Option<(SongId, Option<(UnixStream, bool, Arc)>, T)>, + next: Option<(SongId, Option<(UnixStream, bool, Arc)>, T)>, + /// unused, but could be used to do something smarter than polling at some point + #[allow(unused)] + command_sender: Option)>>, +} + +impl PlayerBackendMpv { + pub fn new( + command_sender: std::sync::mpsc::Sender<(Command, Option)>, + ) -> 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 { + id: (std::process::id(), 0), + current: None, + next: None, + command_sender, + }) + } +} + +impl PlayerBackend for PlayerBackendMpv { + fn load_next_song( + &mut self, + id: SongId, + _song: &Song, + _filename: &OsStr, + bytes: Arc>, + _load_duration: bool, + custom_data: T, + ) { + if let Some((_, Some((mut ipc, _, quit)), _)) = self.next.take() { + quit.store(true, Ordering::Release); + ipc.write_all(IPC_QUIT).ok(); + } + self.id.1 = 1 + (self.id.1 % 9); + let ipc_path = format!("/tmp/musicdb-server-mpv-ipc-{:X}-{}", self.id.0, self.id.1); + match std::process::Command::new("mpv") + .args(["--no-config", "--no-video", "--pause"]) + .arg(format!("--input-ipc-server={ipc_path}")) + .args(["--no-terminal", "--cache=yes", "--cache-on-disk=no", "-"]) + .stdin(Stdio::piped()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + { + Ok(mut proc) => { + let quit = Arc::new(AtomicBool::new(false)); + for i in 1..=34 { + std::thread::sleep(Duration::from_millis(100 * i)); + if let Ok(ipc) = UnixStream::connect(&ipc_path) { + self.next = Some((id, Some((ipc, false, Arc::clone(&quit))), custom_data)); + break; + } + } + if self.next.is_some() + && let Some(mut stdin) = proc.stdin.take() + { + let s = self.command_sender.clone(); + std::thread::spawn(move || { + stdin.write_all(&bytes).ok(); + drop(stdin); + match proc.wait() { + Ok(status) => { + if quit.load(Ordering::Acquire) { + return; + } + quit.store(true, Ordering::Release); + if let Some(s) = &s { + if status.success() { + eprintln!("mpv exited, success"); + s.send((Action::NextSong.cmd(0xFFu8), None)).unwrap(); + } else { + s.send(( + Action::ErrorInfo( + "mpv process crashed!".to_owned(), + format!( + "Exit code: {}", + status + .code() + .map(|n| n.to_string()) + .unwrap_or("unknown".to_owned()) + ), + ) + .cmd(0xFFu8), + None, + )) + .unwrap(); + } + } + } + Err(e) => { + if quit.load(Ordering::Acquire) { + return; + } + quit.store(true, Ordering::Release); + if let Some(s) = &s { + s.send(( + Action::ErrorInfo( + "Error waiting for mpv to exit!".to_owned(), + format!("Error: {e}"), + ) + .cmd(0xFFu8), + None, + )) + .unwrap(); + } + } + } + }); + } else { + proc.kill().ok(); + if let Some(s) = &self.command_sender { + s.send(( + Action::ErrorInfo( + "Error waiting for mpv to start!".to_owned(), + "Could not get process' stdin or could not connect to ipc socket." + .to_owned(), + ) + .cmd(0xFFu8), + None, + )) + .unwrap(); + } + } + } + Err(e) => { + if let Some(s) = &self.command_sender { + s.send(( + Action::ErrorInfo( + "Error starting mpv process!".to_owned(), + format!("Error: {e}"), + ) + .cmd(0xFFu8), + None, + )) + .unwrap(); + } + } + } + } + fn pause(&mut self) { + if let Some((_, Some((ipc, playing, _)), _)) = &mut self.current { + ipc.write_all(IPC_PAUSE).ok(); + ipc.flush().ok(); + *playing = false; + } + } + fn stop(&mut self) { + if let Some((_, Some((ipc, playing, _)), _)) = &mut self.current { + ipc.write_all(IPC_STOP).ok(); + ipc.flush().ok(); + *playing = false; + } + } + fn resume(&mut self) { + if let Some((_, Some((ipc, playing, _)), _)) = &mut self.current { + ipc.write_all(IPC_RESUME).ok(); + ipc.flush().ok(); + *playing = true; + } + } + fn next(&mut self, play: bool, _load_duration: bool) { + if let Some((_, Some((mut ipc, _, quit)), _)) = self.current.take() { + quit.store(true, Ordering::Release); + ipc.write_all(IPC_QUIT).ok(); + } + self.current = self.next.take(); + if play { + self.resume(); + } + } + fn clear(&mut self) { + self.next(false, false); + self.next(false, false); + } + fn playing(&self) -> bool { + if let Some((_, Some((_, playing, _)), _)) = self.current { + playing + } else { + false + } + } + fn current_song(&self) -> Option<(SongId, bool, &T)> { + self.current + .as_ref() + .map(|(id, _, custom)| (*id, true, custom)) + } + fn next_song(&self) -> Option<(SongId, bool, &T)> { + self.next + .as_ref() + .map(|(id, _, custom)| (*id, true, custom)) + } + fn gen_data_mut(&mut self) -> (Option<&mut T>, Option<&mut T>) { + ( + self.current.as_mut().map(|(_, _, t)| t), + self.next.as_mut().map(|(_, _, t)| t), + ) + } + fn song_finished_polling(&self) -> bool { + self.command_sender.is_none() + } + fn song_finished(&self) -> bool { + if self.command_sender.is_none() + && let Some((_, Some((_, _, quit)), _)) = &self.current + { + quit.load(Ordering::Relaxed) + } else { + false + } + } + fn current_song_duration(&self) -> Option { + None + } + fn current_song_playback_position(&self) -> Option { + None + } +} diff --git a/musicdb-lib/src/player/sleep.rs b/musicdb-lib/src/player/sleep.rs index 6e2ddcd..e5ced77 100644 --- a/musicdb-lib/src/player/sleep.rs +++ b/musicdb-lib/src/player/sleep.rs @@ -5,7 +5,7 @@ use std::{ }; use crate::{ - data::{song::Song, SongId}, + data::{SongId, song::Song}, server::Command, }; @@ -28,6 +28,14 @@ enum SongFinished { impl PlayerBackendSleep { pub fn new( + command_sender: std::sync::mpsc::Sender<(Command, Option)>, + ) -> 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 { diff --git a/musicdb-lib/src/server/mod.rs b/musicdb-lib/src/server/mod.rs index f018595..c035a24 100755 --- a/musicdb-lib/src/server/mod.rs +++ b/musicdb-lib/src/server/mod.rs @@ -3,7 +3,7 @@ pub mod get; use std::{ io::{BufRead as _, BufReader, Read, Write}, net::TcpListener, - sync::{mpsc, Arc, Mutex}, + sync::{Arc, Mutex, mpsc}, thread, time::Duration, }; @@ -15,12 +15,12 @@ use crate::player::Player; use crate::server::get::handle_one_connection_as_get; use crate::{ data::{ + AlbumId, ArtistId, SongId, album::Album, artist::Artist, database::{Cover, Database, UpdateEndpoint}, queue::Queue, song::Song, - AlbumId, ArtistId, SongId, }, load::ToFromBytes, }; @@ -284,23 +284,15 @@ pub fn run_server_caching_thread_opt( ) { #[cfg(not(feature = "playback"))] if play_audio { - panic!("Can't run the server: cannot play audio because the `playback` feature was disabled when compiling, but `play_audio` was set to `true`!"); + panic!( + "Can't run the server: cannot play audio because the `playback` feature was disabled when compiling, but `play_audio` was set to `true`!" + ); } 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-via-sleep")] - use crate::player::sleep::PlayerBackendSleep; - #[cfg(any( - feature = "playback", - feature = "playback-via-playback-rs", - feature = "playback-via-rodio" - ))] + #[cfg(any(feature = "playback"))] use crate::player::PlayerBackend; // commands sent to this will be handeled later in this function in an infinite loop. @@ -309,12 +301,8 @@ pub fn run_server_caching_thread_opt( #[cfg(feature = "playback")] let mut player = if play_audio { - #[cfg(feature = "playback-via-sleep")] - let backend = PlayerBackendSleep::new(Some(command_sender.clone())).unwrap(); - #[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(); + use crate::player::PlayerBackendFeat; + let backend = PlayerBackendFeat::new(command_sender.clone()).unwrap(); Some(Player::new(backend)) } else { None @@ -336,42 +324,46 @@ pub fn run_server_caching_thread_opt( Ok(v) => { let command_sender = command_sender.clone(); let db = Arc::clone(&database); - thread::spawn(move || loop { - if let Ok((connection, _con_addr)) = v.accept() { - let command_sender = command_sender.clone(); - let db = Arc::clone(&db); - thread::spawn(move || { - // each connection first has to send one line to tell us what it wants - let mut connection = BufReader::new(connection); - let mut line = String::new(); - if connection.read_line(&mut line).is_ok() { - // based on that line, we adjust behavior - match line.as_str().trim() { - // sends all updates to this connection and reads commands from it - "main" => { - let connection = connection.into_inner(); - _ = handle_one_connection_as_main( - db, - &mut connection.try_clone().unwrap(), - connection, + thread::spawn(move || { + loop { + if let Ok((connection, _con_addr)) = v.accept() { + let command_sender = command_sender.clone(); + let db = Arc::clone(&db); + thread::spawn(move || { + // each connection first has to send one line to tell us what it wants + let mut connection = BufReader::new(connection); + let mut line = String::new(); + if connection.read_line(&mut line).is_ok() { + // based on that line, we adjust behavior + match line.as_str().trim() { + // sends all updates to this connection and reads commands from it + "main" => { + let connection = connection.into_inner(); + _ = handle_one_connection_as_main( + db, + &mut connection.try_clone().unwrap(), + connection, + &command_sender, + ) + } + // reads commands from the connection, but (unlike main) doesn't send any updates + "control" => handle_one_connection_as_control( + &mut connection, &command_sender, - ) - } - // reads commands from the connection, but (unlike main) doesn't send any updates - "control" => handle_one_connection_as_control( - &mut connection, - &command_sender, - None, - ), - "get" => _ = handle_one_connection_as_get(db, &mut connection), - _ => { - _ = connection - .into_inner() - .shutdown(std::net::Shutdown::Both) + None, + ), + "get" => { + _ = handle_one_connection_as_get(db, &mut connection) + } + _ => { + _ = connection + .into_inner() + .shutdown(std::net::Shutdown::Both) + } } } - } - }); + }); + } } }); } diff --git a/musicdb-server/Cargo.toml b/musicdb-server/Cargo.toml index f94e099..d54c5e8 100644 --- a/musicdb-server/Cargo.toml +++ b/musicdb-server/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "musicdb-server" version = "0.1.0" -edition = "2021" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -27,5 +27,6 @@ website = ["dep:tokio", "dep:rocket", "dep:html-escape"] playback = [] default-playback = ["playback", "musicdb-lib/default-playback"] playback-via-sleep = ["playback", "musicdb-lib/playback-via-sleep"] +playback-via-mpv = ["playback", "musicdb-lib/playback-via-mpv"] playback-via-playback-rs = ["playback", "musicdb-lib/playback-via-playback-rs"] playback-via-rodio = ["playback", "musicdb-lib/playback-via-rodio"]