diff --git a/src/mpris.vala b/src/mpris.vala index ed9763b..fa7e8b5 100644 --- a/src/mpris.vala +++ b/src/mpris.vala @@ -125,7 +125,7 @@ class MprisPlayer : Object { }); playbin.new_track.connect ((playbin) => { - Subsonic.Song song = (Subsonic.Song) playbin.play_queue.get_item (playbin.play_queue_position); + PlaybinSong song = (PlaybinSong) playbin.play_queue.get_item (playbin.play_queue_position); var metadata = new HashTable (null, null); metadata["mpris:trackid"] = new ObjectPath (@"/eu/callcc/audrey/track/$(song.id)"); @@ -137,7 +137,7 @@ class MprisPlayer : Object { metadata["xesam:title"] = song.title; metadata["xesam:trackNumber"] = song.track; metadata["xesam:useCount"] = song.play_count; - metadata["xesam:userRating"] = song.starred != null ? 1.0 : 0.0; + // TODO metadata["xesam:userRating"] = song.starred != null ? 1.0 : 0.0; this.metadata = metadata; }); diff --git a/src/playbin.vala b/src/playbin.vala index 296394b..424f3ab 100644 --- a/src/playbin.vala +++ b/src/playbin.vala @@ -9,12 +9,67 @@ private struct CommandCallback { int error; } +public class PlaybinSong : Object { + private Subsonic.Song inner; + public string id { get { return inner.id; } } + public string title { get { return inner.title; } } + public string artist { get { return inner.artist; } } + public string album { get { return inner.album; } } + public string? genre { get { return inner.genre; } } + public int64 duration { get { return inner.duration; } } + public int64 track { get { return inner.track; } } + public int64 play_count { get { return inner.play_count; } } + + public Gdk.Paintable? thumbnail { get; private set; } + + private Cancellable cancel_loading_thumbnail; + + public PlaybinSong (Subsonic.Client api, Subsonic.Song song) { + this.api = api; + this.inner = song; + } + + private Subsonic.Client api; + + public void need_cover_art () { + /* TODO + if (this.cancel_loading_thumbnail != null) return; + if (this.thumbnail != null) return; + + this.cancel_loading_thumbnail = new Cancellable (); + // TODO: dpi scaling maybe?? probably + api.cover_art.begin (this.id, 50, Priority.LOW, this.cancel_loading_thumbnail, (obj, res) => { + try { + var pixbuf = api.cover_art.end (res); + this.thumbnail = Gdk.Texture.for_pixbuf (pixbuf); + } catch (Error e) { + if (!(e is IOError.CANCELLED)) { + warning ("could not fetch cover art for song %s: %s", this.id, e.message); + } + } + this.cancel_loading_thumbnail = null; + }); + */ + } + + ~PlaybinSong () { + if (this.cancel_loading_thumbnail != null) { + this.cancel_loading_thumbnail.cancel (); + } + } +} + public class Playbin : GLib.Object { private Mpv.Handle mpv = new Mpv.Handle (); + private int _volume = 100; + private bool _mute = false; + private ListStore _play_queue = new ListStore (typeof (PlaybinSong)); + + // try to prevent wait_event to be called twice + private bool is_handling_event = false; public PlaybinState state { get; private set; default = PlaybinState.STOPPED; } - private int _volume = 100; public int volume { get { return _volume; } set { @@ -27,7 +82,6 @@ public class Playbin : GLib.Object { } } - public bool _mute = false; public bool mute { get { return _mute; } set { @@ -54,13 +108,9 @@ public class Playbin : GLib.Object { public weak Subsonic.Client api { get; set; default = null; } - private ListStore _play_queue = new ListStore (typeof (Subsonic.Song)); public ListModel play_queue { get { return this._play_queue; } } public uint play_queue_length { get; private set; default = 0; } - // try to prevent wait_event to be called twice - private bool is_handling_event = false; - private async Mpv.Error mpv_command_async (string[] args) { CommandCallback cc = {}; @@ -161,7 +211,7 @@ public class Playbin : GLib.Object { // estimate duration from api data // while mpv doesn't know it - this.duration = ((Subsonic.Song) this._play_queue.get_item (this.play_queue_position)).duration; + this.duration = ((PlaybinSong) this._play_queue.get_item (this.play_queue_position)).duration; this.new_track (); break; @@ -308,7 +358,7 @@ public class Playbin : GLib.Object { this.api.stream_uri (song.id), "append", }) >= 0); - this._play_queue.append (song); + this._play_queue.append (new PlaybinSong (this.api, song)); this.play_queue_length += 1; } @@ -320,7 +370,7 @@ public class Playbin : GLib.Object { }); assert (err >= 0); - this._play_queue.append (song); + this._play_queue.append (new PlaybinSong (this.api, song)); this.play_queue_length += 1; } diff --git a/src/subsonic.vala b/src/subsonic.vala index 3209fd7..b9d553c 100644 --- a/src/subsonic.vala +++ b/src/subsonic.vala @@ -53,18 +53,18 @@ public class Subsonic.Album : Object { } } -public class Subsonic.Song : Object { - public string id { get; private set; } - public string title { get; private set; } - public string album { get; private set; } - public string artist { get; private set; } - public int64 track { get; private set; } - public int64 year { get; private set; } - public DateTime? starred { get; private set; } // TODO - public int64 duration { get; private set; } - public int64 play_count { get; private set; } - public string? genre { get; private set; } - public string cover_art { get; private set; } +public struct Subsonic.Song { + public string id; + public string title; + public string album; + public string artist; + public int64 track; + public int64 year; + public DateTime? starred; // TODO + public int64 duration; + public int64 play_count; + public string? genre; + public string cover_art; public Song (Json.Reader reader) { reader.read_member ("id"); @@ -109,50 +109,7 @@ public class Subsonic.Song : Object { } } -public struct Subsonic.PlayQueue { - public string current; - public int64 position; - public DateTime changed; - public string changed_by; - public ListStore songs; - - internal PlayQueue.from_reader (Json.Reader reader) { - reader.read_member ("current"); - this.current = reader.get_string_value (); - reader.end_member (); - - reader.read_member ("position"); - this.position = reader.get_int_value (); - reader.end_member (); - - reader.read_member ("changed"); - this.changed = new DateTime.from_iso8601 (reader.get_string_value (), null); - reader.end_member (); - - reader.read_member ("changed_by"); - this.changed_by = reader.get_string_value (); - reader.end_member (); - - //print("%s %lli %s %s\n",this.current, this.position, this.changed, this.changed_by); - - this.songs = new ListStore (typeof (Song)); - - reader.read_member ("song"); - for (int i = 0; i < reader.count_elements (); i += 1) { - reader.read_element (i); - this.songs.append (new Song (reader)); - reader.end_element (); - } - reader.end_member (); - assert (reader.get_error () == null); - } -} - public class Subsonic.Client : Object { - public ListStore artist_list; - public ListStore album_list; - public ListStore song_list; - private Soup.Session session; private string url; private string parameters; @@ -163,10 +120,6 @@ public class Subsonic.Client : Object { this.session = new Soup.Session (); this.session.user_agent = Audrey.Const.user_agent; - - this.artist_list = new ListStore (typeof (Artist)); - this.album_list = new ListStore (typeof (Album)); - this.song_list = new ListStore (typeof (Song)); } private void unwrap_response (Json.Reader reader) throws GLib.Error { @@ -237,7 +190,7 @@ public class Subsonic.Client : Object { for (int i = 0; i < reader.count_elements (); i += 1) { reader.read_element (i); - callback (new Song (reader)); + callback (Song (reader)); reader.end_element (); Idle.add (this.get_random_songs.callback); @@ -251,16 +204,29 @@ public class Subsonic.Client : Object { return @"$(this.url)/rest/stream?id=$(Uri.escape_string(id))&$(this.parameters)"; } - public string cover_art_uri (string id) { - return @"$(this.url)/rest/getCoverArt?id=$(Uri.escape_string(id))&$(this.parameters)"; + public string cover_art_uri (string id, int size = -1) { + if (size >= 0) { + return @"$(this.url)/rest/getCoverArt?id=$(Uri.escape_string(id))&$(this.parameters)"; + } else { + return @"$(this.url)/rest/getCoverArt?size=$size&id=$(Uri.escape_string(id))&$(this.parameters)"; + } } - public async Gdk.Pixbuf cover_art (string id, Cancellable cancellable) throws GLib.Error { - var msg = new Soup.Message("GET", this.cover_art_uri (id)); + public async Gdk.Pixbuf cover_art ( + string id, + int size = -1, + int priority = Priority.DEFAULT, + Cancellable? cancellable = null + ) + throws GLib.Error + { + var msg = new Soup.Message("GET", this.cover_art_uri (id, size)); assert (msg != null); - var stream = yield this.session.send_async (msg, Priority.DEFAULT, cancellable); - assert (msg.get_status () == Soup.Status.OK); + var stream = yield this.session.send_async (msg, priority, cancellable); + if (msg.get_status () != Soup.Status.OK) { + warning ("could not load cover art for %s: %s", id, Soup.Status.get_phrase (msg.get_status ())); + } return yield new Gdk.Pixbuf.from_stream_async (stream, cancellable); } diff --git a/src/ui/play_queue.vala b/src/ui/play_queue.vala index dbbf8ed..475ef78 100644 --- a/src/ui/play_queue.vala +++ b/src/ui/play_queue.vala @@ -7,8 +7,6 @@ class Ui.PlayQueueSong : Gtk.Box { public bool show_artist { get; set; default = false; } public bool show_cover { get; set; default = false; } - public Gdk.Paintable cover { get; private set; } // FIXME actually show the thing lol - private bool _current = false; public bool current { get { return _current; } @@ -22,7 +20,7 @@ class Ui.PlayQueueSong : Gtk.Box { } } public uint displayed_position { get; set; } - public Subsonic.Song song { get; set; } + public PlaybinSong song { get; set; } private weak Playbin playbin; public PlayQueueSong (Playbin playbin) { @@ -40,13 +38,15 @@ class Ui.PlayQueueSong : Gtk.Box { } private ulong connection; - public void bind (uint position, Subsonic.Song song) { + public void bind (uint position, PlaybinSong song) { this.displayed_position = position+1; this.song = song; this.current = this.playbin.play_queue_position == position; this.connection = this.playbin.notify["play-queue-position"].connect (() => { this.current = this.playbin.play_queue_position == position; }); + + song.need_cover_art (); } public void unbind () { @@ -151,7 +151,7 @@ public class Ui.PlayQueue : Adw.Bin { var item = object as Gtk.ListItem; var child = item.child as PlayQueueSong; - child.bind (item.position, item.item as Subsonic.Song); + child.bind (item.position, item.item as PlaybinSong); } [GtkCallback] private void on_song_list_unbind (Gtk.SignalListItemFactory factory, Object object) { diff --git a/src/ui/play_queue_song.blp b/src/ui/play_queue_song.blp index 86ffa52..d6603d6 100644 --- a/src/ui/play_queue_song.blp +++ b/src/ui/play_queue_song.blp @@ -35,7 +35,7 @@ template $UiPlayQueueSong: Box { margin-top: 1; margin-bottom: 1; pixel-size: 50; - paintable: bind template.cover; + paintable: bind template.song as <$PlaybinSong>.thumbnail; } Box title_box { @@ -58,7 +58,7 @@ template $UiPlayQueueSong: Box { max-width-chars: 90; justify: fill; - label: bind template.song as <$SubsonicSong>.title; + label: bind template.song as <$PlaybinSong>.title; } Label { @@ -71,7 +71,7 @@ template $UiPlayQueueSong: Box { max-width-chars: 90; justify: fill; - label: bind template.song as <$SubsonicSong>.artist; + label: bind template.song as <$PlaybinSong>.artist; } } } @@ -83,12 +83,13 @@ template $UiPlayQueueSong: Box { single-line-mode: true; styles [ "numeric", "dim-label" ] - label: bind $format_duration (template.song as <$SubsonicSong>.duration) as ; + label: bind $format_duration (template.song as <$PlaybinSong>.duration) as ; } Button { focusable: true; - icon-name: bind $star_button_icon_name (template.song as <$SubsonicSong>.starred) as ; + // TODO icon-name: bind $star_button_icon_name (template.song as <$PlaybinSong>.starred) as ; + icon-name: bind $star_button_icon_name () as ; styles [ "flat" ] valign: center; } diff --git a/src/ui/playbar.vala b/src/ui/playbar.vala index b98514e..411fc41 100644 --- a/src/ui/playbar.vala +++ b/src/ui/playbar.vala @@ -1,6 +1,6 @@ [GtkTemplate (ui = "/eu/callcc/audrey/ui/playbar.ui")] class Ui.Playbar : Adw.Bin { - public Subsonic.Song? song { get; set; } + public PlaybinSong? song { get; set; } public Gdk.Paintable? playing_cover_art { get; set; } public weak Playbin playbin { get; set; } public bool show_cover_art { get; set; default = true; } @@ -75,15 +75,15 @@ class Ui.Playbar : Adw.Bin { this.playbin.seek (new_position); } - [GtkCallback] private string song_title (Subsonic.Song? song) { + [GtkCallback] private string song_title (PlaybinSong? song) { return song == null ? "" : song.title; } - [GtkCallback] private string song_artist (Subsonic.Song? song) { + [GtkCallback] private string song_artist (PlaybinSong? song) { return song == null ? "" : song.artist; } - [GtkCallback] private string song_album (Subsonic.Song? song) { + [GtkCallback] private string song_album (PlaybinSong? song) { return song == null ? "" : song.album; } diff --git a/src/ui/window.vala b/src/ui/window.vala index fbfba51..40c0359 100644 --- a/src/ui/window.vala +++ b/src/ui/window.vala @@ -17,7 +17,7 @@ class Ui.Window : Adw.ApplicationWindow { set { this.playbin.mute = value; } } - public Subsonic.Song? song { get; private set; } + public PlaybinSong? song { get; private set; } public Gdk.Paintable? playing_cover_art { get; set; default = null; } private Cancellable cancel_loading_art; @@ -32,7 +32,7 @@ class Ui.Window : Adw.ApplicationWindow { private Mpris mpris; private MprisPlayer mpris_player; - private void now_playing (Subsonic.Song song) { + private void now_playing (PlaybinSong song) { this.song = song; // api.scrobble.begin (this.song.id); TODO @@ -45,7 +45,7 @@ class Ui.Window : Adw.ApplicationWindow { this.cover_art_loading = true; string song_id = this.song.id; - this.api.cover_art.begin (song_id, this.cancel_loading_art, (obj, res) => { + this.api.cover_art.begin (song_id, -1, Priority.DEFAULT, this.cancel_loading_art, (obj, res) => { try { this.playing_cover_art = Gdk.Texture.for_pixbuf (this.api.cover_art.end (res)); this.cover_art_loading = false; @@ -88,7 +88,7 @@ class Ui.Window : Adw.ApplicationWindow { this.setup.load (); this.playbin.new_track.connect (() => { - this.now_playing (this.playbin.play_queue.get_item (this.playbin.play_queue_position) as Subsonic.Song); + this.now_playing (this.playbin.play_queue.get_item (this.playbin.play_queue_position) as PlaybinSong); }); this.playbin.stopped.connect (() => {