thumbnails in play queue

This commit is contained in:
Erica Z 2024-11-06 12:46:49 +01:00
parent ba3cd74a68
commit db8d99e180
4 changed files with 62 additions and 15 deletions

View file

@ -24,6 +24,8 @@ template $AudreyUiPlayQueueSong: Box {
focusable: false; focusable: false;
halign: end; halign: end;
justify: right; justify: right;
margin-top: 1;
margin-bottom: 1;
styles [ styles [
"dim-label", "dim-label",
@ -39,7 +41,7 @@ template $AudreyUiPlayQueueSong: Box {
margin-top: 1; margin-top: 1;
margin-bottom: 1; margin-bottom: 1;
pixel-size: 50; pixel-size: 50;
// paintable: bind template.song as <$AudreyModelSong>.thumbnail; paintable: bind template.song as <$AudreyModelSong>.thumbnail;
} }
Box title_box { Box title_box {

View file

@ -1,7 +1,7 @@
mod imp { mod imp {
use adw::prelude::*; use adw::prelude::*;
use gtk::glib;
use gtk::subclass::prelude::*; use gtk::subclass::prelude::*;
use gtk::{gdk, glib};
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
static NEXT_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); static NEXT_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
@ -31,6 +31,10 @@ mod imp {
cover_art_url: RefCell<String>, cover_art_url: RefCell<String>,
#[property(get, set)] #[property(get, set)]
stream_url: RefCell<String>, stream_url: RefCell<String>,
#[property(get, set)]
thumbnail: RefCell<Option<gdk::Paintable>>,
pub(super) thumbnail_loading: RefCell<Option<glib::JoinHandle<()>>>,
} }
#[glib::object_subclass] #[glib::object_subclass]
@ -48,19 +52,31 @@ mod imp {
.set_counter(NEXT_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed)); .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 crate::subsonic;
use adw::{prelude::*, subclass::prelude::*};
use glib::Object; use glib::Object;
use gtk::glib; use gtk::{gdk, glib};
use std::rc::Rc;
glib::wrapper! { glib::wrapper! {
pub struct Song(ObjectSubclass<imp::Song>); pub struct Song(ObjectSubclass<imp::Song>);
} }
impl Song { impl Song {
pub fn from_child(api: &subsonic::Client, song: &subsonic::schema::Child) -> Self { pub fn from_child(
Object::builder() api: &Rc<subsonic::Client>,
song: &subsonic::schema::Child,
load_thumbnail: bool,
) -> Self {
let song: Song = Object::builder()
.property("id", &song.id) .property("id", &song.id)
.property("title", &song.title) .property("title", &song.title)
.property("artist", &song.artist) .property("artist", &song.artist)
@ -68,8 +84,31 @@ impl Song {
.property("genre", &song.genre) .property("genre", &song.genre)
.property("duration", song.duration as i64) .property("duration", song.duration as i64)
//.property("track", song.track) //.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()) .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
} }
} }

View file

@ -219,12 +219,19 @@ impl Client {
.map(|response| response.random_songs.song) .map(|response| response.random_songs.song)
} }
pub fn cover_art_url(&self, id: &str) -> url::Url { pub fn cover_art_url(&self, id: &str, size: u32) -> url::Url {
if size == 0 {
self.url(&["rest", "getCoverArt"], &[("id", id)]) 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<Bytes, Error> { pub async fn cover_art(&self, id: &str, size: u32) -> Result<Bytes, Error> {
self.send_bytes(self.client.get(self.cover_art_url(id))) self.send_bytes(self.client.get(self.cover_art_url(id, size)))
.await .await
} }

View file

@ -270,7 +270,7 @@ mod imp {
Rc::clone(api.as_ref().unwrap()) Rc::clone(api.as_ref().unwrap())
}; };
for song in api.get_random_songs(10).await.unwrap().into_iter() { 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 self.mpv
.command(["loadfile", &song.stream_url(), "append-play"]) .command(["loadfile", &song.stream_url(), "append-play"])
.unwrap(); .unwrap();
@ -472,12 +472,11 @@ mod imp {
let window = self.obj().clone(); let window = self.obj().clone();
let song_id = window.song().unwrap().id(); let song_id = window.song().unwrap().id();
self self.loading_cover_handle
.loading_cover_handle
.replace(Some(glib::spawn_future_local(async move { .replace(Some(glib::spawn_future_local(async move {
let api = window.imp().api.borrow().as_ref().unwrap().clone(); let api = window.imp().api.borrow().as_ref().unwrap().clone();
let bytes = api let bytes = api
.cover_art(&song_id) .cover_art(&song_id, 0) // 0: full size
.await .await
.expect("could not load cover art for song {song_id}"); .expect("could not load cover art for song {song_id}");