Merge pull request #537 from jodal/tidy-up-core
Core playback and tracklist modularity improvement v2
This commit is contained in:
commit
c8f5c1aacf
2
.gitignore
vendored
2
.gitignore
vendored
@ -14,3 +14,5 @@ docs/_build/
|
||||
mopidy.log*
|
||||
node_modules/
|
||||
nosetests.xml
|
||||
*~
|
||||
*.orig
|
||||
|
||||
@ -39,6 +39,45 @@ of the following extensions as well:
|
||||
|
||||
**Core**
|
||||
|
||||
- Parts of the functionality in :class:`mopidy.core.PlaybackController` have
|
||||
been moved to :class:`mopidy.core.TracklistController`:
|
||||
|
||||
=================================== ==================================
|
||||
Old location New location
|
||||
=================================== ==================================
|
||||
playback.get_consume() tracklist.get_consume()
|
||||
playback.set_consume(v) tracklist.set_consume(v)
|
||||
playback.consume tracklist.consume
|
||||
|
||||
playback.get_random() tracklist.get_random()
|
||||
playback.set_random(v) tracklist.set_random(v)
|
||||
playback.random tracklist.random
|
||||
|
||||
playback.get_repeat() tracklist.get_repeat()
|
||||
playback.set_repeat(v) tracklist.set_repeat(v)
|
||||
playback.repeat tracklist.repeat
|
||||
|
||||
playback.get_single() tracklist.get_single()
|
||||
playback.set_single(v) tracklist.set_single(v)
|
||||
playback.single tracklist.single
|
||||
|
||||
playback.get_tracklist_position() tracklist.index(tl_track)
|
||||
playback.tracklist_position tracklist.index(tl_track)
|
||||
|
||||
playback.get_tl_track_at_eot() tracklist.eot_track(tl_track)
|
||||
playback.tl_track_at_eot tracklist.eot_track(tl_track)
|
||||
|
||||
playback.get_tl_track_at_next() tracklist.next_track(tl_track)
|
||||
playback.tl_track_at_next tracklist.next_track(tl_track)
|
||||
|
||||
playback.get_tl_track_at_previous() tracklist.previous_track(tl_track)
|
||||
playback.tl_track_at_previous tracklist.previous_track(tl_track)
|
||||
=================================== ==================================
|
||||
|
||||
The ``tl_track`` argument to the last four new functions are used as the
|
||||
reference ``tl_track`` in the tracklist to find e.g. the next track. Usually,
|
||||
this will be :attr:`~mopidy.core.PlaybackController.current_tl_track`.
|
||||
|
||||
- Added :attr:`mopidy.core.PlaybackController.mute` for muting and unmuting
|
||||
audio. (Fixes: :issue:`186`)
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import random
|
||||
import urlparse
|
||||
|
||||
from mopidy.audio import PlaybackState
|
||||
@ -21,8 +20,6 @@ class PlaybackController(object):
|
||||
self.core = core
|
||||
|
||||
self._state = PlaybackState.STOPPED
|
||||
self._shuffled = []
|
||||
self._first_shuffle = True
|
||||
self._volume = None
|
||||
self._mute = False
|
||||
|
||||
@ -35,22 +32,6 @@ class PlaybackController(object):
|
||||
|
||||
### Properties
|
||||
|
||||
def get_consume(self):
|
||||
return getattr(self, '_consume', False)
|
||||
|
||||
def set_consume(self, value):
|
||||
if self.get_consume() != value:
|
||||
self._trigger_options_changed()
|
||||
return setattr(self, '_consume', value)
|
||||
|
||||
consume = property(get_consume, set_consume)
|
||||
"""
|
||||
:class:`True`
|
||||
Tracks are removed from the playlist when they have been played.
|
||||
:class:`False`
|
||||
Tracks are not removed from the playlist.
|
||||
"""
|
||||
|
||||
def get_current_tl_track(self):
|
||||
return self.current_tl_track
|
||||
|
||||
@ -70,56 +51,6 @@ class PlaybackController(object):
|
||||
Read-only. Extracted from :attr:`current_tl_track` for convenience.
|
||||
"""
|
||||
|
||||
def get_random(self):
|
||||
return getattr(self, '_random', False)
|
||||
|
||||
def set_random(self, value):
|
||||
if self.get_random() != value:
|
||||
self._trigger_options_changed()
|
||||
return setattr(self, '_random', value)
|
||||
|
||||
random = property(get_random, set_random)
|
||||
"""
|
||||
:class:`True`
|
||||
Tracks are selected at random from the playlist.
|
||||
:class:`False`
|
||||
Tracks are played in the order of the playlist.
|
||||
"""
|
||||
|
||||
def get_repeat(self):
|
||||
return getattr(self, '_repeat', False)
|
||||
|
||||
def set_repeat(self, value):
|
||||
if self.get_repeat() != value:
|
||||
self._trigger_options_changed()
|
||||
return setattr(self, '_repeat', value)
|
||||
|
||||
repeat = property(get_repeat, set_repeat)
|
||||
"""
|
||||
:class:`True`
|
||||
The current playlist is played repeatedly. To repeat a single track,
|
||||
select both :attr:`repeat` and :attr:`single`.
|
||||
:class:`False`
|
||||
The current playlist is played once.
|
||||
"""
|
||||
|
||||
def get_single(self):
|
||||
return getattr(self, '_single', False)
|
||||
|
||||
def set_single(self, value):
|
||||
if self.get_single() != value:
|
||||
self._trigger_options_changed()
|
||||
return setattr(self, '_single', value)
|
||||
|
||||
single = property(get_single, set_single)
|
||||
"""
|
||||
:class:`True`
|
||||
Playback is stopped after current song, unless in :attr:`repeat`
|
||||
mode.
|
||||
:class:`False`
|
||||
Playback continues after current song.
|
||||
"""
|
||||
|
||||
def get_state(self):
|
||||
return self._state
|
||||
|
||||
@ -157,119 +88,6 @@ class PlaybackController(object):
|
||||
time_position = property(get_time_position)
|
||||
"""Time position in milliseconds."""
|
||||
|
||||
def get_tracklist_position(self):
|
||||
if self.current_tl_track is None:
|
||||
return None
|
||||
try:
|
||||
return self.core.tracklist.tl_tracks.index(self.current_tl_track)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
tracklist_position = property(get_tracklist_position)
|
||||
"""
|
||||
The position of the current track in the tracklist.
|
||||
|
||||
Read-only.
|
||||
"""
|
||||
|
||||
def get_tl_track_at_eot(self):
|
||||
tl_tracks = self.core.tracklist.tl_tracks
|
||||
|
||||
if not tl_tracks:
|
||||
return None
|
||||
|
||||
if self.random and not self._shuffled:
|
||||
if self.repeat or self._first_shuffle:
|
||||
logger.debug('Shuffling tracks')
|
||||
self._shuffled = tl_tracks
|
||||
random.shuffle(self._shuffled)
|
||||
self._first_shuffle = False
|
||||
|
||||
if self.random and self._shuffled:
|
||||
return self._shuffled[0]
|
||||
|
||||
if self.current_tl_track is None:
|
||||
return tl_tracks[0]
|
||||
|
||||
if self.repeat and self.single:
|
||||
return tl_tracks[self.tracklist_position]
|
||||
|
||||
if self.repeat and not self.single:
|
||||
return tl_tracks[(self.tracklist_position + 1) % len(tl_tracks)]
|
||||
|
||||
try:
|
||||
return tl_tracks[self.tracklist_position + 1]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
tl_track_at_eot = property(get_tl_track_at_eot)
|
||||
"""
|
||||
The track that will be played at the end of the current track.
|
||||
|
||||
Read-only. A :class:`mopidy.models.TlTrack`.
|
||||
|
||||
Not necessarily the same track as :attr:`tl_track_at_next`.
|
||||
"""
|
||||
|
||||
def get_tl_track_at_next(self):
|
||||
tl_tracks = self.core.tracklist.tl_tracks
|
||||
|
||||
if not tl_tracks:
|
||||
return None
|
||||
|
||||
if self.random and not self._shuffled:
|
||||
if self.repeat or self._first_shuffle:
|
||||
logger.debug('Shuffling tracks')
|
||||
self._shuffled = tl_tracks
|
||||
random.shuffle(self._shuffled)
|
||||
self._first_shuffle = False
|
||||
|
||||
if self.random and self._shuffled:
|
||||
return self._shuffled[0]
|
||||
|
||||
if self.current_tl_track is None:
|
||||
return tl_tracks[0]
|
||||
|
||||
if self.repeat:
|
||||
return tl_tracks[(self.tracklist_position + 1) % len(tl_tracks)]
|
||||
|
||||
try:
|
||||
return tl_tracks[self.tracklist_position + 1]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
tl_track_at_next = property(get_tl_track_at_next)
|
||||
"""
|
||||
The track that will be played if calling :meth:`next()`.
|
||||
|
||||
Read-only. A :class:`mopidy.models.TlTrack`.
|
||||
|
||||
For normal playback this is the next track in the playlist. If repeat
|
||||
is enabled the next track can loop around the playlist. When random is
|
||||
enabled this should be a random track, all tracks should be played once
|
||||
before the list repeats.
|
||||
"""
|
||||
|
||||
def get_tl_track_at_previous(self):
|
||||
if self.repeat or self.consume or self.random:
|
||||
return self.current_tl_track
|
||||
|
||||
if self.tracklist_position in (None, 0):
|
||||
return None
|
||||
|
||||
return self.core.tracklist.tl_tracks[self.tracklist_position - 1]
|
||||
|
||||
tl_track_at_previous = property(get_tl_track_at_previous)
|
||||
"""
|
||||
The track that will be played if calling :meth:`previous()`.
|
||||
|
||||
A :class:`mopidy.models.TlTrack`.
|
||||
|
||||
For normal playback this is the previous track in the playlist. If
|
||||
random and/or consume is enabled it should return the current track
|
||||
instead.
|
||||
"""
|
||||
|
||||
def get_volume(self):
|
||||
if self.audio:
|
||||
return self.audio.get_volume().get()
|
||||
@ -339,15 +157,15 @@ class PlaybackController(object):
|
||||
return
|
||||
|
||||
original_tl_track = self.current_tl_track
|
||||
next_tl_track = self.core.tracklist.eot_track(original_tl_track)
|
||||
|
||||
if self.tl_track_at_eot:
|
||||
if next_tl_track:
|
||||
self._trigger_track_playback_ended()
|
||||
self.play(self.tl_track_at_eot)
|
||||
self.play(next_tl_track)
|
||||
else:
|
||||
self.stop(clear_current_track=True)
|
||||
|
||||
if self.consume:
|
||||
self.core.tracklist.remove(tlid=original_tl_track.tlid)
|
||||
self.core.tracklist.mark_played(original_tl_track)
|
||||
|
||||
def on_tracklist_change(self):
|
||||
"""
|
||||
@ -355,12 +173,7 @@ class PlaybackController(object):
|
||||
|
||||
Used by :class:`mopidy.core.TracklistController`.
|
||||
"""
|
||||
self._first_shuffle = True
|
||||
self._shuffled = []
|
||||
|
||||
if (not self.core.tracklist.tl_tracks or
|
||||
self.current_tl_track not in
|
||||
self.core.tracklist.tl_tracks):
|
||||
if self.current_tl_track not in self.core.tracklist.tl_tracks:
|
||||
self.stop(clear_current_track=True)
|
||||
|
||||
def next(self):
|
||||
@ -370,9 +183,10 @@ 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.
|
||||
"""
|
||||
if self.tl_track_at_next:
|
||||
tl_track = self.core.tracklist.next_track(self.current_tl_track)
|
||||
if tl_track:
|
||||
self._trigger_track_playback_ended()
|
||||
self.change_track(self.tl_track_at_next)
|
||||
self.change_track(tl_track)
|
||||
else:
|
||||
self.stop(clear_current_track=True)
|
||||
|
||||
@ -395,37 +209,40 @@ class PlaybackController(object):
|
||||
:type on_error_step: int, -1 or 1
|
||||
"""
|
||||
|
||||
if tl_track is not None:
|
||||
assert tl_track in self.core.tracklist.tl_tracks
|
||||
elif tl_track is None:
|
||||
assert on_error_step in (-1, 1)
|
||||
|
||||
if tl_track is None:
|
||||
if self.state == PlaybackState.PAUSED:
|
||||
return self.resume()
|
||||
elif self.current_tl_track is not None:
|
||||
tl_track = self.current_tl_track
|
||||
elif self.current_tl_track is None and on_error_step == 1:
|
||||
tl_track = self.tl_track_at_next
|
||||
elif self.current_tl_track is None and on_error_step == -1:
|
||||
tl_track = self.tl_track_at_previous
|
||||
|
||||
if tl_track is not None:
|
||||
self.current_tl_track = tl_track
|
||||
self.state = PlaybackState.PLAYING
|
||||
backend = self._get_backend()
|
||||
if not backend or not backend.playback.play(tl_track.track).get():
|
||||
logger.warning('Track is not playable: %s', tl_track.track.uri)
|
||||
if self.random and self._shuffled:
|
||||
self._shuffled.remove(tl_track)
|
||||
if self.current_tl_track is not None:
|
||||
tl_track = self.current_tl_track
|
||||
else:
|
||||
if on_error_step == 1:
|
||||
# TODO: can cause an endless loop for single track repeat.
|
||||
self.next()
|
||||
tl_track = self.core.tracklist.next_track(tl_track)
|
||||
elif on_error_step == -1:
|
||||
self.previous()
|
||||
tl_track = self.core.tracklist.previous_track(tl_track)
|
||||
|
||||
if tl_track is None:
|
||||
return
|
||||
|
||||
if self.random and self.current_tl_track in self._shuffled:
|
||||
self._shuffled.remove(self.current_tl_track)
|
||||
assert tl_track in self.core.tracklist.tl_tracks
|
||||
|
||||
self._trigger_track_playback_started()
|
||||
self.current_tl_track = tl_track
|
||||
self.state = PlaybackState.PLAYING
|
||||
backend = self._get_backend()
|
||||
success = backend and backend.playback.play(tl_track.track).get()
|
||||
|
||||
if success:
|
||||
self.core.tracklist.mark_playing(tl_track)
|
||||
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()
|
||||
|
||||
def previous(self):
|
||||
"""
|
||||
@ -435,7 +252,9 @@ class PlaybackController(object):
|
||||
will continue. If it was paused, it will still be paused, etc.
|
||||
"""
|
||||
self._trigger_track_playback_ended()
|
||||
self.change_track(self.tl_track_at_previous, on_error_step=-1)
|
||||
tl_track = self.current_tl_track
|
||||
self.change_track(
|
||||
self.core.tracklist.previous_track(tl_track), on_error_step=-1)
|
||||
|
||||
def resume(self):
|
||||
"""If paused, resume playing the current track."""
|
||||
@ -531,10 +350,6 @@ class PlaybackController(object):
|
||||
'playback_state_changed',
|
||||
old_state=old_state, new_state=new_state)
|
||||
|
||||
def _trigger_options_changed(self):
|
||||
logger.debug('Triggering options changed event')
|
||||
listener.CoreListener.send('options_changed')
|
||||
|
||||
def _trigger_volume_changed(self, volume):
|
||||
logger.debug('Triggering volume changed event')
|
||||
listener.CoreListener.send('volume_changed', volume=volume)
|
||||
|
||||
@ -15,11 +15,16 @@ class TracklistController(object):
|
||||
pykka_traversable = True
|
||||
|
||||
def __init__(self, core):
|
||||
self._core = core
|
||||
self.core = core
|
||||
self._next_tlid = 0
|
||||
self._tl_tracks = []
|
||||
self._version = 0
|
||||
|
||||
self._shuffled = []
|
||||
self._first_shuffle = True
|
||||
|
||||
### Properties
|
||||
|
||||
def get_tl_tracks(self):
|
||||
return self._tl_tracks[:]
|
||||
|
||||
@ -51,7 +56,7 @@ class TracklistController(object):
|
||||
|
||||
def _increase_version(self):
|
||||
self._version += 1
|
||||
self._core.playback.on_tracklist_change()
|
||||
self.core.playback.on_tracklist_change()
|
||||
self._trigger_tracklist_changed()
|
||||
|
||||
version = property(get_version)
|
||||
@ -62,6 +67,190 @@ class TracklistController(object):
|
||||
Is not reset before Mopidy is restarted.
|
||||
"""
|
||||
|
||||
def get_consume(self):
|
||||
return getattr(self, '_consume', False)
|
||||
|
||||
def set_consume(self, value):
|
||||
if self.get_consume() != value:
|
||||
self._trigger_options_changed()
|
||||
return setattr(self, '_consume', value)
|
||||
|
||||
consume = property(get_consume, set_consume)
|
||||
"""
|
||||
:class:`True`
|
||||
Tracks are removed from the playlist when they have been played.
|
||||
:class:`False`
|
||||
Tracks are not removed from the playlist.
|
||||
"""
|
||||
|
||||
def get_random(self):
|
||||
return getattr(self, '_random', False)
|
||||
|
||||
def set_random(self, value):
|
||||
if self.get_random() != value:
|
||||
self._trigger_options_changed()
|
||||
return setattr(self, '_random', value)
|
||||
|
||||
random = property(get_random, set_random)
|
||||
"""
|
||||
:class:`True`
|
||||
Tracks are selected at random from the playlist.
|
||||
:class:`False`
|
||||
Tracks are played in the order of the playlist.
|
||||
"""
|
||||
|
||||
def get_repeat(self):
|
||||
return getattr(self, '_repeat', False)
|
||||
|
||||
def set_repeat(self, value):
|
||||
if self.get_repeat() != value:
|
||||
self._trigger_options_changed()
|
||||
return setattr(self, '_repeat', value)
|
||||
|
||||
repeat = property(get_repeat, set_repeat)
|
||||
"""
|
||||
:class:`True`
|
||||
The current playlist is played repeatedly. To repeat a single track,
|
||||
select both :attr:`repeat` and :attr:`single`.
|
||||
:class:`False`
|
||||
The current playlist is played once.
|
||||
"""
|
||||
|
||||
def get_single(self):
|
||||
return getattr(self, '_single', False)
|
||||
|
||||
def set_single(self, value):
|
||||
if self.get_single() != value:
|
||||
self._trigger_options_changed()
|
||||
return setattr(self, '_single', value)
|
||||
|
||||
single = property(get_single, set_single)
|
||||
"""
|
||||
:class:`True`
|
||||
Playback is stopped after current song, unless in :attr:`repeat`
|
||||
mode.
|
||||
:class:`False`
|
||||
Playback continues after current song.
|
||||
"""
|
||||
|
||||
### Methods
|
||||
|
||||
def index(self, tl_track):
|
||||
"""
|
||||
The position of the given track in the tracklist.
|
||||
|
||||
:param tl_track: the track to find the index of
|
||||
:type tl_track: :class:`mopidy.models.TlTrack`
|
||||
:rtype: :class:`int` or :class:`None`
|
||||
"""
|
||||
try:
|
||||
return self._tl_tracks.index(tl_track)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
def eot_track(self, tl_track):
|
||||
"""
|
||||
The track that will be played after the given track.
|
||||
|
||||
Not necessarily the same track as :meth:`next_track`.
|
||||
|
||||
:param tl_track: the reference track
|
||||
:type tl_track: :class:`mopidy.models.TlTrack` or :class:`None`
|
||||
:rtype: :class:`mopidy.models.TlTrack` or :class:`None`
|
||||
"""
|
||||
if not self.tl_tracks:
|
||||
return None
|
||||
|
||||
if self.random and not self._shuffled:
|
||||
if self.repeat or self._first_shuffle:
|
||||
logger.debug('Shuffling tracks')
|
||||
self._shuffled = self.tl_tracks
|
||||
random.shuffle(self._shuffled)
|
||||
self._first_shuffle = False
|
||||
|
||||
if self.random and self._shuffled:
|
||||
return self._shuffled[0]
|
||||
|
||||
if tl_track is None:
|
||||
return self.tl_tracks[0]
|
||||
|
||||
position = self.index(tl_track)
|
||||
|
||||
if self.repeat and self.single:
|
||||
return self.tl_tracks[position]
|
||||
|
||||
if self.repeat and not self.single:
|
||||
return self.tl_tracks[(position + 1) % len(self.tl_tracks)]
|
||||
|
||||
try:
|
||||
return self.tl_tracks[position + 1]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def next_track(self, tl_track):
|
||||
"""
|
||||
The track that will be played if calling
|
||||
:meth:`mopidy.core.PlaybackController.next()`.
|
||||
|
||||
For normal playback this is the next track in the playlist. If repeat
|
||||
is enabled the next track can loop around the playlist. When random is
|
||||
enabled this should be a random track, all tracks should be played once
|
||||
before the list repeats.
|
||||
|
||||
:param tl_track: the reference track
|
||||
:type tl_track: :class:`mopidy.models.TlTrack` or :class:`None`
|
||||
:rtype: :class:`mopidy.models.TlTrack` or :class:`None`
|
||||
"""
|
||||
|
||||
if not self.tl_tracks:
|
||||
return None
|
||||
|
||||
if self.random and not self._shuffled:
|
||||
if self.repeat or self._first_shuffle:
|
||||
logger.debug('Shuffling tracks')
|
||||
self._shuffled = self.tl_tracks
|
||||
random.shuffle(self._shuffled)
|
||||
self._first_shuffle = False
|
||||
|
||||
if self.random and self._shuffled:
|
||||
return self._shuffled[0]
|
||||
|
||||
if tl_track is None:
|
||||
return self.tl_tracks[0]
|
||||
|
||||
position = self.index(tl_track)
|
||||
|
||||
if self.repeat:
|
||||
return self.tl_tracks[(position + 1) % len(self.tl_tracks)]
|
||||
|
||||
try:
|
||||
return self.tl_tracks[position + 1]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def previous_track(self, tl_track):
|
||||
"""
|
||||
Returns the track that will be played if calling
|
||||
:meth:`mopidy.core.PlaybackController.previous()`.
|
||||
|
||||
For normal playback this is the previous track in the playlist. If
|
||||
random and/or consume is enabled it should return the current track
|
||||
instead.
|
||||
|
||||
:param tl_track: the reference track
|
||||
:type tl_track: :class:`mopidy.models.TlTrack` or :class:`None`
|
||||
:rtype: :class:`mopidy.models.TlTrack` or :class:`None`
|
||||
"""
|
||||
if self.repeat or self.consume or self.random:
|
||||
return tl_track
|
||||
|
||||
position = self.index(tl_track)
|
||||
|
||||
if position in (None, 0):
|
||||
return None
|
||||
|
||||
return self.tl_tracks[position - 1]
|
||||
|
||||
def add(self, tracks=None, at_position=None, uri=None):
|
||||
"""
|
||||
Add the track or list of tracks to the tracklist.
|
||||
@ -87,7 +276,7 @@ class TracklistController(object):
|
||||
'tracks or uri must be provided'
|
||||
|
||||
if tracks is None and uri is not None:
|
||||
tracks = self._core.library.lookup(uri)
|
||||
tracks = self.core.library.lookup(uri)
|
||||
|
||||
tl_tracks = []
|
||||
|
||||
@ -151,18 +340,6 @@ class TracklistController(object):
|
||||
lambda ct: getattr(ct.track, key) == value, matches)
|
||||
return matches
|
||||
|
||||
def index(self, tl_track):
|
||||
"""
|
||||
Get index of the given :class:`mopidy.models.TlTrack` in the tracklist.
|
||||
|
||||
Raises :exc:`ValueError` if not found.
|
||||
|
||||
:param tl_track: track to find the index of
|
||||
:type tl_track: :class:`mopidy.models.TlTrack`
|
||||
:rtype: int
|
||||
"""
|
||||
return self._tl_tracks.index(tl_track)
|
||||
|
||||
def move(self, start, end, to_position):
|
||||
"""
|
||||
Move the tracks in the slice ``[start:end]`` to ``to_position``.
|
||||
@ -259,6 +436,31 @@ class TracklistController(object):
|
||||
"""
|
||||
return self._tl_tracks[start:end]
|
||||
|
||||
def mark_playing(self, tl_track):
|
||||
"""Private method used by :class:`mopidy.core.PlaybackController`."""
|
||||
if self.random and tl_track in self._shuffled:
|
||||
self._shuffled.remove(tl_track)
|
||||
|
||||
def mark_unplayable(self, tl_track):
|
||||
"""Private method used by :class:`mopidy.core.PlaybackController`."""
|
||||
logger.warning('Track is not playable: %s', tl_track.track.uri)
|
||||
if self.random and tl_track in self._shuffled:
|
||||
self._shuffled.remove(tl_track)
|
||||
|
||||
def mark_played(self, tl_track):
|
||||
"""Private method used by :class:`mopidy.core.PlaybackController`."""
|
||||
if not self.consume:
|
||||
return False
|
||||
self.remove(tlid=tl_track.tlid)
|
||||
return True
|
||||
|
||||
def _trigger_tracklist_changed(self):
|
||||
self._first_shuffle = True
|
||||
self._shuffled = []
|
||||
|
||||
logger.debug('Triggering event: tracklist_changed()')
|
||||
listener.CoreListener.send('tracklist_changed')
|
||||
|
||||
def _trigger_options_changed(self):
|
||||
logger.debug('Triggering options changed event')
|
||||
listener.CoreListener.send('options_changed')
|
||||
|
||||
@ -19,9 +19,9 @@ def consume(context, state):
|
||||
playlist.
|
||||
"""
|
||||
if int(state):
|
||||
context.core.playback.consume = True
|
||||
context.core.tracklist.consume = True
|
||||
else:
|
||||
context.core.playback.consume = False
|
||||
context.core.tracklist.consume = False
|
||||
|
||||
|
||||
@handle_request(r'^crossfade "(?P<seconds>\d+)"$')
|
||||
@ -263,9 +263,9 @@ def random(context, state):
|
||||
Sets random state to ``STATE``, ``STATE`` should be 0 or 1.
|
||||
"""
|
||||
if int(state):
|
||||
context.core.playback.random = True
|
||||
context.core.tracklist.random = True
|
||||
else:
|
||||
context.core.playback.random = False
|
||||
context.core.tracklist.random = False
|
||||
|
||||
|
||||
@handle_request(r'^repeat (?P<state>[01])$')
|
||||
@ -279,9 +279,9 @@ def repeat(context, state):
|
||||
Sets repeat state to ``STATE``, ``STATE`` should be 0 or 1.
|
||||
"""
|
||||
if int(state):
|
||||
context.core.playback.repeat = True
|
||||
context.core.tracklist.repeat = True
|
||||
else:
|
||||
context.core.playback.repeat = False
|
||||
context.core.tracklist.repeat = False
|
||||
|
||||
|
||||
@handle_request(r'^replay_gain_mode "(?P<mode>(off|track|album))"$')
|
||||
@ -329,7 +329,8 @@ def seek(context, songpos, seconds):
|
||||
|
||||
- issues ``seek 1 120`` without quotes around the arguments.
|
||||
"""
|
||||
if context.core.playback.tracklist_position.get() != int(songpos):
|
||||
tl_track = context.core.playback.current_tl_track.get()
|
||||
if context.core.tracklist.index(tl_track).get() != int(songpos):
|
||||
playpos(context, songpos)
|
||||
context.core.playback.seek(int(seconds) * 1000).get()
|
||||
|
||||
@ -404,9 +405,9 @@ def single(context, state):
|
||||
song is repeated if the ``repeat`` mode is enabled.
|
||||
"""
|
||||
if int(state):
|
||||
context.core.playback.single = True
|
||||
context.core.tracklist.single = True
|
||||
else:
|
||||
context.core.playback.single = False
|
||||
context.core.tracklist.single = False
|
||||
|
||||
|
||||
@handle_request(r'^stop$')
|
||||
|
||||
@ -36,10 +36,10 @@ def currentsong(context):
|
||||
Displays the song info of the current song (same song that is
|
||||
identified in status).
|
||||
"""
|
||||
current_tl_track = context.core.playback.current_tl_track.get()
|
||||
if current_tl_track is not None:
|
||||
position = context.core.playback.tracklist_position.get()
|
||||
return track_to_mpd_format(current_tl_track, position=position)
|
||||
tl_track = context.core.playback.current_tl_track.get()
|
||||
if tl_track is not None:
|
||||
position = context.core.tracklist.index(tl_track).get()
|
||||
return track_to_mpd_format(tl_track, position=position)
|
||||
|
||||
|
||||
@handle_request(r'^idle$')
|
||||
@ -178,14 +178,15 @@ def status(context):
|
||||
'tracklist.length': context.core.tracklist.length,
|
||||
'tracklist.version': context.core.tracklist.version,
|
||||
'playback.volume': context.core.playback.volume,
|
||||
'playback.consume': context.core.playback.consume,
|
||||
'playback.random': context.core.playback.random,
|
||||
'playback.repeat': context.core.playback.repeat,
|
||||
'playback.single': context.core.playback.single,
|
||||
'tracklist.consume': context.core.tracklist.consume,
|
||||
'tracklist.random': context.core.tracklist.random,
|
||||
'tracklist.repeat': context.core.tracklist.repeat,
|
||||
'tracklist.single': context.core.tracklist.single,
|
||||
'playback.state': context.core.playback.state,
|
||||
'playback.current_tl_track': context.core.playback.current_tl_track,
|
||||
'playback.tracklist_position': (
|
||||
context.core.playback.tracklist_position),
|
||||
'tracklist.index': (
|
||||
context.core.tracklist.index(
|
||||
context.core.playback.current_tl_track.get())),
|
||||
'playback.time_position': context.core.playback.time_position,
|
||||
}
|
||||
pykka.get_all(futures.values())
|
||||
@ -218,7 +219,7 @@ def _status_bitrate(futures):
|
||||
|
||||
|
||||
def _status_consume(futures):
|
||||
if futures['playback.consume'].get():
|
||||
if futures['tracklist.consume'].get():
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
@ -233,15 +234,15 @@ def _status_playlist_version(futures):
|
||||
|
||||
|
||||
def _status_random(futures):
|
||||
return int(futures['playback.random'].get())
|
||||
return int(futures['tracklist.random'].get())
|
||||
|
||||
|
||||
def _status_repeat(futures):
|
||||
return int(futures['playback.repeat'].get())
|
||||
return int(futures['tracklist.repeat'].get())
|
||||
|
||||
|
||||
def _status_single(futures):
|
||||
return int(futures['playback.single'].get())
|
||||
return int(futures['tracklist.single'].get())
|
||||
|
||||
|
||||
def _status_songid(futures):
|
||||
@ -253,7 +254,7 @@ def _status_songid(futures):
|
||||
|
||||
|
||||
def _status_songpos(futures):
|
||||
return futures['playback.tracklist_position'].get()
|
||||
return futures['tracklist.index'].get()
|
||||
|
||||
|
||||
def _status_state(futures):
|
||||
|
||||
@ -212,13 +212,15 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
def test_next(self):
|
||||
self.playback.play()
|
||||
|
||||
old_position = self.playback.tracklist_position
|
||||
old_uri = self.playback.current_track.uri
|
||||
tl_track = self.playback.current_tl_track
|
||||
old_position = self.tracklist.index(tl_track)
|
||||
old_uri = tl_track.track.uri
|
||||
|
||||
self.playback.next()
|
||||
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(
|
||||
self.playback.tracklist_position, old_position + 1)
|
||||
self.tracklist.index(tl_track), old_position + 1)
|
||||
self.assertNotEqual(self.playback.current_track.uri, old_uri)
|
||||
|
||||
@populate_tracklist
|
||||
@ -238,7 +240,8 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
for i, track in enumerate(self.tracks):
|
||||
self.assertEqual(self.playback.state, PlaybackState.PLAYING)
|
||||
self.assertEqual(self.playback.current_track, track)
|
||||
self.assertEqual(self.playback.tracklist_position, i)
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(self.tracklist.index(tl_track), i)
|
||||
|
||||
self.playback.next()
|
||||
|
||||
@ -274,55 +277,67 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_next_track_before_play(self):
|
||||
self.assertEqual(self.playback.tl_track_at_next, self.tl_tracks[0])
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(
|
||||
self.tracklist.next_track(tl_track), self.tl_tracks[0])
|
||||
|
||||
@populate_tracklist
|
||||
def test_next_track_during_play(self):
|
||||
self.playback.play()
|
||||
self.assertEqual(self.playback.tl_track_at_next, self.tl_tracks[1])
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(
|
||||
self.tracklist.next_track(tl_track), self.tl_tracks[1])
|
||||
|
||||
@populate_tracklist
|
||||
def test_next_track_after_previous(self):
|
||||
self.playback.play()
|
||||
self.playback.next()
|
||||
self.playback.previous()
|
||||
self.assertEqual(self.playback.tl_track_at_next, self.tl_tracks[1])
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(
|
||||
self.tracklist.next_track(tl_track), self.tl_tracks[1])
|
||||
|
||||
def test_next_track_empty_playlist(self):
|
||||
self.assertEqual(self.playback.tl_track_at_next, None)
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(self.tracklist.next_track(tl_track), None)
|
||||
|
||||
@populate_tracklist
|
||||
def test_next_track_at_end_of_playlist(self):
|
||||
self.playback.play()
|
||||
for _ in self.tracklist.tl_tracks[1:]:
|
||||
self.playback.next()
|
||||
self.assertEqual(self.playback.tl_track_at_next, None)
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(self.tracklist.next_track(tl_track), None)
|
||||
|
||||
@populate_tracklist
|
||||
def test_next_track_at_end_of_playlist_with_repeat(self):
|
||||
self.playback.repeat = True
|
||||
self.tracklist.repeat = True
|
||||
self.playback.play()
|
||||
for _ in self.tracks[1:]:
|
||||
self.playback.next()
|
||||
self.assertEqual(self.playback.tl_track_at_next, self.tl_tracks[0])
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(
|
||||
self.tracklist.next_track(tl_track), self.tl_tracks[0])
|
||||
|
||||
@populate_tracklist
|
||||
def test_next_track_with_random(self):
|
||||
random.seed(1)
|
||||
self.playback.random = True
|
||||
self.assertEqual(self.playback.tl_track_at_next, self.tl_tracks[2])
|
||||
self.tracklist.random = True
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(
|
||||
self.tracklist.next_track(tl_track), self.tl_tracks[2])
|
||||
|
||||
@populate_tracklist
|
||||
def test_next_with_consume(self):
|
||||
self.playback.consume = True
|
||||
self.tracklist.consume = True
|
||||
self.playback.play()
|
||||
self.playback.next()
|
||||
self.assertIn(self.tracks[0], self.tracklist.tracks)
|
||||
|
||||
@populate_tracklist
|
||||
def test_next_with_single_and_repeat(self):
|
||||
self.playback.single = True
|
||||
self.playback.repeat = True
|
||||
self.tracklist.single = True
|
||||
self.tracklist.repeat = True
|
||||
self.playback.play()
|
||||
self.playback.next()
|
||||
self.assertEqual(self.playback.current_track, self.tracks[1])
|
||||
@ -331,7 +346,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
def test_next_with_random(self):
|
||||
# FIXME feels very fragile
|
||||
random.seed(1)
|
||||
self.playback.random = True
|
||||
self.tracklist.random = True
|
||||
self.playback.play()
|
||||
self.playback.next()
|
||||
self.assertEqual(self.playback.current_track, self.tracks[1])
|
||||
@ -339,22 +354,28 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
@populate_tracklist
|
||||
def test_next_track_with_random_after_append_playlist(self):
|
||||
random.seed(1)
|
||||
self.playback.random = True
|
||||
self.assertEqual(self.playback.tl_track_at_next, self.tl_tracks[2])
|
||||
self.tracklist.random = True
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(self.tracklist.next_track(tl_track),
|
||||
self.tl_tracks[2])
|
||||
self.tracklist.add(self.tracks[:1])
|
||||
self.assertEqual(self.playback.tl_track_at_next, self.tl_tracks[1])
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(
|
||||
self.tracklist.next_track(tl_track), self.tl_tracks[1])
|
||||
|
||||
@populate_tracklist
|
||||
def test_end_of_track(self):
|
||||
self.playback.play()
|
||||
|
||||
old_position = self.playback.tracklist_position
|
||||
old_uri = self.playback.current_track.uri
|
||||
tl_track = self.playback.current_tl_track
|
||||
old_position = self.tracklist.index(tl_track)
|
||||
old_uri = tl_track.track.uri
|
||||
|
||||
self.playback.on_end_of_track()
|
||||
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(
|
||||
self.playback.tracklist_position, old_position + 1)
|
||||
self.tracklist.index(tl_track), old_position + 1)
|
||||
self.assertNotEqual(self.playback.current_track.uri, old_uri)
|
||||
|
||||
@populate_tracklist
|
||||
@ -374,7 +395,8 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
for i, track in enumerate(self.tracks):
|
||||
self.assertEqual(self.playback.state, PlaybackState.PLAYING)
|
||||
self.assertEqual(self.playback.current_track, track)
|
||||
self.assertEqual(self.playback.tracklist_position, i)
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(self.tracklist.index(tl_track), i)
|
||||
|
||||
self.playback.on_end_of_track()
|
||||
|
||||
@ -410,47 +432,59 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_end_of_track_track_before_play(self):
|
||||
self.assertEqual(self.playback.tl_track_at_next, self.tl_tracks[0])
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(
|
||||
self.tracklist.next_track(tl_track), self.tl_tracks[0])
|
||||
|
||||
@populate_tracklist
|
||||
def test_end_of_track_track_during_play(self):
|
||||
self.playback.play()
|
||||
self.assertEqual(self.playback.tl_track_at_next, self.tl_tracks[1])
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(
|
||||
self.tracklist.next_track(tl_track), self.tl_tracks[1])
|
||||
|
||||
@populate_tracklist
|
||||
def test_end_of_track_track_after_previous(self):
|
||||
self.playback.play()
|
||||
self.playback.on_end_of_track()
|
||||
self.playback.previous()
|
||||
self.assertEqual(self.playback.tl_track_at_next, self.tl_tracks[1])
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(
|
||||
self.tracklist.next_track(tl_track), self.tl_tracks[1])
|
||||
|
||||
def test_end_of_track_track_empty_playlist(self):
|
||||
self.assertEqual(self.playback.tl_track_at_next, None)
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(self.tracklist.next_track(tl_track), None)
|
||||
|
||||
@populate_tracklist
|
||||
def test_end_of_track_track_at_end_of_playlist(self):
|
||||
self.playback.play()
|
||||
for _ in self.tracklist.tl_tracks[1:]:
|
||||
self.playback.on_end_of_track()
|
||||
self.assertEqual(self.playback.tl_track_at_next, None)
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(self.tracklist.next_track(tl_track), None)
|
||||
|
||||
@populate_tracklist
|
||||
def test_end_of_track_track_at_end_of_playlist_with_repeat(self):
|
||||
self.playback.repeat = True
|
||||
self.tracklist.repeat = True
|
||||
self.playback.play()
|
||||
for _ in self.tracks[1:]:
|
||||
self.playback.on_end_of_track()
|
||||
self.assertEqual(self.playback.tl_track_at_next, self.tl_tracks[0])
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(
|
||||
self.tracklist.next_track(tl_track), self.tl_tracks[0])
|
||||
|
||||
@populate_tracklist
|
||||
def test_end_of_track_track_with_random(self):
|
||||
random.seed(1)
|
||||
self.playback.random = True
|
||||
self.assertEqual(self.playback.tl_track_at_next, self.tl_tracks[2])
|
||||
self.tracklist.random = True
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(
|
||||
self.tracklist.next_track(tl_track), self.tl_tracks[2])
|
||||
|
||||
@populate_tracklist
|
||||
def test_end_of_track_with_consume(self):
|
||||
self.playback.consume = True
|
||||
self.tracklist.consume = True
|
||||
self.playback.play()
|
||||
self.playback.on_end_of_track()
|
||||
self.assertNotIn(self.tracks[0], self.tracklist.tracks)
|
||||
@ -459,7 +493,7 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
def test_end_of_track_with_random(self):
|
||||
# FIXME feels very fragile
|
||||
random.seed(1)
|
||||
self.playback.random = True
|
||||
self.tracklist.random = True
|
||||
self.playback.play()
|
||||
self.playback.on_end_of_track()
|
||||
self.assertEqual(self.playback.current_track, self.tracks[1])
|
||||
@ -467,25 +501,33 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
@populate_tracklist
|
||||
def test_end_of_track_track_with_random_after_append_playlist(self):
|
||||
random.seed(1)
|
||||
self.playback.random = True
|
||||
self.assertEqual(self.playback.tl_track_at_next, self.tl_tracks[2])
|
||||
self.tracklist.random = True
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(
|
||||
self.tracklist.next_track(tl_track), self.tl_tracks[2])
|
||||
self.tracklist.add(self.tracks[:1])
|
||||
self.assertEqual(self.playback.tl_track_at_next, self.tl_tracks[1])
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(
|
||||
self.tracklist.next_track(tl_track), self.tl_tracks[1])
|
||||
|
||||
@populate_tracklist
|
||||
def test_previous_track_before_play(self):
|
||||
self.assertEqual(self.playback.tl_track_at_previous, None)
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(self.tracklist.previous_track(tl_track), None)
|
||||
|
||||
@populate_tracklist
|
||||
def test_previous_track_after_play(self):
|
||||
self.playback.play()
|
||||
self.assertEqual(self.playback.tl_track_at_previous, None)
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(self.tracklist.previous_track(tl_track), None)
|
||||
|
||||
@populate_tracklist
|
||||
def test_previous_track_after_next(self):
|
||||
self.playback.play()
|
||||
self.playback.next()
|
||||
self.assertEqual(self.playback.tl_track_at_previous, self.tl_tracks[0])
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(
|
||||
self.tracklist.previous_track(tl_track), self.tl_tracks[0])
|
||||
|
||||
@populate_tracklist
|
||||
def test_previous_track_after_previous(self):
|
||||
@ -493,27 +535,32 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
self.playback.next() # At track 1
|
||||
self.playback.next() # At track 2
|
||||
self.playback.previous() # At track 1
|
||||
self.assertEqual(self.playback.tl_track_at_previous, self.tl_tracks[0])
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(
|
||||
self.tracklist.previous_track(tl_track), self.tl_tracks[0])
|
||||
|
||||
def test_previous_track_empty_playlist(self):
|
||||
self.assertEqual(self.playback.tl_track_at_previous, None)
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(self.tracklist.previous_track(tl_track), None)
|
||||
|
||||
@populate_tracklist
|
||||
def test_previous_track_with_consume(self):
|
||||
self.playback.consume = True
|
||||
self.tracklist.consume = True
|
||||
for _ in self.tracks:
|
||||
self.playback.next()
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(
|
||||
self.playback.tl_track_at_previous,
|
||||
self.tracklist.previous_track(tl_track),
|
||||
self.playback.current_tl_track)
|
||||
|
||||
@populate_tracklist
|
||||
def test_previous_track_with_random(self):
|
||||
self.playback.random = True
|
||||
self.tracklist.random = True
|
||||
for _ in self.tracks:
|
||||
self.playback.next()
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(
|
||||
self.playback.tl_track_at_previous,
|
||||
self.tracklist.previous_track(tl_track),
|
||||
self.playback.current_tl_track)
|
||||
|
||||
@populate_tracklist
|
||||
@ -533,24 +580,28 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_initial_tracklist_position(self):
|
||||
self.assertEqual(self.playback.tracklist_position, None)
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(self.tracklist.index(tl_track), None)
|
||||
|
||||
@populate_tracklist
|
||||
def test_tracklist_position_during_play(self):
|
||||
self.playback.play()
|
||||
self.assertEqual(self.playback.tracklist_position, 0)
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(self.tracklist.index(tl_track), 0)
|
||||
|
||||
@populate_tracklist
|
||||
def test_tracklist_position_after_next(self):
|
||||
self.playback.play()
|
||||
self.playback.next()
|
||||
self.assertEqual(self.playback.tracklist_position, 1)
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(self.tracklist.index(tl_track), 1)
|
||||
|
||||
@populate_tracklist
|
||||
def test_tracklist_position_at_end_of_playlist(self):
|
||||
self.playback.play(self.tracklist.tl_tracks[-1])
|
||||
self.playback.on_end_of_track()
|
||||
self.assertEqual(self.playback.tracklist_position, None)
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(self.tracklist.index(tl_track), None)
|
||||
|
||||
def test_on_tracklist_change_gets_called(self):
|
||||
callback = self.playback.on_tracklist_change
|
||||
@ -808,13 +859,13 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_play_with_consume(self):
|
||||
self.playback.consume = True
|
||||
self.tracklist.consume = True
|
||||
self.playback.play()
|
||||
self.assertEqual(self.playback.current_track, self.tracks[0])
|
||||
|
||||
@populate_tracklist
|
||||
def test_playlist_is_empty_after_all_tracks_are_played_with_consume(self):
|
||||
self.playback.consume = True
|
||||
self.tracklist.consume = True
|
||||
self.playback.play()
|
||||
for _ in range(len(self.tracklist.tracks)):
|
||||
self.playback.on_end_of_track()
|
||||
@ -823,14 +874,14 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
@populate_tracklist
|
||||
def test_play_with_random(self):
|
||||
random.seed(1)
|
||||
self.playback.random = True
|
||||
self.tracklist.random = True
|
||||
self.playback.play()
|
||||
self.assertEqual(self.playback.current_track, self.tracks[2])
|
||||
|
||||
@populate_tracklist
|
||||
def test_previous_with_random(self):
|
||||
random.seed(1)
|
||||
self.playback.random = True
|
||||
self.tracklist.random = True
|
||||
self.playback.play()
|
||||
self.playback.next()
|
||||
current_track = self.playback.current_track
|
||||
@ -845,8 +896,8 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
|
||||
@populate_tracklist
|
||||
def test_end_of_song_with_single_and_repeat_starts_same(self):
|
||||
self.playback.single = True
|
||||
self.playback.repeat = True
|
||||
self.tracklist.single = True
|
||||
self.tracklist.repeat = True
|
||||
self.playback.play()
|
||||
self.playback.on_end_of_track()
|
||||
self.assertEqual(self.playback.current_track, self.tracks[0])
|
||||
@ -858,44 +909,47 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
||||
self.assertEqual(self.playback.state, PlaybackState.STOPPED)
|
||||
|
||||
def test_repeat_off_by_default(self):
|
||||
self.assertEqual(self.playback.repeat, False)
|
||||
self.assertEqual(self.tracklist.repeat, False)
|
||||
|
||||
def test_random_off_by_default(self):
|
||||
self.assertEqual(self.playback.random, False)
|
||||
self.assertEqual(self.tracklist.random, False)
|
||||
|
||||
def test_consume_off_by_default(self):
|
||||
self.assertEqual(self.playback.consume, False)
|
||||
self.assertEqual(self.tracklist.consume, False)
|
||||
|
||||
@populate_tracklist
|
||||
def test_random_until_end_of_playlist(self):
|
||||
self.playback.random = True
|
||||
self.tracklist.random = True
|
||||
self.playback.play()
|
||||
for _ in self.tracks[1:]:
|
||||
self.playback.next()
|
||||
self.assertEqual(self.playback.tl_track_at_next, None)
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertEqual(self.tracklist.next_track(tl_track), None)
|
||||
|
||||
@populate_tracklist
|
||||
def test_random_until_end_of_playlist_and_play_from_start(self):
|
||||
self.playback.repeat = True
|
||||
self.tracklist.repeat = True
|
||||
for _ in self.tracks:
|
||||
self.playback.next()
|
||||
self.assertNotEqual(self.playback.tl_track_at_next, None)
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertNotEqual(self.tracklist.next_track(tl_track), None)
|
||||
self.assertEqual(self.playback.state, PlaybackState.STOPPED)
|
||||
self.playback.play()
|
||||
self.assertEqual(self.playback.state, PlaybackState.PLAYING)
|
||||
|
||||
@populate_tracklist
|
||||
def test_random_until_end_of_playlist_with_repeat(self):
|
||||
self.playback.repeat = True
|
||||
self.playback.random = True
|
||||
self.tracklist.repeat = True
|
||||
self.tracklist.random = True
|
||||
self.playback.play()
|
||||
for _ in self.tracks:
|
||||
self.playback.next()
|
||||
self.assertNotEqual(self.playback.tl_track_at_next, None)
|
||||
tl_track = self.playback.current_tl_track
|
||||
self.assertNotEqual(self.tracklist.next_track(tl_track), None)
|
||||
|
||||
@populate_tracklist
|
||||
def test_played_track_during_random_not_played_again(self):
|
||||
self.playback.random = True
|
||||
self.tracklist.random = True
|
||||
self.playback.play()
|
||||
played = []
|
||||
for _ in self.tracks:
|
||||
|
||||
@ -171,13 +171,13 @@ class LocalTracklistProviderTest(unittest.TestCase):
|
||||
|
||||
def test_index_returns_index_of_track(self):
|
||||
tl_tracks = self.controller.add(self.tracks)
|
||||
self.assertEquals(0, self.controller.index(tl_tracks[0]))
|
||||
self.assertEquals(1, self.controller.index(tl_tracks[1]))
|
||||
self.assertEquals(2, self.controller.index(tl_tracks[2]))
|
||||
self.assertEqual(0, self.controller.index(tl_tracks[0]))
|
||||
self.assertEqual(1, self.controller.index(tl_tracks[1]))
|
||||
self.assertEqual(2, self.controller.index(tl_tracks[2]))
|
||||
|
||||
def test_index_raises_value_error_if_item_not_found(self):
|
||||
test = lambda: self.controller.index(TlTrack(0, Track()))
|
||||
self.assertRaises(ValueError, test)
|
||||
def test_index_returns_none_if_item_not_found(self):
|
||||
tl_track = TlTrack(0, Track())
|
||||
self.assertEqual(self.controller.index(tl_track), None)
|
||||
|
||||
@populate_tracklist
|
||||
def test_move_single(self):
|
||||
|
||||
@ -16,22 +16,22 @@ STOPPED = PlaybackState.STOPPED
|
||||
class PlaybackOptionsHandlerTest(protocol.BaseTestCase):
|
||||
def test_consume_off(self):
|
||||
self.sendRequest('consume "0"')
|
||||
self.assertFalse(self.core.playback.consume.get())
|
||||
self.assertFalse(self.core.tracklist.consume.get())
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_consume_off_without_quotes(self):
|
||||
self.sendRequest('consume 0')
|
||||
self.assertFalse(self.core.playback.consume.get())
|
||||
self.assertFalse(self.core.tracklist.consume.get())
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_consume_on(self):
|
||||
self.sendRequest('consume "1"')
|
||||
self.assertTrue(self.core.playback.consume.get())
|
||||
self.assertTrue(self.core.tracklist.consume.get())
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_consume_on_without_quotes(self):
|
||||
self.sendRequest('consume 1')
|
||||
self.assertTrue(self.core.playback.consume.get())
|
||||
self.assertTrue(self.core.tracklist.consume.get())
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_crossfade(self):
|
||||
@ -40,42 +40,42 @@ class PlaybackOptionsHandlerTest(protocol.BaseTestCase):
|
||||
|
||||
def test_random_off(self):
|
||||
self.sendRequest('random "0"')
|
||||
self.assertFalse(self.core.playback.random.get())
|
||||
self.assertFalse(self.core.tracklist.random.get())
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_random_off_without_quotes(self):
|
||||
self.sendRequest('random 0')
|
||||
self.assertFalse(self.core.playback.random.get())
|
||||
self.assertFalse(self.core.tracklist.random.get())
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_random_on(self):
|
||||
self.sendRequest('random "1"')
|
||||
self.assertTrue(self.core.playback.random.get())
|
||||
self.assertTrue(self.core.tracklist.random.get())
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_random_on_without_quotes(self):
|
||||
self.sendRequest('random 1')
|
||||
self.assertTrue(self.core.playback.random.get())
|
||||
self.assertTrue(self.core.tracklist.random.get())
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_repeat_off(self):
|
||||
self.sendRequest('repeat "0"')
|
||||
self.assertFalse(self.core.playback.repeat.get())
|
||||
self.assertFalse(self.core.tracklist.repeat.get())
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_repeat_off_without_quotes(self):
|
||||
self.sendRequest('repeat 0')
|
||||
self.assertFalse(self.core.playback.repeat.get())
|
||||
self.assertFalse(self.core.tracklist.repeat.get())
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_repeat_on(self):
|
||||
self.sendRequest('repeat "1"')
|
||||
self.assertTrue(self.core.playback.repeat.get())
|
||||
self.assertTrue(self.core.tracklist.repeat.get())
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_repeat_on_without_quotes(self):
|
||||
self.sendRequest('repeat 1')
|
||||
self.assertTrue(self.core.playback.repeat.get())
|
||||
self.assertTrue(self.core.tracklist.repeat.get())
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_setvol_below_min(self):
|
||||
@ -115,22 +115,22 @@ class PlaybackOptionsHandlerTest(protocol.BaseTestCase):
|
||||
|
||||
def test_single_off(self):
|
||||
self.sendRequest('single "0"')
|
||||
self.assertFalse(self.core.playback.single.get())
|
||||
self.assertFalse(self.core.tracklist.single.get())
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_single_off_without_quotes(self):
|
||||
self.sendRequest('single 0')
|
||||
self.assertFalse(self.core.playback.single.get())
|
||||
self.assertFalse(self.core.tracklist.single.get())
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_single_on(self):
|
||||
self.sendRequest('single "1"')
|
||||
self.assertTrue(self.core.playback.single.get())
|
||||
self.assertTrue(self.core.tracklist.single.get())
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_single_on_without_quotes(self):
|
||||
self.sendRequest('single 1')
|
||||
self.assertTrue(self.core.playback.single.get())
|
||||
self.assertTrue(self.core.tracklist.single.get())
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_replay_gain_mode_off(self):
|
||||
|
||||
@ -64,7 +64,7 @@ class StatusHandlerTest(unittest.TestCase):
|
||||
self.assertEqual(int(result['repeat']), 0)
|
||||
|
||||
def test_status_method_contains_repeat_is_1(self):
|
||||
self.core.playback.repeat = 1
|
||||
self.core.tracklist.repeat = 1
|
||||
result = dict(status.status(self.context))
|
||||
self.assertIn('repeat', result)
|
||||
self.assertEqual(int(result['repeat']), 1)
|
||||
@ -75,7 +75,7 @@ class StatusHandlerTest(unittest.TestCase):
|
||||
self.assertEqual(int(result['random']), 0)
|
||||
|
||||
def test_status_method_contains_random_is_1(self):
|
||||
self.core.playback.random = 1
|
||||
self.core.tracklist.random = 1
|
||||
result = dict(status.status(self.context))
|
||||
self.assertIn('random', result)
|
||||
self.assertEqual(int(result['random']), 1)
|
||||
@ -91,7 +91,7 @@ class StatusHandlerTest(unittest.TestCase):
|
||||
self.assertEqual(int(result['consume']), 0)
|
||||
|
||||
def test_status_method_contains_consume_is_1(self):
|
||||
self.core.playback.consume = 1
|
||||
self.core.tracklist.consume = 1
|
||||
result = dict(status.status(self.context))
|
||||
self.assertIn('consume', result)
|
||||
self.assertEqual(int(result['consume']), 1)
|
||||
|
||||
@ -34,6 +34,9 @@ class Calculator(object):
|
||||
def _secret(self):
|
||||
return 'Grand Unified Theory'
|
||||
|
||||
def fail(self):
|
||||
raise ValueError('What did you expect?')
|
||||
|
||||
|
||||
class JsonRpcTestBase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@ -266,12 +269,12 @@ class JsonRpcSingleNotificationTest(JsonRpcTestBase):
|
||||
|
||||
class JsonRpcBatchTest(JsonRpcTestBase):
|
||||
def test_batch_of_only_commands_returns_all(self):
|
||||
self.core.playback.set_random(True).get()
|
||||
self.core.tracklist.set_random(True).get()
|
||||
|
||||
request = [
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.get_repeat', 'id': 1},
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.get_random', 'id': 2},
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.get_single', 'id': 3},
|
||||
{'jsonrpc': '2.0', 'method': 'core.tracklist.get_repeat', 'id': 1},
|
||||
{'jsonrpc': '2.0', 'method': 'core.tracklist.get_random', 'id': 2},
|
||||
{'jsonrpc': '2.0', 'method': 'core.tracklist.get_single', 'id': 3},
|
||||
]
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
@ -283,12 +286,12 @@ class JsonRpcBatchTest(JsonRpcTestBase):
|
||||
self.assertEqual(response[3]['result'], False)
|
||||
|
||||
def test_batch_of_commands_and_notifications_returns_some(self):
|
||||
self.core.playback.set_random(True).get()
|
||||
self.core.tracklist.set_random(True).get()
|
||||
|
||||
request = [
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.get_repeat'},
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.get_random', 'id': 2},
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.get_single', 'id': 3},
|
||||
{'jsonrpc': '2.0', 'method': 'core.tracklist.get_repeat'},
|
||||
{'jsonrpc': '2.0', 'method': 'core.tracklist.get_random', 'id': 2},
|
||||
{'jsonrpc': '2.0', 'method': 'core.tracklist.get_single', 'id': 3},
|
||||
]
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
@ -300,12 +303,12 @@ class JsonRpcBatchTest(JsonRpcTestBase):
|
||||
self.assertEqual(response[3]['result'], False)
|
||||
|
||||
def test_batch_of_only_notifications_returns_nothing(self):
|
||||
self.core.playback.set_random(True).get()
|
||||
self.core.tracklist.set_random(True).get()
|
||||
|
||||
request = [
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.get_repeat'},
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.get_random'},
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.get_single'},
|
||||
{'jsonrpc': '2.0', 'method': 'core.tracklist.get_repeat'},
|
||||
{'jsonrpc': '2.0', 'method': 'core.tracklist.get_random'},
|
||||
{'jsonrpc': '2.0', 'method': 'core.tracklist.get_single'},
|
||||
]
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
@ -316,8 +319,8 @@ class JsonRpcSingleCommandErrorTest(JsonRpcTestBase):
|
||||
def test_application_error_response(self):
|
||||
request = {
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'core.tracklist.index',
|
||||
'params': ['bogus'],
|
||||
'method': 'calc.fail',
|
||||
'params': [],
|
||||
'id': 1,
|
||||
}
|
||||
response = self.jrw.handle_data(request)
|
||||
@ -330,7 +333,7 @@ class JsonRpcSingleCommandErrorTest(JsonRpcTestBase):
|
||||
|
||||
data = error['data']
|
||||
self.assertEqual(data['type'], 'ValueError')
|
||||
self.assertIn('not in list', data['message'])
|
||||
self.assertIn('What did you expect?', data['message'])
|
||||
self.assertIn('traceback', data)
|
||||
self.assertIn('Traceback (most recent call last):', data['traceback'])
|
||||
|
||||
@ -522,10 +525,10 @@ class JsonRpcBatchErrorTest(JsonRpcTestBase):
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.set_volume',
|
||||
'params': [47], 'id': '1'},
|
||||
# Notification
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.set_consume',
|
||||
{'jsonrpc': '2.0', 'method': 'core.tracklist.set_consume',
|
||||
'params': [True]},
|
||||
# Call with positional params
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.set_repeat',
|
||||
{'jsonrpc': '2.0', 'method': 'core.tracklist.set_repeat',
|
||||
'params': [False], 'id': '2'},
|
||||
# Invalid request
|
||||
{'foo': 'boo'},
|
||||
@ -533,7 +536,7 @@ class JsonRpcBatchErrorTest(JsonRpcTestBase):
|
||||
{'jsonrpc': '2.0', 'method': 'foo.get',
|
||||
'params': {'name': 'myself'}, 'id': '5'},
|
||||
# Call without params
|
||||
{'jsonrpc': '2.0', 'method': 'core.playback.get_random',
|
||||
{'jsonrpc': '2.0', 'method': 'core.tracklist.get_random',
|
||||
'id': '9'},
|
||||
]
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user