diff --git a/docs/api/backends/controllers.rst b/docs/api/backends/controllers.rst
index 28112cf7..20dc2d61 100644
--- a/docs/api/backends/controllers.rst
+++ b/docs/api/backends/controllers.rst
@@ -15,7 +15,6 @@ The backend
.. autoclass:: mopidy.backends.base.Backend
:members:
- :undoc-members:
Playback controller
@@ -26,7 +25,6 @@ seek.
.. autoclass:: mopidy.backends.base.PlaybackController
:members:
- :undoc-members:
Mixer controller
@@ -42,7 +40,6 @@ Manages everything related to the currently loaded playlist.
.. autoclass:: mopidy.backends.base.CurrentPlaylistController
:members:
- :undoc-members:
Stored playlists controller
@@ -52,7 +49,6 @@ Manages stored playlist.
.. autoclass:: mopidy.backends.base.StoredPlaylistsController
:members:
- :undoc-members:
Library controller
@@ -62,4 +58,3 @@ Manages the music library, e.g. searching for tracks to be added to a playlist.
.. autoclass:: mopidy.backends.base.LibraryController
:members:
- :undoc-members:
diff --git a/docs/api/backends/providers.rst b/docs/api/backends/providers.rst
index 903e220b..61e5f68a 100644
--- a/docs/api/backends/providers.rst
+++ b/docs/api/backends/providers.rst
@@ -14,7 +14,6 @@ Playback provider
.. autoclass:: mopidy.backends.base.BasePlaybackProvider
:members:
- :undoc-members:
Stored playlists provider
@@ -22,7 +21,6 @@ Stored playlists provider
.. autoclass:: mopidy.backends.base.BaseStoredPlaylistsProvider
:members:
- :undoc-members:
Library provider
@@ -30,7 +28,6 @@ Library provider
.. autoclass:: mopidy.backends.base.BaseLibraryProvider
:members:
- :undoc-members:
Backend provider implementations
diff --git a/docs/api/frontends.rst b/docs/api/frontends.rst
index 0c1e32a3..792e4bc9 100644
--- a/docs/api/frontends.rst
+++ b/docs/api/frontends.rst
@@ -2,22 +2,26 @@
Frontend API
************
-A frontend may do whatever it wants to, including creating threads, opening TCP
-ports and exposing Mopidy for a type of clients.
-
-Frontends got one main limitation: they are restricted to passing messages
-through the ``core_queue`` for all communication with the rest of Mopidy. Thus,
-the frontend API is very small and reveals little of what a frontend may do.
-
-.. warning::
-
- A stable frontend API is not available yet, as we've only implemented a
- couple of frontend modules.
-
-.. automodule:: mopidy.frontends.base
- :synopsis: Base class for frontends
- :members:
+The following requirements applies to any frontend implementation:
+- A frontend MAY do mostly whatever it wants to, including creating threads,
+ opening TCP ports and exposing Mopidy for a group of clients.
+- A frontend MUST implement at least one `Pykka
+ `_ actor, called the "main actor" from here
+ on.
+- It MAY use additional actors to implement whatever it does, and using actors
+ in frontend implementations is encouraged.
+- The frontend is activated by including its main actor in the
+ :attr:`mopidy.settings.FRONTENDS` setting.
+- The main actor MUST be able to start and stop the frontend when the main
+ actor is started and stopped.
+- The frontend MAY require additional settings to be set for it to
+ work.
+- Such settings MUST be documented.
+- The main actor MUST stop itself if the defined settings are not adequate for
+ the frontend to work properly.
+- Any actor which is part of the frontend MAY implement any listener interface
+ from :mod:`mopidy.listeners` to receive notification of the specified events.
Frontend implementations
========================
diff --git a/docs/api/listeners.rst b/docs/api/listeners.rst
new file mode 100644
index 00000000..609dc3c7
--- /dev/null
+++ b/docs/api/listeners.rst
@@ -0,0 +1,7 @@
+************
+Listener API
+************
+
+.. automodule:: mopidy.listeners
+ :synopsis: Listener API
+ :members:
diff --git a/docs/api/mixers.rst b/docs/api/mixers.rst
index 6daa7a4e..2459db8c 100644
--- a/docs/api/mixers.rst
+++ b/docs/api/mixers.rst
@@ -30,7 +30,6 @@ methods as described below.
.. automodule:: mopidy.mixers.base
:synopsis: Mixer API
:members:
- :undoc-members:
Mixer implementations
diff --git a/docs/api/models.rst b/docs/api/models.rst
index ef11547e..5833e58c 100644
--- a/docs/api/models.rst
+++ b/docs/api/models.rst
@@ -25,4 +25,3 @@ Data model API
.. automodule:: mopidy.models
:synopsis: Data model API
:members:
- :undoc-members:
diff --git a/docs/changes.rst b/docs/changes.rst
index 07212cc7..db03498c 100644
--- a/docs/changes.rst
+++ b/docs/changes.rst
@@ -13,6 +13,9 @@ v0.6.0 (in development)
- Replace :attr:`mopidy.backends.base.Backend.uri_handlers` with
:attr:`mopidy.backends.base.Backend.uri_schemes`, which just takes the part
up to the colon of an URI, and not any prefix.
+- Add Listener API, :mod:`mopidy.listeners`, to be implemented by actors
+ wanting to receive events from the backend. This is a formalization of the
+ ad hoc events the Last.fm scrobbler has already been using for some time.
v0.5.0 (2011-06-15)
diff --git a/docs/modules/frontends/mpd.rst b/docs/modules/frontends/mpd.rst
index 6120c2a6..6f69b2a9 100644
--- a/docs/modules/frontends/mpd.rst
+++ b/docs/modules/frontends/mpd.rst
@@ -5,9 +5,8 @@
.. inheritance-diagram:: mopidy.frontends.mpd
.. automodule:: mopidy.frontends.mpd
- :synopsis: MPD frontend
+ :synopsis: MPD server frontend
:members:
- :undoc-members:
MPD server
@@ -18,7 +17,6 @@ MPD server
.. automodule:: mopidy.frontends.mpd.server
:synopsis: MPD server
:members:
- :undoc-members:
MPD session
@@ -29,7 +27,6 @@ MPD session
.. automodule:: mopidy.frontends.mpd.session
:synopsis: MPD client session
:members:
- :undoc-members:
MPD dispatcher
@@ -40,7 +37,6 @@ MPD dispatcher
.. automodule:: mopidy.frontends.mpd.dispatcher
:synopsis: MPD request dispatcher
:members:
- :undoc-members:
MPD protocol
diff --git a/docs/settings.rst b/docs/settings.rst
index f0888670..68adfd55 100644
--- a/docs/settings.rst
+++ b/docs/settings.rst
@@ -151,4 +151,3 @@ Available settings
.. automodule:: mopidy.settings
:synopsis: Available settings and their default values
:members:
- :undoc-members:
diff --git a/mopidy/backends/base/playback.py b/mopidy/backends/base/playback.py
index 29188f55..e94ddf4d 100644
--- a/mopidy/backends/base/playback.py
+++ b/mopidy/backends/base/playback.py
@@ -4,7 +4,7 @@ import time
from pykka.registry import ActorRegistry
-from mopidy.frontends.base import BaseFrontend
+from mopidy.listeners import BackendListener
logger = logging.getLogger('mopidy.backends.base')
@@ -486,23 +486,21 @@ class PlaybackController(object):
def _trigger_started_playing_event(self):
"""
- Notifies frontends that a track has started playing.
+ Notifies implementors of :class:`mopidy.listeners.BackendListener` that
+ a track has started playing.
For internal use only. Should be called by the backend directly after a
track has started playing.
"""
if self.current_track is None:
return
- frontend_refs = ActorRegistry.get_by_class(BaseFrontend)
- for frontend_ref in frontend_refs:
- frontend_ref.send_one_way({
- 'command': 'started_playing',
- 'track': self.current_track,
- })
+ for listener_ref in ActorRegistry.get_by_class(BackendListener):
+ listener_ref.proxy().started_playing(track=self.current_track)
def _trigger_stopped_playing_event(self):
"""
- Notifies frontends that a track has stopped playing.
+ Notifies implementors of :class:`mopidy.listeners.BackendListener` that
+ a track has stopped playing.
For internal use only. Should be called by the backend before a track
is stopped playing, e.g. at the next, previous, and stop actions and at
@@ -510,13 +508,9 @@ class PlaybackController(object):
"""
if self.current_track is None:
return
- frontend_refs = ActorRegistry.get_by_class(BaseFrontend)
- for frontend_ref in frontend_refs:
- frontend_ref.send_one_way({
- 'command': 'stopped_playing',
- 'track': self.current_track,
- 'stop_position': self.time_position,
- })
+ for listener_ref in ActorRegistry.get_by_class(BackendListener):
+ listener_ref.proxy().stopped_playing(
+ track=self.current_track, stop_position=self.time_position)
class BasePlaybackProvider(object):
diff --git a/mopidy/backends/spotify/translator.py b/mopidy/backends/spotify/translator.py
index 1bf7e5aa..95287d77 100644
--- a/mopidy/backends/spotify/translator.py
+++ b/mopidy/backends/spotify/translator.py
@@ -4,7 +4,7 @@ import logging
from spotify import Link, SpotifyError
from mopidy import settings
-from mopidy.backends.spotify import ENCODING, BITRATES
+from mopidy.backends.spotify import ENCODING
from mopidy.models import Artist, Album, Track, Playlist
logger = logging.getLogger('mopidy.backends.spotify.translator')
@@ -44,7 +44,7 @@ class SpotifyTranslator(object):
track_no=spotify_track.index(),
date=date,
length=spotify_track.duration(),
- bitrate=BITRATES[settings.SPOTIFY_BITRATE],
+ bitrate=settings.SPOTIFY_BITRATE,
)
@classmethod
diff --git a/mopidy/frontends/base.py b/mopidy/frontends/base.py
deleted file mode 100644
index 811644b1..00000000
--- a/mopidy/frontends/base.py
+++ /dev/null
@@ -1,5 +0,0 @@
-class BaseFrontend(object):
- """
- Base class for frontends.
- """
- pass
diff --git a/mopidy/frontends/lastfm.py b/mopidy/frontends/lastfm.py
index 04716c61..33e724c0 100644
--- a/mopidy/frontends/lastfm.py
+++ b/mopidy/frontends/lastfm.py
@@ -10,14 +10,14 @@ except ImportError as import_error:
from pykka.actor import ThreadingActor
from mopidy import settings, SettingsError
-from mopidy.frontends.base import BaseFrontend
+from mopidy.listeners import BackendListener
logger = logging.getLogger('mopidy.frontends.lastfm')
API_KEY = '2236babefa8ebb3d93ea467560d00d04'
API_SECRET = '94d9a09c0cd5be955c4afaeaffcaefcd'
-class LastfmFrontend(ThreadingActor, BaseFrontend):
+class LastfmFrontend(ThreadingActor, BackendListener):
"""
Frontend which scrobbles the music you play to your `Last.fm
`_ profile.
@@ -57,14 +57,6 @@ class LastfmFrontend(ThreadingActor, BaseFrontend):
logger.error(u'Error during Last.fm setup: %s', e)
self.stop()
- def on_receive(self, message):
- if message.get('command') == 'started_playing':
- self.started_playing(message['track'])
- elif message.get('command') == 'stopped_playing':
- self.stopped_playing(message['track'], message['stop_position'])
- else:
- pass # Ignore any other messages
-
def started_playing(self, track):
artists = ', '.join([a.name for a in track.artists])
duration = track.length and track.length // 1000 or 0
diff --git a/mopidy/frontends/mpd/__init__.py b/mopidy/frontends/mpd/__init__.py
index 175aa0ee..f37b2deb 100644
--- a/mopidy/frontends/mpd/__init__.py
+++ b/mopidy/frontends/mpd/__init__.py
@@ -3,13 +3,12 @@ import logging
from pykka.actor import ThreadingActor
-from mopidy.frontends.base import BaseFrontend
from mopidy.frontends.mpd.server import MpdServer
from mopidy.utils.process import BaseThread
logger = logging.getLogger('mopidy.frontends.mpd')
-class MpdFrontend(ThreadingActor, BaseFrontend):
+class MpdFrontend(ThreadingActor):
"""
The MPD frontend.
diff --git a/mopidy/listeners.py b/mopidy/listeners.py
new file mode 100644
index 00000000..f6d1c67e
--- /dev/null
+++ b/mopidy/listeners.py
@@ -0,0 +1,34 @@
+class BackendListener(object):
+ """
+ Marker interface for recipients of events sent by the backend.
+
+ Any Pykka actor that mixes in this class will receive calls to the methods
+ defined here when the corresponding events happen in the backend. This
+ interface is used both for looking up what actors to notify of the events,
+ and for providing default implementations for those listeners that are not
+ interested in all events.
+ """
+
+ def started_playing(self, track):
+ """
+ Called whenever a new track starts playing.
+
+ *MAY* be implemented by actor.
+
+ :param track: the track that just started playing
+ :type track: :class:`mopidy.models.Track`
+ """
+ pass
+
+ def stopped_playing(self, track, stop_position):
+ """
+ Called whenever playback is stopped.
+
+ *MAY* be implemented by actor.
+
+ :param track: the track that was played before playback stopped
+ :type track: :class:`mopidy.models.Track`
+ :param stop_position: the time position when stopped in milliseconds
+ :type stop_position: int
+ """
+ pass
diff --git a/tests/listeners_test.py b/tests/listeners_test.py
new file mode 100644
index 00000000..761aff4f
--- /dev/null
+++ b/tests/listeners_test.py
@@ -0,0 +1,14 @@
+import unittest
+
+from mopidy.listeners import BackendListener
+from mopidy.models import Track
+
+class BackendListenerTest(unittest.TestCase):
+ def setUp(self):
+ self.listener = BackendListener()
+
+ def test_listener_has_default_impl_for_the_started_playing_event(self):
+ self.listener.started_playing(Track())
+
+ def test_listener_has_default_impl_for_the_stopped_playing_event(self):
+ self.listener.stopped_playing(Track(), 0)