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 4125b788..5d2ab57d 100644
--- a/docs/changes.rst
+++ b/docs/changes.rst
@@ -8,9 +8,18 @@ This change log is used to track all major changes to Mopidy.
v0.6.0 (in development)
=======================
+**Important changes**
+
+- Pykka 0.12.3 or greater is required.
+
**Changes**
-- None yet
+- 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/installation/index.rst b/docs/installation/index.rst
index 5101cc84..198ac9e8 100644
--- a/docs/installation/index.rst
+++ b/docs/installation/index.rst
@@ -25,7 +25,7 @@ Otherwise, make sure you got the required dependencies installed.
- Python >= 2.6, < 3
-- `Pykka `_ >= 0.12
+- `Pykka `_ >= 0.12.3
- GStreamer >= 0.10, with Python bindings. See :doc:`gstreamer`.
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/__init__.py b/mopidy/backends/base/__init__.py
index 038e2d7b..76c7f078 100644
--- a/mopidy/backends/base/__init__.py
+++ b/mopidy/backends/base/__init__.py
@@ -25,5 +25,5 @@ class Backend(object):
#: :class:`mopidy.backends.base.StoredPlaylistsController`.
stored_playlists = None
- #: List of URI prefixes this backend can handle.
- uri_handlers = []
+ #: List of URI schemes this backend can handle.
+ uri_schemes = []
diff --git a/mopidy/backends/base/playback.py b/mopidy/backends/base/playback.py
index 530c4840..088a5ad4 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')
@@ -461,38 +461,30 @@ class PlaybackController(object):
self.current_cp_track = None
def _trigger_started_playing_event(self):
- """
- Notifies frontends that a track has started playing.
-
- For internal use only. Should be called by the backend directly after a
- track has started playing.
- """
+ logger.debug(u'Triggering started playing event')
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,
- })
+ ActorRegistry.broadcast({
+ 'command': 'pykka_call',
+ 'attr_path': ('started_playing',),
+ 'args': [],
+ 'kwargs': {'track': self.current_track},
+ }, target_class=BackendListener)
def _trigger_stopped_playing_event(self):
- """
- Notifies frontends 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
- end-of-track.
- """
+ # TODO Test that this is called on next/prev/end-of-track
+ logger.debug(u'Triggering stopped playing event')
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',
+ ActorRegistry.broadcast({
+ 'command': 'pykka_call',
+ 'attr_path': ('stopped_playing',),
+ 'args': [],
+ 'kwargs': {
'track': self.current_track,
- 'stop_position': self.time_position,
- })
+ 'time_position': self.time_position,
+ },
+ }, target_class=BackendListener)
class BasePlaybackProvider(object):
diff --git a/mopidy/backends/dummy/__init__.py b/mopidy/backends/dummy/__init__.py
index 90c87dac..70efb028 100644
--- a/mopidy/backends/dummy/__init__.py
+++ b/mopidy/backends/dummy/__init__.py
@@ -32,7 +32,7 @@ class DummyBackend(ThreadingActor, Backend):
self.stored_playlists = StoredPlaylistsController(backend=self,
provider=stored_playlists_provider)
- self.uri_handlers = [u'dummy:']
+ self.uri_schemes = [u'dummy']
class DummyLibraryProvider(BaseLibraryProvider):
diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py
index 93cf3534..af80a8eb 100644
--- a/mopidy/backends/local/__init__.py
+++ b/mopidy/backends/local/__init__.py
@@ -52,7 +52,7 @@ class LocalBackend(ThreadingActor, Backend):
self.stored_playlists = StoredPlaylistsController(backend=self,
provider=stored_playlists_provider)
- self.uri_handlers = [u'file://']
+ self.uri_schemes = [u'file']
self.gstreamer = None
diff --git a/mopidy/backends/spotify/__init__.py b/mopidy/backends/spotify/__init__.py
index 66bcffd4..02ccd802 100644
--- a/mopidy/backends/spotify/__init__.py
+++ b/mopidy/backends/spotify/__init__.py
@@ -67,7 +67,7 @@ class SpotifyBackend(ThreadingActor, Backend):
self.stored_playlists = StoredPlaylistsController(backend=self,
provider=stored_playlists_provider)
- self.uri_handlers = [u'spotify:', u'http://open.spotify.com/']
+ self.uri_schemes = [u'spotify']
self.gstreamer = None
self.spotify = None
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/core.py b/mopidy/core.py
index 4420c319..e831fc55 100644
--- a/mopidy/core.py
+++ b/mopidy/core.py
@@ -25,7 +25,8 @@ from mopidy.gstreamer import GStreamer
from mopidy.utils import get_class
from mopidy.utils.log import setup_logging
from mopidy.utils.path import get_or_create_folder, get_or_create_file
-from mopidy.utils.process import exit_handler, stop_all_actors
+from mopidy.utils.process import (exit_handler, stop_remaining_actors,
+ stop_actors_by_class)
from mopidy.utils.settings import list_settings_optparse_callback
logger = logging.getLogger('mopidy.core')
@@ -50,7 +51,11 @@ def main():
logger.exception(e)
finally:
loop.quit()
- stop_all_actors()
+ stop_frontends()
+ stop_backend()
+ stop_mixer()
+ stop_gstreamer()
+ stop_remaining_actors()
def parse_options():
parser = optparse.OptionParser(version=u'Mopidy %s' % get_version())
@@ -59,7 +64,7 @@ def parse_options():
help='show GStreamer help options')
parser.add_option('-i', '--interactive',
action='store_true', dest='interactive',
- help='ask interactively for required settings which is missing')
+ help='ask interactively for required settings which are missing')
parser.add_option('-q', '--quiet',
action='store_const', const=0, dest='verbosity_level',
help='less output (warning level)')
@@ -86,15 +91,31 @@ def setup_settings(interactive):
def setup_gstreamer():
GStreamer.start()
+def stop_gstreamer():
+ stop_actors_by_class(GStreamer)
+
def setup_mixer():
get_class(settings.MIXER).start()
+def stop_mixer():
+ stop_actors_by_class(get_class(settings.MIXER))
+
def setup_backend():
get_class(settings.BACKENDS[0]).start()
+def stop_backend():
+ stop_actors_by_class(get_class(settings.BACKENDS[0]))
+
def setup_frontends():
for frontend_class_name in settings.FRONTENDS:
try:
get_class(frontend_class_name).start()
except OptionalDependencyError as e:
logger.info(u'Disabled: %s (%s)', frontend_class_name, e)
+
+def stop_frontends():
+ for frontend_class_name in settings.FRONTENDS:
+ try:
+ stop_actors_by_class(get_class(frontend_class_name))
+ except OptionalDependencyError:
+ pass
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 0432850d..1e18f6e2 100644
--- a/mopidy/frontends/mpd/__init__.py
+++ b/mopidy/frontends/mpd/__init__.py
@@ -3,7 +3,6 @@ import sys
from pykka.actor import ThreadingActor
-from mopidy.frontends.base import BaseFrontend
from mopidy import settings
from mopidy.utils import network
from mopidy.frontends.mpd.dispatcher import MpdDispatcher
@@ -11,8 +10,7 @@ from mopidy.frontends.mpd.protocol import ENCODING, VERSION, LINE_TERMINATOR
logger = logging.getLogger('mopidy.frontends.mpd')
-# FIXME no real need for frontend to be threading actor
-class MpdFrontend(ThreadingActor, BaseFrontend):
+class MpdFrontend(ThreadingActor):
"""
The MPD frontend.
diff --git a/mopidy/frontends/mpd/dispatcher.py b/mopidy/frontends/mpd/dispatcher.py
index 91cdc5e7..18f994de 100644
--- a/mopidy/frontends/mpd/dispatcher.py
+++ b/mopidy/frontends/mpd/dispatcher.py
@@ -128,7 +128,7 @@ class MpdDispatcher(object):
return self._call_next_filter(request, response, filter_chain)
except ActorDeadError as e:
logger.warning(u'Tried to communicate with dead actor.')
- raise exceptions.MpdSystemError(e.message)
+ raise exceptions.MpdSystemError(e)
def _call_handler(self, request):
(handler, kwargs) = self._find_handler(request)
diff --git a/mopidy/frontends/mpd/protocol/current_playlist.py b/mopidy/frontends/mpd/protocol/current_playlist.py
index 8e26013d..c7136804 100644
--- a/mopidy/frontends/mpd/protocol/current_playlist.py
+++ b/mopidy/frontends/mpd/protocol/current_playlist.py
@@ -19,8 +19,8 @@ def add(context, uri):
"""
if not uri:
return
- for handler_prefix in context.backend.uri_handlers.get():
- if uri.startswith(handler_prefix):
+ for uri_scheme in context.backend.uri_schemes.get():
+ if uri.startswith(uri_scheme):
track = context.backend.library.lookup(uri).get()
if track is not None:
context.backend.current_playlist.add(track)
diff --git a/mopidy/frontends/mpd/protocol/reflection.py b/mopidy/frontends/mpd/protocol/reflection.py
index 920f48a5..3618f5e1 100644
--- a/mopidy/frontends/mpd/protocol/reflection.py
+++ b/mopidy/frontends/mpd/protocol/reflection.py
@@ -95,4 +95,5 @@ def urlhandlers(context):
Gets a list of available URL handlers.
"""
- return [(u'handler', uri) for uri in context.backend.uri_handlers.get()]
+ return [(u'handler', uri_scheme)
+ for uri_scheme in context.backend.uri_schemes.get()]
diff --git a/mopidy/listeners.py b/mopidy/listeners.py
new file mode 100644
index 00000000..dfc5c60b
--- /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, time_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 time_position: the time position in milliseconds
+ :type time_position: int
+ """
+ pass
diff --git a/mopidy/utils/process.py b/mopidy/utils/process.py
index f9577496..80d850fe 100644
--- a/mopidy/utils/process.py
+++ b/mopidy/utils/process.py
@@ -22,9 +22,17 @@ def exit_handler(signum, frame):
logger.info(u'Got %s signal', signals[signum])
exit_process()
-def stop_all_actors():
+def stop_actors_by_class(klass):
+ actors = ActorRegistry.get_by_class(klass)
+ logger.debug(u'Stopping %d instance(s) of %s', len(actors), klass.__name__)
+ for actor in actors:
+ actor.stop()
+
+def stop_remaining_actors():
num_actors = len(ActorRegistry.get_all())
while num_actors:
+ logger.error(
+ u'There are actor threads still running, this is probably a bug')
logger.debug(u'Seeing %d actor and %d non-actor thread(s): %s',
num_actors, threading.active_count() - num_actors,
', '.join([t.name for t in threading.enumerate()]))
diff --git a/requirements/core.txt b/requirements/core.txt
index aaae84f8..8f9da622 100644
--- a/requirements/core.txt
+++ b/requirements/core.txt
@@ -1 +1 @@
-Pykka >= 0.12
+Pykka >= 0.12.3
diff --git a/tests/backends/events_test.py b/tests/backends/events_test.py
new file mode 100644
index 00000000..44529e90
--- /dev/null
+++ b/tests/backends/events_test.py
@@ -0,0 +1,45 @@
+import threading
+import unittest
+
+from pykka.actor import ThreadingActor
+from pykka.registry import ActorRegistry
+
+from mopidy.backends.dummy import DummyBackend
+from mopidy.listeners import BackendListener
+from mopidy.models import Track
+
+class BackendEventsTest(unittest.TestCase):
+ def setUp(self):
+ self.events = {
+ 'started_playing': threading.Event(),
+ 'stopped_playing': threading.Event(),
+ }
+ self.backend = DummyBackend.start().proxy()
+ self.listener = DummyBackendListener.start(self.events).proxy()
+
+ def tearDown(self):
+ ActorRegistry.stop_all()
+
+ def test_play_sends_started_playing_event(self):
+ self.backend.current_playlist.add([Track(uri='a')])
+ self.backend.playback.play()
+ self.events['started_playing'].wait(timeout=1)
+ self.assertTrue(self.events['started_playing'].is_set())
+
+ def test_stop_sends_stopped_playing_event(self):
+ self.backend.current_playlist.add([Track(uri='a')])
+ self.backend.playback.play()
+ self.backend.playback.stop()
+ self.events['stopped_playing'].wait(timeout=1)
+ self.assertTrue(self.events['stopped_playing'].is_set())
+
+
+class DummyBackendListener(ThreadingActor, BackendListener):
+ def __init__(self, events):
+ self.events = events
+
+ def started_playing(self, track):
+ self.events['started_playing'].set()
+
+ def stopped_playing(self, track, time_position):
+ self.events['stopped_playing'].set()
diff --git a/tests/backends/local/playback_test.py b/tests/backends/local/playback_test.py
index 2cdeadb9..5f80e691 100644
--- a/tests/backends/local/playback_test.py
+++ b/tests/backends/local/playback_test.py
@@ -36,8 +36,8 @@ class LocalPlaybackControllerTest(PlaybackControllerTest, unittest.TestCase):
track = Track(uri=uri, length=4464)
self.backend.current_playlist.add(track)
- def test_uri_handler(self):
- self.assert_('file://' in self.backend.uri_handlers)
+ def test_uri_scheme(self):
+ self.assertIn('file', self.backend.uri_schemes)
def test_play_mp3(self):
self.add_track('blank.mp3')
diff --git a/tests/frontends/mpd/reflection_test.py b/tests/frontends/mpd/reflection_test.py
index 2abf5acc..c4fd632a 100644
--- a/tests/frontends/mpd/reflection_test.py
+++ b/tests/frontends/mpd/reflection_test.py
@@ -76,4 +76,4 @@ class ReflectionHandlerTest(unittest.TestCase):
def test_urlhandlers(self):
result = self.dispatcher.handle_request(u'urlhandlers')
self.assert_(u'OK' in result)
- self.assert_(u'handler: dummy:' in result)
+ self.assert_(u'handler: dummy' in result)
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)