Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion psst-core/src/player/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ impl DecoderSource {
norm_factor: f32,
event_send: Sender<PlayerEvent>,
) -> Self {
const REPORT_PRECISION: Duration = Duration::from_millis(900);
const REPORT_PRECISION: Duration = Duration::from_millis(200);

// Gather the source signal parameters and compute how often we should report
// the play-head position.
Expand Down
28 changes: 27 additions & 1 deletion psst-gui/src/data/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ pub struct AppState {
pub added_queue: Vector<QueueEntry>,
pub lyrics: Promise<Vector<TrackLines>>,
pub credits: Option<TrackCredits>,
pub lyrics_offset: Option<f64>,
}

impl AppState {
Expand All @@ -104,6 +105,7 @@ impl AppState {
library: Arc::clone(&library),
show_track_cover: config.show_track_cover,
nav: Nav::Home,
progress: Duration::default(),
});
let playback = Playback {
state: PlaybackState::Stopped,
Expand Down Expand Up @@ -170,6 +172,7 @@ impl AppState {
finder: Finder::new(),
lyrics: Promise::Empty,
credits: None,
lyrics_offset: None,
}
}
}
Expand Down Expand Up @@ -236,6 +239,7 @@ impl AppState {

pub fn loading_playback(&mut self, item: Playable, origin: PlaybackOrigin) {
self.common_ctx_mut().now_playing.take();
self.set_common_progress(Duration::default());
self.playback.state = PlaybackState::Loading;
self.playback.now_playing.replace(NowPlaying {
item,
Expand All @@ -247,6 +251,7 @@ impl AppState {

pub fn start_playback(&mut self, item: Playable, origin: PlaybackOrigin, progress: Duration) {
self.common_ctx_mut().now_playing.replace(item.clone());
self.set_common_progress(progress);
self.playback.state = PlaybackState::Playing;
self.playback.now_playing.replace(NowPlaying {
item,
Expand All @@ -260,6 +265,7 @@ impl AppState {
if let Some(now_playing) = &mut self.playback.now_playing {
now_playing.progress = progress;
}
self.set_common_progress(progress);
}

pub fn pause_playback(&mut self) {
Expand All @@ -271,13 +277,14 @@ impl AppState {
}

pub fn block_playback(&mut self) {
// TODO: Figure out how to signal blocked playback properly.
self.playback.state = PlaybackState::Loading;
}

pub fn stop_playback(&mut self) {
self.playback.state = PlaybackState::Stopped;
self.playback.now_playing.take();
self.common_ctx_mut().now_playing.take();
self.set_common_progress(Duration::default());
}

pub fn set_queue_behavior(&mut self, queue_behavior: QueueBehavior) {
Expand Down Expand Up @@ -335,6 +342,18 @@ impl AppState {
}
}

impl AppState {
fn set_common_progress(&mut self, progress: Duration) {
let mut ctx = (*self.common_ctx).clone();
ctx.progress = progress;
self.common_ctx = Arc::new(ctx);
}

pub fn reset_lyrics_offset(&mut self) {
self.lyrics_offset = None;
}
}

#[derive(Clone, Data, Lens)]
pub struct Library {
pub user_profile: Promise<UserProfile>,
Expand Down Expand Up @@ -541,12 +560,19 @@ pub struct CommonCtx {
pub library: Arc<Library>,
pub show_track_cover: bool,
pub nav: Nav,
pub progress: Duration,
}

impl CommonCtx {
pub fn is_playing(&self, item: &Playable) -> bool {
matches!(&self.now_playing, Some(i) if i.same(item))
}

pub fn current_progress(&self) -> Duration {
// Use the stored progress directly for better accuracy
// The progress is updated by the audio player events
self.progress
}
}

pub type WithCtx<T> = Ctx<Arc<CommonCtx>, T>;
Expand Down
58 changes: 57 additions & 1 deletion psst-gui/src/ui/lyrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ use druid::{Insets, LensExt, Selector, Widget, WidgetExt};

use crate::cmd;
use crate::data::{AppState, Ctx, NowPlaying, Playable, TrackLines};
use crate::data::CommonCtx;
use crate::widget::MyWidgetExt;
use crate::{webapi::WebApi, widget::Async};

use super::theme;
use super::utils;

use std::sync::Arc;
use druid::im::Vector;

pub const SHOW_LYRICS: Selector<NowPlaying> = Selector::new("app.home.show_lyrics");

pub fn lyrics_widget() -> impl Widget<AppState> {
Expand Down Expand Up @@ -73,7 +77,34 @@ fn track_lyrics_widget() -> impl Widget<AppState> {
.center()
.padding(Insets::uniform_xy(theme::grid(1.0), theme::grid(0.5)))
.link()
.active(|c: &Ctx<Arc<CommonCtx>, TrackLines>, _env| {
let current_progress = c.ctx.current_progress().as_millis() as f64;
let start_ms = c.data.start_time_ms.parse::<f64>().unwrap_or(0.0);
let parsed_end = c.data.end_time_ms.parse::<f64>().unwrap_or(0.0);
let end_ms = if parsed_end > start_ms { parsed_end } else { start_ms + 800.0 };
current_progress >= start_ms && current_progress < end_ms
})
.rounded(theme::BUTTON_BORDER_RADIUS)
.env_scope(|env, _| {
let active = env.get(theme::BLUE_100).with_alpha(0.25);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One comment here, most of the buttons we have don't use blue as the hover. I would use the default hover which is a gray.

env.set(theme::LINK_ACTIVE_COLOR, active);
})
.on_update(|ctx, old, new, _env| {
let is_line_active = |ctx: &Arc<CommonCtx>, line: &TrackLines| {
let current_progress = ctx.current_progress().as_millis() as f64;
let start_ms = line.start_time_ms.parse::<f64>().unwrap_or(0.0);
let parsed_end = line.end_time_ms.parse::<f64>().unwrap_or(0.0);
let end_ms = if parsed_end > start_ms { parsed_end } else { start_ms + 800.0 };
current_progress >= start_ms && current_progress < end_ms
};

let was_active = is_line_active(&old.ctx, &old.data);
let is_active = is_line_active(&new.ctx, &new.data);

if !was_active && is_active {
ctx.scroll_to_view();
}
})
.on_left_click(|ctx, _, c, _| {
if c.data.start_time_ms.parse::<u64>().unwrap() != 0 {
ctx.submit_command(
Expand All @@ -91,6 +122,31 @@ fn track_lyrics_widget() -> impl Widget<AppState> {
SHOW_LYRICS,
|t| WebApi::global().get_lyrics(t.item.id().to_base62()),
|_, data, _| data.lyrics.defer(()),
|_, data, r| data.lyrics.update(((), r.1)),
|_, data, r| {
data.reset_lyrics_offset();
let processed = match r.1 {
Ok(lines) => {
let mut out = Vector::new();
let len = lines.len();
for idx in 0..len {
let mut l = lines[idx].clone();
let end_zero = l.end_time_ms.parse::<u64>().unwrap_or(0) == 0;
if end_zero {
if idx + 1 < len {
l.end_time_ms = lines[idx + 1].start_time_ms.clone();
} else {
if let Ok(start) = l.start_time_ms.parse::<u64>() {
l.end_time_ms = (start + 800).to_string();
}
}
}
out.push_back(l);
}
Ok(out)
}
Err(e) => Err(e),
};
data.lyrics.update(((), processed));
},
)
}
Loading