Replace BaseBackend with new API, keeping the existing logic. MPD formatting moved to MpdHandler.
This commit is contained in:
parent
969beea69b
commit
035e43b4f5
@ -1,23 +1,96 @@
|
||||
from copy import copy
|
||||
import logging
|
||||
import random
|
||||
import time
|
||||
|
||||
from mopidy.exceptions import MpdNotImplemented
|
||||
from mopidy.models import Playlist
|
||||
|
||||
logger = logging.getLogger('backends.base')
|
||||
|
||||
class BaseBackend(object):
|
||||
PLAY = u'play'
|
||||
PAUSE = u'pause'
|
||||
STOP = u'stop'
|
||||
current_playlist = None
|
||||
library = None
|
||||
playback = None
|
||||
stored_playlists = None
|
||||
uri_handlers = []
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._state = self.STOP
|
||||
self._playlists = []
|
||||
self._x_current_playlist = Playlist()
|
||||
self._current_playlist_version = 0
|
||||
|
||||
# Backend state
|
||||
class BaseCurrentPlaylistController(object):
|
||||
def __init__(self, backend):
|
||||
self.backend = backend
|
||||
self.version = 0
|
||||
self.playlist = Playlist()
|
||||
|
||||
@property
|
||||
def playlist(self):
|
||||
return copy(self._playlist)
|
||||
|
||||
@playlist.setter
|
||||
def playlist(self, new_playlist):
|
||||
self._playlist = new_playlist
|
||||
self.version += 1
|
||||
|
||||
def add(self, uri, at_position=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def clear(self):
|
||||
self.backend.playback.stop()
|
||||
self.playlist = Playlist()
|
||||
|
||||
def load(self, playlist):
|
||||
self.playlist = playlist
|
||||
self.version = 0
|
||||
|
||||
def move(self, start, end, to_position):
|
||||
tracks = self.playlist.tracks
|
||||
new_tracks = tracks[:start] + tracks[end:]
|
||||
|
||||
for track in tracks[start:end]:
|
||||
new_tracks.insert(to_position, track)
|
||||
to_position += 1
|
||||
|
||||
self.playlist = self.playlist.with_(tracks=new_tracks)
|
||||
|
||||
def remove(self, position):
|
||||
tracks = self.playlist.tracks
|
||||
del tracks[position]
|
||||
self.playlist = self.playlist.with_(tracks=tracks)
|
||||
|
||||
def shuffle(self, start=None, end=None):
|
||||
tracks = self.playlist.tracks
|
||||
|
||||
before = tracks[:start or 0]
|
||||
shuffled = tracks[start:end]
|
||||
after = tracks[end or len(tracks):]
|
||||
|
||||
random.shuffle(shuffled)
|
||||
|
||||
self.playlist = self.playlist.with_(tracks=before+shuffled+after)
|
||||
|
||||
|
||||
class BasePlaybackController(object):
|
||||
PAUSED = u'paused'
|
||||
PLAYING = u'playing'
|
||||
STOPPED = u'stopped'
|
||||
|
||||
def __init__(self, backend):
|
||||
self.backend = backend
|
||||
self.consume = False
|
||||
self.current_track = None
|
||||
self.random = False
|
||||
self.repeat = False
|
||||
self.state = self.STOPPED
|
||||
self.volume = None
|
||||
|
||||
@property
|
||||
def playlist_position(self):
|
||||
if self.current_track is None:
|
||||
return None
|
||||
try:
|
||||
return self.backend.current_playlist.playlist.index(
|
||||
self.current_track)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
@ -35,7 +108,7 @@ class BaseBackend(object):
|
||||
self._play_time_resume()
|
||||
|
||||
@property
|
||||
def _play_time_elapsed(self):
|
||||
def time_position(self):
|
||||
if self.state == self.PLAY:
|
||||
time_since_started = int(time.time()) - self._play_time_started
|
||||
return self._play_time_accumulated + time_since_started
|
||||
@ -55,184 +128,113 @@ class BaseBackend(object):
|
||||
def _play_time_resume(self):
|
||||
self._play_time_started = int(time.time())
|
||||
|
||||
@property
|
||||
def _current_playlist(self):
|
||||
return self._x_current_playlist
|
||||
|
||||
@_current_playlist.setter
|
||||
def _current_playlist(self, playlist):
|
||||
self._x_current_playlist = playlist
|
||||
self._current_playlist_version += 1
|
||||
|
||||
@property
|
||||
def _current_track(self):
|
||||
if self._current_song_pos is not None:
|
||||
return self._current_playlist.tracks[self._current_song_pos]
|
||||
|
||||
@property
|
||||
def _current_song_pos(self):
|
||||
if not hasattr(self, '_x_current_song_pos'):
|
||||
self._x_current_song_pos = None
|
||||
if (self._current_playlist is None
|
||||
or self._current_playlist.length == 0):
|
||||
self._x_current_song_pos = None
|
||||
elif self._x_current_song_pos < 0:
|
||||
self._x_current_song_pos = 0
|
||||
elif self._x_current_song_pos >= self._current_playlist.length:
|
||||
self._x_current_song_pos = self._current_playlist.length - 1
|
||||
return self._x_current_song_pos
|
||||
|
||||
@_current_song_pos.setter
|
||||
def _current_song_pos(self, songid):
|
||||
self._x_current_song_pos = songid
|
||||
|
||||
# Status methods
|
||||
|
||||
def current_song(self):
|
||||
if self.state is not self.STOP and self._current_track is not None:
|
||||
return self._current_track.mpd_format(self._current_song_pos)
|
||||
|
||||
def status_bitrate(self):
|
||||
return 0
|
||||
|
||||
def status_consume(self):
|
||||
return 0
|
||||
|
||||
def status_volume(self):
|
||||
return 0
|
||||
|
||||
def status_repeat(self):
|
||||
return 0
|
||||
|
||||
def status_random(self):
|
||||
return 0
|
||||
|
||||
def status_single(self):
|
||||
return 0
|
||||
|
||||
def status_song_id(self):
|
||||
return self._current_song_pos # Override if you got a better ID scheme
|
||||
|
||||
def status_playlist(self):
|
||||
return self._current_playlist_version
|
||||
|
||||
def status_playlist_length(self):
|
||||
return self._current_playlist.length
|
||||
|
||||
def status_state(self):
|
||||
return self.state
|
||||
|
||||
def status_time(self):
|
||||
return u'%s:%s' % (self._play_time_elapsed, self.status_time_total())
|
||||
|
||||
def status_time_total(self):
|
||||
if self._current_track is not None:
|
||||
return self._current_track.length // 1000
|
||||
else:
|
||||
return 0
|
||||
|
||||
def status_xfade(self):
|
||||
return 0
|
||||
|
||||
def url_handlers(self):
|
||||
return []
|
||||
|
||||
# Control methods
|
||||
|
||||
def end_of_track(self):
|
||||
self.next()
|
||||
def new_playlist_loaded_callback(self):
|
||||
self.current_track = None
|
||||
if self.state == self.PLAYING:
|
||||
if self.backend.current_playlist.playlist.length > 0:
|
||||
self.play(self.backend.current_playlist.playlist.tracks[0])
|
||||
else:
|
||||
self.stop()
|
||||
|
||||
def next(self):
|
||||
self.stop()
|
||||
if self._next():
|
||||
self.state = self.PLAY
|
||||
self.state = self.PLAYING
|
||||
|
||||
def _next(self):
|
||||
raise MpdNotImplemented
|
||||
raise NotImplementedError
|
||||
|
||||
def pause(self):
|
||||
if self.state == self.PLAY and self._pause():
|
||||
self.state = self.PAUSE
|
||||
if self.state == self.PLAYING and self._pause():
|
||||
self.state = self.PAUSED
|
||||
|
||||
def _pause(self):
|
||||
raise MpdNotImplemented
|
||||
raise NotImplementedError
|
||||
|
||||
def play(self, songpos=None, songid=None):
|
||||
if self.state == self.PAUSE and songpos is None and songid is None:
|
||||
def play(self, track=None):
|
||||
if self.state == self.PAUSED and track is None:
|
||||
return self.resume()
|
||||
if track is not None:
|
||||
self.current_track = track
|
||||
self.stop()
|
||||
if songpos is not None and self._play_pos(songpos):
|
||||
self.state = self.PLAY
|
||||
elif songid is not None and self._play_id(songid):
|
||||
self.state = self.PLAY
|
||||
elif self._play():
|
||||
self.state = self.PLAY
|
||||
if self._play(track):
|
||||
self.state = self.PLAYING
|
||||
|
||||
def _play(self):
|
||||
raise MpdNotImplemented
|
||||
|
||||
def _play_id(self, songid):
|
||||
raise MpdNotImplemented
|
||||
|
||||
def _play_pos(self, songpos):
|
||||
raise MpdNotImplemented
|
||||
def _play(self, track):
|
||||
raise NotImplementedError
|
||||
|
||||
def previous(self):
|
||||
self.stop()
|
||||
if self._previous():
|
||||
self.state = self.PLAY
|
||||
self.state = self.PLAYING
|
||||
|
||||
def _previous(self):
|
||||
raise MpdNotImplemented
|
||||
raise NotImplementedError
|
||||
|
||||
def resume(self):
|
||||
if self.state == self.PAUSE and self._resume():
|
||||
self.state = self.PLAY
|
||||
if self.state == self.PAUSED and self._resume():
|
||||
self.state = self.PLAYING
|
||||
|
||||
def _resume(self):
|
||||
raise MpdNotImplemented
|
||||
raise NotImplementedError
|
||||
|
||||
def seek(self, time_position):
|
||||
raise NotImplementedError
|
||||
|
||||
def stop(self):
|
||||
if self.state != self.STOP and self._stop():
|
||||
self.state = self.STOP
|
||||
if self.state != self.STOPPED and self._stop():
|
||||
self.state = self.STOPPED
|
||||
|
||||
def _stop(self):
|
||||
raise MpdNotImplemented
|
||||
raise NotImplementedError
|
||||
|
||||
# Current/single playlist methods
|
||||
|
||||
def playlist_load(self, name):
|
||||
self._current_song_pos = None
|
||||
matches = filter(lambda p: p.name == name, self._playlists)
|
||||
if matches:
|
||||
self._current_playlist = matches[0]
|
||||
if self.state == self.PLAY:
|
||||
self.play(songpos=0)
|
||||
else:
|
||||
self._current_playlist = None
|
||||
class BaseLibraryController(object):
|
||||
def __init__(self, backend):
|
||||
self.backend = backend
|
||||
|
||||
def playlist_changes_since(self, version='0'):
|
||||
if int(version) < self._current_playlist_version:
|
||||
return self._current_playlist.mpd_format()
|
||||
def find_exact(self, type, query):
|
||||
raise NotImplementedError
|
||||
|
||||
def playlist_info(self, songpos=None, start=0, end=None):
|
||||
if songpos is not None:
|
||||
start = int(songpos)
|
||||
end = start + 1
|
||||
else:
|
||||
if start is None:
|
||||
start = 0
|
||||
start = int(start)
|
||||
if end is not None:
|
||||
end = int(end)
|
||||
return self._current_playlist.mpd_format(start, end)
|
||||
def lookup(self, uri):
|
||||
raise NotImplementedError
|
||||
|
||||
# Stored playlist methods
|
||||
def refresh(self, uri=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def playlists_list(self):
|
||||
return [u'playlist: %s' % p.name for p in self._playlists]
|
||||
def search(self, type, query):
|
||||
raise NotImplementedError
|
||||
|
||||
# Music database methods
|
||||
|
||||
def search(self, type, what):
|
||||
return None
|
||||
class BaseStoredPlaylistController(object):
|
||||
def __init__(self, backend):
|
||||
self.backend = backend
|
||||
self._playlists = []
|
||||
|
||||
@property
|
||||
def playlists(self):
|
||||
return copy(self._playlists)
|
||||
|
||||
def add(self, uri):
|
||||
raise NotImplementedError
|
||||
|
||||
def create(self, name):
|
||||
raise NotImplementedError
|
||||
|
||||
def delete(self, playlist):
|
||||
raise NotImplementedError
|
||||
|
||||
def lookup(self, uri):
|
||||
raise NotImplementedError
|
||||
|
||||
def refresh(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def rename(self, playlist, new_name):
|
||||
raise NotImplementedError
|
||||
|
||||
def save(self, playlist):
|
||||
raise NotImplementedError
|
||||
|
||||
def search(self, query):
|
||||
return filter(lambda p: query in p.name, self._playlists)
|
||||
|
||||
@ -189,11 +189,15 @@ class MpdHandler(object):
|
||||
|
||||
@register(r'^listplaylists$')
|
||||
def _listplaylists(self):
|
||||
return self.backend.playlists_list()
|
||||
return [u'playlist: %s' % p.name
|
||||
for p in self.backend.stored_playlists.playlists]
|
||||
|
||||
@register(r'^load "(?P<name>[^"]+)"$')
|
||||
def _load(self, name):
|
||||
return self.backend.playlist_load(name)
|
||||
matches = self.backend.stored_playlists.search(name)
|
||||
if matches:
|
||||
self.backend.current_playlist.load(matches[0])
|
||||
self.backend.playback.new_playlist_loaded_callback()
|
||||
|
||||
@register(r'^lsinfo$')
|
||||
@register(r'^lsinfo "(?P<uri>[^"]*)"$')
|
||||
@ -264,13 +268,22 @@ class MpdHandler(object):
|
||||
|
||||
@register(r'^playlistid( "(?P<songid>\S+)")*$')
|
||||
def _playlistid(self, songid=None):
|
||||
return self.backend.playlist_info(songid, None, None)
|
||||
return self.backend.current_playlist.playlist.mpd_format()
|
||||
|
||||
@register(r'^playlistinfo$')
|
||||
@register(r'^playlistinfo "(?P<songpos>\d+)"$')
|
||||
@register(r'^playlistinfo "(?P<start>\d+):(?P<end>\d+)*"$')
|
||||
def _playlistinfo(self, songpos=None, start=None, end=None):
|
||||
return self.backend.playlist_info(songpos, start, end)
|
||||
if songpos is not None:
|
||||
return self.backend.current_playlist.playlist.mpd_format(
|
||||
songpos, songpos + 1)
|
||||
else:
|
||||
if start is None:
|
||||
start = 0
|
||||
start = int(start)
|
||||
if end is not None:
|
||||
end = int(end)
|
||||
return self.backend.current_playlist.playlist.mpd_format(start, end)
|
||||
|
||||
@register(r'^playlistmove "(?P<name>[^"]+)" "(?P<songid>\d+)" "(?P<songpos>\d+)"$')
|
||||
def _playlistdelete(self, name, songid, songpos):
|
||||
@ -282,7 +295,8 @@ class MpdHandler(object):
|
||||
|
||||
@register(r'^plchanges "(?P<version>\d+)"$')
|
||||
def _plchanges(self, version):
|
||||
return self.backend.playlist_changes_since(version)
|
||||
if int(version) < self.backend.current_playlist.version:
|
||||
return self.backend.current_playlist.playlist.mpd_format()
|
||||
|
||||
@register(r'^plchangesposid "(?P<version>\d+)"$')
|
||||
def _plchangesposid(self, version):
|
||||
@ -400,10 +414,23 @@ class MpdHandler(object):
|
||||
result.append(('song', self.backend.status_song_id()))
|
||||
result.append(('songid', self.backend.status_song_id()))
|
||||
if self.backend.state in (self.backend.PLAY, self.backend.PAUSE):
|
||||
result.append(('time', self.backend.status_time()))
|
||||
result.append(('time', self._status_time()))
|
||||
result.append(('bitrate', self.backend.status_bitrate()))
|
||||
return result
|
||||
|
||||
def _status_time(self):
|
||||
return u'%s:%s' % (
|
||||
self._status_time_elapsed(), self._status_time_total())
|
||||
|
||||
def _status_time_elapsed(self):
|
||||
return self.backend.playback.time_position
|
||||
|
||||
def _status_time_total(self):
|
||||
if self.backend.playback.current_track is not None:
|
||||
return self.backend.playback.current_track.length // 1000
|
||||
else:
|
||||
return 0
|
||||
|
||||
@register(r'^swap "(?P<songpos1>\d+)" "(?P<songpos2>\d+)"$')
|
||||
def _swap(self, songpos1, songpos2):
|
||||
raise MpdNotImplemented # TODO
|
||||
|
||||
Loading…
Reference in New Issue
Block a user