working slidebar for player

This commit is contained in:
Daniel Bulant 2024-11-20 21:29:31 +01:00
parent 35139b84a9
commit 4ccd69e628
No known key found for this signature in database
3 changed files with 87 additions and 10 deletions

View file

@ -1,5 +1,5 @@
[target.x86_64-unknown-linux-gnu] [target.x86_64-unknown-linux-gnu]
# linker = "/usr/bin/clang-15" # linker = "/usr/bin/clang-15"
# rustflags = ["-C", "link-arg=--ld-path=/home/dan/.nix-profile/bin/mold"] rustflags = ["-C", "link-arg=--ld-path=/home/dan/.nix-profile/bin/mold"]
# rustflags = ["-C", "link-arg=-fuse-ld=/home/dan/.nix-profile/bin/mold"] # rustflags = ["-C", "link-arg=-fuse-ld=/home/dan/.nix-profile/bin/mold"]

View file

@ -3,9 +3,10 @@ use std::{
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use cushy::value::{Destination, Dynamic}; use cushy::value::{Destination, Dynamic, Source};
use librespot_metadata::audio::AudioItem; use librespot_metadata::audio::AudioItem;
use librespot_playback::player::{Player, PlayerEvent}; use librespot_playback::player::{Player, PlayerEvent};
use tokio::time;
pub type DynamicPlayer = Arc<DynamicPlayerInner>; pub type DynamicPlayer = Arc<DynamicPlayerInner>;
@ -59,11 +60,38 @@ impl DynamicPlayerInner {
track_progress: Default::default(), track_progress: Default::default(),
} }
} }
fn update_position(&self) {
let state = self.state.get();
let track_progress = match state {
PlayerState::Loading {
loading_at: duration,
}
| PlayerState::Paused {
paused_at: duration,
} => Some(duration),
PlayerState::Stopped | PlayerState::Disconnected => None,
PlayerState::Playing => {
let started_at = *self.started_at.lock().unwrap();
let started_at = started_at.unwrap_or_else(Instant::now);
let position = Instant::now() - started_at;
Some(position)
}
};
self.track_progress.set(track_progress);
}
/// Run the DynamicPlayer event loop
/// This updates the player state and track progress
/// Run this only once per player (usually once per app)
pub async fn run(&self) { pub async fn run(&self) {
let mut channel = self.player.get_player_event_channel(); let mut channel = self.player.get_player_event_channel();
let mut interval = time::interval(time::Duration::from_millis(100));
interval.set_missed_tick_behavior(time::MissedTickBehavior::Skip);
let mut id = 0u64; let mut id = 0u64;
loop { loop {
tokio::select! { tokio::select! {
_ = interval.tick() => {
self.update_position();
}
event = channel.recv() => { event = channel.recv() => {
if let Some(event) = event { if let Some(event) = event {
match event { match event {
@ -82,6 +110,7 @@ impl DynamicPlayerInner {
self.state.set(PlayerState::Loading { self.state.set(PlayerState::Loading {
loading_at: Duration::from_millis(position_ms as u64), loading_at: Duration::from_millis(position_ms as u64),
}); });
self.update_position();
} }
PlayerEvent::Playing { PlayerEvent::Playing {
play_request_id, play_request_id,
@ -92,6 +121,7 @@ impl DynamicPlayerInner {
self.state.set(PlayerState::Playing); self.state.set(PlayerState::Playing);
*self.started_at.lock().unwrap() = *self.started_at.lock().unwrap() =
Some(Instant::now() - Duration::from_millis(position_ms as u64)); Some(Instant::now() - Duration::from_millis(position_ms as u64));
self.update_position();
} }
PlayerEvent::Paused { PlayerEvent::Paused {
play_request_id, play_request_id,
@ -102,6 +132,7 @@ impl DynamicPlayerInner {
self.state.set(PlayerState::Paused { self.state.set(PlayerState::Paused {
paused_at: Duration::from_millis(position_ms as u64), paused_at: Duration::from_millis(position_ms as u64),
}); });
self.update_position();
} }
PlayerEvent::Unavailable { PlayerEvent::Unavailable {
play_request_id, play_request_id,
@ -121,6 +152,7 @@ impl DynamicPlayerInner {
println!("PositionCorrection {play_request_id} {position_ms} {track_id}"); println!("PositionCorrection {play_request_id} {position_ms} {track_id}");
*self.started_at.lock().unwrap() = *self.started_at.lock().unwrap() =
Some(Instant::now() - Duration::from_millis(position_ms as u64)); Some(Instant::now() - Duration::from_millis(position_ms as u64));
self.update_position();
} }
PlayerEvent::Seeked { PlayerEvent::Seeked {
play_request_id, play_request_id,
@ -130,11 +162,13 @@ impl DynamicPlayerInner {
println!("Seeked {play_request_id} {position_ms} {track_id}"); println!("Seeked {play_request_id} {position_ms} {track_id}");
*self.started_at.lock().unwrap() = *self.started_at.lock().unwrap() =
Some(Instant::now() - Duration::from_millis(position_ms as u64)); Some(Instant::now() - Duration::from_millis(position_ms as u64));
self.update_position();
} }
PlayerEvent::TrackChanged { audio_item } => { PlayerEvent::TrackChanged { audio_item } => {
println!("TrackChanged {}", audio_item.uri); println!("TrackChanged {}", audio_item.uri);
dbg!(&audio_item); dbg!(&audio_item);
*self.track.lock() = Some(audio_item); *self.track.lock() = Some(audio_item);
self.update_position();
} }
PlayerEvent::SessionConnected { PlayerEvent::SessionConnected {
connection_id, connection_id,

View file

@ -1,9 +1,11 @@
use std::time::Duration;
use cushy::{ use cushy::{
figures::{units::Lp, Size}, figures::{units::Lp, Size},
styles::{Dimension, DimensionRange}, styles::{Dimension, DimensionRange},
value::Source, value::{Dynamic, Source},
widget::MakeWidget, widget::MakeWidget,
widgets::{Button, Image, Label}, widgets::{Button, Image, Label, Slider},
}; };
use itertools::Itertools; use itertools::Itertools;
use librespot_metadata::audio::UniqueFields; use librespot_metadata::audio::UniqueFields;
@ -16,17 +18,17 @@ use crate::{
pub fn bar(player: DynamicPlayer) -> impl MakeWidget { pub fn bar(player: DynamicPlayer) -> impl MakeWidget {
meta(player).size(Size { meta(player).size(Size {
width: DimensionRange::default(), width: DimensionRange::default(),
height: Dimension::Lp(Lp::inches_f(1.5)).into(), height: Dimension::Lp(Lp::inches_f(1.)).into(),
}) })
} }
fn meta(player: DynamicPlayer) -> impl MakeWidget { fn meta(player: DynamicPlayer) -> impl MakeWidget {
Image::new_empty() Image::new_empty()
.with_url(player.track.map_each(|track| { .with_url(player.track.map_each(|track| {
track dbg!(track
.as_ref() .as_ref()
.map(|track| track.covers.first().map(|cover| cover.url.clone())) .map(|track| track.covers.first().map(|cover| cover.url.clone()))
.flatten() .flatten())
})) }))
.size(Size::squared(Dimension::Lp(Lp::inches_f(1.)))) .size(Size::squared(Dimension::Lp(Lp::inches_f(1.))))
.and( .and(
@ -64,6 +66,11 @@ fn meta(player: DynamicPlayer) -> impl MakeWidget {
.expand(), .expand(),
) )
.into_columns() .into_columns()
.align_left()
.expand()
.and(controls(player.clone()).expand())
.and(vol(player).align_right().expand())
.into_columns()
} }
fn controls(player: DynamicPlayer) -> impl MakeWidget { fn controls(player: DynamicPlayer) -> impl MakeWidget {
@ -82,12 +89,48 @@ fn controls(player: DynamicPlayer) -> impl MakeWidget {
.and("skip".into_button()) .and("skip".into_button())
.and("repeat".into_button()) .and("repeat".into_button())
.into_columns() .into_columns()
.and(progress(player)) .centered()
.and(time(player))
.into_rows() .into_rows()
} }
fn progress(player: DynamicPlayer) -> impl MakeWidget { fn time(player: DynamicPlayer) -> impl MakeWidget {
"progress bar here" let duration = player.track.map_each(|track| {
track
.as_ref()
.map(|track| track.duration_ms as f64 / 1000.)
.unwrap_or(0.)
});
let slider = Slider::from_value(player.track_progress.map_each(|progress| {
progress
.map(|progress| progress.as_secs_f64())
.unwrap_or(0.)
}))
.minimum(0.)
.maximum(duration.clone())
.size(Size::<DimensionRange> {
height: Dimension::Lp(Lp::inches_f(0.2)).into(),
width: (..Dimension::Lp(Lp::inches_f(5.))).into(),
});
player
.track_progress
.map_each(|progress| {
progress
.map(|progress| format_time(progress))
.unwrap_or_else(|| "0:00".to_string())
})
.and(slider.expand_horizontally())
.and(duration.map_each(|duration| format_time(Duration::from_secs_f64(*duration))))
.into_columns()
.expand_horizontally()
.centered()
}
fn format_time(time: Duration) -> String {
let time = time.as_secs_f64();
let seconds = time % 60.;
let minutes = time / 60.;
format!("{}:{:02}", minutes.round(), seconds.round())
} }
fn vol(player: DynamicPlayer) -> impl MakeWidget { fn vol(player: DynamicPlayer) -> impl MakeWidget {