diff --git a/mopidy/backends/base/__init__.py b/mopidy/backends/base/__init__.py index 491c5b73..f42a042c 100644 --- a/mopidy/backends/base/__init__.py +++ b/mopidy/backends/base/__init__.py @@ -6,7 +6,8 @@ import time from mopidy import settings from mopidy.backends.base.current_playlist import BaseCurrentPlaylistController from mopidy.backends.base.library import BaseLibraryController -from mopidy.backends.base.playback import BasePlaybackController +from mopidy.backends.base.playback import (BasePlaybackController, + BasePlaybackProvider) from mopidy.backends.base.stored_playlists import BaseStoredPlaylistsController from mopidy.frontends.mpd import translator from mopidy.models import Playlist diff --git a/mopidy/backends/base/playback.py b/mopidy/backends/base/playback.py index b7ceeee2..0d4ef52f 100644 --- a/mopidy/backends/base/playback.py +++ b/mopidy/backends/base/playback.py @@ -6,8 +6,10 @@ logger = logging.getLogger('mopidy.backends.base') class BasePlaybackController(object): """ - :param backend: backend the controller is a part of + :param backend: the backend :type backend: :class:`BaseBackend` + :param provider: provider the controller should use + :type provider: instance of :class:`BasePlaybackProvider` """ # pylint: disable = R0902 @@ -54,8 +56,9 @@ class BasePlaybackController(object): #: Playback continues after current song. single = False - def __init__(self, backend): + def __init__(self, backend, provider): self.backend = backend + self.provider = provider self._state = self.STOPPED self._shuffled = [] self._first_shuffle = True @@ -353,18 +356,9 @@ class BasePlaybackController(object): def pause(self): """Pause playback.""" - if self.state == self.PLAYING and self._pause(): + if self.state == self.PLAYING and self.provider.pause(): self.state = self.PAUSED - def _pause(self): - """ - To be overridden by subclass. Implement your backend's pause - functionality here. - - :rtype: :class:`True` if successful, else :class:`False` - """ - raise NotImplementedError - def play(self, cp_track=None, on_error_step=1): """ Play the given track, or if the given track is :class:`None`, play the @@ -391,7 +385,7 @@ class BasePlaybackController(object): self.state = self.STOPPED self.current_cp_track = cp_track self.state = self.PLAYING - if not self._play(cp_track[1]): + if not self.provider.play(cp_track[1]): # Track is not playable if self.random and self._shuffled: self._shuffled.remove(cp_track) @@ -405,18 +399,6 @@ class BasePlaybackController(object): self._trigger_started_playing_event() - def _play(self, track): - """ - To be overridden by subclass. Implement your backend's play - functionality here. - - :param track: the track to play - :type track: :class:`mopidy.models.Track` - :rtype: :class:`True` if successful, else :class:`False` - """ - - raise NotImplementedError - def previous(self): """Play the previous track.""" if self.cp_track_at_previous is None: @@ -428,18 +410,9 @@ class BasePlaybackController(object): def resume(self): """If paused, resume playing the current track.""" - if self.state == self.PAUSED and self._resume(): + if self.state == self.PAUSED and self.provider.resume(): self.state = self.PLAYING - def _resume(self): - """ - To be overridden by subclass. Implement your backend's resume - functionality here. - - :rtype: :class:`True` if successful, else :class:`False` - """ - raise NotImplementedError - def seek(self, time_position): """ Seeks to time position given in milliseconds. @@ -465,18 +438,7 @@ class BasePlaybackController(object): self._play_time_started = self._current_wall_time self._play_time_accumulated = time_position - return self._seek(time_position) - - def _seek(self, time_position): - """ - To be overridden by subclass. Implement your backend's seek - functionality here. - - :param time_position: time position in milliseconds - :type time_position: int - :rtype: :class:`True` if successful, else :class:`False` - """ - raise NotImplementedError + return self.provider.seek(time_position) def stop(self, clear_current_track=False): """ @@ -489,20 +451,11 @@ class BasePlaybackController(object): if self.state == self.STOPPED: return self._trigger_stopped_playing_event() - if self._stop(): + if self.provider.stop(): self.state = self.STOPPED if clear_current_track: self.current_cp_track = None - def _stop(self): - """ - To be overridden by subclass. Implement your backend's stop - functionality here. - - :rtype: :class:`True` if successful, else :class:`False` - """ - raise NotImplementedError - def _trigger_started_playing_event(self): """ Notifies frontends that a track has started playing. @@ -532,3 +485,62 @@ class BasePlaybackController(object): 'track': self.current_track, 'stop_position': self.time_position, }) + + +class BasePlaybackProvider(object): + """ + :param backend: the backend + :type backend: :class:`BaseBackend` + """ + + def __init__(self, backend): + self.backend = backend + + def pause(self): + """ + To be overridden by subclass. Implement your backend's pause + functionality here. + + :rtype: :class:`True` if successful, else :class:`False` + """ + raise NotImplementedError + + def play(self, track): + """ + To be overridden by subclass. Implement your backend's play + functionality here. + + :param track: the track to play + :type track: :class:`mopidy.models.Track` + :rtype: :class:`True` if successful, else :class:`False` + """ + raise NotImplementedError + + def resume(self): + """ + To be overridden by subclass. Implement your backend's resume + functionality here. + + :rtype: :class:`True` if successful, else :class:`False` + """ + raise NotImplementedError + + def seek(self, time_position): + """ + To be overridden by subclass. Implement your backend's seek + functionality here. + + :param time_position: time position in milliseconds + :type time_position: int + :rtype: :class:`True` if successful, else :class:`False` + """ + raise NotImplementedError + + def stop(self): + """ + To be overridden by subclass. Implement your backend's stop + functionality here. + + :rtype: :class:`True` if successful, else :class:`False` + """ + raise NotImplementedError diff --git a/mopidy/backends/dummy/__init__.py b/mopidy/backends/dummy/__init__.py index 62cbd7e2..7a2788b7 100644 --- a/mopidy/backends/dummy/__init__.py +++ b/mopidy/backends/dummy/__init__.py @@ -1,5 +1,5 @@ from mopidy.backends.base import (BaseBackend, BaseCurrentPlaylistController, - BasePlaybackController, BaseLibraryController, + BasePlaybackController, BasePlaybackProvider, BaseLibraryController, BaseStoredPlaylistsController) from mopidy.models import Playlist @@ -13,10 +13,17 @@ class DummyBackend(BaseBackend): def __init__(self, *args, **kwargs): super(DummyBackend, self).__init__(*args, **kwargs) + self.current_playlist = DummyCurrentPlaylistController(backend=self) + self.library = DummyLibraryController(backend=self) - self.playback = DummyPlaybackController(backend=self) + + playback_provider = DummyPlaybackProvider(backend=self) + self.playback = DummyPlaybackController(backend=self, + provider=playback_provider) + self.stored_playlists = DummyStoredPlaylistsController(backend=self) + self.uri_handlers = [u'dummy:'] @@ -43,30 +50,6 @@ class DummyLibraryController(BaseLibraryController): class DummyPlaybackController(BasePlaybackController): - def _next(self, track): - """Pass None as track to force failure""" - return track is not None - - def _pause(self): - return True - - def _play(self, track): - """Pass None as track to force failure""" - return track is not None - - def _previous(self, track): - """Pass None as track to force failure""" - return track is not None - - def _resume(self): - return True - - def _seek(self, time_position): - return True - - def _stop(self): - return True - def _trigger_started_playing_event(self): pass # noop @@ -74,6 +57,24 @@ class DummyPlaybackController(BasePlaybackController): pass # noop +class DummyPlaybackProvider(BasePlaybackProvider): + def pause(self): + return True + + def play(self, track): + """Pass None as track to force failure""" + return track is not None + + def resume(self): + return True + + def seek(self, time_position): + return True + + def stop(self): + return True + + class DummyStoredPlaylistsController(BaseStoredPlaylistsController): _playlists = [] diff --git a/mopidy/backends/libspotify/__init__.py b/mopidy/backends/libspotify/__init__.py index 223d9968..c067cb67 100644 --- a/mopidy/backends/libspotify/__init__.py +++ b/mopidy/backends/libspotify/__init__.py @@ -1,7 +1,8 @@ import logging from mopidy import settings -from mopidy.backends.base import BaseBackend, BaseCurrentPlaylistController +from mopidy.backends.base import (BaseBackend, BaseCurrentPlaylistController, + BasePlaybackController) logger = logging.getLogger('mopidy.backends.libspotify') @@ -34,17 +35,24 @@ class LibspotifyBackend(BaseBackend): def __init__(self, *args, **kwargs): from .library import LibspotifyLibraryController - from .playback import LibspotifyPlaybackController + from .playback import LibspotifyPlaybackProvider from .stored_playlists import LibspotifyStoredPlaylistsController super(LibspotifyBackend, self).__init__(*args, **kwargs) self.current_playlist = BaseCurrentPlaylistController(backend=self) + self.library = LibspotifyLibraryController(backend=self) - self.playback = LibspotifyPlaybackController(backend=self) + + playback_provider = LibspotifyPlaybackProvider(backend=self) + self.playback = BasePlaybackController(backend=self, + provider=playback_provider) + self.stored_playlists = LibspotifyStoredPlaylistsController( backend=self) + self.uri_handlers = [u'spotify:', u'http://open.spotify.com/'] + self.spotify = self._connect() def _connect(self): diff --git a/mopidy/backends/libspotify/playback.py b/mopidy/backends/libspotify/playback.py index 39c56bf6..29409ff4 100644 --- a/mopidy/backends/libspotify/playback.py +++ b/mopidy/backends/libspotify/playback.py @@ -2,17 +2,17 @@ import logging from spotify import Link, SpotifyError -from mopidy.backends.base import BasePlaybackController +from mopidy.backends.base import BasePlaybackProvider logger = logging.getLogger('mopidy.backends.libspotify.playback') -class LibspotifyPlaybackController(BasePlaybackController): - def _pause(self): +class LibspotifyPlaybackProvider(BasePlaybackProvider): + def pause(self): return self.backend.output.set_state('PAUSED') - def _play(self, track): + def play(self, track): self.backend.output.set_state('READY') - if self.state == self.PLAYING: + if self.backend.playback.state == self.backend.playback.PLAYING: self.backend.spotify.session.play(0) if track.uri is None: return False @@ -26,16 +26,16 @@ class LibspotifyPlaybackController(BasePlaybackController): logger.warning('Play %s failed: %s', track.uri, e) return False - def _resume(self): - return self._seek(self.time_position) + def resume(self): + return self.seek(self.backend.playback.time_position) - def _seek(self, time_position): + def seek(self, time_position): self.backend.output.set_state('READY') self.backend.spotify.session.seek(time_position) self.backend.output.set_state('PLAYING') return True - def _stop(self): + def stop(self): result = self.backend.output.set_state('READY') self.backend.spotify.session.play(0) return result diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index 04761e17..4ad8947b 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -5,9 +5,10 @@ import os import shutil from mopidy import settings -from mopidy.backends.base import (BaseBackend, BaseLibraryController, - BaseStoredPlaylistsController, BaseCurrentPlaylistController, - BasePlaybackController) +from mopidy.backends.base import (BaseBackend, + BaseCurrentPlaylistController, BaseLibraryController, + BasePlaybackController, BasePlaybackProvider, + BaseStoredPlaylistsController) from mopidy.models import Playlist, Track, Album from mopidy.utils.process import pickle_connection @@ -31,41 +32,51 @@ class LocalBackend(BaseBackend): def __init__(self, *args, **kwargs): super(LocalBackend, self).__init__(*args, **kwargs) - self.library = LocalLibraryController(self) - self.stored_playlists = LocalStoredPlaylistsController(self) - self.current_playlist = BaseCurrentPlaylistController(self) - self.playback = LocalPlaybackController(self) + self.library = LocalLibraryController(backend=self) + + self.stored_playlists = LocalStoredPlaylistsController(backend=self) + + self.current_playlist = BaseCurrentPlaylistController(backend=self) + + playback_provider = LocalPlaybackProvider(backend=self) + self.playback = LocalPlaybackController(backend=self, + provider=playback_provider) + self.uri_handlers = [u'file://'] class LocalPlaybackController(BasePlaybackController): - def __init__(self, backend): - super(LocalPlaybackController, self).__init__(backend) + def __init__(self, *args, **kwargs): + super(LocalPlaybackController, self).__init__(*args, **kwargs) + + # XXX Why do we call stop()? Is it to set GStreamer state to 'READY'? self.stop() - def _play(self, track): - return self.backend.output.play_uri(track.uri) - - def _stop(self): - return self.backend.output.set_state('READY') - - def _pause(self): - return self.backend.output.set_state('PAUSED') - - def _resume(self): - return self.backend.output.set_state('PLAYING') - - def _seek(self, time_position): - return self.backend.output.set_position(time_position) - @property def time_position(self): return self.backend.output.get_position() +class LocalPlaybackProvider(BasePlaybackProvider): + def pause(self): + return self.backend.output.set_state('PAUSED') + + def play(self, track): + return self.backend.output.play_uri(track.uri) + + def resume(self): + return self.backend.output.set_state('PLAYING') + + def seek(self, time_position): + return self.backend.output.set_position(time_position) + + def stop(self): + return self.backend.output.set_state('READY') + + class LocalStoredPlaylistsController(BaseStoredPlaylistsController): - def __init__(self, *args): - super(LocalStoredPlaylistsController, self).__init__(*args) + def __init__(self, *args, **kwargs): + super(LocalStoredPlaylistsController, self).__init__(*args, **kwargs) self._folder = os.path.expanduser(settings.LOCAL_PLAYLIST_FOLDER) self.refresh() @@ -137,8 +148,8 @@ class LocalStoredPlaylistsController(BaseStoredPlaylistsController): class LocalLibraryController(BaseLibraryController): - def __init__(self, backend): - super(LocalLibraryController, self).__init__(backend) + def __init__(self, *args, **kwargs): + super(LocalLibraryController, self).__init__(*args, **kwargs) self._uri_mapping = {} self.refresh() diff --git a/tests/backends/base/playback.py b/tests/backends/base/playback.py index 4caaf44b..7b6efe7a 100644 --- a/tests/backends/base/playback.py +++ b/tests/backends/base/playback.py @@ -104,8 +104,8 @@ class BasePlaybackControllerTest(object): @populate_playlist def test_play_skips_to_next_track_on_failure(self): - # If _play() returns False, it is a failure. - self.playback._play = lambda track: track != self.tracks[0] + # If provider.play() returns False, it is a failure. + self.playback.provider.play = lambda track: track != self.tracks[0] self.playback.play() self.assertNotEqual(self.playback.current_track, self.tracks[0]) self.assertEqual(self.playback.current_track, self.tracks[1]) @@ -164,8 +164,8 @@ class BasePlaybackControllerTest(object): @populate_playlist def test_previous_skips_to_previous_track_on_failure(self): - # If _play() returns False, it is a failure. - self.playback._play = lambda track: track != self.tracks[1] + # If provider.play() returns False, it is a failure. + self.playback.provider.play = lambda track: track != self.tracks[1] self.playback.play(self.current_playlist.cp_tracks[2]) self.assertEqual(self.playback.current_track, self.tracks[2]) self.playback.previous() @@ -228,8 +228,8 @@ class BasePlaybackControllerTest(object): @populate_playlist def test_next_skips_to_next_track_on_failure(self): - # If _play() returns False, it is a failure. - self.playback._play = lambda track: track != self.tracks[1] + # If provider.play() returns False, it is a failure. + self.playback.provider.play = lambda track: track != self.tracks[1] self.playback.play() self.assertEqual(self.playback.current_track, self.tracks[0]) self.playback.next() @@ -364,8 +364,8 @@ class BasePlaybackControllerTest(object): @populate_playlist def test_end_of_track_skips_to_next_track_on_failure(self): - # If _play() returns False, it is a failure. - self.playback._play = lambda track: track != self.tracks[1] + # If provider.play() returns False, it is a failure. + self.playback.provider.play = lambda track: track != self.tracks[1] self.playback.play() self.assertEqual(self.playback.current_track, self.tracks[0]) self.playback.on_end_of_track()