From 905e0ee0986401e7ab40693144c8b6576b0af62d Mon Sep 17 00:00:00 2001 From: Tal <83217276+talwat@users.noreply.github.com> Date: Wed, 10 Dec 2025 19:35:25 +0100 Subject: [PATCH] fix: small improvements in state & rng --- src/audio.rs | 1 + src/download.rs | 12 +++++++++--- src/error.rs | 15 --------------- src/main.rs | 24 +++++++++++------------- src/player.rs | 13 +------------ src/tests/tracks.rs | 13 ++++++++----- src/tracks/list.rs | 15 ++++++++++----- src/ui/interface.rs | 12 ++++++++++++ src/ui/mpris.rs | 18 ++++++++++++++---- 9 files changed, 66 insertions(+), 57 deletions(-) diff --git a/src/audio.rs b/src/audio.rs index 114aea4..c202ab6 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -41,6 +41,7 @@ fn silent_get_output_stream() -> crate::Result { Ok(stream) } +/// Creates an audio stream, doing so silently on Linux. pub fn stream() -> crate::Result { #[cfg(target_os = "linux")] let mut stream = silent_get_output_stream()?; diff --git a/src/download.rs b/src/download.rs index e222b94..8796144 100644 --- a/src/download.rs +++ b/src/download.rs @@ -46,6 +46,9 @@ pub struct Downloader { /// The [`reqwest`] client to use for downloads. client: Client, + + /// The RNG generator to use. + rng: fastrand::Rng, } impl Downloader { @@ -73,6 +76,7 @@ impl Downloader { tx, tracks, client, + rng: fastrand::Rng::new(), }; Ok(Handle { @@ -84,15 +88,17 @@ impl Downloader { /// Actually runs the downloader, consuming it and beginning /// the cycle of downloading tracks and reporting to the /// rest of the program. - async fn run(self) -> crate::Result<()> { + async fn run(mut self) -> crate::Result<()> { const ERROR_TIMEOUT: Duration = Duration::from_secs(1); loop { - let result = self.tracks.random(&self.client, &PROGRESS).await; + let result = self + .tracks + .random(&self.client, &PROGRESS, &mut self.rng) + .await; match result { Ok(track) => { self.queue.send(track).await?; - if LOADING.load(atomic::Ordering::Relaxed) { self.tx.send(crate::Message::Loaded).await?; LOADING.store(false, atomic::Ordering::Relaxed); diff --git a/src/error.rs b/src/error.rs index 5ba8a4e..0476fde 100644 --- a/src/error.rs +++ b/src/error.rs @@ -14,63 +14,48 @@ pub type Result = std::result::Result; /// Central application error. #[derive(Debug, thiserror::Error)] pub enum Error { - /// Errors while loading or saving the persistent volume settings. #[error("unable to load/save the persistent volume")] PersistentVolume(#[from] volume::Error), - /// Errors while loading or saving bookmarks. #[error("unable to load/save bookmarks")] Bookmarks(#[from] bookmark::Error), - /// Network request failures from `reqwest`. #[error("unable to fetch data")] Request(#[from] reqwest::Error), - /// Failure converting to/from a C string (FFI helpers). #[error("C string null error")] FfiNull(#[from] std::ffi::NulError), - /// Errors coming from the audio backend / stream handling. #[error("audio playing error")] Rodio(#[from] rodio::StreamError), - /// Failure to send an internal `Message` over the mpsc channel. #[error("couldn't send internal message")] Send(#[from] mpsc::error::SendError), - /// Failure to enqueue a track into the queue channel. #[error("couldn't add track to the queue")] Queue(#[from] mpsc::error::SendError), - /// Failure to broadcast UI updates. #[error("couldn't update UI state")] Broadcast(#[from] broadcast::error::SendError), - /// Generic IO error. #[error("io error")] Io(#[from] std::io::Error), - /// Data directory was not found or could not be determined. #[error("directory not found")] Directory, - /// Downloader failed to provide the requested track. #[error("couldn't fetch track from downloader")] Download, - /// Integer parsing errors. #[error("couldn't parse integer")] Parse(#[from] std::num::ParseIntError), - /// Track subsystem error. #[error("track failure")] Track(#[from] tracks::Error), - /// UI subsystem error. #[error("ui failure")] UI(#[from] ui::Error), - /// Error returned when a spawned task join failed. #[error("join error")] JoinError(#[from] tokio::task::JoinError), } diff --git a/src/main.rs b/src/main.rs index 2a50596..7ee1c53 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,28 +1,26 @@ //! An extremely simple lofi player. -pub mod error; +use crate::player::Player; +use clap::{Parser, Subcommand}; +use futures_util::TryFutureExt; use std::path::PathBuf; -use clap::{Parser, Subcommand}; -mod tests; -pub use error::{Error, Result}; -pub mod message; -pub mod ui; -use futures_util::TryFutureExt; -pub use message::Message; - -use crate::player::Player; pub mod audio; pub mod bookmark; pub mod download; +pub mod error; +pub mod message; pub mod player; +#[cfg(feature = "scrape")] +mod scrapers; +mod tests; pub mod tracks; +pub mod ui; pub mod volume; -#[cfg(feature = "scrape")] -mod scrapers; - #[cfg(feature = "scrape")] use crate::scrapers::Source; +pub use error::{Error, Result}; +pub use message::Message; /// An extremely simple lofi player. #[derive(Parser, Clone)] diff --git a/src/player.rs b/src/player.rs index 0768c22..84e318a 100644 --- a/src/player.rs +++ b/src/player.rs @@ -226,18 +226,7 @@ impl Player { } #[cfg(feature = "mpris")] - match message { - Message::ChangeVolume(_) | Message::SetVolume(_) => { - self.ui.mpris.update_volume().await? - } - Message::Play | Message::Pause | Message::PlayPause => { - self.ui.mpris.update_playback().await? - } - Message::Init | Message::Loaded | Message::Next => { - self.ui.mpris.update_metadata().await? - } - _ => (), - } + self.ui.mpris.handle(&message).await?; } self.close().await?; diff --git a/src/tests/tracks.rs b/src/tests/tracks.rs index d783ae4..f280a65 100644 --- a/src/tests/tracks.rs +++ b/src/tests/tracks.rs @@ -123,7 +123,7 @@ mod list { let text = "http://x/\npath!Display"; let list = List::new("t", text, None); - let (p, d) = list.random_path(); + let (p, d) = list.random_path(&mut fastrand::Rng::new()); assert_eq!(p, "path"); assert_eq!(d, Some("Display".into())); } @@ -133,7 +133,7 @@ mod list { let text = "http://x/\ntrackA"; let list = List::new("t", text, None); - let (p, d) = list.random_path(); + let (p, d) = list.random_path(&mut fastrand::Rng::new()); assert_eq!(p, "trackA"); assert!(d.is_none()); } @@ -152,7 +152,7 @@ mod list { fn custom_display_with_exclamation() { let text = "http://base/\nfile.mp3!My Custom Name"; let list = List::new("t", text, None); - let (path, display) = list.random_path(); + let (path, display) = list.random_path(&mut fastrand::Rng::new()); assert_eq!(path, "file.mp3"); assert_eq!(display, Some("My Custom Name".into())); } @@ -161,7 +161,7 @@ mod list { fn single_track() { let text = "base\nonly_track.mp3"; let list = List::new("name", text, None); - let (path, _) = list.random_path(); + let (path, _) = list.random_path(&mut fastrand::Rng::new()); assert_eq!(path, "only_track.mp3"); } @@ -171,7 +171,10 @@ mod list { let list = List::new("name", text, None); let client = Client::new(); - let track = list.random(&client, &PROGRESS).await.unwrap(); + let track = list + .random(&client, &PROGRESS, &mut fastrand::Rng::new()) + .await + .unwrap(); assert_eq!(track.display, "Apple Juice"); assert_eq!(track.path, "https://stream.chillhop.com/mp3/9476"); assert_eq!(track.data.len(), 3150424); diff --git a/src/tracks/list.rs b/src/tracks/list.rs index 4ce7258..2baf5bc 100644 --- a/src/tracks/list.rs +++ b/src/tracks/list.rs @@ -49,13 +49,13 @@ impl List { /// /// The second value in the tuple specifies whether the /// track has a custom display name. - pub fn random_path(&self) -> (String, Option) { + pub fn random_path(&self, rng: &mut fastrand::Rng) -> (String, Option) { // We're getting from 1 here, since the base is at `self.lines[0]`. // // We're also not pre-trimming `self.lines` into `base` & `tracks` due to // how rust vectors work, since it is slower to drain only a single element from // the start, so it's faster to just keep it in & work around it. - let random = fastrand::usize(1..self.lines.len()); + let random = rng.usize(1..self.lines.len()); let line = self.lines[random].clone(); if let Some((first, second)) = line.split_once('!') { @@ -128,10 +128,15 @@ impl List { /// Fetches and downloads a random track from the [List]. /// - /// The Result's error is a bool, which is true if a timeout error occured, + /// The Result's error is a bool, which is true if a timeout error occurred, /// and false otherwise. This tells lowfi if it shouldn't wait to try again. - pub async fn random(&self, client: &Client, progress: &AtomicU8) -> tracks::Result { - let (path, display) = self.random_path(); + pub async fn random( + &self, + client: &Client, + progress: &AtomicU8, + rng: &mut fastrand::Rng, + ) -> tracks::Result { + let (path, display) = self.random_path(rng); let (data, path) = self.download(&path, client, Some(progress)).await?; Queued::new(path, data, display) diff --git a/src/ui/interface.rs b/src/ui/interface.rs index 4e7bdef..7befea8 100644 --- a/src/ui/interface.rs +++ b/src/ui/interface.rs @@ -5,11 +5,23 @@ use crate::{ Args, }; +/// UI-specific parameters and options. #[derive(Copy, Clone, Debug, Default)] pub struct Params { + /// Whether to include borders. pub borderless: bool, + + /// Whether to include the bottom control bar. pub minimalist: bool, + + /// Whether the visual part of the UI should be enabled. + /// This only applies if the MPRIS feature is enabled. pub enabled: bool, + + /// The total delta between frames, which takes into account + /// the time it takes to actually render each frame. + /// + /// Derived from the FPS. pub delta: Duration, } diff --git a/src/ui/mpris.rs b/src/ui/mpris.rs index d8855a0..5228ec4 100644 --- a/src/ui/mpris.rs +++ b/src/ui/mpris.rs @@ -268,8 +268,18 @@ pub struct Server { } impl Server { + /// Handles a player message to update the state of the MPRIS player. + pub async fn handle(&mut self, message: &crate::Message) -> ui::Result<()> { + match message { + Message::ChangeVolume(_) | Message::SetVolume(_) => self.update_volume().await, + Message::Play | Message::Pause | Message::PlayPause => self.update_playback().await, + Message::Init | Message::Loaded | Message::Next => self.update_metadata().await, + _ => Ok(()), + } + } + /// Shorthand to emit a `PropertiesChanged` signal, like when pausing/unpausing. - pub async fn changed( + async fn changed( &mut self, properties: impl IntoIterator + Send + Sync, ) -> ui::Result<()> { @@ -284,7 +294,7 @@ impl Server { } /// Updates the volume with the latest information. - pub async fn update_volume(&mut self) -> ui::Result<()> { + async fn update_volume(&mut self) -> ui::Result<()> { self.changed(vec![Property::Volume(self.player().sink.volume().into())]) .await?; @@ -292,7 +302,7 @@ impl Server { } /// Updates the playback with the latest information. - pub async fn update_playback(&mut self) -> ui::Result<()> { + async fn update_playback(&mut self) -> ui::Result<()> { let status = self.player().playback_status().await?; self.changed(vec![Property::PlaybackStatus(status)]).await?; @@ -300,7 +310,7 @@ impl Server { } /// Updates the current track data with the current information. - pub async fn update_metadata(&mut self) -> ui::Result<()> { + async fn update_metadata(&mut self) -> ui::Result<()> { let metadata = self.player().metadata().await?; self.changed(vec![Property::Metadata(metadata)]).await?;