Merge pull request #118 from jodal/feature/event-listeners
feature/event-listeners
This commit is contained in:
commit
15953aefb4
@ -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
|
||||
<http://jodal.github.com/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
|
||||
========================
|
||||
|
||||
7
docs/api/listeners.rst
Normal file
7
docs/api/listeners.rst
Normal file
@ -0,0 +1,7 @@
|
||||
************
|
||||
Listener API
|
||||
************
|
||||
|
||||
.. automodule:: mopidy.listeners
|
||||
:synopsis: Listener API
|
||||
:members:
|
||||
@ -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)
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -462,23 +462,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
|
||||
@ -486,13 +484,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):
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
class BaseFrontend(object):
|
||||
"""
|
||||
Base class for frontends.
|
||||
"""
|
||||
pass
|
||||
@ -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
|
||||
<http://www.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
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
34
mopidy/listeners.py
Normal file
34
mopidy/listeners.py
Normal file
@ -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
|
||||
14
tests/listeners_test.py
Normal file
14
tests/listeners_test.py
Normal file
@ -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)
|
||||
Loading…
Reference in New Issue
Block a user