feat: make loading indicator have optional progress

This commit is contained in:
talwat 2025-12-02 15:30:48 +01:00
parent 1f3a751a90
commit f8b39da92f
4 changed files with 32 additions and 30 deletions

View File

@ -2,39 +2,35 @@ use std::{sync::Arc, thread::sleep, time::Duration};
use rodio::Sink; use rodio::Sink;
use tokio::{ use tokio::{
sync::{broadcast, mpsc}, sync::{mpsc, Notify},
task::{self, JoinHandle}, task::{self, JoinHandle},
}; };
use crate::ui::{self, Update};
pub struct Handle { pub struct Handle {
_task: JoinHandle<()>, _task: JoinHandle<()>,
notify: Arc<Notify>,
} }
impl Handle { impl Handle {
pub fn new( pub fn new(sink: Arc<Sink>, tx: mpsc::Sender<crate::Message>) -> Self {
sink: Arc<Sink>, let notify = Arc::new(Notify::new());
tx: mpsc::Sender<crate::Message>,
rx: broadcast::Receiver<ui::Update>,
) -> Self {
Self { Self {
_task: task::spawn_blocking(|| Self::waiter(sink, tx, rx)), _task: task::spawn(Self::waiter(sink, tx, notify.clone())),
notify,
} }
} }
fn waiter( pub fn notify(&self) {
sink: Arc<Sink>, self.notify.notify_one();
tx: mpsc::Sender<crate::Message>, }
mut rx: broadcast::Receiver<ui::Update>,
) { async fn waiter(sink: Arc<Sink>, tx: mpsc::Sender<crate::Message>, notify: Arc<Notify>) {
'main: loop { 'main: loop {
if !matches!(rx.blocking_recv(), Ok(Update::Track(_))) { notify.notified().await;
continue;
}
while !sink.empty() { while !sink.empty() {
if matches!(rx.try_recv(), Ok(Update::Quit)) { if Arc::strong_count(&notify) <= 1 {
break 'main; break 'main;
} }

View File

@ -78,7 +78,7 @@ pub struct Handle {
} }
pub enum Output { pub enum Output {
Loading(Progress), Loading(Option<Progress>),
Queued(tracks::Queued), Queued(tracks::Queued),
} }
@ -88,7 +88,7 @@ impl Handle {
Ok(queued) => Output::Queued(queued), Ok(queued) => Output::Queued(queued),
Err(_) => { Err(_) => {
PROGRESS.store(0, atomic::Ordering::Relaxed); PROGRESS.store(0, atomic::Ordering::Relaxed);
Output::Loading(progress()) Output::Loading(Some(progress()))
} }
} }
} }

View File

@ -17,10 +17,16 @@ use crate::{
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Current { pub enum Current {
Loading(download::Progress), Loading(Option<download::Progress>),
Track(tracks::Info), Track(tracks::Info),
} }
impl Default for Current {
fn default() -> Self {
Current::Loading(None)
}
}
impl Current { impl Current {
pub fn loading(&self) -> bool { pub fn loading(&self) -> bool {
return matches!(self, Current::Loading(_)); return matches!(self, Current::Loading(_));
@ -35,15 +41,14 @@ pub struct Player {
broadcast: broadcast::Sender<ui::Update>, broadcast: broadcast::Sender<ui::Update>,
current: Current, current: Current,
ui: ui::Handle, ui: ui::Handle,
waiter: waiter::Handle,
_tx: Sender<crate::Message>, _tx: Sender<crate::Message>,
_waiter: waiter::Handle,
_stream: rodio::OutputStream, _stream: rodio::OutputStream,
} }
impl Drop for Player { impl Drop for Player {
fn drop(&mut self) { fn drop(&mut self) {
self.sink.stop(); self.sink.stop();
self.broadcast.send(ui::Update::Quit).unwrap();
} }
} }
@ -82,7 +87,7 @@ impl Player {
let (tx, rx) = mpsc::channel(8); let (tx, rx) = mpsc::channel(8);
tx.send(Message::Init).await?; tx.send(Message::Init).await?;
let (utx, urx) = broadcast::channel(8); let (utx, urx) = broadcast::channel(8);
let current = Current::Loading(download::progress()); let current = Current::Loading(None);
let list = List::load(args.track_list.as_ref()).await?; let list = List::load(args.track_list.as_ref()).await?;
let state = ui::State::initial(sink.clone(), &args, current.clone(), list.name.clone()); let state = ui::State::initial(sink.clone(), &args, current.clone(), list.name.clone());
@ -92,8 +97,8 @@ impl Player {
Ok(Self { Ok(Self {
downloader: Downloader::init(args.buffer_size, list, tx.clone()).await, downloader: Downloader::init(args.buffer_size, list, tx.clone()).await,
ui: ui::Handle::init(tx.clone(), urx.resubscribe(), state.clone(), &args).await?, ui: ui::Handle::init(tx.clone(), urx, state, &args).await?,
_waiter: waiter::Handle::new(sink.clone(), tx.clone(), urx), waiter: waiter::Handle::new(sink.clone(), tx.clone()),
bookmarks: Bookmarks::load().await?, bookmarks: Bookmarks::load().await?,
current, current,
broadcast: utx, broadcast: utx,
@ -115,6 +120,7 @@ impl Player {
let decoded = queued.decode()?; let decoded = queued.decode()?;
self.sink.append(decoded.data); self.sink.append(decoded.data);
self.set_current(Current::Track(decoded.info)).await?; self.set_current(Current::Track(decoded.info)).await?;
self.waiter.notify();
Ok(()) Ok(())
} }

View File

@ -66,7 +66,7 @@ enum ActionBar {
Playing(tracks::Info), Playing(tracks::Info),
/// When the app is loading. /// When the app is loading.
Loading(u8), Loading(Option<u8>),
/// When the app is muted. /// When the app is muted.
Muted, Muted,
@ -80,9 +80,9 @@ impl ActionBar {
Self::Playing(x) => ("playing", Some((x.display.clone(), x.width))), Self::Playing(x) => ("playing", Some((x.display.clone(), x.width))),
Self::Paused(x) => ("paused", Some((x.display.clone(), x.width))), Self::Paused(x) => ("paused", Some((x.display.clone(), x.width))),
Self::Loading(progress) => { Self::Loading(progress) => {
let progress = format!("{: <2.0}%", progress.min(&99)); let progress = progress.map(|progress| (format!("{: <2.0}%", progress.min(99)), 3));
("loading", Some((progress, 3))) ("loading", progress)
} }
Self::Muted => { Self::Muted => {
let msg = "+ to increase volume"; let msg = "+ to increase volume";
@ -108,7 +108,7 @@ impl ActionBar {
pub fn action(state: &ui::State, width: usize) -> String { pub fn action(state: &ui::State, width: usize) -> String {
let action = match state.current.clone() { let action = match state.current.clone() {
Current::Loading(progress) => { Current::Loading(progress) => {
ActionBar::Loading(progress.load(std::sync::atomic::Ordering::Relaxed)) ActionBar::Loading(progress.map(|x| x.load(std::sync::atomic::Ordering::Relaxed)))
} }
Current::Track(info) => { Current::Track(info) => {
if state.sink.volume() < 0.01 { if state.sink.volume() < 0.01 {