diff --git a/src/download.rs b/src/download.rs index ee4308b..e222b94 100644 --- a/src/download.rs +++ b/src/download.rs @@ -46,19 +46,26 @@ pub struct Downloader { /// The [`reqwest`] client to use for downloads. client: Client, - - /// The timeout to use for both the client, - /// and also how long to wait between trying - /// again after a failed download. - timeout: Duration, } impl Downloader { /// Initializes the downloader with a track list. /// /// `tx` specifies the [`Sender`] to be notified with [`crate::Message::Loaded`]. - pub fn init(size: usize, tracks: tracks::List, tx: Sender) -> Handle { - let client = Client::new(); + pub fn init( + size: usize, + timeout: u64, + tracks: tracks::List, + tx: Sender, + ) -> crate::Result { + let client = Client::builder() + .user_agent(concat!( + env!("CARGO_PKG_NAME"), + "/", + env!("CARGO_PKG_VERSION") + )) + .timeout(Duration::from_secs(timeout)) + .build()?; let (qtx, qrx) = mpsc::channel(size - 1); let downloader = Self { @@ -66,19 +73,20 @@ impl Downloader { tx, tracks, client, - timeout: Duration::from_secs(1), }; - Handle { + Ok(Handle { queue: qrx, task: tokio::spawn(downloader.run()), - } + }) } /// 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<()> { + const ERROR_TIMEOUT: Duration = Duration::from_secs(1); + loop { let result = self.tracks.random(&self.client, &PROGRESS).await; match result { @@ -93,7 +101,7 @@ impl Downloader { Err(error) => { PROGRESS.store(0, atomic::Ordering::Relaxed); if !error.timeout() { - tokio::time::sleep(self.timeout).await; + tokio::time::sleep(ERROR_TIMEOUT).await; } } } diff --git a/src/main.rs b/src/main.rs index 020bbb4..c38599f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,7 +49,7 @@ pub struct Args { fps: u8, /// Timeout in seconds for music downloads. - #[clap(long, default_value_t = 3)] + #[clap(long, default_value_t = 16)] timeout: u64, /// Include ALSA & other logs. @@ -117,10 +117,9 @@ async fn main() -> eyre::Result<()> { } let stream = audio::stream()?; - let player = Player::init(args, stream.mixer()).await?; - let environment = player.environment(); + let mut player = Player::init(args, stream.mixer()).await?; let result = player.run().await; - environment.cleanup(result.is_ok())?; + player.environment().cleanup(result.is_ok())?; Ok(result?) } diff --git a/src/message.rs b/src/message.rs index c704625..d40c96d 100644 --- a/src/message.rs +++ b/src/message.rs @@ -2,7 +2,8 @@ #[allow(dead_code, reason = "this code may not be dead depending on features")] #[derive(PartialEq, Debug, Clone)] pub enum Message { - /// Deliberate user request to go to the next song. + /// Deliberate user request to go to the next song, also sent when the + /// song is over by the waiter. Next, /// When a track is loaded after the caller previously being told to wait. diff --git a/src/player.rs b/src/player.rs index 4513ceb..c1d8d1c 100644 --- a/src/player.rs +++ b/src/player.rs @@ -117,6 +117,10 @@ impl Player { /// to be driven via `run`. pub async fn init(args: crate::Args, mixer: &rodio::mixer::Mixer) -> crate::Result { let (tx, rx) = mpsc::channel(8); + if args.paused { + tx.send(Message::Pause).await?; + } + tx.send(Message::Init).await?; let (utx, urx) = broadcast::channel(8); @@ -130,7 +134,12 @@ impl Player { Ok(Self { ui: ui::Handle::init(tx.clone(), urx, state, &args).await?, - downloader: Downloader::init(args.buffer_size as usize, list, tx.clone()), + downloader: Downloader::init( + args.buffer_size as usize, + args.timeout, + list, + tx.clone(), + )?, waiter: waiter::Handle::new(Arc::clone(&sink), tx), bookmarks: Bookmarks::load().await?, current: Current::default(), @@ -159,10 +168,11 @@ impl Player { Ok(()) } - /// Drive the main message loop. This function consumes the `Player` and - /// will return when a `Message::Quit` is received. It handles commands + /// Drives the main message loop of the player. + /// + /// This will return when a `Message::Quit` is received. It handles commands /// coming from the frontend and updates playback/UI state accordingly. - pub async fn run(mut self) -> crate::Result<()> { + pub async fn run(&mut self) -> crate::Result<()> { while let Some(message) = self.rx.recv().await { match message { Message::Next | Message::Init | Message::Loaded => { @@ -175,9 +185,7 @@ impl Player { download::Output::Loading(progress) => { self.set_current(Current::Loading(progress))?; } - download::Output::Queued(queued) => { - self.play(queued)?; - } + download::Output::Queued(queued) => self.play(queued)?, } } Message::Play => { diff --git a/src/ui.rs b/src/ui.rs index 3fc5abe..d0fb7da 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -27,24 +27,24 @@ type Result = std::result::Result; /// that occur while drawing the UI or handling input. #[derive(Debug, thiserror::Error)] pub enum Error { - #[error("unable to convert number")] + #[error("unable to convert number: {0}")] Conversion(#[from] std::num::TryFromIntError), - #[error("unable to write output")] + #[error("unable to write output: {0}")] Write(#[from] std::io::Error), - #[error("sending message to backend from ui failed")] + #[error("sending message to backend from ui failed: {0}")] CrateSend(#[from] tokio::sync::mpsc::error::SendError), - #[error("sharing state between backend and frontend failed")] + #[error("sharing state between backend and frontend failed: {0}")] Send(#[from] tokio::sync::broadcast::error::SendError), #[cfg(feature = "mpris")] - #[error("mpris bus error")] + #[error("mpris bus error: {0}")] ZBus(#[from] mpris_server::zbus::Error), #[cfg(feature = "mpris")] - #[error("mpris fdo (zbus interface) error")] + #[error("mpris fdo (zbus interface) error: {0}")] Fdo(#[from] mpris_server::zbus::fdo::Error), } diff --git a/src/volume.rs b/src/volume.rs index feb103d..dc7956a 100644 --- a/src/volume.rs +++ b/src/volume.rs @@ -77,13 +77,6 @@ impl PersistentVolume { pub async fn save(volume: f32) -> Result<()> { let config = Self::config().await?; let path = config.join(PathBuf::from("volume.txt")); - - // Already rounded & absolute, therefore this should be safe. - #[expect( - clippy::as_conversions, - clippy::cast_sign_loss, - clippy::cast_possible_truncation - )] let percentage = (volume * 100.0).abs().round() as u16; fs::write(path, percentage.to_string()).await?;