From 859b8bb032ac6cb4f1118b869cd115c561d4f2f5 Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Wed, 10 Jul 2013 17:06:52 +0200 Subject: Expose playlists in the menu if the player exports them --- src/media-player.vala | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/service.vala | 47 +++++++++++++++++++++++++++++++++--- 2 files changed, 110 insertions(+), 3 deletions(-) diff --git a/src/media-player.vala b/src/media-player.vala index 9c18f10..7326708 100644 --- a/src/media-player.vala +++ b/src/media-player.vala @@ -84,6 +84,8 @@ public class MediaPlayer: Object { get; set; } + public signal void playlists_changed (); + /** * Attach this object to a process of the associated media player. The player must own @dbus_name and * implement the org.mpris.MediaPlayer2.Player interface. @@ -98,6 +100,8 @@ public class MediaPlayer: Object { this._dbus_name = dbus_name; Bus.get_proxy.begin (BusType.SESSION, dbus_name, "/org/mpris/MediaPlayer2", DBusProxyFlags.GET_INVALIDATED_PROPERTIES, null, got_proxy); + Bus.get_proxy.begin (BusType.SESSION, dbus_name, "/org/mpris/MediaPlayer2", + DBusProxyFlags.GET_INVALIDATED_PROPERTIES, null, got_playlists_proxy); } /** @@ -160,10 +164,31 @@ public class MediaPlayer: Object { this.proxy.Previous.begin (); } + public uint get_n_playlists () { + return this.playlists != null ? this.playlists.length : 0; + } + + public string get_playlist_id (int index) { + return_val_if_fail (index < this.playlists.length, ""); + return this.playlists[index].path; + } + + public string get_playlist_name (int index) { + return_val_if_fail (index < this.playlists.length, ""); + return this.playlists[index].name; + } + + public void activate_playlist_by_name (string name) { + if (this.playlists_proxy != null) + this.playlists_proxy.ActivatePlaylist.begin (new ObjectPath (name)); + } + DesktopAppInfo appinfo; MprisPlayer? proxy; + MprisPlaylists ?playlists_proxy; string _dbus_name; bool play_when_attached = false; + PlaylistDetails[] playlists = null; void got_proxy (Object? obj, AsyncResult res) { try { @@ -191,6 +216,42 @@ public class MediaPlayer: Object { } } + void fetch_playlists () { + /* The proxy is created even when the interface is not supported. GDBusProxy will + return 0 for the PlaylistCount property in that case. */ + if (this.playlists_proxy != null && this.playlists_proxy.PlaylistCount > 0) { + this.playlists_proxy.GetPlaylists.begin (0, 100, "Alphabetical", false, (obj, res) => { + try { + this.playlists = playlists_proxy.GetPlaylists.end (res); + this.playlists_changed (); + } + catch (Error e) { + warning ("could not fetch playlists: %s", e.message); + this.playlists = null; + } + }); + } + else { + this.playlists = null; + this.playlists_changed (); + } + } + + void got_playlists_proxy (Object? obj, AsyncResult res) { + try { + this.playlists_proxy = Bus.get_proxy.end (res); + + var gproxy = this.proxy as DBusProxy; + gproxy.g_properties_changed.connect (this.playlists_proxy_properties_changed); + } + catch (Error e) { + warning ("unable to create mpris plalists proxy: %s", e.message); + return; + } + + Timeout.add (500, () => { this.fetch_playlists (); return false; } ); + } + /* some players (e.g. Spotify) don't follow the spec closely and pass single strings in metadata fields * where an array of string is expected */ static string sanitize_metadata_value (Variant? v) { @@ -215,6 +276,11 @@ public class MediaPlayer: Object { this.update_current_track (metadata); } + void playlists_proxy_properties_changed (DBusProxy proxy, Variant changed_properties, string[] invalidated_properties) { + if (changed_properties.lookup ("PlaylistCount", "u", null)) + this.fetch_playlists (); + } + void update_current_track (Variant metadata) { if (metadata != null) { this.current_track = new Track ( diff --git a/src/service.vala b/src/service.vala index 7f89c66..6a330f2 100644 --- a/src/service.vala +++ b/src/service.vala @@ -266,6 +266,37 @@ public class IndicatorSound.Service { this.settings.set_value ("preferred-media-players", builder.end ()); } + void update_playlists (MediaPlayer player) { + int index = find_player_section (player); + if (index < 0) + return; + + var section = this.menu.get_item_link (index, Menu.LINK_SECTION) as Menu; + + /* if a section has three items, the playlists menu is in it */ + if (section.get_n_items () == 3) + section.remove (2); + + if (!player.is_running) + return; + + var count = player.get_n_playlists (); + if (count == 0) + return; + + var playlists_section = new Menu (); + for (int i = 0; i < count; i++) { + var playlist_id = player.get_playlist_id (i); + playlists_section.append (player.get_playlist_name (i), + @"indicator.play-playlist.$(player.id)::$playlist_id"); + + } + + var submenu = new Menu (); + submenu.append_section (null, playlists_section); + section.append_submenu ("Choose Playlist", submenu); + } + void player_added (MediaPlayer player) { var player_item = new MenuItem (player.name, "indicator." + player.id); player_item.set_attribute ("x-canonical-type", "s", "com.canonical.unity.media-player"); @@ -303,19 +334,28 @@ public class IndicatorSound.Service { prev_action.activate.connect ( () => player.previous () ); this.actions.insert (prev_action); + var playlist_action = new SimpleAction ("play-playlist." + player.id, VariantType.STRING); + playlist_action.activate.connect ( (parameter) => player.activate_playlist_by_name (parameter.get_string ()) ); + this.actions.insert (playlist_action); + player.notify.connect (this.eventually_update_player_actions); + player.playlists_changed.connect (this.update_playlists); + player.notify["is-running"].connect ( () => this.update_playlists (player) ); + update_playlists (player); + this.update_preferred_players (); } /* returns the position in this.menu of the section that's associated with @player */ int find_player_section (MediaPlayer player) { - int n = this.menu.get_n_items (); - for (int i = 0; i < n; i++) { + string action_name = @"indicator.$(player.id)"; + int n = this.menu.get_n_items () -1; + for (int i = 1; i < n; i++) { var section = this.menu.get_item_link (i, Menu.LINK_SECTION); string action; section.get_item_attribute (0, "action", "s", out action); - if (action == player.id) + if (action == action_name) return i; } @@ -327,6 +367,7 @@ public class IndicatorSound.Service { this.actions.remove ("play." + player.id); this.actions.remove ("next." + player.id); this.actions.remove ("previous." + player.id); + this.actions.remove ("play-playlist." + player.id); int index = this.find_player_section (player); if (index >= 0) -- cgit v1.2.3