diff --git a/docs/changes.rst b/docs/changes.rst index 12028a17..76f01610 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -35,12 +35,14 @@ greatly improved MPD client support. - Added new :mod:`mopidy.mixers.GStreamerSoftwareMixer` which now is the default mixer on all platforms. - New setting ``MIXER_MAX_VOLUME`` for capping the maximum output volume. +- If failing to play a track, playback will skip to the next track. - MPD frontend: - Relocate from :mod:`mopidy.mpd` to :mod:`mopidy.frontends.mpd`. - Split gigantic protocol implementation into eleven modules. - Search improvements, including support for multi-word search. - - Fixed ``play "-1"`` and ``playid "-1"`` behaviour when playlist is empty. + - Fixed ``play "-1"`` and ``playid "-1"`` behaviour when playlist is empty + or when a current track is set. - Support ``plchanges "-1"`` to work better with MPDroid. - Support ``pause`` without arguments to work better with MPDroid. - Support ``plchanges``, ``play``, ``consume``, ``random``, ``repeat``, and @@ -50,6 +52,7 @@ greatly improved MPD client support. - Implement ``seek`` and ``seekid``. - Fix ``playlistfind`` output so the correct song is played when playing songs directly from search results in GMPC. + - Fix ``load`` so that one can append a playlist to the current playlist. - Backends: @@ -73,7 +76,10 @@ greatly improved MPD client support. - :meth:`mopidy.backends.base.BaseBackend()` now accepts an ``output_queue`` which it can use to send messages (i.e. audio data) to the output process. - + - :meth:`mopidy.backends.base.BaseCurrentPlaylistController.load()` now + appends to the existing playlist. Use + :meth:`mopidy.backends.base.BaseCurrentPlaylistController.clear()` if you + want to clear it first. 0.1.0a3 (2010-08-03) diff --git a/docs/installation/libspotify.rst b/docs/installation/libspotify.rst index 911bf39e..629998b9 100644 --- a/docs/installation/libspotify.rst +++ b/docs/installation/libspotify.rst @@ -2,15 +2,21 @@ libspotify installation *********************** -We are working on a -`libspotify `_ backend. -To use the libspotify backend you must install libspotify and -`pyspotify `_. +Mopidy uses `libspotify +`_ for playing music from +the Spotify music service. To use :mod:`mopidy.backends.libspotify` you must +install libspotify and `pyspotify `_. .. warning:: - This backend requires a Spotify premium account, and it requires you to get - an application key from Spotify before use. + This backend requires a `Spotify premium account + `_. + +.. note:: + + This product uses SPOTIFY CORE but is not endorsed, certified or otherwise + approved in any way by Spotify. Spotify is the registered trade mark of the + Spotify Group. Installing libspotify on Linux diff --git a/docs/licenses.rst b/docs/licenses.rst index c7bf9433..c3a13904 100644 --- a/docs/licenses.rst +++ b/docs/licenses.rst @@ -2,7 +2,7 @@ Licenses ******** -For a list of contributors, see :ref:`authors`. For details on who have +For a list of contributors, see :doc:`authors`. For details on who have contributed what, please refer to our git repository. Source code license diff --git a/mopidy/backends/base/current_playlist.py b/mopidy/backends/base/current_playlist.py index fc17bbee..8aefe8cd 100644 --- a/mopidy/backends/base/current_playlist.py +++ b/mopidy/backends/base/current_playlist.py @@ -66,10 +66,9 @@ class BaseCurrentPlaylistController(object): def clear(self): """Clear the current playlist.""" - self.backend.playback.stop() - self.backend.playback.current_cp_track = None self._cp_tracks = [] self.version += 1 + self.backend.playback.on_current_playlist_change() def get(self, **criteria): """ @@ -107,16 +106,15 @@ class BaseCurrentPlaylistController(object): def load(self, tracks): """ - Replace the tracks in the current playlist with the given tracks. + Append the given tracks to the current playlist. :param tracks: tracks to load :type tracks: list of :class:`mopidy.models.Track` """ - self._cp_tracks = [] self.version += 1 for track in tracks: self.add(track) - self.backend.playback.new_playlist_loaded_callback() + self.backend.playback.on_current_playlist_change() def move(self, start, end, to_position): """ @@ -148,6 +146,7 @@ class BaseCurrentPlaylistController(object): to_position += 1 self._cp_tracks = new_cp_tracks self.version += 1 + self.backend.playback.on_current_playlist_change() def remove(self, **criteria): """ @@ -192,6 +191,7 @@ class BaseCurrentPlaylistController(object): random.shuffle(shuffled) self._cp_tracks = before + shuffled + after self.version += 1 + self.backend.playback.on_current_playlist_change() def mpd_format(self, *args, **kwargs): """Not a part of the generic backend API.""" diff --git a/mopidy/backends/base/playback.py b/mopidy/backends/base/playback.py index 08b7932d..973743e5 100644 --- a/mopidy/backends/base/playback.py +++ b/mopidy/backends/base/playback.py @@ -287,11 +287,9 @@ class BasePlaybackController(object): Typically called by :class:`mopidy.process.CoreProcess` after a message from a library thread is received. """ - next_cp_track = self.cp_track_at_eot - if next_cp_track is not None and self._next(next_cp_track[1]): - original_cp_track = self.current_cp_track - self.current_cp_track = next_cp_track - self.state = self.PLAYING + original_cp_track = self.current_cp_track + if self.cp_track_at_eot: + self.play(self.cp_track_at_eot) if self.consume: self.backend.current_playlist.remove(cpid=original_cp_track[0]) @@ -302,48 +300,43 @@ class BasePlaybackController(object): self.stop() self.current_cp_track = None - def new_playlist_loaded_callback(self): + def on_current_playlist_change(self): """ - Tell the playback controller that a new playlist has been loaded. + Tell the playback controller that the current playlist has changed. - Typically called by :class:`mopidy.process.CoreProcess` after a message - from a library thread is received. + Used by :class:`mopidy.backends.base.BaseCurrentPlaylistController`. """ - self.current_cp_track = None self._first_shuffle = True self._shuffled = [] - if self.state == self.PLAYING: - if len(self.backend.current_playlist.tracks) > 0: - self.play() - else: - self.stop() - elif self.state == self.PAUSED: + if not self.backend.current_playlist.cp_tracks: + self.stop() + self.current_cp_track = None + elif (self.current_cp_track not in + self.backend.current_playlist.cp_tracks): + self.current_cp_track = None self.stop() def next(self): """Play the next track.""" - original_cp_track = self.current_cp_track - if self.state == self.STOPPED: return - elif self.cp_track_at_next is not None and self._next(self.next_track): - self.current_cp_track = self.cp_track_at_next - self.state = self.PLAYING - elif self.cp_track_at_next is None: + + original_cp_track = self.current_cp_track + if self.cp_track_at_next: + self.play(self.cp_track_at_next) + else: self.stop() self.current_cp_track = None - # FIXME handle in play aswell? + # FIXME This should only be applied when reaching end of track, and not + # when pressing "next" if self.consume: self.backend.current_playlist.remove(cpid=original_cp_track[0]) if self.random and self.current_cp_track in self._shuffled: self._shuffled.remove(self.current_cp_track) - def _next(self, track): - return self._play(track) - def pause(self): """Pause playback.""" if self.state == self.PLAYING and self._pause(): @@ -352,13 +345,16 @@ class BasePlaybackController(object): def _pause(self): raise NotImplementedError - def play(self, cp_track=None): + def play(self, cp_track=None, on_error_step=1): """ Play the given track or the currently active track. :param cp_track: track to play :type cp_track: two-tuple (CPID integer, :class:`mopidy.models.Track`) or :class:`None` + :param on_error_step: direction to step at play error, 1 for next + track (default), -1 for previous track + :type on_error_step: int, -1 or 1 """ if cp_track is not None: @@ -368,13 +364,14 @@ class BasePlaybackController(object): if self.state == self.PAUSED and cp_track is None: self.resume() - elif cp_track is not None and self._play(cp_track[1]): + elif cp_track is not None: self.current_cp_track = cp_track self.state = self.PLAYING - - # TODO Do something sensible when _play() returns False, like calling - # next(). Adding this todo instead of just implementing it as I want a - # test case first. + if not self._play(cp_track[1]): + if on_error_step == 1: + self.next() + elif on_error_step == -1: + self.previous() if self.random and self.current_cp_track in self._shuffled: self._shuffled.remove(self.current_cp_track) @@ -384,14 +381,11 @@ class BasePlaybackController(object): def previous(self): """Play the previous track.""" - if (self.previous_cp_track is not None - and self.state != self.STOPPED - and self._previous(self.previous_track)): - self.current_cp_track = self.previous_cp_track - self.state = self.PLAYING - - def _previous(self, track): - return self._play(track) + if self.previous_cp_track is None: + return + if self.state == self.STOPPED: + return + self.play(self.previous_cp_track, on_error_step=-1) def resume(self): """If paused, resume playing the current track.""" diff --git a/mopidy/backends/libspotify/__init__.py b/mopidy/backends/libspotify/__init__.py index 7a971bc5..f00ec1f0 100644 --- a/mopidy/backends/libspotify/__init__.py +++ b/mopidy/backends/libspotify/__init__.py @@ -9,14 +9,18 @@ ENCODING = 'utf-8' class LibspotifyBackend(BaseBackend): """ - A Spotify backend which uses the official `libspotify library - `_. - - `pyspotify `_ is the Python bindings - for libspotify. It got no documentation, but multiple examples are - available. Like libspotify, pyspotify's calls are mostly asynchronous. + A `Spotify `_ backend which uses the official + `libspotify `_ + library and the `pyspotify `_ Python + bindings for libspotify. **Issues:** http://github.com/jodal/mopidy/issues/labels/backend-libspotify + + .. note:: + + This product uses SPOTIFY(R) CORE but is not endorsed, certified or + otherwise approved in any way by Spotify. Spotify is the registered + trade mark of the Spotify Group. """ # Imports inside methods are to prevent loading of __init__.py to fail on @@ -40,6 +44,7 @@ class LibspotifyBackend(BaseBackend): def _connect(self): from .session_manager import LibspotifySessionManager + logger.info(u'Mopidy uses SPOTIFY(R) CORE') logger.info(u'Connecting to Spotify') spotify = LibspotifySessionManager( settings.SPOTIFY_USERNAME, settings.SPOTIFY_PASSWORD, diff --git a/mopidy/backends/libspotify/playback.py b/mopidy/backends/libspotify/playback.py index 60a5d355..1195e9bc 100644 --- a/mopidy/backends/libspotify/playback.py +++ b/mopidy/backends/libspotify/playback.py @@ -26,7 +26,7 @@ class LibspotifyPlaybackController(BasePlaybackController): def _play(self, track): self._set_output_state('READY') if self.state == self.PLAYING: - self.stop() + self.backend.spotify.session.play(0) if track.uri is None: return False try: diff --git a/mopidy/backends/libspotify/translator.py b/mopidy/backends/libspotify/translator.py index 3a39aad5..ff8f3c5c 100644 --- a/mopidy/backends/libspotify/translator.py +++ b/mopidy/backends/libspotify/translator.py @@ -39,7 +39,7 @@ class LibspotifyTranslator(object): track_no=spotify_track.index(), date=date, length=spotify_track.duration(), - bitrate=320, + bitrate=160, ) @classmethod diff --git a/mopidy/frontends/mpd/protocol/current_playlist.py b/mopidy/frontends/mpd/protocol/current_playlist.py index c10d1dad..17b019e9 100644 --- a/mopidy/frontends/mpd/protocol/current_playlist.py +++ b/mopidy/frontends/mpd/protocol/current_playlist.py @@ -341,6 +341,7 @@ def swap(frontend, songpos1, songpos2): tracks.insert(songpos1, song2) del tracks[songpos2] tracks.insert(songpos2, song1) + frontend.backend.current_playlist.clear() frontend.backend.current_playlist.load(tracks) @handle_pattern(r'^swapid "(?P\d+)" "(?P\d+)"$') diff --git a/mopidy/frontends/mpd/protocol/playback.py b/mopidy/frontends/mpd/protocol/playback.py index bfff275e..7abc4509 100644 --- a/mopidy/frontends/mpd/protocol/playback.py +++ b/mopidy/frontends/mpd/protocol/playback.py @@ -139,9 +139,7 @@ def playid(frontend, cpid): cpid = int(cpid) try: if cpid == -1: - if not frontend.backend.current_playlist.cp_tracks: - return # Fail silently - cp_track = frontend.backend.current_playlist.cp_tracks[0] + cp_track = _get_cp_track_for_play_minus_one(frontend) else: cp_track = frontend.backend.current_playlist.get(cpid=cpid) return frontend.backend.playback.play(cp_track) @@ -158,10 +156,11 @@ def playpos(frontend, songpos): Begins playing the playlist at song number ``SONGPOS``. - *MPoD:* + *Many clients:* - - issues ``play "-1"`` after playlist replacement to start playback at - the first track. + - issue ``play "-1"`` after playlist replacement to start the current + track. If the current track is not set, start playback at the first + track. *BitMPC:* @@ -170,15 +169,21 @@ def playpos(frontend, songpos): songpos = int(songpos) try: if songpos == -1: - if not frontend.backend.current_playlist.cp_tracks: - return # Fail silently - cp_track = frontend.backend.current_playlist.cp_tracks[0] + cp_track = _get_cp_track_for_play_minus_one(frontend) else: cp_track = frontend.backend.current_playlist.cp_tracks[songpos] return frontend.backend.playback.play(cp_track) except IndexError: raise MpdArgError(u'Bad song index', command=u'play') +def _get_cp_track_for_play_minus_one(frontend): + if not frontend.backend.current_playlist.cp_tracks: + return # Fail silently + cp_track = frontend.backend.playback.current_cp_track + if cp_track is None: + cp_track = frontend.backend.current_playlist.cp_tracks[0] + return cp_track + @handle_pattern(r'^previous$') def previous(frontend): """ diff --git a/mopidy/frontends/mpd/protocol/stored_playlists.py b/mopidy/frontends/mpd/protocol/stored_playlists.py index ecd8b321..adc455c3 100644 --- a/mopidy/frontends/mpd/protocol/stored_playlists.py +++ b/mopidy/frontends/mpd/protocol/stored_playlists.py @@ -86,6 +86,10 @@ def load(frontend, name): ``load {NAME}`` Loads the playlist ``NAME.m3u`` from the playlist directory. + + *Clarifications:* + + - ``load`` appends the given playlist to the current playlist. """ matches = frontend.backend.stored_playlists.search(name) if matches: @@ -139,9 +143,9 @@ def playlistmove(frontend, name, from_pos, to_pos): *Clarifications:* - - The second argument is not a ``SONGID`` as used elsewhere in the - protocol documentation, but just the ``SONGPOS`` to move *from*, - i.e. ``playlistmove {NAME} {FROM_SONGPOS} {TO_SONGPOS}``. + - The second argument is not a ``SONGID`` as used elsewhere in the protocol + documentation, but just the ``SONGPOS`` to move *from*, i.e. + ``playlistmove {NAME} {FROM_SONGPOS} {TO_SONGPOS}``. """ raise MpdNotImplemented # TODO diff --git a/tests/backends/base.py b/tests/backends/base.py index 19f28ba5..05379c57 100644 --- a/tests/backends/base.py +++ b/tests/backends/base.py @@ -91,12 +91,6 @@ class BaseCurrentPlaylistControllerTest(object): self.controller.clear() self.assertEqual(self.playback.state, self.playback.STOPPED) - def test_load(self): - tracks = [] - self.assertNotEqual(id(tracks), id(self.controller.tracks)) - self.controller.load(tracks) - self.assertEqual(tracks, self.controller.tracks) - def test_get_by_uri_returns_unique_match(self): track = Track(uri='a') self.controller.load([Track(uri='z'), track, Track(uri='y')]) @@ -136,10 +130,15 @@ class BaseCurrentPlaylistControllerTest(object): self.controller.load([track1, track2, track3]) self.assertEqual(track2, self.controller.get(uri='b')[1]) - @populate_playlist - def test_load_replaces_playlist(self): - self.backend.current_playlist.load([]) - self.assertEqual(len(self.backend.current_playlist.tracks), 0) + def test_load_appends_to_the_current_playlist(self): + self.controller.load([Track(uri='a'), Track(uri='b')]) + self.assertEqual(len(self.controller.tracks), 2) + self.controller.load([Track(uri='c'), Track(uri='d')]) + self.assertEqual(len(self.controller.tracks), 4) + self.assertEqual(self.controller.tracks[0].uri, 'a') + self.assertEqual(self.controller.tracks[1].uri, 'b') + self.assertEqual(self.controller.tracks[2].uri, 'c') + self.assertEqual(self.controller.tracks[3].uri, 'd') def test_load_does_not_reset_version(self): version = self.controller.version @@ -148,22 +147,17 @@ class BaseCurrentPlaylistControllerTest(object): @populate_playlist def test_load_preserves_playing_state(self): - tracks = self.controller.tracks - playback = self.playback - self.playback.play() - self.controller.load([tracks[1]]) - self.assertEqual(playback.state, playback.PLAYING) - self.assertEqual(tracks[1], self.playback.current_track) + track = self.playback.current_track + self.controller.load(self.controller.tracks[1:2]) + self.assertEqual(self.playback.state, self.playback.PLAYING) + self.assertEqual(self.playback.current_track, track) @populate_playlist def test_load_preserves_stopped_state(self): - tracks = self.controller.tracks - playback = self.playback - - self.controller.load([tracks[2]]) - self.assertEqual(playback.state, playback.STOPPED) - self.assertEqual(None, self.playback.current_track) + self.controller.load(self.controller.tracks[1:2]) + self.assertEqual(self.playback.state, self.playback.STOPPED) + self.assertEqual(self.playback.current_track, None) @populate_playlist def test_move_single(self): @@ -351,6 +345,14 @@ class BasePlaybackControllerTest(object): self.playback.play(self.current_playlist.cp_tracks[-1]) self.assertEqual(self.playback.current_track, self.tracks[-1]) + @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] + self.playback.play() + self.assertNotEqual(self.playback.current_track, self.tracks[0]) + self.assertEqual(self.playback.current_track, self.tracks[1]) + @populate_playlist def test_current_track_after_completed_playlist(self): self.playback.play(self.current_playlist.cp_tracks[-1]) @@ -417,6 +419,16 @@ class BasePlaybackControllerTest(object): self.playback.next() self.assertEqual(self.playback.state, self.playback.STOPPED) + @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] + self.playback.play() + self.assertEqual(self.playback.current_track, self.tracks[0]) + self.playback.next() + self.assertNotEqual(self.playback.current_track, self.tracks[1]) + self.assertEqual(self.playback.current_track, self.tracks[2]) + @populate_playlist def test_previous(self): self.playback.play() @@ -457,6 +469,16 @@ class BasePlaybackControllerTest(object): self.assertEqual(self.playback.state, self.playback.STOPPED) self.assertEqual(self.playback.current_track, None) + @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] + self.playback.play(self.current_playlist.cp_tracks[2]) + self.assertEqual(self.playback.current_track, self.tracks[2]) + self.playback.previous() + self.assertNotEqual(self.playback.current_track, self.tracks[1]) + self.assertEqual(self.playback.current_track, self.tracks[0]) + @populate_playlist def test_next_track_before_play(self): self.assertEqual(self.playback.next_track, self.tracks[0]) @@ -575,15 +597,15 @@ class BasePlaybackControllerTest(object): self.playback.end_of_track_callback() self.assertEqual(self.playback.current_playlist_position, None) - def test_new_playlist_loaded_callback_gets_called(self): - callback = self.playback.new_playlist_loaded_callback + def test_on_current_playlist_change_gets_called(self): + callback = self.playback.on_current_playlist_change def wrapper(): wrapper.called = True return callback() wrapper.called = False - self.playback.new_playlist_loaded_callback = wrapper + self.playback.on_current_playlist_change = wrapper self.backend.current_playlist.load([]) self.assert_(wrapper.called) @@ -608,27 +630,28 @@ class BasePlaybackControllerTest(object): self.assert_(event.is_set()) @populate_playlist - def test_new_playlist_loaded_callback_when_playing(self): + def test_on_current_playlist_change_when_playing(self): self.playback.play() + current_track = self.playback.current_track self.backend.current_playlist.load([self.tracks[2]]) self.assertEqual(self.playback.state, self.playback.PLAYING) - self.assertEqual(self.playback.current_track, self.tracks[2]) + self.assertEqual(self.playback.current_track, current_track) @populate_playlist - def test_new_playlist_loaded_callback_when_stopped(self): + def test_on_current_playlist_change_when_stopped(self): + current_track = self.playback.current_track self.backend.current_playlist.load([self.tracks[2]]) self.assertEqual(self.playback.state, self.playback.STOPPED) self.assertEqual(self.playback.current_track, None) - self.assertEqual(self.playback.next_track, self.tracks[2]) @populate_playlist - def test_new_playlist_loaded_callback_when_paused(self): + def test_on_current_playlist_change_when_paused(self): self.playback.play() self.playback.pause() + current_track = self.playback.current_track self.backend.current_playlist.load([self.tracks[2]]) - self.assertEqual(self.playback.state, self.playback.STOPPED) - self.assertEqual(self.playback.current_track, None) - self.assertEqual(self.playback.next_track, self.tracks[2]) + self.assertEqual(self.playback.state, self.backend.playback.PAUSED) + self.assertEqual(self.playback.current_track, current_track) @populate_playlist def test_pause_when_stopped(self): @@ -915,7 +938,7 @@ class BasePlaybackControllerTest(object): self.playback.random = True self.assertEqual(self.playback.next_track, self.tracks[2]) self.backend.current_playlist.load(self.tracks[:1]) - self.assertEqual(self.playback.next_track, self.tracks[0]) + self.assertEqual(self.playback.next_track, self.tracks[1]) @populate_playlist def test_played_track_during_random_not_played_again(self): @@ -927,13 +950,9 @@ class BasePlaybackControllerTest(object): played.append(self.playback.current_track) self.playback.next() - def test_playing_track_with_invalid_uri(self): - self.backend.current_playlist.load([Track(uri='foobar')]) - self.playback.play() - self.assertEqual(self.playback.state, self.playback.STOPPED) - + @populate_playlist def test_playing_track_that_isnt_in_playlist(self): - test = lambda: self.playback.play(self.tracks[0]) + test = lambda: self.playback.play((17, Track())) self.assertRaises(AssertionError, test) diff --git a/tests/data/blank.flac b/tests/data/blank.flac index b838b98e..ae18d36f 100644 Binary files a/tests/data/blank.flac and b/tests/data/blank.flac differ diff --git a/tests/data/blank.mp3 b/tests/data/blank.mp3 index 3e0b4abb..6aa48cd8 100644 Binary files a/tests/data/blank.mp3 and b/tests/data/blank.mp3 differ diff --git a/tests/data/blank.ogg b/tests/data/blank.ogg index 3b1c57a1..e67e428b 100644 Binary files a/tests/data/blank.ogg and b/tests/data/blank.ogg differ diff --git a/tests/data/blank.wav b/tests/data/blank.wav index 5217ec6f..0041c7ba 100644 Binary files a/tests/data/blank.wav and b/tests/data/blank.wav differ diff --git a/tests/frontends/mpd/playback_test.py b/tests/frontends/mpd/playback_test.py index a1331bb3..ce3130bf 100644 --- a/tests/frontends/mpd/playback_test.py +++ b/tests/frontends/mpd/playback_test.py @@ -225,13 +225,25 @@ class PlaybackControlHandlerTest(unittest.TestCase): self.assertEqual(result[0], u'ACK [2@0] {play} Bad song index') self.assertEqual(self.b.playback.STOPPED, self.b.playback.state) - def test_play_minus_one_plays_first_in_playlist(self): - track = Track() - self.b.current_playlist.load([track]) + def test_play_minus_one_plays_first_in_playlist_if_no_current_track(self): + self.assertEqual(self.b.playback.current_track, None) + self.b.current_playlist.load([Track(uri='a'), Track(uri='b')]) result = self.h.handle_request(u'play "-1"') self.assert_(u'OK' in result) self.assertEqual(self.b.playback.PLAYING, self.b.playback.state) - self.assertEqual(self.b.playback.current_track, track) + self.assertEqual(self.b.playback.current_track.uri, 'a') + + def test_play_minus_one_plays_current_track_if_current_track_is_set(self): + self.b.current_playlist.load([Track(uri='a'), Track(uri='b')]) + self.assertEqual(self.b.playback.current_track, None) + self.b.playback.play() + self.b.playback.next() + self.b.playback.stop() + self.assertNotEqual(self.b.playback.current_track, None) + result = self.h.handle_request(u'play "-1"') + self.assert_(u'OK' in result) + self.assertEqual(self.b.playback.PLAYING, self.b.playback.state) + self.assertEqual(self.b.playback.current_track.uri, 'b') def test_play_minus_one_on_empty_playlist_does_not_ack(self): self.b.current_playlist.clear() @@ -246,13 +258,25 @@ class PlaybackControlHandlerTest(unittest.TestCase): self.assert_(u'OK' in result) self.assertEqual(self.b.playback.PLAYING, self.b.playback.state) - def test_playid_minus_one_plays_first_in_playlist(self): - track = Track() - self.b.current_playlist.load([track]) + def test_playid_minus_one_plays_first_in_playlist_if_no_current_track(self): + self.assertEqual(self.b.playback.current_track, None) + self.b.current_playlist.load([Track(uri='a'), Track(uri='b')]) result = self.h.handle_request(u'playid "-1"') self.assert_(u'OK' in result) self.assertEqual(self.b.playback.PLAYING, self.b.playback.state) - self.assertEqual(self.b.playback.current_track, track) + self.assertEqual(self.b.playback.current_track.uri, 'a') + + def test_play_minus_one_plays_current_track_if_current_track_is_set(self): + self.b.current_playlist.load([Track(uri='a'), Track(uri='b')]) + self.assertEqual(self.b.playback.current_track, None) + self.b.playback.play() + self.b.playback.next() + self.b.playback.stop() + self.assertNotEqual(self.b.playback.current_track, None) + result = self.h.handle_request(u'playid "-1"') + self.assert_(u'OK' in result) + self.assertEqual(self.b.playback.PLAYING, self.b.playback.state) + self.assertEqual(self.b.playback.current_track.uri, 'b') def test_playid_minus_one_on_empty_playlist_does_not_ack(self): self.b.current_playlist.clear()