nuvola-player-devel team mailing list archive
-
nuvola-player-devel team
-
Mailing list archive
-
Message #00156
[Merge] lp:~rameshdharan/nuvola-player/songza into lp:nuvola-player
Ramesh Dharan has proposed merging lp:~rameshdharan/nuvola-player/songza into lp:nuvola-player.
Requested reviews:
Jiří Janoušek (fenryxo)
For more details, see:
https://code.launchpad.net/~rameshdharan/nuvola-player/songza/+merge/230325
Add Songza service integration.
--
https://code.launchpad.net/~rameshdharan/nuvola-player/songza/+merge/230325
Your team Nuvola Player Development is subscribed to branch lp:nuvola-player.
=== added directory 'data/nuvolaplayer/services/songza'
=== added file 'data/nuvolaplayer/services/songza/description.html'
--- data/nuvolaplayer/services/songza/description.html 1970-01-01 00:00:00 +0000
+++ data/nuvolaplayer/services/songza/description.html 2014-08-11 14:57:25 +0000
@@ -0,0 +1,10 @@
+<p><strong>Songza</strong> is a free music streaming and recommendation
+service.</p>
+<p>
+<em>Source:
+<a href="http://songza.com">Official website</a></em>
+<a href="http://en.wikipedia.org/wiki/Songza">Songza on Wikipedia</a>,
+</p>
+<h2>Country Availability</h2>
+<p><strong>Songza</strong> is currently only available in the United States and
+Canada.</p>
=== added file 'data/nuvolaplayer/services/songza/integration.js'
--- data/nuvolaplayer/services/songza/integration.js 1970-01-01 00:00:00 +0000
+++ data/nuvolaplayer/services/songza/integration.js 2014-08-11 14:57:25 +0000
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2014 Google Inc. All Rights Reserved.
+ * Author: dharan@xxxxxxxxxx (Ramesh Dharan)
+ *
+ * Copyright 2011-2013 Jiří Janoušek <janousek.jiri@xxxxxxxxx>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* Anonymous function is used not to pollute environment */
+(function(Nuvola)
+{
+ /**
+ * Creates integration binded to Nuvola JS API
+ */
+ var Integration = function()
+ {
+ /* For debug output */
+ this.name = "Songza";
+
+ /* Overwrite default command function */
+ Nuvola.onMessageReceived = Nuvola.bind(this, this.messageHandler);
+
+ require(["songza/app", ], Nuvola.bind(this, function(App) {
+ App.postInit(Nuvola.bind(this, function() {
+ this.songza = App.getInstance();
+ }));
+ }));
+
+ /* Let's run */
+ this.state = Nuvola.STATE_NONE;
+ this.can_prev = null;
+ this.can_next = null;
+ this.can_thumbs_up = null;
+ this.can_thumbs_down = null;
+
+ this.update();
+ };
+
+ /**
+ * Updates current playback state
+ */
+ Integration.prototype.update = function()
+ {
+ // Default values
+ var state = Nuvola.STATE_NONE;
+ var can_prev = false;
+ var can_next = false;
+ var can_thumbs_up = false;
+ var can_thumbs_down = false;
+
+ var song = null;
+ var artist = null;
+ var album = null;
+ var art = null;
+
+ try {
+ var player = this.songza.getPlayer();
+ var manager = player.getManager();
+ var songContext = player.songContext(player.findSongOrCurrent());
+
+ state = player.isPlaying() ? Nuvola.STATE_PLAYING :
+ player.isPaused() ? Nuvola.STATE_PAUSED : state;
+
+ can_next = manager.canSkip();
+
+ song = songContext.song.title;
+ artist = songContext.song.artist;
+ album = songContext.album;
+ art = songContext.song.image_url;
+
+ can_thumbs_up = true;
+ can_thumbs_down = true;
+
+ } catch(e) {
+ console.debug(this.name + ": could not update song state");
+ }
+
+ // Save state
+ this.state = state;
+
+ // Submit data to Nuvola backend
+ Nuvola.updateSong(song, artist, album, art, state);
+
+ if (this.can_next !== can_next) {
+ this.can_next = can_next;
+ Nuvola.updateAction(Nuvola.ACTION_NEXT_SONG, can_next);
+ }
+
+ if (this.can_thumbs_up !== can_thumbs_up) {
+ this.can_thumbs_up = can_thumbs_up;
+ Nuvola.updateAction(Nuvola.ACTION_THUMBS_UP, can_thumbs_up);
+ }
+
+ if (this.can_thumbs_down !== can_thumbs_down) {
+ this.can_thumbs_down = can_thumbs_down;
+ Nuvola.updateAction(Nuvola.ACTION_THUMBS_DOWN, can_thumbs_down);
+ }
+
+ // Schedule update
+ setTimeout(Nuvola.bind(this, this.update), 500);
+ };
+
+ /**
+ * Message handler
+ * @param cmd command to execute
+ */
+ Integration.prototype.messageHandler = function(cmd)
+ {
+ /* Respond to user actions */
+ try
+ {
+ var player = this.songza.getPlayer();
+ var song = player.findSongOrCurrent();
+ var stream = song.get("streamSong");
+
+ switch (cmd)
+ {
+ case Nuvola.ACTION_PLAY:
+ player.play();
+ break;
+ case Nuvola.ACTION_PAUSE:
+ player.pause();
+ break;
+ case Nuvola.ACTION_TOGGLE_PLAY:
+ player.play_or_pause();
+ break;
+ case Nuvola.ACTION_NEXT_SONG:
+ player.skip();
+ break;
+
+ /*
+ * XXX The thumbs up/down action implementations are potentially
+ * fragile. I couldn't figure out how to make player.voteUp() work,
+ * it tries to resolve the target using a jQuery selector in a way
+ * that didn't work inside this closure.
+ *
+ * So I just mimicked what it was doing, but of course if the
+ * implementation changes this could break. --rameshdharan
+ */
+ case Nuvola.ACTION_THUMBS_UP:
+ song.set({vote : "up"});
+ stream.voteUp();
+ player.songTrigger("vote-up");
+ break;
+ case Nuvola.ACTION_THUMBS_DOWN:
+ song.set({vote : "down"});
+ stream.voteDown();
+ player.songTrigger("vote-down");
+ break;
+
+ default:
+ // Other commands are not supported
+ throw {"message": "Not supported."};
+ }
+ console.log(this.name + ": comand '" + cmd + "' executed.");
+ }
+ catch (e)
+ {
+ // Older API expected exception to be a string.
+ throw (this.name + ": " + e.message);
+ }
+ };
+
+ /* Store reference */
+ Nuvola.integration = new Integration(); // Singleton
+
+ // Immediately call the anonymous function with Nuvola JS API main object as an argument.
+ // Note that "this" is set to the Nuvola JS API main object.
+})(this);
=== added file 'data/nuvolaplayer/services/songza/metadata.conf'
--- data/nuvolaplayer/services/songza/metadata.conf 1970-01-01 00:00:00 +0000
+++ data/nuvolaplayer/services/songza/metadata.conf 2014-08-11 14:57:25 +0000
@@ -0,0 +1,10 @@
+name = Songza
+home_page = https://songza.com/
+sandbox_pattern = https?://songza.com/
+maintainer_name = Ramesh Dharan
+maintainer_link = https://launchpad.net/~rameshdharan
+version = 1
+version_minor = 0
+flash_plugin = no
+requirements_specified = yes
+api_major = 2
=== modified file 'src/nuvola/extensions/lastfm.vala'
--- src/nuvola/extensions/lastfm.vala 2014-07-26 12:38:38 +0000
+++ src/nuvola/extensions/lastfm.vala 2014-08-11 14:57:25 +0000
@@ -150,6 +150,7 @@
private weak Diorite.ActionGroups action_groups;
private string? current_service_id = null;
private uint scrobble_track_source = 0;
+ private uint now_playing_source = 0;
private string? song = null;
private string? artist = null;
private int64 timestamp = 0;
@@ -188,8 +189,9 @@
var service = web_backend.service;
current_service_id = service == null ? null : service.id;
on_song_changed(player.song, player.artist, player.album, player.album_art);
- player.song_changed.connect_after(on_song_changed);
- player.notify["playback-state"].connect_after(on_playback_state_changed);
+ on_song_changed_after(player.song, player.artist, player.album, player.album_art);
+ player.song_changed.connect(on_song_changed);
+ player.song_changed.connect_after(on_song_changed_after);
web_backend.notify["service"].connect_after(on_service_changed);
foreach (var scrobbler in scrobblers)
scrobbler.notify["has-session"].connect_after(on_has_session_changed);
@@ -227,8 +229,8 @@
}
scrobblers = {};
web_backend.notify["service"].disconnect(on_service_changed);
- player.notify["playback-state"].disconnect(on_playback_state_changed);
player.song_changed.disconnect(on_song_changed);
+ player.song_changed.disconnect(on_song_changed_after);
var timestamp = get_timestamp();
if (can_scrobble && this.song != null && this.artist != null)
{
@@ -242,6 +244,12 @@
Source.remove(scrobble_track_source);
scrobble_track_source = 0;
}
+
+ if (now_playing_source != 0)
+ {
+ Source.remove(now_playing_source);
+ now_playing_source = 0;
+ }
}
/**
@@ -400,6 +408,12 @@
scrobble_track_source = 0;
}
+ if (now_playing_source != 0)
+ {
+ Source.remove(now_playing_source);
+ now_playing_source = 0;
+ }
+
if (song == null || artist == null)
return;
@@ -416,21 +430,25 @@
this.timestamp = timestamp;
can_scrobble = false;
- update_now_playing();
+ update_now_playing.begin(song, artist, (obj, res) =>
+ {
+ update_now_playing.end(res);
+ });
scrobble_track_source = Timeout.add_seconds(SCROBBLE_SONG_DELAY, enable_scrobbling_cb);
-
+ now_playing_source = Timeout.add_seconds(NOW_PLAYING_TIMEOUT, update_now_playing_cb);
+ }
+
+ /**
+ * Callback for song change. Queues "now playing" update and track scrobbling.
+ */
+ private void on_song_changed_after(string? song, string? artist, string? album, string? album_art)
+ {
update_actions.begin(song, artist, (obj, res) =>
{
update_actions.end(res);
});
}
-
- private void on_playback_state_changed(GLib.Object o, ParamSpec p)
- {
- update_now_playing();
- }
-
private bool enable_scrobbling_cb()
{
if (scrobble_track_source != 0)
@@ -521,19 +539,29 @@
}
}
- private async void update_now_playing()
- {
- if (current_service_id == null || player.playback_state != STATE_PLAYING
- || player.artist == null || player.song == null)
+ private bool update_now_playing_cb()
+ {
+ if (now_playing_source == 0 || song == null || artist == null)
+ return false;
+
+ update_now_playing.begin(song, artist, (obj, res) =>
+ {
+ update_now_playing.end(res);
+ });
+ return true;
+ }
+
+ private async void update_now_playing(string song, string artist)
+ {
+ if (current_service_id == null || player.artist != artist || player.song != song)
return;
-
foreach (var scrobbler in scrobblers)
{
if (scrobbler.is_service_enabled(current_service_id))
{
try
{
- yield scrobbler.update_now_playing(player.song, player.artist);
+ yield scrobbler.update_now_playing(song, artist);
}
catch (Error e)
{