From 57006a68511b18a00899fb9af28217623f4f58ae Mon Sep 17 00:00:00 2001 From: Samuel Oldham Date: Tue, 1 Oct 2024 12:51:48 +0100 Subject: [PATCH] Cache lyrics and add ability to skip to position in song via lyrics --- psst-gui/src/cmd.rs | 1 + psst-gui/src/controller/playback.rs | 6 +++++ psst-gui/src/ui/lyrics.rs | 8 ++++-- psst-gui/src/webapi/client.rs | 39 ++++++----------------------- 4 files changed, 20 insertions(+), 34 deletions(-) diff --git a/psst-gui/src/cmd.rs b/psst-gui/src/cmd.rs index 65957e13..0a6748af 100644 --- a/psst-gui/src/cmd.rs +++ b/psst-gui/src/cmd.rs @@ -53,6 +53,7 @@ pub const PLAY_STOP: Selector = Selector::new("app.play-stop"); pub const ADD_TO_QUEUE: Selector<(QueueEntry, PlaybackItem)> = Selector::new("app.add-to-queue"); pub const PLAY_QUEUE_BEHAVIOR: Selector = Selector::new("app.play-queue-behavior"); pub const PLAY_SEEK: Selector = Selector::new("app.play-seek"); +pub const SKIP_TO_POSITION: Selector = Selector::new("app.skip-to-position"); // Sorting control pub const SORT_BY_DATE_ADDED: Selector = Selector::new("app.sort-by-date-added"); diff --git a/psst-gui/src/controller/playback.rs b/psst-gui/src/controller/playback.rs index 79b8b811..f1c81db6 100644 --- a/psst-gui/src/controller/playback.rs +++ b/psst-gui/src/controller/playback.rs @@ -436,6 +436,12 @@ where } ctx.set_handled(); } + Event::Command(cmd) if cmd.is(cmd::SKIP_TO_POSITION) => { + let location = cmd.get_unchecked(cmd::SKIP_TO_POSITION); + self.seek(Duration::from_millis(location.clone())); + + ctx.set_handled(); + } // Keyboard shortcuts. Event::KeyDown(key) if key.code == Code::Space => { self.pause_or_resume(); diff --git a/psst-gui/src/ui/lyrics.rs b/psst-gui/src/ui/lyrics.rs index ee28ddd3..931df75f 100644 --- a/psst-gui/src/ui/lyrics.rs +++ b/psst-gui/src/ui/lyrics.rs @@ -3,6 +3,7 @@ use druid::widget::{Flex, Label, LineBreaking}; use druid::Insets; use druid::{widget::List, LensExt, Selector, Widget, WidgetExt}; +use crate::cmd; use crate::data::{Ctx, NowPlaying, TrackLines}; use crate::{ data::AppState, @@ -31,8 +32,11 @@ fn user_top_tracks_widget() -> impl Widget { .expand_width() .padding(Insets::uniform_xy(theme::grid(2.0), theme::grid(0.6))) .link() + // 19360 + .on_left_click(|ctx, _, c, _| ctx.submit_command(cmd::SKIP_TO_POSITION.with(c.data.start_time_ms.parse::().unwrap()))) + })}, - error_widget, + || Label::new("No lyrics for this song!"), ) .lens( Ctx::make( @@ -43,7 +47,7 @@ fn user_top_tracks_widget() -> impl Widget { ) .on_command_async( LOAD_LYRICS, - |t| WebApi::global().get_lyrics(t.item.id().to_base62(), t.cover_image_url(250.0, 250.0).unwrap().to_string()), + |t| WebApi::global().get_lyrics(t.item.id().to_base62()), |_, data, _| data.lyrics.defer(()), |_, data, r| data.lyrics.update(((), r.1)), ) diff --git a/psst-gui/src/webapi/client.rs b/psst-gui/src/webapi/client.rs index 57952fc6..d19fa6da 100644 --- a/psst-gui/src/webapi/client.rs +++ b/psst-gui/src/webapi/client.rs @@ -820,44 +820,26 @@ impl WebApi { Ok(result) } - pub fn get_lyrics(&self, track_id: String, image_url: String) -> Result, Error> { - #[derive(Default, Debug, Clone, PartialEq, Deserialize)] + pub fn get_lyrics(&self, track_id: String) -> Result, Error> { + #[derive(Default, Debug, Clone, PartialEq, Deserialize, Data)] #[serde(rename_all = "camelCase")] pub struct Root { pub lyrics: Lyrics, - pub colors: Colors, - pub has_vocal_removal: bool, } - #[derive(Default, Debug, Clone, PartialEq, Deserialize)] + + #[derive(Default, Debug, Clone, PartialEq, Deserialize, Data)] #[serde(rename_all = "camelCase")] pub struct Lyrics { - pub sync_type: String, pub lines: Vector, pub provider: String, pub provider_lyrics_id: String, - pub provider_display_name: String, - pub sync_lyrics_uri: String, - pub is_dense_typeface: bool, - pub language: String, - pub is_rtl_language: bool, - pub show_upsell: bool, - pub cap_status: String, - pub is_snippet: bool, } - #[derive(Default, Debug, Clone, PartialEq, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Colors { - pub background: i64, - pub text: i64, - pub highlight_text: i64, - } - // https://spclient.wg.spotify.com/color-lyrics/v2/track/6h4yONyGIFXYhrvEX6jVeb/image/https%3A%2F%2Fi.scdn.co%2Fimage%2Fab67616d0000b273f8bd876cdda0e7a825bb9afb?format=json&vocalRemoval=false&market=from_token let token = self.access_token()?; let request = self .agent - .request("GET", &format!("https://{}/{}", "spclient.wg.spotify.com", format!("color-lyrics/v2/track/{}/image/https%3A%2F%2Fi.scdn.co%2Fimage%2F{}", track_id, track_id.clone().split_off(3)))) + .request("GET", &format!("https://spclient.wg.spotify.com/color-lyrics/v2/track/{}/image/https%3A%2F%2Fi.scdn.co%2Fimage%2F{}", track_id, track_id.clone().split_off(3))) .query("format", "json") .query("vocalRemoval", "false") .query("market", "from_token") @@ -865,15 +847,8 @@ impl WebApi { .set("app-platform", "WebPlayer") .set("Authorization", &format!("Bearer {}", &token)); -// WE can defiantly cache this very effectivly! - match self.load::(request) { - Ok(result) => { - Ok(result.lyrics.lines) - }, - Err(e) => { - Err(e) - } - } + let result: Cached = self.load_cached(request.clone(), "Lyrics", &track_id)?; + Ok(result.data.lyrics.lines) } }