diff --git a/resources/play_queue_song.blp b/resources/play_queue_song.blp index b70cd3c..8457b8a 100644 --- a/resources/play_queue_song.blp +++ b/resources/play_queue_song.blp @@ -24,6 +24,8 @@ template $AudreyUiPlayQueueSong: Box { focusable: false; halign: end; justify: right; + margin-top: 1; + margin-bottom: 1; styles [ "dim-label", @@ -39,7 +41,7 @@ template $AudreyUiPlayQueueSong: Box { margin-top: 1; margin-bottom: 1; pixel-size: 50; - // paintable: bind template.song as <$AudreyModelSong>.thumbnail; + paintable: bind template.song as <$AudreyModelSong>.thumbnail; } Box title_box { diff --git a/src/model/song.rs b/src/model/song.rs index 18fe3ad..224bf8c 100644 --- a/src/model/song.rs +++ b/src/model/song.rs @@ -1,7 +1,7 @@ mod imp { use adw::prelude::*; - use gtk::glib; use gtk::subclass::prelude::*; + use gtk::{gdk, glib}; use std::cell::{Cell, RefCell}; static NEXT_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); @@ -31,6 +31,10 @@ mod imp { cover_art_url: RefCell, #[property(get, set)] stream_url: RefCell, + + #[property(get, set)] + thumbnail: RefCell>, + pub(super) thumbnail_loading: RefCell>>, } #[glib::object_subclass] @@ -48,19 +52,31 @@ mod imp { .set_counter(NEXT_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed)); } } + + impl Drop for Song { + fn drop(&mut self) { + self.thumbnail_loading.take().map(|handle| handle.abort()); + } + } } use crate::subsonic; +use adw::{prelude::*, subclass::prelude::*}; use glib::Object; -use gtk::glib; +use gtk::{gdk, glib}; +use std::rc::Rc; glib::wrapper! { pub struct Song(ObjectSubclass); } impl Song { - pub fn from_child(api: &subsonic::Client, song: &subsonic::schema::Child) -> Self { - Object::builder() + pub fn from_child( + api: &Rc, + song: &subsonic::schema::Child, + load_thumbnail: bool, + ) -> Self { + let song: Song = Object::builder() .property("id", &song.id) .property("title", &song.title) .property("artist", &song.artist) @@ -68,8 +84,31 @@ impl Song { .property("genre", &song.genre) .property("duration", song.duration as i64) //.property("track", song.track) - .property("cover-art-url", api.cover_art_url(&song.id).as_str()) + .property("cover-art-url", api.cover_art_url(&song.id, 0).as_str()) .property("stream-url", api.stream_url(&song.id).as_str()) - .build() + .build(); + + if load_thumbnail { + let api = Rc::clone(&api); + let id = song.id(); + let song_weak = song.downgrade(); + song.imp() + .thumbnail_loading + .replace(Some(glib::spawn_future_local(async move { + let bytes = api + .cover_art(&id, 50) // TODO: constify thumbnail size, maybe take dpi into account etc + .await + .unwrap(); + let song = match song_weak.upgrade() { + None => return, + Some(song) => song, + }; + song.set_thumbnail( + gdk::Texture::from_bytes(&glib::Bytes::from_owned(bytes)).unwrap(), + ); + }))); + } + + song } } diff --git a/src/subsonic.rs b/src/subsonic.rs index 69397eb..5126302 100644 --- a/src/subsonic.rs +++ b/src/subsonic.rs @@ -219,12 +219,19 @@ impl Client { .map(|response| response.random_songs.song) } - pub fn cover_art_url(&self, id: &str) -> url::Url { - self.url(&["rest", "getCoverArt"], &[("id", id)]) + pub fn cover_art_url(&self, id: &str, size: u32) -> url::Url { + if size == 0 { + self.url(&["rest", "getCoverArt"], &[("id", id)]) + } else { + self.url( + &["rest", "getCoverArt"], + &[("id", id), ("size", &size.to_string())], + ) + } } - pub async fn cover_art(&self, id: &str) -> Result { - self.send_bytes(self.client.get(self.cover_art_url(id))) + pub async fn cover_art(&self, id: &str, size: u32) -> Result { + self.send_bytes(self.client.get(self.cover_art_url(id, size))) .await } diff --git a/src/ui/window.rs b/src/ui/window.rs index 7635a0b..01a9df2 100644 --- a/src/ui/window.rs +++ b/src/ui/window.rs @@ -270,7 +270,7 @@ mod imp { Rc::clone(api.as_ref().unwrap()) }; for song in api.get_random_songs(10).await.unwrap().into_iter() { - let song = Song::from_child(&api, &song); + let song = Song::from_child(&api, &song, true); self.mpv .command(["loadfile", &song.stream_url(), "append-play"]) .unwrap(); @@ -472,12 +472,11 @@ mod imp { let window = self.obj().clone(); let song_id = window.song().unwrap().id(); - self - .loading_cover_handle + self.loading_cover_handle .replace(Some(glib::spawn_future_local(async move { let api = window.imp().api.borrow().as_ref().unwrap().clone(); let bytes = api - .cover_art(&song_id) + .cover_art(&song_id, 0) // 0: full size .await .expect("could not load cover art for song {song_id}");