diff --git a/docs/changes.rst b/docs/changes.rst index 97199291..09116519 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -10,6 +10,9 @@ v0.11.0 (in development) **Spotify backend** +- Add :attr:`mopidy.settings.SPOTIFY_TIMEOUT` setting which allows you to + control how long we should wait before giving up on Spotify searches, etc. + - Add support for looking up albums, artists, and playlists by URI in addition to tracks. (Fixes: :issue:`67`) diff --git a/mopidy/audio/actor.py b/mopidy/audio/actor.py index 3910ee80..1b6c79b3 100644 --- a/mopidy/audio/actor.py +++ b/mopidy/audio/actor.py @@ -41,6 +41,7 @@ class Audio(pykka.ThreadingActor): self._playbin = None self._mixer = None self._mixer_track = None + self._mixer_scale = None self._software_mixing = False self._appsrc = None self._volume_set = None @@ -150,6 +151,8 @@ class Audio(pykka.ThreadingActor): self._mixer = mixer self._mixer_track = track + self._mixer_scale = ( + self._mixer_track.min_volume, self._mixer_track.max_volume) logger.info( 'Audio mixer set to "%s" using track "%s"', mixer.get_factory().get_name(), track.label) @@ -390,15 +393,18 @@ class Audio(pykka.ThreadingActor): avg_volume = float(sum(volumes)) / len(volumes) internal_scale = (0, 100) - mixer_scale = ( - self._mixer_track.min_volume, self._mixer_track.max_volume) - if self._volume_set is not None and self._rescale(self._volume_set, - old=internal_scale, new=mixer_scale) == avg_volume: + if self._volume_set is not None: + volume_set_on_mixer_scale = self._rescale( + self._volume_set, old=internal_scale, new=self._mixer_scale) + else: + volume_set_on_mixer_scale = None + + if volume_set_on_mixer_scale == avg_volume: return self._volume_set else: return self._rescale( - avg_volume, old=mixer_scale, new=internal_scale) + avg_volume, old=self._mixer_scale, new=internal_scale) def set_volume(self, volume): """ @@ -418,10 +424,9 @@ class Audio(pykka.ThreadingActor): self._volume_set = volume internal_scale = (0, 100) - mixer_scale = ( - self._mixer_track.min_volume, self._mixer_track.max_volume) - volume = self._rescale(volume, old=internal_scale, new=mixer_scale) + volume = self._rescale( + volume, old=internal_scale, new=self._mixer_scale) volumes = (volume,) * self._mixer_track.num_channels self._mixer.set_volume(self._mixer_track, volumes) diff --git a/mopidy/backends/spotify/library.py b/mopidy/backends/spotify/library.py index 25c58a17..d171ecae 100644 --- a/mopidy/backends/spotify/library.py +++ b/mopidy/backends/spotify/library.py @@ -1,11 +1,12 @@ from __future__ import unicode_literals import logging -import Queue import time +import pykka from spotify import Link, SpotifyError +from mopidy import settings from mopidy.backends import base from mopidy.models import Track @@ -114,14 +115,48 @@ class SpotifyLibraryProvider(base.BaseLibraryProvider): def search(self, **query): if not query: - # Since we can't search for the entire Spotify library, we return - # all tracks in the playlists when the query is empty. - tracks = [] - for playlist in self.backend.playlists.playlists: - tracks += playlist.tracks - return tracks + return self._get_all_tracks() + + spotify_query = self._translate_search_query(query) + logger.debug('Spotify search query: %s' % spotify_query) + + future = pykka.ThreadingFuture() + + def callback(results, userdata=None): + # TODO Include results from results.albums(), etc. too + # TODO Consider launching a second search if results.total_tracks() + # is larger than len(results.tracks()) + tracks = [ + translator.to_mopidy_track(t) for t in results.tracks()] + future.set(tracks) + + if not self.backend.spotify.connected.wait(settings.SPOTIFY_TIMEOUT): + logger.debug('Not connected: Spotify search cancelled') + return [] + + self.backend.spotify.session.search( + spotify_query, callback, + track_count=100, album_count=0, artist_count=0) + + try: + return future.get(timeout=settings.SPOTIFY_TIMEOUT) + except pykka.Timeout: + logger.debug( + 'Timeout: Spotify search did not return in %ds', + settings.SPOTIFY_TIMEOUT) + return [] + + def _get_all_tracks(self): + # Since we can't search for the entire Spotify library, we return + # all tracks in the playlists when the query is empty. + tracks = [] + for playlist in self.backend.playlists.playlists: + tracks += playlist.tracks + return tracks + + def _translate_search_query(self, mopidy_query): spotify_query = [] - for (field, values) in query.iteritems(): + for (field, values) in mopidy_query.iteritems(): if field == 'uri': tracks = [] for value in values: @@ -144,10 +179,4 @@ class SpotifyLibraryProvider(base.BaseLibraryProvider): else: spotify_query.append('%s:"%s"' % (field, value)) spotify_query = ' '.join(spotify_query) - logger.debug('Spotify search query: %s' % spotify_query) - queue = Queue.Queue() - self.backend.spotify.search(spotify_query, queue) - try: - return queue.get(timeout=3) # XXX What is an reasonable timeout? - except Queue.Empty: - return [] + return spotify_query diff --git a/mopidy/backends/spotify/session_manager.py b/mopidy/backends/spotify/session_manager.py index 288c61f2..f2631406 100644 --- a/mopidy/backends/spotify/session_manager.py +++ b/mopidy/backends/spotify/session_manager.py @@ -165,19 +165,6 @@ class SpotifySessionManager(process.BaseThread, PyspotifySessionManager): logger.info('Loaded %d Spotify playlist(s)', len(playlists)) BackendListener.send('playlists_loaded') - def search(self, query, queue): - """Search method used by Mopidy backend""" - def callback(results, userdata=None): - # TODO Include results from results.albums(), etc. too - # TODO Consider launching a second search if results.total_tracks() - # is larger than len(results.tracks()) - tracks = [ - translator.to_mopidy_track(t) for t in results.tracks()] - queue.put(tracks) - self.connected.wait() - self.session.search( - query, callback, track_count=100, album_count=0, artist_count=0) - def logout(self): """Log out from spotify""" logger.debug('Logging out from Spotify') diff --git a/mopidy/settings.py b/mopidy/settings.py index 0a71ccfa..0a272035 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -282,3 +282,12 @@ SPOTIFY_PROXY_USERNAME = None #: #: SPOTIFY_PROXY_PASSWORD = None SPOTIFY_PROXY_PASSWORD = None + +#: Max number of seconds to wait for Spotify operations to complete. +#: +#: Used by :mod:`mopidy.backends.spotify` +#: +#: Default:: +#: +#: SPOTIFY_TIMEOUT = 10 +SPOTIFY_TIMEOUT = 10