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]
# 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"]

View file

@ -3,9 +3,10 @@ use std::{
time::{Duration, Instant},
};
use cushy::value::{Destination, Dynamic};
use cushy::value::{Destination, Dynamic, Source};
use librespot_metadata::audio::AudioItem;
use librespot_playback::player::{Player, PlayerEvent};
use tokio::time;
pub type DynamicPlayer = Arc<DynamicPlayerInner>;
@ -59,11 +60,38 @@ impl DynamicPlayerInner {
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) {
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;
loop {
tokio::select! {
_ = interval.tick() => {
self.update_position();
}
event = channel.recv() => {
if let Some(event) = event {
match event {
@ -82,6 +110,7 @@ impl DynamicPlayerInner {
self.state.set(PlayerState::Loading {
loading_at: Duration::from_millis(position_ms as u64),
});
self.update_position();
}
PlayerEvent::Playing {
play_request_id,
@ -92,6 +121,7 @@ impl DynamicPlayerInner {
self.state.set(PlayerState::Playing);
*self.started_at.lock().unwrap() =
Some(Instant::now() - Duration::from_millis(position_ms as u64));
self.update_position();
}
PlayerEvent::Paused {
play_request_id,
@ -102,6 +132,7 @@ impl DynamicPlayerInner {
self.state.set(PlayerState::Paused {
paused_at: Duration::from_millis(position_ms as u64),
});
self.update_position();
}
PlayerEvent::Unavailable {
play_request_id,
@ -121,6 +152,7 @@ impl DynamicPlayerInner {
println!("PositionCorrection {play_request_id} {position_ms} {track_id}");
*self.started_at.lock().unwrap() =
Some(Instant::now() - Duration::from_millis(position_ms as u64));
self.update_position();
}
PlayerEvent::Seeked {
play_request_id,
@ -130,11 +162,13 @@ impl DynamicPlayerInner {
println!("Seeked {play_request_id} {position_ms} {track_id}");
*self.started_at.lock().unwrap() =
Some(Instant::now() - Duration::from_millis(position_ms as u64));
self.update_position();
}
PlayerEvent::TrackChanged { audio_item } => {
println!("TrackChanged {}", audio_item.uri);
dbg!(&audio_item);
*self.track.lock() = Some(audio_item);
self.update_position();
}
PlayerEvent::SessionConnected {
connection_id,

View file

@ -1,9 +1,11 @@
use std::time::Duration;
use cushy::{
figures::{units::Lp, Size},
styles::{Dimension, DimensionRange},
value::Source,
value::{Dynamic, Source},
widget::MakeWidget,
widgets::{Button, Image, Label},
widgets::{Button, Image, Label, Slider},
};
use itertools::Itertools;
use librespot_metadata::audio::UniqueFields;
@ -16,17 +18,17 @@ use crate::{
pub fn bar(player: DynamicPlayer) -> impl MakeWidget {
meta(player).size(Size {
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 {
Image::new_empty()
.with_url(player.track.map_each(|track| {
track
dbg!(track
.as_ref()
.map(|track| track.covers.first().map(|cover| cover.url.clone()))
.flatten()
.flatten())
}))
.size(Size::squared(Dimension::Lp(Lp::inches_f(1.))))
.and(
@ -64,6 +66,11 @@ fn meta(player: DynamicPlayer) -> impl MakeWidget {
.expand(),
)
.into_columns()
.align_left()
.expand()
.and(controls(player.clone()).expand())
.and(vol(player).align_right().expand())
.into_columns()
}
fn controls(player: DynamicPlayer) -> impl MakeWidget {
@ -82,12 +89,48 @@ fn controls(player: DynamicPlayer) -> impl MakeWidget {
.and("skip".into_button())
.and("repeat".into_button())
.into_columns()
.and(progress(player))
.centered()
.and(time(player))
.into_rows()
}
fn progress(player: DynamicPlayer) -> impl MakeWidget {
"progress bar here"
fn time(player: DynamicPlayer) -> impl MakeWidget {
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 {