mirror of
https://github.com/danbulant/despot
synced 2026-05-20 12:38:34 +00:00
working slidebar for player
This commit is contained in:
parent
35139b84a9
commit
4ccd69e628
3 changed files with 87 additions and 10 deletions
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue