Merge pull request #1288 from adamcik/feature/implement-gapless
Gapless part 1
This commit is contained in:
commit
93899c8599
@ -59,6 +59,18 @@ Cleanups
|
||||
- Removed warning if :file:`~/.config/mopidy/settings.py` exists. We stopped
|
||||
using this settings file in 0.14, released in April 2013.
|
||||
|
||||
Gapless
|
||||
-------
|
||||
|
||||
- Add partial support for gapless playback. Gapless now works as long as you
|
||||
don't change tracks or use next/previous. (PR: :issue:`1288`)
|
||||
|
||||
- Core playback has been refactored to better handle gapless, and async state
|
||||
changes.
|
||||
|
||||
- Tests have been updated to always use a core actor so async state changes
|
||||
don't trip us up.
|
||||
|
||||
|
||||
v1.1.1 (2015-09-14)
|
||||
===================
|
||||
|
||||
@ -2,6 +2,7 @@ from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
|
||||
import gobject
|
||||
|
||||
@ -406,6 +407,7 @@ class Audio(pykka.ThreadingActor):
|
||||
self.mixer = SoftwareMixer(mixer)
|
||||
|
||||
def on_start(self):
|
||||
self._thread = threading.current_thread()
|
||||
try:
|
||||
self._setup_preferences()
|
||||
self._setup_playbin()
|
||||
@ -499,6 +501,11 @@ class Audio(pykka.ThreadingActor):
|
||||
self.mixer.teardown()
|
||||
|
||||
def _on_about_to_finish(self, element):
|
||||
if self._thread == threading.current_thread():
|
||||
logger.error(
|
||||
'about-to-finish in actor, aborting to avoid deadlock.')
|
||||
return
|
||||
|
||||
gst_logger.debug('Got about-to-finish event.')
|
||||
if self._about_to_finish_callback:
|
||||
logger.debug('Running about to finish callback.')
|
||||
|
||||
@ -54,7 +54,8 @@ class Core(
|
||||
self.library = LibraryController(backends=self.backends, core=self)
|
||||
self.history = HistoryController()
|
||||
self.mixer = MixerController(mixer=mixer)
|
||||
self.playback = PlaybackController(backends=self.backends, core=self)
|
||||
self.playback = PlaybackController(
|
||||
audio=audio, backends=self.backends, core=self)
|
||||
self.playlists = PlaylistsController(backends=self.backends, core=self)
|
||||
self.tracklist = TracklistController(core=self)
|
||||
|
||||
@ -84,7 +85,7 @@ class Core(
|
||||
"""
|
||||
|
||||
def reached_end_of_stream(self):
|
||||
self.playback._on_end_of_track()
|
||||
self.playback._on_end_of_stream()
|
||||
|
||||
def stream_changed(self, uri):
|
||||
self.playback._on_stream_changed(uri)
|
||||
|
||||
@ -14,21 +14,26 @@ logger = logging.getLogger(__name__)
|
||||
class PlaybackController(object):
|
||||
pykka_traversable = True
|
||||
|
||||
def __init__(self, backends, core):
|
||||
def __init__(self, audio, backends, core):
|
||||
# TODO: these should be internal
|
||||
self.backends = backends
|
||||
self.core = core
|
||||
self._audio = audio
|
||||
|
||||
self._current_tl_track = None
|
||||
self._stream_title = None
|
||||
self._state = PlaybackState.STOPPED
|
||||
|
||||
def _get_backend(self):
|
||||
# TODO: take in track instead
|
||||
track = self.get_current_track()
|
||||
if track is None:
|
||||
self._current_tl_track = None
|
||||
self._pending_tl_track = None
|
||||
|
||||
if self._audio:
|
||||
self._audio.set_about_to_finish_callback(
|
||||
self._on_about_to_finish_callback)
|
||||
|
||||
def _get_backend(self, tl_track):
|
||||
if tl_track is None:
|
||||
return None
|
||||
uri = track.uri
|
||||
uri_scheme = urlparse.urlparse(uri).scheme
|
||||
uri_scheme = urlparse.urlparse(tl_track.track.uri).scheme
|
||||
return self.backends.with_playback.get(uri_scheme, None)
|
||||
|
||||
# Properties
|
||||
@ -122,7 +127,7 @@ class PlaybackController(object):
|
||||
|
||||
def get_time_position(self):
|
||||
"""Get time position in milliseconds."""
|
||||
backend = self._get_backend()
|
||||
backend = self._get_backend(self.get_current_tl_track())
|
||||
if backend:
|
||||
return backend.playback.get_time_position().get()
|
||||
else:
|
||||
@ -190,49 +195,45 @@ class PlaybackController(object):
|
||||
|
||||
# Methods
|
||||
|
||||
# TODO: remove this.
|
||||
def _change_track(self, tl_track, on_error_step=1):
|
||||
"""
|
||||
Change to the given track, keeping the current playback state.
|
||||
def _on_end_of_stream(self):
|
||||
self.set_state(PlaybackState.STOPPED)
|
||||
self._set_current_tl_track(None)
|
||||
# TODO: self._trigger_track_playback_ended?
|
||||
|
||||
:param tl_track: track to change to
|
||||
:type tl_track: :class:`mopidy.models.TlTrack` or :class:`None`
|
||||
:param on_error_step: direction to step at play error, 1 for next
|
||||
track (default), -1 for previous track. **INTERNAL**
|
||||
:type on_error_step: int, -1 or 1
|
||||
"""
|
||||
old_state = self.get_state()
|
||||
self.stop()
|
||||
self._set_current_tl_track(tl_track)
|
||||
if old_state == PlaybackState.PLAYING:
|
||||
self._play(on_error_step=on_error_step)
|
||||
elif old_state == PlaybackState.PAUSED:
|
||||
# NOTE: this is just a quick hack to fix #1177 as this code has
|
||||
# already been killed in the gapless branch.
|
||||
backend = self._get_backend()
|
||||
if backend:
|
||||
backend.playback.prepare_change()
|
||||
backend.playback.change_track(tl_track.track).get()
|
||||
self.pause()
|
||||
def _on_stream_changed(self, uri):
|
||||
self._stream_title = None
|
||||
if self._pending_tl_track:
|
||||
self._set_current_tl_track(self._pending_tl_track)
|
||||
self._pending_tl_track = None
|
||||
self._trigger_track_playback_started()
|
||||
|
||||
# TODO: this is not really end of track, this is on_need_next_track
|
||||
def _on_end_of_track(self):
|
||||
"""
|
||||
Tell the playback controller that end of track is reached.
|
||||
def _on_about_to_finish_callback(self):
|
||||
"""Callback that performs a blocking actor call to the real callback.
|
||||
|
||||
Used by event handler in :class:`mopidy.core.Core`.
|
||||
This is passed to audio, which is allowed to call this code from the
|
||||
audio thread. We pass execution into the core actor to ensure that
|
||||
there is no unsafe access of state in core. This must block until
|
||||
we get a response.
|
||||
"""
|
||||
if self.get_state() == PlaybackState.STOPPED:
|
||||
return
|
||||
self.core.actor_ref.ask({
|
||||
'command': 'pykka_call', 'args': tuple(), 'kwargs': {},
|
||||
'attr_path': ('playback', '_on_about_to_finish'),
|
||||
})
|
||||
|
||||
def _on_about_to_finish(self):
|
||||
self._trigger_track_playback_ended(self.get_time_position())
|
||||
|
||||
# TODO: check that we always have a current track
|
||||
original_tl_track = self.get_current_tl_track()
|
||||
next_tl_track = self.core.tracklist.eot_track(original_tl_track)
|
||||
|
||||
if next_tl_track:
|
||||
self._change_track(next_tl_track)
|
||||
else:
|
||||
self.stop()
|
||||
self._set_current_tl_track(None)
|
||||
# TODO: only set pending if we have a backend that can play it?
|
||||
# TODO: skip tracks that don't have a backend?
|
||||
self._pending_tl_track = next_tl_track
|
||||
backend = self._get_backend(next_tl_track)
|
||||
|
||||
if backend:
|
||||
backend.playback.change_track(next_tl_track.track).get()
|
||||
|
||||
self.core.tracklist._mark_played(original_tl_track)
|
||||
|
||||
@ -242,13 +243,11 @@ class PlaybackController(object):
|
||||
|
||||
Used by :class:`mopidy.core.TracklistController`.
|
||||
"""
|
||||
tracklist = self.core.tracklist.get_tl_tracks()
|
||||
if self.get_current_tl_track() not in tracklist:
|
||||
if not self.core.tracklist.tl_tracks:
|
||||
self.stop()
|
||||
self._set_current_tl_track(None)
|
||||
|
||||
def _on_stream_changed(self, uri):
|
||||
self._stream_title = None
|
||||
elif self.get_current_tl_track() not in self.core.tracklist.tl_tracks:
|
||||
self._set_current_tl_track(None)
|
||||
|
||||
def next(self):
|
||||
"""
|
||||
@ -257,23 +256,29 @@ class PlaybackController(object):
|
||||
The current playback state will be kept. If it was playing, playing
|
||||
will continue. If it was paused, it will still be paused, etc.
|
||||
"""
|
||||
original_tl_track = self.get_current_tl_track()
|
||||
next_tl_track = self.core.tracklist.next_track(original_tl_track)
|
||||
state = self.get_state()
|
||||
current = self._pending_tl_track or self._current_tl_track
|
||||
|
||||
if next_tl_track:
|
||||
# TODO: switch to:
|
||||
# backend.play(track)
|
||||
# wait for state change?
|
||||
self._change_track(next_tl_track)
|
||||
else:
|
||||
self.stop()
|
||||
self._set_current_tl_track(None)
|
||||
# TODO: move to pending track?
|
||||
self._trigger_track_playback_ended(self.get_time_position())
|
||||
self.core.tracklist._mark_played(self._current_tl_track)
|
||||
|
||||
self.core.tracklist._mark_played(original_tl_track)
|
||||
while current:
|
||||
pending = self.core.tracklist.next_track(current)
|
||||
if self._change(pending, state):
|
||||
break
|
||||
else:
|
||||
self.core.tracklist._mark_unplayable(pending)
|
||||
# TODO: this could be needed to prevent a loop in rare cases
|
||||
# if current == pending:
|
||||
# break
|
||||
current = pending
|
||||
|
||||
# TODO return result?
|
||||
|
||||
def pause(self):
|
||||
"""Pause playback."""
|
||||
backend = self._get_backend()
|
||||
backend = self._get_backend(self.get_current_tl_track())
|
||||
if not backend or backend.playback.pause().get():
|
||||
# TODO: switch to:
|
||||
# backend.track(pause)
|
||||
@ -302,9 +307,6 @@ class PlaybackController(object):
|
||||
if tl_track:
|
||||
deprecation.warn('core.playback.play:tl_track_kwarg', pending=True)
|
||||
|
||||
self._play(tl_track=tl_track, tlid=tlid, on_error_step=1)
|
||||
|
||||
def _play(self, tl_track=None, tlid=None, on_error_step=1):
|
||||
if tl_track is None and tlid is not None:
|
||||
for tl_track in self.core.tracklist.get_tl_tracks():
|
||||
if tl_track.tlid == tlid:
|
||||
@ -312,60 +314,71 @@ class PlaybackController(object):
|
||||
else:
|
||||
tl_track = None
|
||||
|
||||
if tl_track is None:
|
||||
if self.get_state() == PlaybackState.PAUSED:
|
||||
return self.resume()
|
||||
if tl_track is not None:
|
||||
# TODO: allow from outside tracklist, would make sense given refs?
|
||||
assert tl_track in self.core.tracklist.get_tl_tracks()
|
||||
elif tl_track is None and self.get_state() == PlaybackState.PAUSED:
|
||||
self.resume()
|
||||
return
|
||||
|
||||
if self.get_current_tl_track() is not None:
|
||||
tl_track = self.get_current_tl_track()
|
||||
original = self._current_tl_track
|
||||
current = self._pending_tl_track or self._current_tl_track
|
||||
pending = tl_track or current or self.core.tracklist.next_track(None)
|
||||
|
||||
if original != pending and self.get_state() != PlaybackState.STOPPED:
|
||||
self._trigger_track_playback_ended(self.get_time_position())
|
||||
|
||||
if pending:
|
||||
# TODO: remove?
|
||||
self.set_state(PlaybackState.PLAYING)
|
||||
|
||||
while pending:
|
||||
# TODO: should we consume unplayable tracks in this loop?
|
||||
if self._change(pending, PlaybackState.PLAYING):
|
||||
break
|
||||
else:
|
||||
if on_error_step == 1:
|
||||
tl_track = self.core.tracklist.next_track(tl_track)
|
||||
elif on_error_step == -1:
|
||||
tl_track = self.core.tracklist.previous_track(tl_track)
|
||||
self.core.tracklist._mark_unplayable(pending)
|
||||
current = pending
|
||||
pending = self.core.tracklist.next_track(current)
|
||||
|
||||
if tl_track is None:
|
||||
return
|
||||
# TODO: move to top and get rid of original?
|
||||
self.core.tracklist._mark_played(original)
|
||||
# TODO return result?
|
||||
|
||||
assert tl_track in self.core.tracklist.get_tl_tracks()
|
||||
def _change(self, pending_tl_track, state):
|
||||
self._pending_tl_track = pending_tl_track
|
||||
|
||||
# TODO: switch to:
|
||||
# backend.play(track)
|
||||
# wait for state change?
|
||||
|
||||
if self.get_state() == PlaybackState.PLAYING:
|
||||
if not pending_tl_track:
|
||||
self.stop()
|
||||
self._on_end_of_stream() # pretend an EOS happened for cleanup
|
||||
return True
|
||||
|
||||
self._set_current_tl_track(tl_track)
|
||||
self.set_state(PlaybackState.PLAYING)
|
||||
backend = self._get_backend()
|
||||
success = False
|
||||
backend = self._get_backend(pending_tl_track)
|
||||
if not backend:
|
||||
return False
|
||||
|
||||
if backend:
|
||||
backend.playback.prepare_change()
|
||||
backend.playback.prepare_change()
|
||||
if not backend.playback.change_track(pending_tl_track.track).get():
|
||||
return False # TODO: test for this path
|
||||
|
||||
if state == PlaybackState.PLAYING:
|
||||
try:
|
||||
success = (
|
||||
backend.playback.change_track(tl_track.track).get() and
|
||||
backend.playback.play().get())
|
||||
return backend.playback.play().get()
|
||||
except TypeError:
|
||||
logger.error(
|
||||
'%s needs to be updated to work with this '
|
||||
'version of Mopidy.',
|
||||
backend.actor_ref.actor_class.__name__)
|
||||
logger.debug('Backend exception', exc_info=True)
|
||||
# TODO: check by binding against underlying play method using
|
||||
# inspect and otherwise re-raise?
|
||||
logger.error('%s needs to be updated to work with this '
|
||||
'version of Mopidy.', backend)
|
||||
return False
|
||||
elif state == PlaybackState.PAUSED:
|
||||
return backend.playback.pause().get()
|
||||
elif state == PlaybackState.STOPPED:
|
||||
# TODO: emit some event now?
|
||||
self._current_tl_track = self._pending_tl_track
|
||||
self._pending_tl_track = None
|
||||
return True
|
||||
|
||||
if success:
|
||||
self.core.tracklist._mark_playing(tl_track)
|
||||
self.core.history._add_track(tl_track.track)
|
||||
# TODO: replace with stream-changed
|
||||
self._trigger_track_playback_started()
|
||||
else:
|
||||
self.core.tracklist._mark_unplayable(tl_track)
|
||||
if on_error_step == 1:
|
||||
# TODO: can cause an endless loop for single track repeat.
|
||||
self.next()
|
||||
elif on_error_step == -1:
|
||||
self.previous()
|
||||
raise Exception('Unknown state: %s' % state)
|
||||
|
||||
def previous(self):
|
||||
"""
|
||||
@ -374,18 +387,29 @@ class PlaybackController(object):
|
||||
The current playback state will be kept. If it was playing, playing
|
||||
will continue. If it was paused, it will still be paused, etc.
|
||||
"""
|
||||
tl_track = self.get_current_tl_track()
|
||||
# TODO: switch to:
|
||||
# self.play(....)
|
||||
# wait for state change?
|
||||
self._change_track(
|
||||
self.core.tracklist.previous_track(tl_track), on_error_step=-1)
|
||||
self._trigger_track_playback_ended(self.get_time_position())
|
||||
|
||||
state = self.get_state()
|
||||
current = self._pending_tl_track or self._current_tl_track
|
||||
|
||||
while current:
|
||||
pending = self.core.tracklist.previous_track(current)
|
||||
if self._change(pending, state):
|
||||
break
|
||||
else:
|
||||
self.core.tracklist._mark_unplayable(pending)
|
||||
# TODO: this could be needed to prevent a loop in rare cases
|
||||
# if current == pending:
|
||||
# break
|
||||
current = pending
|
||||
|
||||
# TODO: no return value?
|
||||
|
||||
def resume(self):
|
||||
"""If paused, resume playing the current track."""
|
||||
if self.get_state() != PlaybackState.PAUSED:
|
||||
return
|
||||
backend = self._get_backend()
|
||||
backend = self._get_backend(self.get_current_tl_track())
|
||||
if backend and backend.playback.resume().get():
|
||||
self.set_state(PlaybackState.PLAYING)
|
||||
# TODO: trigger via gst messages
|
||||
@ -402,6 +426,7 @@ class PlaybackController(object):
|
||||
:type time_position: int
|
||||
:rtype: :class:`True` if successful, else :class:`False`
|
||||
"""
|
||||
# TODO: seek needs to take pending tracks into account :(
|
||||
validation.check_integer(time_position)
|
||||
|
||||
if time_position < 0:
|
||||
@ -412,19 +437,28 @@ class PlaybackController(object):
|
||||
if not self.core.tracklist.tracks:
|
||||
return False
|
||||
|
||||
if self.current_track and self.current_track.length is None:
|
||||
return False
|
||||
|
||||
if self.get_state() == PlaybackState.STOPPED:
|
||||
self.play()
|
||||
|
||||
# TODO: uncomment once we have tests for this. Should fix seek after
|
||||
# about to finish doing wrong track.
|
||||
# if self._current_tl_track and self._pending_tl_track:
|
||||
# self.play(self._current_tl_track)
|
||||
|
||||
# We need to prefer the still playing track, but if nothing is playing
|
||||
# we fall back to the pending one.
|
||||
tl_track = self._current_tl_track or self._pending_tl_track
|
||||
if tl_track and tl_track.track.length is None:
|
||||
return False
|
||||
|
||||
if time_position < 0:
|
||||
time_position = 0
|
||||
elif time_position > self.current_track.length:
|
||||
elif time_position > tl_track.track.length:
|
||||
# TODO: gstreamer will trigger a about to finish for us, use that?
|
||||
self.next()
|
||||
return True
|
||||
|
||||
backend = self._get_backend()
|
||||
backend = self._get_backend(self.get_current_tl_track())
|
||||
if not backend:
|
||||
return False
|
||||
|
||||
@ -436,7 +470,7 @@ class PlaybackController(object):
|
||||
def stop(self):
|
||||
"""Stop playing."""
|
||||
if self.get_state() != PlaybackState.STOPPED:
|
||||
backend = self._get_backend()
|
||||
backend = self._get_backend(self.get_current_tl_track())
|
||||
time_position_before_stop = self.get_time_position()
|
||||
if not backend or backend.playback.stop().get():
|
||||
self.set_state(PlaybackState.STOPPED)
|
||||
@ -461,12 +495,15 @@ class PlaybackController(object):
|
||||
time_position=self.get_time_position())
|
||||
|
||||
def _trigger_track_playback_started(self):
|
||||
# TODO: replace with stream-changed
|
||||
logger.debug('Triggering track playback started event')
|
||||
if self.get_current_tl_track() is None:
|
||||
return
|
||||
listener.CoreListener.send(
|
||||
'track_playback_started',
|
||||
tl_track=self.get_current_tl_track())
|
||||
|
||||
tl_track = self.get_current_tl_track()
|
||||
self.core.tracklist._mark_playing(tl_track)
|
||||
self.core.history._add_track(tl_track.track)
|
||||
listener.CoreListener.send('track_playback_started', tl_track=tl_track)
|
||||
|
||||
def _trigger_track_playback_ended(self, time_position_before_stop):
|
||||
logger.debug('Triggering track playback ended event')
|
||||
|
||||
@ -318,10 +318,11 @@ class TracklistController(object):
|
||||
return self._shuffled[0]
|
||||
return None
|
||||
|
||||
if tl_track is None:
|
||||
next_index = self.index(tl_track)
|
||||
if next_index is None:
|
||||
next_index = 0
|
||||
else:
|
||||
next_index = self.index(tl_track) + 1
|
||||
next_index += 1
|
||||
|
||||
if self.get_repeat():
|
||||
next_index %= len(self._tl_tracks)
|
||||
|
||||
@ -137,13 +137,13 @@ def pause(context, state=None):
|
||||
|
||||
playback_state = context.core.playback.get_state().get()
|
||||
if (playback_state == PlaybackState.PLAYING):
|
||||
context.core.playback.pause()
|
||||
context.core.playback.pause().get()
|
||||
elif (playback_state == PlaybackState.PAUSED):
|
||||
context.core.playback.resume()
|
||||
context.core.playback.resume().get()
|
||||
elif state:
|
||||
context.core.playback.pause()
|
||||
context.core.playback.pause().get()
|
||||
else:
|
||||
context.core.playback.resume()
|
||||
context.core.playback.resume().get()
|
||||
|
||||
|
||||
@protocol.commands.add('play', songpos=protocol.INT)
|
||||
|
||||
@ -59,7 +59,7 @@ class BaseTest(unittest.TestCase):
|
||||
def tearDown(self): # noqa
|
||||
pykka.ActorRegistry.stop_all()
|
||||
|
||||
def possibly_trigger_fake_playback_error(self):
|
||||
def possibly_trigger_fake_playback_error(self, uri):
|
||||
pass
|
||||
|
||||
def possibly_trigger_fake_about_to_finish(self):
|
||||
@ -69,8 +69,8 @@ class BaseTest(unittest.TestCase):
|
||||
class DummyMixin(object):
|
||||
audio_class = dummy_audio.DummyAudio
|
||||
|
||||
def possibly_trigger_fake_playback_error(self):
|
||||
self.audio.trigger_fake_playback_failure()
|
||||
def possibly_trigger_fake_playback_error(self, uri):
|
||||
self.audio.trigger_fake_playback_failure(uri)
|
||||
|
||||
def possibly_trigger_fake_about_to_finish(self):
|
||||
callback = self.audio.get_about_to_finish_callback().get()
|
||||
@ -86,7 +86,7 @@ class AudioTest(BaseTest):
|
||||
self.assertTrue(self.audio.start_playback().get())
|
||||
|
||||
def test_start_playback_non_existing_file(self):
|
||||
self.possibly_trigger_fake_playback_error()
|
||||
self.possibly_trigger_fake_playback_error(self.uris[0] + 'bogus')
|
||||
|
||||
self.audio.prepare_change()
|
||||
self.audio.set_uri(self.uris[0] + 'bogus')
|
||||
@ -133,186 +133,253 @@ class AudioDummyTest(DummyMixin, AudioTest):
|
||||
pass
|
||||
|
||||
|
||||
@mock.patch.object(audio.AudioListener, 'send')
|
||||
class DummyAudioListener(pykka.ThreadingActor, audio.AudioListener):
|
||||
def __init__(self):
|
||||
super(DummyAudioListener, self).__init__()
|
||||
self.events = []
|
||||
self.waiters = {}
|
||||
|
||||
def on_event(self, event, **kwargs):
|
||||
self.events.append((event, kwargs))
|
||||
if event in self.waiters:
|
||||
self.waiters[event].set()
|
||||
|
||||
def wait(self, event):
|
||||
self.waiters[event] = threading.Event()
|
||||
return self.waiters[event]
|
||||
|
||||
def get_events(self):
|
||||
return self.events
|
||||
|
||||
def clear_events(self):
|
||||
self.events = []
|
||||
|
||||
|
||||
class AudioEventTest(BaseTest):
|
||||
|
||||
def setUp(self): # noqa: N802
|
||||
super(AudioEventTest, self).setUp()
|
||||
self.audio.enable_sync_handler().get()
|
||||
self.listener = DummyAudioListener.start().proxy()
|
||||
|
||||
def tearDown(self): # noqa: N802
|
||||
super(AudioEventTest, self).setUp()
|
||||
|
||||
def assertEvent(self, event, **kwargs): # noqa: N802
|
||||
self.assertIn((event, kwargs), self.listener.get_events().get())
|
||||
|
||||
def assertNotEvent(self, event, **kwargs): # noqa: N802
|
||||
self.assertNotIn((event, kwargs), self.listener.get_events().get())
|
||||
|
||||
# TODO: test without uri set, with bad uri and gapless...
|
||||
# TODO: playing->playing triggered by seek should be removed
|
||||
# TODO: codify expected state after EOS
|
||||
# TODO: consider returning a future or a threading event?
|
||||
|
||||
def test_state_change_stopped_to_playing_event(self, send_mock):
|
||||
def test_state_change_stopped_to_playing_event(self):
|
||||
self.audio.prepare_change()
|
||||
self.audio.set_uri(self.uris[0])
|
||||
self.audio.start_playback()
|
||||
|
||||
self.audio.wait_for_state_change().get()
|
||||
call = mock.call('state_changed', old_state=PlaybackState.STOPPED,
|
||||
self.assertEvent('state_changed', old_state=PlaybackState.STOPPED,
|
||||
new_state=PlaybackState.PLAYING, target_state=None)
|
||||
self.assertIn(call, send_mock.call_args_list)
|
||||
|
||||
def test_state_change_stopped_to_paused_event(self, send_mock):
|
||||
def test_state_change_stopped_to_paused_event(self):
|
||||
self.audio.prepare_change()
|
||||
self.audio.set_uri(self.uris[0])
|
||||
self.audio.pause_playback()
|
||||
|
||||
self.audio.wait_for_state_change().get()
|
||||
call = mock.call('state_changed', old_state=PlaybackState.STOPPED,
|
||||
self.assertEvent('state_changed', old_state=PlaybackState.STOPPED,
|
||||
new_state=PlaybackState.PAUSED, target_state=None)
|
||||
self.assertIn(call, send_mock.call_args_list)
|
||||
|
||||
def test_state_change_paused_to_playing_event(self, send_mock):
|
||||
def test_state_change_paused_to_playing_event(self):
|
||||
self.audio.prepare_change()
|
||||
self.audio.set_uri(self.uris[0])
|
||||
self.audio.pause_playback()
|
||||
|
||||
self.audio.wait_for_state_change()
|
||||
self.listener.clear_events()
|
||||
self.audio.start_playback()
|
||||
|
||||
self.audio.wait_for_state_change().get()
|
||||
call = mock.call('state_changed', old_state=PlaybackState.PAUSED,
|
||||
self.assertEvent('state_changed', old_state=PlaybackState.PAUSED,
|
||||
new_state=PlaybackState.PLAYING, target_state=None)
|
||||
self.assertIn(call, send_mock.call_args_list)
|
||||
|
||||
def test_state_change_paused_to_stopped_event(self, send_mock):
|
||||
def test_state_change_paused_to_stopped_event(self):
|
||||
self.audio.prepare_change()
|
||||
self.audio.set_uri(self.uris[0])
|
||||
self.audio.pause_playback()
|
||||
|
||||
self.audio.wait_for_state_change()
|
||||
self.listener.clear_events()
|
||||
self.audio.stop_playback()
|
||||
|
||||
self.audio.wait_for_state_change().get()
|
||||
call = mock.call('state_changed', old_state=PlaybackState.PAUSED,
|
||||
self.assertEvent('state_changed', old_state=PlaybackState.PAUSED,
|
||||
new_state=PlaybackState.STOPPED, target_state=None)
|
||||
self.assertIn(call, send_mock.call_args_list)
|
||||
|
||||
def test_state_change_playing_to_paused_event(self, send_mock):
|
||||
def test_state_change_playing_to_paused_event(self):
|
||||
self.audio.prepare_change()
|
||||
self.audio.set_uri(self.uris[0])
|
||||
self.audio.start_playback()
|
||||
|
||||
self.audio.wait_for_state_change()
|
||||
self.listener.clear_events()
|
||||
self.audio.pause_playback()
|
||||
|
||||
self.audio.wait_for_state_change().get()
|
||||
call = mock.call('state_changed', old_state=PlaybackState.PLAYING,
|
||||
self.assertEvent('state_changed', old_state=PlaybackState.PLAYING,
|
||||
new_state=PlaybackState.PAUSED, target_state=None)
|
||||
self.assertIn(call, send_mock.call_args_list)
|
||||
|
||||
def test_state_change_playing_to_stopped_event(self, send_mock):
|
||||
def test_state_change_playing_to_stopped_event(self):
|
||||
self.audio.prepare_change()
|
||||
self.audio.set_uri(self.uris[0])
|
||||
self.audio.start_playback()
|
||||
|
||||
self.audio.wait_for_state_change()
|
||||
self.listener.clear_events()
|
||||
self.audio.stop_playback()
|
||||
|
||||
self.audio.wait_for_state_change().get()
|
||||
call = mock.call('state_changed', old_state=PlaybackState.PLAYING,
|
||||
self.assertEvent('state_changed', old_state=PlaybackState.PLAYING,
|
||||
new_state=PlaybackState.STOPPED, target_state=None)
|
||||
self.assertIn(call, send_mock.call_args_list)
|
||||
|
||||
def test_stream_changed_event_on_playing(self, send_mock):
|
||||
def test_stream_changed_event_on_playing(self):
|
||||
self.audio.prepare_change()
|
||||
self.audio.set_uri(self.uris[0])
|
||||
self.listener.clear_events()
|
||||
self.audio.start_playback()
|
||||
|
||||
# Since we are going from stopped to playing, the state change is
|
||||
# enough to ensure the stream changed.
|
||||
self.audio.wait_for_state_change().get()
|
||||
self.assertEvent('stream_changed', uri=self.uris[0])
|
||||
|
||||
call = mock.call('stream_changed', uri=self.uris[0])
|
||||
self.assertIn(call, send_mock.call_args_list)
|
||||
def test_stream_changed_event_on_multiple_changes(self):
|
||||
self.audio.prepare_change()
|
||||
self.audio.set_uri(self.uris[0])
|
||||
self.listener.clear_events()
|
||||
self.audio.start_playback()
|
||||
|
||||
def test_stream_changed_event_on_paused_to_stopped(self, send_mock):
|
||||
self.audio.wait_for_state_change().get()
|
||||
self.assertEvent('stream_changed', uri=self.uris[0])
|
||||
|
||||
self.audio.prepare_change()
|
||||
self.audio.set_uri(self.uris[1])
|
||||
self.audio.pause_playback()
|
||||
|
||||
self.audio.wait_for_state_change().get()
|
||||
self.assertEvent('stream_changed', uri=self.uris[1])
|
||||
|
||||
def test_stream_changed_event_on_playing_to_paused(self):
|
||||
self.audio.prepare_change()
|
||||
self.audio.set_uri(self.uris[0])
|
||||
self.listener.clear_events()
|
||||
self.audio.start_playback()
|
||||
|
||||
self.audio.wait_for_state_change().get()
|
||||
self.assertEvent('stream_changed', uri=self.uris[0])
|
||||
|
||||
self.listener.clear_events()
|
||||
self.audio.pause_playback()
|
||||
|
||||
self.audio.wait_for_state_change().get()
|
||||
self.assertNotEvent('stream_changed', uri=self.uris[0])
|
||||
|
||||
def test_stream_changed_event_on_paused_to_stopped(self):
|
||||
self.audio.prepare_change()
|
||||
self.audio.set_uri(self.uris[0])
|
||||
self.audio.pause_playback()
|
||||
|
||||
self.audio.wait_for_state_change()
|
||||
self.listener.clear_events()
|
||||
self.audio.stop_playback()
|
||||
|
||||
self.audio.wait_for_state_change().get()
|
||||
self.assertEvent('stream_changed', uri=None)
|
||||
|
||||
call = mock.call('stream_changed', uri=None)
|
||||
self.assertIn(call, send_mock.call_args_list)
|
||||
|
||||
def test_position_changed_on_pause(self, send_mock):
|
||||
def test_position_changed_on_pause(self):
|
||||
self.audio.prepare_change()
|
||||
self.audio.set_uri(self.uris[0])
|
||||
self.audio.pause_playback()
|
||||
self.audio.wait_for_state_change()
|
||||
|
||||
self.audio.wait_for_state_change().get()
|
||||
self.assertEvent('position_changed', position=0)
|
||||
|
||||
call = mock.call('position_changed', position=0)
|
||||
self.assertIn(call, send_mock.call_args_list)
|
||||
def test_stream_changed_event_on_paused_to_playing(self):
|
||||
self.audio.prepare_change()
|
||||
self.audio.set_uri(self.uris[0])
|
||||
self.listener.clear_events()
|
||||
self.audio.pause_playback()
|
||||
|
||||
def test_position_changed_on_play(self, send_mock):
|
||||
self.audio.wait_for_state_change().get()
|
||||
self.assertEvent('stream_changed', uri=self.uris[0])
|
||||
|
||||
self.listener.clear_events()
|
||||
self.audio.start_playback()
|
||||
|
||||
self.audio.wait_for_state_change().get()
|
||||
self.assertNotEvent('stream_changed', uri=self.uris[0])
|
||||
|
||||
def test_position_changed_on_play(self):
|
||||
self.audio.prepare_change()
|
||||
self.audio.set_uri(self.uris[0])
|
||||
self.audio.start_playback()
|
||||
self.audio.wait_for_state_change()
|
||||
|
||||
self.audio.wait_for_state_change().get()
|
||||
self.assertEvent('position_changed', position=0)
|
||||
|
||||
call = mock.call('position_changed', position=0)
|
||||
self.assertIn(call, send_mock.call_args_list)
|
||||
|
||||
def test_position_changed_on_seek(self, send_mock):
|
||||
def test_position_changed_on_seek_while_stopped(self):
|
||||
self.audio.prepare_change()
|
||||
self.audio.set_uri(self.uris[0])
|
||||
self.audio.set_position(2000)
|
||||
|
||||
self.audio.wait_for_state_change().get()
|
||||
self.assertNotEvent('position_changed', position=0)
|
||||
|
||||
call = mock.call('position_changed', position=0)
|
||||
self.assertNotIn(call, send_mock.call_args_list)
|
||||
|
||||
def test_position_changed_on_seek_after_play(self, send_mock):
|
||||
def test_position_changed_on_seek_after_play(self):
|
||||
self.audio.prepare_change()
|
||||
self.audio.set_uri(self.uris[0])
|
||||
self.audio.start_playback()
|
||||
|
||||
self.audio.wait_for_state_change()
|
||||
self.listener.clear_events()
|
||||
self.audio.set_position(2000)
|
||||
|
||||
self.audio.wait_for_state_change().get()
|
||||
self.assertEvent('position_changed', position=2000)
|
||||
|
||||
call = mock.call('position_changed', position=2000)
|
||||
self.assertIn(call, send_mock.call_args_list)
|
||||
|
||||
def test_position_changed_on_seek_after_pause(self, send_mock):
|
||||
def test_position_changed_on_seek_after_pause(self):
|
||||
self.audio.prepare_change()
|
||||
self.audio.set_uri(self.uris[0])
|
||||
self.audio.pause_playback()
|
||||
|
||||
self.audio.wait_for_state_change()
|
||||
self.listener.clear_events()
|
||||
self.audio.set_position(2000)
|
||||
|
||||
self.audio.wait_for_state_change().get()
|
||||
self.assertEvent('position_changed', position=2000)
|
||||
|
||||
call = mock.call('position_changed', position=2000)
|
||||
self.assertIn(call, send_mock.call_args_list)
|
||||
|
||||
def test_tags_changed_on_playback(self, send_mock):
|
||||
def test_tags_changed_on_playback(self):
|
||||
self.audio.prepare_change()
|
||||
self.audio.set_uri(self.uris[0])
|
||||
self.audio.start_playback()
|
||||
self.audio.wait_for_state_change().get()
|
||||
|
||||
send_mock.assert_any_call('tags_changed', tags=mock.ANY)
|
||||
self.assertEvent('tags_changed', tags=mock.ANY)
|
||||
|
||||
# Unlike the other events, having the state changed done is not
|
||||
# enough to ensure our event is called. So we setup a threading
|
||||
# event that we can wait for with a timeout while the track playback
|
||||
# completes.
|
||||
|
||||
def test_stream_changed_event_on_paused(self, send_mock):
|
||||
event = threading.Event()
|
||||
|
||||
def send(name, **kwargs):
|
||||
if name == 'stream_changed':
|
||||
event.set()
|
||||
send_mock.side_effect = send
|
||||
def test_stream_changed_event_on_paused(self):
|
||||
event = self.listener.wait('stream_changed').get()
|
||||
|
||||
self.audio.prepare_change()
|
||||
self.audio.set_uri(self.uris[0])
|
||||
@ -322,13 +389,10 @@ class AudioEventTest(BaseTest):
|
||||
if not event.wait(timeout=1.0):
|
||||
self.fail('Stream changed not reached within deadline')
|
||||
|
||||
def test_reached_end_of_stream_event(self, send_mock):
|
||||
event = threading.Event()
|
||||
self.assertEvent('stream_changed', uri=self.uris[0])
|
||||
|
||||
def send(name, **kwargs):
|
||||
if name == 'reached_end_of_stream':
|
||||
event.set()
|
||||
send_mock.side_effect = send
|
||||
def test_reached_end_of_stream_event(self):
|
||||
event = self.listener.wait('reached_end_of_stream').get()
|
||||
|
||||
self.audio.prepare_change()
|
||||
self.audio.set_uri(self.uris[0])
|
||||
@ -341,21 +405,14 @@ class AudioEventTest(BaseTest):
|
||||
|
||||
self.assertFalse(self.audio.get_current_tags().get())
|
||||
|
||||
def test_gapless(self, send_mock):
|
||||
def test_gapless(self):
|
||||
uris = self.uris[1:]
|
||||
events = []
|
||||
done = threading.Event()
|
||||
event = self.listener.wait('reached_end_of_stream').get()
|
||||
|
||||
def callback():
|
||||
if uris:
|
||||
self.audio.set_uri(uris.pop()).get()
|
||||
|
||||
def send(name, **kwargs):
|
||||
events.append((name, kwargs))
|
||||
if name == 'reached_end_of_stream':
|
||||
done.set()
|
||||
|
||||
send_mock.side_effect = send
|
||||
self.audio.set_about_to_finish_callback(callback).get()
|
||||
|
||||
self.audio.prepare_change()
|
||||
@ -367,15 +424,15 @@ class AudioEventTest(BaseTest):
|
||||
|
||||
self.possibly_trigger_fake_about_to_finish()
|
||||
self.audio.wait_for_state_change().get()
|
||||
if not done.wait(timeout=1.0):
|
||||
if not event.wait(timeout=1.0):
|
||||
self.fail('EOS not received')
|
||||
|
||||
# Check that both uris got played
|
||||
self.assertIn(('stream_changed', {'uri': self.uris[0]}), events)
|
||||
self.assertIn(('stream_changed', {'uri': self.uris[1]}), events)
|
||||
self.assertEvent('stream_changed', uri=self.uris[0])
|
||||
self.assertEvent('stream_changed', uri=self.uris[1])
|
||||
|
||||
# Check that events counts check out.
|
||||
keys = [k for k, v in events]
|
||||
keys = [k for k, v in self.listener.get_events().get()]
|
||||
self.assertEqual(2, keys.count('stream_changed'))
|
||||
self.assertEqual(2, keys.count('position_changed'))
|
||||
self.assertEqual(1, keys.count('state_changed'))
|
||||
@ -383,17 +440,12 @@ class AudioEventTest(BaseTest):
|
||||
|
||||
# TODO: test tag states within gaples
|
||||
|
||||
def test_current_tags_are_blank_to_begin_with(self, send_mock):
|
||||
# TODO: this does not belong in this testcase
|
||||
def test_current_tags_are_blank_to_begin_with(self):
|
||||
self.assertFalse(self.audio.get_current_tags().get())
|
||||
|
||||
def test_current_tags_blank_after_end_of_stream(self, send_mock):
|
||||
done = threading.Event()
|
||||
|
||||
def send(name, **kwargs):
|
||||
if name == 'reached_end_of_stream':
|
||||
done.set()
|
||||
|
||||
send_mock.side_effect = send
|
||||
def test_current_tags_blank_after_end_of_stream(self):
|
||||
event = self.listener.wait('reached_end_of_stream').get()
|
||||
|
||||
self.audio.prepare_change()
|
||||
self.audio.set_uri(self.uris[0])
|
||||
@ -402,23 +454,18 @@ class AudioEventTest(BaseTest):
|
||||
self.possibly_trigger_fake_about_to_finish()
|
||||
self.audio.wait_for_state_change().get()
|
||||
|
||||
if not done.wait(timeout=1.0):
|
||||
if not event.wait(timeout=1.0):
|
||||
self.fail('EOS not received')
|
||||
|
||||
self.assertFalse(self.audio.get_current_tags().get())
|
||||
|
||||
def test_current_tags_stored(self, send_mock):
|
||||
done = threading.Event()
|
||||
def test_current_tags_stored(self):
|
||||
event = self.listener.wait('reached_end_of_stream').get()
|
||||
tags = []
|
||||
|
||||
def callback():
|
||||
tags.append(self.audio.get_current_tags().get())
|
||||
|
||||
def send(name, **kwargs):
|
||||
if name == 'reached_end_of_stream':
|
||||
done.set()
|
||||
|
||||
send_mock.side_effect = send
|
||||
self.audio.set_about_to_finish_callback(callback).get()
|
||||
|
||||
self.audio.prepare_change()
|
||||
@ -428,7 +475,7 @@ class AudioEventTest(BaseTest):
|
||||
self.possibly_trigger_fake_about_to_finish()
|
||||
self.audio.wait_for_state_change().get()
|
||||
|
||||
if not done.wait(timeout=1.0):
|
||||
if not event.wait(timeout=1.0):
|
||||
self.fail('EOS not received')
|
||||
|
||||
self.assertTrue(tags[0])
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -15,6 +15,7 @@ def create_proxy(config=None, mixer=None):
|
||||
return DummyAudio.start(config, mixer).proxy()
|
||||
|
||||
|
||||
# TODO: reset position on track change?
|
||||
class DummyAudio(pykka.ThreadingActor):
|
||||
|
||||
def __init__(self, config=None, mixer=None):
|
||||
@ -24,13 +25,15 @@ class DummyAudio(pykka.ThreadingActor):
|
||||
self._position = 0
|
||||
self._callback = None
|
||||
self._uri = None
|
||||
self._state_change_result = True
|
||||
self._stream_changed = False
|
||||
self._tags = {}
|
||||
self._bad_uris = set()
|
||||
|
||||
def set_uri(self, uri):
|
||||
assert self._uri is None, 'prepare change not called before set'
|
||||
self._tags = {}
|
||||
self._uri = uri
|
||||
self._stream_changed = True
|
||||
|
||||
def set_appsrc(self, *args, **kwargs):
|
||||
pass
|
||||
@ -88,12 +91,15 @@ class DummyAudio(pykka.ThreadingActor):
|
||||
if not self._uri:
|
||||
return False
|
||||
|
||||
if self.state == audio.PlaybackState.STOPPED and self._uri:
|
||||
audio.AudioListener.send('position_changed', position=0)
|
||||
audio.AudioListener.send('stream_changed', uri=self._uri)
|
||||
|
||||
if new_state == audio.PlaybackState.STOPPED:
|
||||
if new_state == audio.PlaybackState.STOPPED and self._uri:
|
||||
self._stream_changed = True
|
||||
self._uri = None
|
||||
|
||||
if self._uri is not None:
|
||||
audio.AudioListener.send('position_changed', position=0)
|
||||
|
||||
if self._stream_changed:
|
||||
self._stream_changed = False
|
||||
audio.AudioListener.send('stream_changed', uri=self._uri)
|
||||
|
||||
old_state, self.state = self.state, new_state
|
||||
@ -105,10 +111,10 @@ class DummyAudio(pykka.ThreadingActor):
|
||||
self._tags['audio-codec'] = [u'fake info...']
|
||||
audio.AudioListener.send('tags_changed', tags=['audio-codec'])
|
||||
|
||||
return self._state_change_result
|
||||
return self._uri not in self._bad_uris
|
||||
|
||||
def trigger_fake_playback_failure(self):
|
||||
self._state_change_result = False
|
||||
def trigger_fake_playback_failure(self, uri):
|
||||
self._bad_uris.add(uri)
|
||||
|
||||
def trigger_fake_tags_changed(self, tags):
|
||||
self._tags.update(tags)
|
||||
|
||||
@ -22,7 +22,10 @@ class DummyBackend(pykka.ThreadingActor, backend.Backend):
|
||||
super(DummyBackend, self).__init__()
|
||||
|
||||
self.library = DummyLibraryProvider(backend=self)
|
||||
self.playback = DummyPlaybackProvider(audio=audio, backend=self)
|
||||
if audio:
|
||||
self.playback = backend.PlaybackProvider(audio=audio, backend=self)
|
||||
else:
|
||||
self.playback = DummyPlaybackProvider(audio=audio, backend=self)
|
||||
self.playlists = DummyPlaylistsProvider(backend=self)
|
||||
|
||||
self.uri_schemes = ['dummy']
|
||||
|
||||
@ -111,17 +111,17 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
def test_play_mp3(self):
|
||||
self.add_track('local:track:blank.mp3')
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assert_state_is(PlaybackState.PLAYING)
|
||||
|
||||
def test_play_ogg(self):
|
||||
self.add_track('local:track:blank.ogg')
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assert_state_is(PlaybackState.PLAYING)
|
||||
|
||||
def test_play_flac(self):
|
||||
self.add_track('local:track:blank.flac')
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assert_state_is(PlaybackState.PLAYING)
|
||||
|
||||
def test_play_uri_with_non_ascii_bytes(self):
|
||||
@ -129,7 +129,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
# string will be decoded from ASCII to Unicode, which will crash on
|
||||
# non-ASCII strings, like the bytestring the following URI decodes to.
|
||||
self.add_track('local:track:12%20Doin%E2%80%99%20It%20Right.flac')
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assert_state_is(PlaybackState.PLAYING)
|
||||
|
||||
def test_initial_state_is_stopped(self):
|
||||
@ -137,7 +137,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
def test_play_with_empty_playlist(self):
|
||||
self.assert_state_is(PlaybackState.STOPPED)
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assert_state_is(PlaybackState.STOPPED)
|
||||
|
||||
def test_play_with_empty_playlist_return_value(self):
|
||||
@ -146,7 +146,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
@populate_tracklist
|
||||
def test_play_state(self):
|
||||
self.assert_state_is(PlaybackState.STOPPED)
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assert_state_is(PlaybackState.PLAYING)
|
||||
|
||||
@populate_tracklist
|
||||
@ -156,7 +156,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
@populate_tracklist
|
||||
def test_play_track_state(self):
|
||||
self.assert_state_is(PlaybackState.STOPPED)
|
||||
self.playback.play(self.tl_tracks.get()[-1])
|
||||
self.playback.play(self.tl_tracks.get()[-1]).get()
|
||||
self.assert_state_is(PlaybackState.PLAYING)
|
||||
|
||||
@populate_tracklist
|
||||
@ -165,139 +165,141 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_play_when_playing(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
track = self.playback.get_current_track().get()
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assert_current_track_is(track)
|
||||
|
||||
@populate_tracklist
|
||||
def test_play_when_paused(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
track = self.playback.get_current_track().get()
|
||||
self.playback.pause()
|
||||
self.playback.play()
|
||||
self.playback.pause().get()
|
||||
self.playback.play().get()
|
||||
self.assert_state_is(PlaybackState.PLAYING)
|
||||
self.assert_current_track_is(track)
|
||||
|
||||
@populate_tracklist
|
||||
def test_play_when_pause_after_next(self):
|
||||
self.playback.play()
|
||||
self.playback.next()
|
||||
self.playback.next()
|
||||
def test_play_when_paused_after_next(self):
|
||||
self.playback.play().get()
|
||||
self.playback.next().get()
|
||||
self.playback.next().get()
|
||||
track = self.playback.get_current_track().get()
|
||||
self.playback.pause()
|
||||
self.playback.play()
|
||||
self.playback.pause().get()
|
||||
self.playback.play().get()
|
||||
self.assert_state_is(PlaybackState.PLAYING)
|
||||
self.assert_current_track_is(track)
|
||||
|
||||
@populate_tracklist
|
||||
def test_play_sets_current_track(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assert_current_track_is(self.tracks[0])
|
||||
|
||||
@populate_tracklist
|
||||
def test_play_track_sets_current_track(self):
|
||||
self.playback.play(self.tl_tracks.get()[-1])
|
||||
self.playback.play(self.tl_tracks.get()[-1]).get()
|
||||
self.assert_current_track_is(self.tracks[-1])
|
||||
|
||||
@populate_tracklist
|
||||
def test_play_skips_to_next_track_on_failure(self):
|
||||
# If backend's play() returns False, it is a failure.
|
||||
return_values = [True, False]
|
||||
self.backend.playback.play = lambda: return_values.pop()
|
||||
self.playback.play()
|
||||
uri = self.backend.playback.translate_uri(self.tracks[0].uri).get()
|
||||
self.audio.trigger_fake_playback_failure(uri)
|
||||
|
||||
self.playback.play().get()
|
||||
self.assert_current_track_is_not(self.tracks[0])
|
||||
self.assert_current_track_is(self.tracks[1])
|
||||
|
||||
@populate_tracklist
|
||||
def test_current_track_after_completed_playlist(self):
|
||||
self.playback.play(self.tl_tracks.get()[-1])
|
||||
self.playback.play(self.tl_tracks.get()[-1]).get()
|
||||
self.trigger_about_to_finish()
|
||||
# EOS should have triggered
|
||||
self.assert_state_is(PlaybackState.STOPPED)
|
||||
self.assert_current_track_is(None)
|
||||
|
||||
self.playback.play(self.tl_tracks.get()[-1])
|
||||
self.playback.next()
|
||||
self.playback.play(self.tl_tracks.get()[-1]).get()
|
||||
self.playback.next().get()
|
||||
self.assert_state_is(PlaybackState.STOPPED)
|
||||
self.assert_current_track_is(None)
|
||||
|
||||
@populate_tracklist
|
||||
def test_previous(self):
|
||||
self.playback.play()
|
||||
self.playback.next()
|
||||
self.playback.previous()
|
||||
self.playback.play().get()
|
||||
self.playback.next().get()
|
||||
self.playback.previous().get()
|
||||
self.assert_current_track_is(self.tracks[0])
|
||||
|
||||
@populate_tracklist
|
||||
def test_previous_more(self):
|
||||
self.playback.play() # At track 0
|
||||
self.playback.next() # At track 1
|
||||
self.playback.next() # At track 2
|
||||
self.playback.previous() # At track 1
|
||||
self.playback.play().get() # At track 0
|
||||
self.playback.next().get() # At track 1
|
||||
self.playback.next().get() # At track 2
|
||||
self.playback.previous().get() # At track 1
|
||||
self.assert_current_track_is(self.tracks[1])
|
||||
|
||||
@populate_tracklist
|
||||
def test_previous_return_value(self):
|
||||
self.playback.play()
|
||||
self.playback.next()
|
||||
self.playback.play().get()
|
||||
self.playback.next().get()
|
||||
self.assertIsNone(self.playback.previous().get())
|
||||
|
||||
@populate_tracklist
|
||||
def test_previous_does_not_trigger_playback(self):
|
||||
self.playback.play()
|
||||
self.playback.next()
|
||||
self.playback.play().get()
|
||||
self.playback.next().get()
|
||||
self.playback.stop()
|
||||
self.playback.previous()
|
||||
self.playback.previous().get()
|
||||
self.assert_state_is(PlaybackState.STOPPED)
|
||||
|
||||
@populate_tracklist
|
||||
def test_previous_at_start_of_playlist(self):
|
||||
self.playback.previous()
|
||||
self.playback.previous().get()
|
||||
self.assert_state_is(PlaybackState.STOPPED)
|
||||
self.assert_current_track_is(None)
|
||||
|
||||
def test_previous_for_empty_playlist(self):
|
||||
self.playback.previous()
|
||||
self.playback.previous().get()
|
||||
self.assert_state_is(PlaybackState.STOPPED)
|
||||
self.assert_current_track_is(None)
|
||||
|
||||
@populate_tracklist
|
||||
def test_previous_skips_to_previous_track_on_failure(self):
|
||||
# If backend's play() returns False, it is a failure.
|
||||
return_values = [True, False, True]
|
||||
self.backend.playback.play = lambda: return_values.pop()
|
||||
self.playback.play(self.tl_tracks.get()[2])
|
||||
uri = self.backend.playback.translate_uri(self.tracks[1].uri).get()
|
||||
self.audio.trigger_fake_playback_failure(uri)
|
||||
|
||||
self.playback.play(self.tl_tracks.get()[2]).get()
|
||||
self.assert_current_track_is(self.tracks[2])
|
||||
self.playback.previous()
|
||||
self.playback.previous().get()
|
||||
self.assert_current_track_is_not(self.tracks[1])
|
||||
self.assert_current_track_is(self.tracks[0])
|
||||
|
||||
@populate_tracklist
|
||||
def test_next(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
|
||||
old_track = self.playback.get_current_track().get()
|
||||
old_position = self.tracklist.index().get()
|
||||
|
||||
self.playback.next()
|
||||
self.playback.next().get()
|
||||
|
||||
self.assertEqual(self.tracklist.index().get(), old_position + 1)
|
||||
self.assert_current_track_is_not(old_track)
|
||||
|
||||
@populate_tracklist
|
||||
def test_next_return_value(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assertEqual(self.playback.next().get(), None)
|
||||
|
||||
@populate_tracklist
|
||||
def test_next_does_not_trigger_playback(self):
|
||||
self.playback.next()
|
||||
self.playback.next().get()
|
||||
self.assert_state_is(PlaybackState.STOPPED)
|
||||
|
||||
@populate_tracklist
|
||||
def test_next_at_end_of_playlist(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
|
||||
for i, track in enumerate(self.tracks):
|
||||
self.assert_state_is(PlaybackState.PLAYING)
|
||||
@ -310,30 +312,31 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_next_until_end_of_playlist_and_play_from_start(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
|
||||
for _ in self.tracks:
|
||||
self.playback.next()
|
||||
self.playback.next().get()
|
||||
|
||||
self.assert_current_track_is(None)
|
||||
self.assert_state_is(PlaybackState.STOPPED)
|
||||
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assert_state_is(PlaybackState.PLAYING)
|
||||
self.assert_current_track_is(self.tracks[0])
|
||||
|
||||
def test_next_for_empty_playlist(self):
|
||||
self.playback.next()
|
||||
self.playback.next().get()
|
||||
self.assert_state_is(PlaybackState.STOPPED)
|
||||
|
||||
@populate_tracklist
|
||||
def test_next_skips_to_next_track_on_failure(self):
|
||||
# If backend's play() returns False, it is a failure.
|
||||
return_values = [True, False, True]
|
||||
self.backend.playback.play = lambda: return_values.pop()
|
||||
self.playback.play()
|
||||
uri = self.backend.playback.translate_uri(self.tracks[1].uri).get()
|
||||
self.audio.trigger_fake_playback_failure(uri)
|
||||
|
||||
self.playback.play().get()
|
||||
self.assert_current_track_is(self.tracks[0])
|
||||
self.playback.next()
|
||||
self.playback.next().get()
|
||||
self.assert_current_track_is_not(self.tracks[1])
|
||||
self.assert_current_track_is(self.tracks[2])
|
||||
|
||||
@ -343,14 +346,14 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_next_track_during_play(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assert_next_tl_track_is(self.tl_tracks.get()[1])
|
||||
|
||||
@populate_tracklist
|
||||
def test_next_track_after_previous(self):
|
||||
self.playback.play()
|
||||
self.playback.next()
|
||||
self.playback.previous()
|
||||
self.playback.play().get()
|
||||
self.playback.next().get()
|
||||
self.playback.previous().get()
|
||||
self.assert_next_tl_track_is(self.tl_tracks.get()[1])
|
||||
|
||||
def test_next_track_empty_playlist(self):
|
||||
@ -358,17 +361,17 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_next_track_at_end_of_playlist(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
for _ in self.tl_tracks.get()[1:]:
|
||||
self.playback.next()
|
||||
self.playback.next().get()
|
||||
self.assert_next_tl_track_is(None)
|
||||
|
||||
@populate_tracklist
|
||||
def test_next_track_at_end_of_playlist_with_repeat(self):
|
||||
self.tracklist.repeat = True
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
for _ in self.tracks[1:]:
|
||||
self.playback.next()
|
||||
self.playback.next().get()
|
||||
self.assert_next_tl_track_is(self.tl_tracks.get()[0])
|
||||
|
||||
@populate_tracklist
|
||||
@ -382,17 +385,17 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
@populate_tracklist
|
||||
def test_next_with_consume(self):
|
||||
self.tracklist.consume = True
|
||||
self.playback.play()
|
||||
self.playback.next()
|
||||
self.playback.play().get()
|
||||
self.playback.next().get()
|
||||
self.assertNotIn(self.tracks[0], self.tracklist.get_tracks().get())
|
||||
|
||||
@populate_tracklist
|
||||
def test_next_with_single_and_repeat(self):
|
||||
self.tracklist.single = True
|
||||
self.tracklist.repeat = True
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assert_current_track_is(self.tracks[0])
|
||||
self.playback.next()
|
||||
self.playback.next().get()
|
||||
self.assert_current_track_is(self.tracks[1])
|
||||
|
||||
@populate_tracklist
|
||||
@ -401,9 +404,9 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
shuffle_mock.side_effect = lambda tracks: tracks.reverse()
|
||||
|
||||
self.tracklist.random = True
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assert_current_track_is(self.tracks[-1])
|
||||
self.playback.next()
|
||||
self.playback.next().get()
|
||||
self.assert_current_track_is(self.tracks[-2])
|
||||
|
||||
@populate_tracklist
|
||||
@ -434,7 +437,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_end_of_track(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
|
||||
old_track = self.playback.get_current_track().get()
|
||||
old_position = self.tracklist.index().get()
|
||||
@ -447,7 +450,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_end_of_track_return_value(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assertEqual(self.trigger_about_to_finish(), None)
|
||||
|
||||
@populate_tracklist
|
||||
@ -457,7 +460,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_end_of_track_at_end_of_playlist(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
|
||||
for i, track in enumerate(self.tracks):
|
||||
self.assert_state_is(PlaybackState.PLAYING)
|
||||
@ -470,7 +473,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_end_of_track_until_end_of_playlist_and_play_from_start(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
|
||||
for _ in self.tracks:
|
||||
self.trigger_about_to_finish()
|
||||
@ -478,7 +481,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
self.assertEqual(self.playback.get_current_track().get(), None)
|
||||
self.assert_state_is(PlaybackState.STOPPED)
|
||||
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assert_state_is(PlaybackState.PLAYING)
|
||||
self.assert_current_track_is(self.tracks[0])
|
||||
|
||||
@ -486,12 +489,14 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
self.trigger_about_to_finish()
|
||||
self.assert_state_is(PlaybackState.STOPPED)
|
||||
|
||||
# TODO: On about to finish does not handle skipping to next track yet.
|
||||
@unittest.expectedFailure
|
||||
@populate_tracklist
|
||||
def test_end_of_track_skips_to_next_track_on_failure(self):
|
||||
# If backend's play() returns False, it is a failure.
|
||||
return_values = [True, False, True]
|
||||
self.backend.playback.play = lambda: return_values.pop()
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assert_current_track_is(self.tracks[0])
|
||||
self.trigger_about_to_finish()
|
||||
self.assert_current_track_is_not(self.tracks[1])
|
||||
@ -503,7 +508,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_end_of_track_track_during_play(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assert_next_tl_track_is(self.tl_tracks.get()[1])
|
||||
|
||||
@populate_tracklist
|
||||
@ -518,7 +523,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_end_of_track_track_at_end_of_playlist(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
for _ in self.tracks[1:]:
|
||||
self.trigger_about_to_finish()
|
||||
|
||||
@ -527,7 +532,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
@populate_tracklist
|
||||
def test_end_of_track_track_at_end_of_playlist_with_repeat(self):
|
||||
self.tracklist.repeat = True
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
for _ in self.tracks[1:]:
|
||||
self.trigger_about_to_finish()
|
||||
|
||||
@ -544,7 +549,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
@populate_tracklist
|
||||
def test_end_of_track_with_consume(self):
|
||||
self.tracklist.consume = True
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.trigger_about_to_finish()
|
||||
self.assertNotIn(self.tracks[0], self.tracklist.get_tracks().get())
|
||||
|
||||
@ -554,7 +559,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
shuffle_mock.side_effect = lambda tracks: tracks.reverse()
|
||||
|
||||
self.tracklist.random = True
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assert_current_track_is(self.tracks[-1])
|
||||
self.trigger_about_to_finish()
|
||||
self.assert_current_track_is(self.tracks[-2])
|
||||
@ -592,21 +597,21 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_previous_track_after_play(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assert_previous_tl_track_is(None)
|
||||
|
||||
@populate_tracklist
|
||||
def test_previous_track_after_next(self):
|
||||
self.playback.play()
|
||||
self.playback.next()
|
||||
self.playback.play().get()
|
||||
self.playback.next().get()
|
||||
self.assert_previous_tl_track_is(self.tl_tracks.get()[0])
|
||||
|
||||
@populate_tracklist
|
||||
def test_previous_track_after_previous(self):
|
||||
self.playback.play() # At track 0
|
||||
self.playback.next() # At track 1
|
||||
self.playback.next() # At track 2
|
||||
self.playback.previous() # At track 1
|
||||
self.playback.play().get() # At track 0
|
||||
self.playback.next().get() # At track 1
|
||||
self.playback.next().get() # At track 2
|
||||
self.playback.previous().get() # At track 1
|
||||
self.assert_previous_tl_track_is(self.tl_tracks.get()[0])
|
||||
|
||||
def test_previous_track_empty_playlist(self):
|
||||
@ -634,13 +639,13 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_current_track_during_play(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assert_current_track_is(self.tracks[0])
|
||||
|
||||
@populate_tracklist
|
||||
def test_current_track_after_next(self):
|
||||
self.playback.play()
|
||||
self.playback.next()
|
||||
self.playback.next().get()
|
||||
self.assert_current_track_is(self.tracks[1])
|
||||
|
||||
@populate_tracklist
|
||||
@ -649,18 +654,18 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_tracklist_position_during_play(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assert_current_track_index_is(0)
|
||||
|
||||
@populate_tracklist
|
||||
def test_tracklist_position_after_next(self):
|
||||
self.playback.play()
|
||||
self.playback.next()
|
||||
self.playback.play().get()
|
||||
self.playback.next().get()
|
||||
self.assert_current_track_index_is(1)
|
||||
|
||||
@populate_tracklist
|
||||
def test_tracklist_position_at_end_of_playlist(self):
|
||||
self.playback.play(self.tl_tracks.get()[-1])
|
||||
self.playback.play(self.tl_tracks.get()[-1]).get()
|
||||
self.trigger_about_to_finish()
|
||||
# EOS should have triggered
|
||||
self.assert_current_track_index_is(None)
|
||||
@ -672,7 +677,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_on_tracklist_change_when_playing(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
current_track = self.playback.get_current_track().get()
|
||||
self.tracklist.add([self.tracks[2]])
|
||||
self.assert_state_is(PlaybackState.PLAYING)
|
||||
@ -686,7 +691,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_on_tracklist_change_when_paused(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.playback.pause()
|
||||
current_track = self.playback.get_current_track().get()
|
||||
self.tracklist.add([self.tracks[2]])
|
||||
@ -700,20 +705,20 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_pause_when_playing(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.playback.pause()
|
||||
self.assert_state_is(PlaybackState.PAUSED)
|
||||
|
||||
@populate_tracklist
|
||||
def test_pause_when_paused(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.playback.pause()
|
||||
self.playback.pause()
|
||||
self.assert_state_is(PlaybackState.PAUSED)
|
||||
|
||||
@populate_tracklist
|
||||
def test_pause_return_value(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assertIsNone(self.playback.pause().get())
|
||||
|
||||
@populate_tracklist
|
||||
@ -723,27 +728,27 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_resume_when_playing(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.playback.resume()
|
||||
self.assert_state_is(PlaybackState.PLAYING)
|
||||
|
||||
@populate_tracklist
|
||||
def test_resume_when_paused(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.playback.pause()
|
||||
self.playback.resume()
|
||||
self.assert_state_is(PlaybackState.PLAYING)
|
||||
|
||||
@populate_tracklist
|
||||
def test_resume_return_value(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.playback.pause()
|
||||
self.assertIsNone(self.playback.resume().get())
|
||||
|
||||
@unittest.SkipTest # Uses sleep and might not work with LocalBackend
|
||||
@populate_tracklist
|
||||
def test_resume_continues_from_right_position(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
time.sleep(0.2)
|
||||
self.playback.pause()
|
||||
self.playback.resume()
|
||||
@ -756,7 +761,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_seek_when_stopped_updates_position(self):
|
||||
self.playback.seek(1000)
|
||||
self.playback.seek(1000).get()
|
||||
position = self.playback.time_position
|
||||
self.assertGreaterEqual(position, 990)
|
||||
|
||||
@ -764,31 +769,31 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
self.assertFalse(self.playback.seek(0).get())
|
||||
|
||||
def test_seek_on_empty_playlist_updates_position(self):
|
||||
self.playback.seek(0)
|
||||
self.playback.seek(0).get()
|
||||
self.assert_state_is(PlaybackState.STOPPED)
|
||||
|
||||
@populate_tracklist
|
||||
def test_seek_when_stopped_triggers_play(self):
|
||||
self.playback.seek(0)
|
||||
self.playback.seek(0).get()
|
||||
self.assert_state_is(PlaybackState.PLAYING)
|
||||
|
||||
@populate_tracklist
|
||||
def test_seek_when_playing(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
result = self.playback.seek(self.tracks[0].length - 1000)
|
||||
self.assert_(result, 'Seek return value was %s' % result)
|
||||
|
||||
@populate_tracklist
|
||||
def test_seek_when_playing_updates_position(self):
|
||||
length = self.tracks[0].length
|
||||
self.playback.play()
|
||||
self.playback.seek(length - 1000)
|
||||
self.playback.play().get()
|
||||
self.playback.seek(length - 1000).get()
|
||||
position = self.playback.get_time_position().get()
|
||||
self.assertGreaterEqual(position, length - 1010)
|
||||
|
||||
@populate_tracklist
|
||||
def test_seek_when_paused(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.playback.pause()
|
||||
result = self.playback.seek(self.tracks[0].length - 1000)
|
||||
self.assert_(result, 'Seek return value was %s' % result)
|
||||
@ -797,7 +802,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
@populate_tracklist
|
||||
def test_seek_when_paused_updates_position(self):
|
||||
length = self.tracks[0].length
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.playback.pause()
|
||||
self.playback.seek(length - 1000)
|
||||
position = self.playback.get_time_position().get()
|
||||
@ -807,19 +812,19 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
@populate_tracklist
|
||||
def test_seek_beyond_end_of_song(self):
|
||||
# FIXME need to decide return value
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
result = self.playback.seek(self.tracks[0].length * 100)
|
||||
self.assert_(not result, 'Seek return value was %s' % result)
|
||||
|
||||
@populate_tracklist
|
||||
def test_seek_beyond_end_of_song_jumps_to_next_song(self):
|
||||
self.playback.play()
|
||||
self.playback.seek(self.tracks[0].length * 100)
|
||||
self.playback.play().get()
|
||||
self.playback.seek(self.tracks[0].length * 100).get()
|
||||
self.assert_current_track_is(self.tracks[1])
|
||||
|
||||
@populate_tracklist
|
||||
def test_seek_beyond_end_of_song_for_last_track(self):
|
||||
self.playback.play(self.tl_tracks.get()[-1])
|
||||
self.playback.play(self.tl_tracks.get()[-1]).get()
|
||||
self.playback.seek(self.tracks[-1].length * 100)
|
||||
self.assert_state_is(PlaybackState.STOPPED)
|
||||
|
||||
@ -830,19 +835,19 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_stop_when_playing(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.playback.stop()
|
||||
self.assert_state_is(PlaybackState.STOPPED)
|
||||
|
||||
@populate_tracklist
|
||||
def test_stop_when_paused(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.playback.pause()
|
||||
self.playback.stop()
|
||||
self.assert_state_is(PlaybackState.STOPPED)
|
||||
|
||||
def test_stop_return_value(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assertIsNone(self.playback.stop().get())
|
||||
|
||||
def test_time_position_when_stopped(self):
|
||||
@ -855,7 +860,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
@unittest.SkipTest # Uses sleep and does might not work with LocalBackend
|
||||
@populate_tracklist
|
||||
def test_time_position_when_playing(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
first = self.playback.time_position
|
||||
time.sleep(1)
|
||||
second = self.playback.time_position
|
||||
@ -863,7 +868,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_time_position_when_paused(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.playback.pause().get()
|
||||
first = self.playback.get_time_position().get()
|
||||
second = self.playback.get_time_position().get()
|
||||
@ -872,13 +877,13 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
@populate_tracklist
|
||||
def test_play_with_consume(self):
|
||||
self.tracklist.consume = True
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assert_current_track_is(self.tracks[0])
|
||||
|
||||
@populate_tracklist
|
||||
def test_playlist_is_empty_after_all_tracks_are_played_with_consume(self):
|
||||
self.tracklist.consume = True
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
|
||||
for t in self.tracks:
|
||||
self.trigger_about_to_finish()
|
||||
@ -892,7 +897,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
shuffle_mock.side_effect = lambda tracks: tracks.reverse()
|
||||
|
||||
self.tracklist.random = True
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assert_current_track_is(self.tracks[-1])
|
||||
|
||||
@populate_tracklist
|
||||
@ -901,15 +906,15 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
shuffle_mock.side_effect = lambda tracks: tracks.reverse()
|
||||
|
||||
self.tracklist.random = True
|
||||
self.playback.play()
|
||||
self.playback.next()
|
||||
self.playback.play().get()
|
||||
self.playback.next().get()
|
||||
current_track = self.playback.get_current_track().get()
|
||||
self.playback.previous()
|
||||
self.assert_current_track_is(current_track)
|
||||
|
||||
@populate_tracklist
|
||||
def test_end_of_song_starts_next_track(self):
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.trigger_about_to_finish()
|
||||
self.assert_current_track_is(self.tracks[1])
|
||||
|
||||
@ -917,7 +922,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
def test_end_of_song_with_single_and_repeat_starts_same(self):
|
||||
self.tracklist.single = True
|
||||
self.tracklist.repeat = True
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assert_current_track_is(self.tracks[0])
|
||||
self.trigger_about_to_finish()
|
||||
self.assert_current_track_is(self.tracks[0])
|
||||
@ -927,7 +932,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
self.tracklist.single = True
|
||||
self.tracklist.repeat = True
|
||||
self.tracklist.random = True
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
current_track = self.playback.get_current_track().get()
|
||||
self.trigger_about_to_finish()
|
||||
self.assert_current_track_is(current_track)
|
||||
@ -935,7 +940,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
@populate_tracklist
|
||||
def test_end_of_song_with_single_stops(self):
|
||||
self.tracklist.single = True
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assert_current_track_is(self.tracks[0])
|
||||
self.trigger_about_to_finish()
|
||||
self.assert_current_track_is(None)
|
||||
@ -946,7 +951,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
def test_end_of_song_with_single_and_random_stops(self):
|
||||
self.tracklist.single = True
|
||||
self.tracklist.random = True
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.trigger_about_to_finish()
|
||||
# EOS should have triggered
|
||||
self.assert_current_track_is(None)
|
||||
@ -954,7 +959,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_end_of_playlist_stops(self):
|
||||
self.playback.play(self.tl_tracks.get()[-1])
|
||||
self.playback.play(self.tl_tracks.get()[-1]).get()
|
||||
self.trigger_about_to_finish()
|
||||
# EOS should have triggered
|
||||
self.assert_state_is(PlaybackState.STOPPED)
|
||||
@ -971,15 +976,15 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
@populate_tracklist
|
||||
def test_random_until_end_of_playlist(self):
|
||||
self.tracklist.random = True
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
for _ in self.tracks[1:]:
|
||||
self.playback.next()
|
||||
self.playback.next().get()
|
||||
self.assert_next_tl_track_is(None)
|
||||
|
||||
@populate_tracklist
|
||||
def test_random_with_eot_until_end_of_playlist(self):
|
||||
self.tracklist.random = True
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
for _ in self.tracks[1:]:
|
||||
self.trigger_about_to_finish()
|
||||
|
||||
@ -988,9 +993,9 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
@populate_tracklist
|
||||
def test_random_until_end_of_playlist_and_play_from_start(self):
|
||||
self.tracklist.random = True
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
for _ in self.tracks:
|
||||
self.playback.next()
|
||||
self.playback.next().get()
|
||||
self.assert_next_tl_track_is_not(None)
|
||||
self.assert_state_is(PlaybackState.STOPPED)
|
||||
self.playback.play()
|
||||
@ -999,21 +1004,21 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
@populate_tracklist
|
||||
def test_random_with_eot_until_end_of_playlist_and_play_from_start(self):
|
||||
self.tracklist.random = True
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
for _ in self.tracks:
|
||||
self.trigger_about_to_finish()
|
||||
# EOS should have triggered
|
||||
|
||||
self.assert_eot_tl_track_is_not(None)
|
||||
self.assert_state_is(PlaybackState.STOPPED)
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.assert_state_is(PlaybackState.PLAYING)
|
||||
|
||||
@populate_tracklist
|
||||
def test_random_until_end_of_playlist_with_repeat(self):
|
||||
self.tracklist.repeat = True
|
||||
self.tracklist.random = True
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
for _ in self.tracks[1:]:
|
||||
self.playback.next()
|
||||
self.assert_next_tl_track_is_not(None)
|
||||
@ -1021,13 +1026,13 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
@populate_tracklist
|
||||
def test_played_track_during_random_not_played_again(self):
|
||||
self.tracklist.random = True
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
played = []
|
||||
for _ in self.tracks:
|
||||
track = self.playback.get_current_track().get()
|
||||
self.assertNotIn(track, played)
|
||||
played.append(track)
|
||||
self.playback.next()
|
||||
self.playback.next().get()
|
||||
|
||||
@populate_tracklist
|
||||
@mock.patch('random.shuffle')
|
||||
@ -1038,10 +1043,10 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
expected = self.tl_tracks.get()[::-1] + [None]
|
||||
actual = []
|
||||
|
||||
self.playback.play()
|
||||
self.playback.play().get()
|
||||
self.tracklist.random = True
|
||||
while self.playback.get_state().get() != PlaybackState.STOPPED:
|
||||
self.playback.next()
|
||||
self.playback.next().get()
|
||||
actual.append(self.playback.get_current_tl_track().get())
|
||||
if len(actual) > len(expected):
|
||||
break
|
||||
|
||||
@ -10,7 +10,7 @@ from mopidy import core
|
||||
from mopidy.internal import deprecation
|
||||
from mopidy.mpd import session, uri_mapper
|
||||
|
||||
from tests import dummy_backend, dummy_mixer
|
||||
from tests import dummy_audio, dummy_backend, dummy_mixer
|
||||
|
||||
|
||||
class MockConnection(mock.Mock):
|
||||
@ -44,11 +44,13 @@ class BaseTestCase(unittest.TestCase):
|
||||
self.mixer = dummy_mixer.create_proxy()
|
||||
else:
|
||||
self.mixer = None
|
||||
self.backend = dummy_backend.create_proxy()
|
||||
self.audio = dummy_audio.create_proxy()
|
||||
self.backend = dummy_backend.create_proxy(audio=self.audio)
|
||||
|
||||
with deprecation.ignore():
|
||||
self.core = core.Core.start(
|
||||
self.get_config(),
|
||||
audio=self.audio,
|
||||
mixer=self.mixer,
|
||||
backends=[self.backend]).proxy()
|
||||
|
||||
|
||||
@ -248,7 +248,7 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase):
|
||||
self.assertEqual(self.core.playback.current_track.get(), None)
|
||||
self.core.playback.play()
|
||||
self.core.playback.next()
|
||||
self.core.playback.stop()
|
||||
self.core.playback.stop().get()
|
||||
self.assertNotEqual(self.core.playback.current_track.get(), None)
|
||||
|
||||
self.send_request('play "-1"')
|
||||
@ -266,6 +266,7 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase):
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_play_minus_is_ignored_if_playing(self):
|
||||
self.core.playback.play().get()
|
||||
self.core.playback.seek(30000)
|
||||
self.assertGreaterEqual(
|
||||
self.core.playback.time_position.get(), 30000)
|
||||
@ -278,6 +279,7 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase):
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_play_minus_one_resumes_if_paused(self):
|
||||
self.core.playback.play().get()
|
||||
self.core.playback.seek(30000)
|
||||
self.assertGreaterEqual(
|
||||
self.core.playback.time_position.get(), 30000)
|
||||
@ -312,8 +314,8 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase):
|
||||
|
||||
def test_playid_minus_1_plays_current_track_if_current_track_is_set(self):
|
||||
self.assertEqual(self.core.playback.current_track.get(), None)
|
||||
self.core.playback.play()
|
||||
self.core.playback.next()
|
||||
self.core.playback.play().get()
|
||||
self.core.playback.next().get()
|
||||
self.core.playback.stop()
|
||||
self.assertNotEqual(None, self.core.playback.current_track.get())
|
||||
|
||||
@ -332,6 +334,7 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase):
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_playid_minus_is_ignored_if_playing(self):
|
||||
self.core.playback.play().get()
|
||||
self.core.playback.seek(30000)
|
||||
self.assertGreaterEqual(
|
||||
self.core.playback.time_position.get(), 30000)
|
||||
@ -344,6 +347,7 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase):
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_playid_minus_one_resumes_if_paused(self):
|
||||
self.core.playback.play().get()
|
||||
self.core.playback.seek(30000)
|
||||
self.assertGreaterEqual(
|
||||
self.core.playback.time_position.get(), 30000)
|
||||
@ -417,7 +421,7 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase):
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_seekcur_absolute_value(self):
|
||||
self.core.playback.play()
|
||||
self.core.playback.play().get()
|
||||
|
||||
self.send_request('seekcur "30"')
|
||||
|
||||
@ -425,7 +429,7 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase):
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_seekcur_positive_diff(self):
|
||||
self.core.playback.play()
|
||||
self.core.playback.play().get()
|
||||
self.core.playback.seek(10000)
|
||||
self.assertGreaterEqual(self.core.playback.time_position.get(), 10000)
|
||||
|
||||
@ -435,7 +439,7 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase):
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_seekcur_negative_diff(self):
|
||||
self.core.playback.play()
|
||||
self.core.playback.play().get()
|
||||
self.core.playback.seek(30000)
|
||||
self.assertGreaterEqual(self.core.playback.time_position.get(), 30000)
|
||||
|
||||
|
||||
@ -31,6 +31,7 @@ class IssueGH17RegressionTest(protocol.BaseTestCase):
|
||||
Track(uri='dummy:e'),
|
||||
Track(uri='dummy:f'),
|
||||
]
|
||||
self.audio.trigger_fake_playback_failure('dummy:error')
|
||||
self.backend.library.dummy_library = tracks
|
||||
self.core.tracklist.add(uris=[t.uri for t in tracks]).get()
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ class StatusHandlerTest(protocol.BaseTestCase):
|
||||
self.backend.library.dummy_library = [track]
|
||||
self.core.tracklist.add(uris=[track.uri]).get()
|
||||
|
||||
self.core.playback.play()
|
||||
self.core.playback.play().get()
|
||||
self.send_request('currentsong')
|
||||
self.assertInResponse('file: dummy:/a')
|
||||
self.assertInResponse('Time: 0')
|
||||
|
||||
@ -11,7 +11,7 @@ from mopidy.models import Track
|
||||
from mopidy.mpd import dispatcher
|
||||
from mopidy.mpd.protocol import status
|
||||
|
||||
from tests import dummy_backend, dummy_mixer
|
||||
from tests import dummy_audio, dummy_backend, dummy_mixer
|
||||
|
||||
|
||||
PAUSED = PlaybackState.PAUSED
|
||||
@ -31,12 +31,14 @@ class StatusHandlerTest(unittest.TestCase):
|
||||
}
|
||||
}
|
||||
|
||||
self.audio = dummy_audio.create_proxy()
|
||||
self.mixer = dummy_mixer.create_proxy()
|
||||
self.backend = dummy_backend.create_proxy()
|
||||
self.backend = dummy_backend.create_proxy(audio=self.audio)
|
||||
|
||||
with deprecation.ignore():
|
||||
self.core = core.Core.start(
|
||||
config,
|
||||
audio=self.audio,
|
||||
mixer=self.mixer,
|
||||
backends=[self.backend]).proxy()
|
||||
|
||||
@ -154,21 +156,21 @@ class StatusHandlerTest(unittest.TestCase):
|
||||
def test_status_method_when_playlist_loaded_contains_song(self):
|
||||
self.set_tracklist(Track(uri='dummy:/a'))
|
||||
|
||||
self.core.playback.play()
|
||||
self.core.playback.play().get()
|
||||
result = dict(status.status(self.context))
|
||||
self.assertIn('song', result)
|
||||
self.assertGreaterEqual(int(result['song']), 0)
|
||||
|
||||
def test_status_method_when_playlist_loaded_contains_tlid_as_songid(self):
|
||||
self.set_tracklist(Track(uri='dummy:/a'))
|
||||
self.core.playback.play()
|
||||
self.core.playback.play().get()
|
||||
result = dict(status.status(self.context))
|
||||
self.assertIn('songid', result)
|
||||
self.assertEqual(int(result['songid']), 1)
|
||||
|
||||
def test_status_method_when_playing_contains_time_with_no_length(self):
|
||||
self.set_tracklist(Track(uri='dummy:/a', length=None))
|
||||
self.core.playback.play()
|
||||
self.core.playback.play().get()
|
||||
result = dict(status.status(self.context))
|
||||
self.assertIn('time', result)
|
||||
(position, total) = result['time'].split(':')
|
||||
@ -188,7 +190,7 @@ class StatusHandlerTest(unittest.TestCase):
|
||||
|
||||
def test_status_method_when_playing_contains_elapsed(self):
|
||||
self.set_tracklist(Track(uri='dummy:/a', length=60000))
|
||||
self.core.playback.play()
|
||||
self.core.playback.play().get()
|
||||
self.core.playback.pause()
|
||||
self.core.playback.seek(59123)
|
||||
result = dict(status.status(self.context))
|
||||
@ -197,7 +199,7 @@ class StatusHandlerTest(unittest.TestCase):
|
||||
|
||||
def test_status_method_when_starting_playing_contains_elapsed_zero(self):
|
||||
self.set_tracklist(Track(uri='dummy:/a', length=10000))
|
||||
self.core.playback.play()
|
||||
self.core.playback.play().get()
|
||||
self.core.playback.pause()
|
||||
result = dict(status.status(self.context))
|
||||
self.assertIn('elapsed', result)
|
||||
@ -205,7 +207,7 @@ class StatusHandlerTest(unittest.TestCase):
|
||||
|
||||
def test_status_method_when_playing_contains_bitrate(self):
|
||||
self.set_tracklist(Track(uri='dummy:/a', bitrate=3200))
|
||||
self.core.playback.play()
|
||||
self.core.playback.play().get()
|
||||
result = dict(status.status(self.context))
|
||||
self.assertIn('bitrate', result)
|
||||
self.assertEqual(int(result['bitrate']), 3200)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user