From ff6a484add5b42b3f819a7a8f8a886c9089c5d10 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 3 Nov 2010 23:00:34 +0100 Subject: [PATCH 01/19] docs: Add dummy output --- docs/api/outputs.rst | 1 + docs/modules/outputs/dummy.rst | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 docs/modules/outputs/dummy.rst diff --git a/docs/api/outputs.rst b/docs/api/outputs.rst index 5ef1606d..0650563e 100644 --- a/docs/api/outputs.rst +++ b/docs/api/outputs.rst @@ -17,4 +17,5 @@ Outputs are responsible for playing audio. Output implementations ====================== +* :mod:`mopidy.outputs.dummy` * :mod:`mopidy.outputs.gstreamer` diff --git a/docs/modules/outputs/dummy.rst b/docs/modules/outputs/dummy.rst new file mode 100644 index 00000000..56436c94 --- /dev/null +++ b/docs/modules/outputs/dummy.rst @@ -0,0 +1,10 @@ +******************************************************* +:mod:`mopidy.outputs.dummy` -- Dummy output for testing +******************************************************* + +.. inheritance-diagram:: mopidy.outputs.dummy + +.. automodule:: mopidy.outputs.dummy + :synopsis: Dummy output for testing + :members: + :undoc-members: From 7accf783ca5725d8e6bf2326cd863db6af56f311 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 3 Nov 2010 23:23:30 +0100 Subject: [PATCH 02/19] Remove call to stop() on LocalPlaybackController init --- mopidy/backends/local/__init__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index 532c3976..6a1d3c51 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -50,12 +50,6 @@ class LocalBackend(Backend): class LocalPlaybackController(PlaybackController): - def __init__(self, *args, **kwargs): - super(LocalPlaybackController, self).__init__(*args, **kwargs) - - # XXX Why do we call stop()? Is it to set GStreamer state to 'READY'? - self.stop() - @property def time_position(self): return self.backend.output.get_position() From be225c23f1a794b10124fb21c4ef61b1ee1f7eb0 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 3 Nov 2010 23:31:11 +0100 Subject: [PATCH 03/19] Simplify DummyBackend instantiation --- mopidy/backends/dummy/__init__.py | 7 +++++-- tests/frontends/mpd/audio_output_test.py | 3 +-- tests/frontends/mpd/command_list_test.py | 3 +-- tests/frontends/mpd/connection_test.py | 3 +-- tests/frontends/mpd/current_playlist_test.py | 3 +-- tests/frontends/mpd/dispatcher_test.py | 3 +-- tests/frontends/mpd/music_db_test.py | 9 ++++----- tests/frontends/mpd/playback_test.py | 5 ++--- tests/frontends/mpd/reflection_test.py | 3 +-- tests/frontends/mpd/regression_test.py | 7 +++---- tests/frontends/mpd/status_test.py | 3 +-- tests/frontends/mpd/stickers_test.py | 3 +-- tests/frontends/mpd/stored_playlists_test.py | 3 +-- 13 files changed, 23 insertions(+), 32 deletions(-) diff --git a/mopidy/backends/dummy/__init__.py b/mopidy/backends/dummy/__init__.py index 9c6885bc..2d72ec8a 100644 --- a/mopidy/backends/dummy/__init__.py +++ b/mopidy/backends/dummy/__init__.py @@ -2,7 +2,9 @@ from mopidy.backends.base import (Backend, CurrentPlaylistController, PlaybackController, BasePlaybackProvider, LibraryController, BaseLibraryProvider, StoredPlaylistsController, BaseStoredPlaylistsProvider) +from mopidy.mixers.dummy import DummyMixer from mopidy.models import Playlist +from mopidy.outputs.dummy import DummyOutput class DummyQueue(object): @@ -22,10 +24,11 @@ class DummyBackend(Backend): """ def __init__(self, *args, **kwargs): + kwargs['core_queue'] = DummyQueue() + kwargs['output'] = DummyOutput(core_queue=DummyQueue()) + kwargs['mixer_class'] = DummyMixer super(DummyBackend, self).__init__(*args, **kwargs) - self.core_queue = DummyQueue() - self.current_playlist = CurrentPlaylistController(backend=self) library_provider = DummyLibraryProvider(backend=self) diff --git a/tests/frontends/mpd/audio_output_test.py b/tests/frontends/mpd/audio_output_test.py index b81e727e..77ed05c4 100644 --- a/tests/frontends/mpd/audio_output_test.py +++ b/tests/frontends/mpd/audio_output_test.py @@ -2,11 +2,10 @@ import unittest from mopidy.backends.dummy import DummyBackend from mopidy.frontends.mpd import dispatcher -from mopidy.mixers.dummy import DummyMixer class AudioOutputHandlerTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend(mixer_class=DummyMixer) + self.b = DummyBackend() self.h = dispatcher.MpdDispatcher(backend=self.b) def test_enableoutput(self): diff --git a/tests/frontends/mpd/command_list_test.py b/tests/frontends/mpd/command_list_test.py index 6c801c3f..effc9862 100644 --- a/tests/frontends/mpd/command_list_test.py +++ b/tests/frontends/mpd/command_list_test.py @@ -2,11 +2,10 @@ import unittest from mopidy.backends.dummy import DummyBackend from mopidy.frontends.mpd import dispatcher -from mopidy.mixers.dummy import DummyMixer class CommandListsTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend(mixer_class=DummyMixer) + self.b = DummyBackend() self.h = dispatcher.MpdDispatcher(backend=self.b) def test_command_list_begin(self): diff --git a/tests/frontends/mpd/connection_test.py b/tests/frontends/mpd/connection_test.py index 21753054..a4abbd27 100644 --- a/tests/frontends/mpd/connection_test.py +++ b/tests/frontends/mpd/connection_test.py @@ -2,11 +2,10 @@ import unittest from mopidy.backends.dummy import DummyBackend from mopidy.frontends.mpd import dispatcher -from mopidy.mixers.dummy import DummyMixer class ConnectionHandlerTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend(mixer_class=DummyMixer) + self.b = DummyBackend() self.h = dispatcher.MpdDispatcher(backend=self.b) def test_close(self): diff --git a/tests/frontends/mpd/current_playlist_test.py b/tests/frontends/mpd/current_playlist_test.py index a4179637..06ff30ac 100644 --- a/tests/frontends/mpd/current_playlist_test.py +++ b/tests/frontends/mpd/current_playlist_test.py @@ -2,12 +2,11 @@ import unittest from mopidy.backends.dummy import DummyBackend from mopidy.frontends.mpd import dispatcher -from mopidy.mixers.dummy import DummyMixer from mopidy.models import Track class CurrentPlaylistHandlerTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend(mixer_class=DummyMixer) + self.b = DummyBackend() self.h = dispatcher.MpdDispatcher(backend=self.b) def test_add(self): diff --git a/tests/frontends/mpd/dispatcher_test.py b/tests/frontends/mpd/dispatcher_test.py index 2a2ee4db..183f01d8 100644 --- a/tests/frontends/mpd/dispatcher_test.py +++ b/tests/frontends/mpd/dispatcher_test.py @@ -4,11 +4,10 @@ from mopidy.backends.dummy import DummyBackend from mopidy.frontends.mpd import dispatcher from mopidy.frontends.mpd.exceptions import MpdAckError from mopidy.frontends.mpd.protocol import request_handlers, handle_pattern -from mopidy.mixers.dummy import DummyMixer class MpdDispatcherTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend(mixer_class=DummyMixer) + self.b = DummyBackend() self.h = dispatcher.MpdDispatcher(backend=self.b) def test_register_same_pattern_twice_fails(self): diff --git a/tests/frontends/mpd/music_db_test.py b/tests/frontends/mpd/music_db_test.py index 05b8ebd0..36d92adf 100644 --- a/tests/frontends/mpd/music_db_test.py +++ b/tests/frontends/mpd/music_db_test.py @@ -2,11 +2,10 @@ import unittest from mopidy.backends.dummy import DummyBackend from mopidy.frontends.mpd import dispatcher -from mopidy.mixers.dummy import DummyMixer class MusicDatabaseHandlerTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend(mixer_class=DummyMixer) + self.b = DummyBackend() self.h = dispatcher.MpdDispatcher(backend=self.b) def test_count(self): @@ -65,7 +64,7 @@ class MusicDatabaseHandlerTest(unittest.TestCase): class MusicDatabaseFindTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend(mixer_class=DummyMixer) + self.b = DummyBackend() self.h = dispatcher.MpdDispatcher(backend=self.b) def test_find_album(self): @@ -104,7 +103,7 @@ class MusicDatabaseFindTest(unittest.TestCase): class MusicDatabaseListTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend(mixer_class=DummyMixer) + self.b = DummyBackend() self.h = dispatcher.MpdDispatcher(backend=self.b) def test_list_foo_returns_ack(self): @@ -295,7 +294,7 @@ class MusicDatabaseListTest(unittest.TestCase): class MusicDatabaseSearchTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend(mixer_class=DummyMixer) + self.b = DummyBackend() self.h = dispatcher.MpdDispatcher(backend=self.b) def test_search_album(self): diff --git a/tests/frontends/mpd/playback_test.py b/tests/frontends/mpd/playback_test.py index 4e60546d..45aaaf83 100644 --- a/tests/frontends/mpd/playback_test.py +++ b/tests/frontends/mpd/playback_test.py @@ -2,12 +2,11 @@ import unittest from mopidy.backends.dummy import DummyBackend from mopidy.frontends.mpd import dispatcher -from mopidy.mixers.dummy import DummyMixer from mopidy.models import Track class PlaybackOptionsHandlerTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend(mixer_class=DummyMixer) + self.b = DummyBackend() self.h = dispatcher.MpdDispatcher(backend=self.b) def test_consume_off(self): @@ -166,7 +165,7 @@ class PlaybackOptionsHandlerTest(unittest.TestCase): class PlaybackControlHandlerTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend(mixer_class=DummyMixer) + self.b = DummyBackend() self.h = dispatcher.MpdDispatcher(backend=self.b) def test_next(self): diff --git a/tests/frontends/mpd/reflection_test.py b/tests/frontends/mpd/reflection_test.py index a4491d75..0f096930 100644 --- a/tests/frontends/mpd/reflection_test.py +++ b/tests/frontends/mpd/reflection_test.py @@ -2,11 +2,10 @@ import unittest from mopidy.backends.dummy import DummyBackend from mopidy.frontends.mpd import dispatcher -from mopidy.mixers.dummy import DummyMixer class ReflectionHandlerTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend(mixer_class=DummyMixer) + self.b = DummyBackend() self.h = dispatcher.MpdDispatcher(backend=self.b) def test_commands_returns_list_of_all_commands(self): diff --git a/tests/frontends/mpd/regression_test.py b/tests/frontends/mpd/regression_test.py index 3cfdb855..63dc5ae4 100644 --- a/tests/frontends/mpd/regression_test.py +++ b/tests/frontends/mpd/regression_test.py @@ -3,7 +3,6 @@ import unittest from mopidy.backends.dummy import DummyBackend from mopidy.frontends.mpd import dispatcher -from mopidy.mixers.dummy import DummyMixer from mopidy.models import Track class IssueGH17RegressionTest(unittest.TestCase): @@ -18,7 +17,7 @@ class IssueGH17RegressionTest(unittest.TestCase): """ def setUp(self): - self.backend = DummyBackend(mixer_class=DummyMixer) + self.backend = DummyBackend() self.backend.current_playlist.append([ Track(uri='a'), Track(uri='b'), None, Track(uri='d'), Track(uri='e'), Track(uri='f')]) @@ -52,7 +51,7 @@ class IssueGH18RegressionTest(unittest.TestCase): """ def setUp(self): - self.backend = DummyBackend(mixer_class=DummyMixer) + self.backend = DummyBackend() self.backend.current_playlist.append([ Track(uri='a'), Track(uri='b'), Track(uri='c'), Track(uri='d'), Track(uri='e'), Track(uri='f')]) @@ -91,7 +90,7 @@ class IssueGH22RegressionTest(unittest.TestCase): """ def setUp(self): - self.backend = DummyBackend(mixer_class=DummyMixer) + self.backend = DummyBackend() self.backend.current_playlist.append([ Track(uri='a'), Track(uri='b'), Track(uri='c'), Track(uri='d'), Track(uri='e'), Track(uri='f')]) diff --git a/tests/frontends/mpd/status_test.py b/tests/frontends/mpd/status_test.py index 1afe6ccd..14fef262 100644 --- a/tests/frontends/mpd/status_test.py +++ b/tests/frontends/mpd/status_test.py @@ -2,12 +2,11 @@ import unittest from mopidy.backends.dummy import DummyBackend from mopidy.frontends.mpd import dispatcher -from mopidy.mixers.dummy import DummyMixer from mopidy.models import Track class StatusHandlerTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend(mixer_class=DummyMixer) + self.b = DummyBackend() self.h = dispatcher.MpdDispatcher(backend=self.b) def test_clearerror(self): diff --git a/tests/frontends/mpd/stickers_test.py b/tests/frontends/mpd/stickers_test.py index 5b66d723..e5aed398 100644 --- a/tests/frontends/mpd/stickers_test.py +++ b/tests/frontends/mpd/stickers_test.py @@ -2,11 +2,10 @@ import unittest from mopidy.backends.dummy import DummyBackend from mopidy.frontends.mpd import dispatcher -from mopidy.mixers.dummy import DummyMixer class StickersHandlerTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend(mixer_class=DummyMixer) + self.b = DummyBackend() self.h = dispatcher.MpdDispatcher(backend=self.b) def test_sticker_get(self): diff --git a/tests/frontends/mpd/stored_playlists_test.py b/tests/frontends/mpd/stored_playlists_test.py index a24cbb88..f0b37b1a 100644 --- a/tests/frontends/mpd/stored_playlists_test.py +++ b/tests/frontends/mpd/stored_playlists_test.py @@ -3,12 +3,11 @@ import unittest from mopidy.backends.dummy import DummyBackend from mopidy.frontends.mpd import dispatcher -from mopidy.mixers.dummy import DummyMixer from mopidy.models import Track, Playlist class StoredPlaylistsHandlerTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend(mixer_class=DummyMixer) + self.b = DummyBackend() self.h = dispatcher.MpdDispatcher(backend=self.b) def test_listplaylist(self): From 52cdaa9d4f5fed89d75294a9ce9a669b2a0fe452 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 3 Nov 2010 23:33:10 +0100 Subject: [PATCH 04/19] Output.get_position() should return None if position is unknown --- mopidy/outputs/base.py | 2 +- mopidy/outputs/dummy.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mopidy/outputs/base.py b/mopidy/outputs/base.py index 372d7d70..ae1af8cf 100644 --- a/mopidy/outputs/base.py +++ b/mopidy/outputs/base.py @@ -67,7 +67,7 @@ class BaseOutput(object): *MUST be implemented by subclass.* - :rtype: int + :rtype: int or :class:`None` if unknown """ raise NotImplementedError diff --git a/mopidy/outputs/dummy.py b/mopidy/outputs/dummy.py index 060ee02f..e78d269c 100644 --- a/mopidy/outputs/dummy.py +++ b/mopidy/outputs/dummy.py @@ -32,7 +32,7 @@ class DummyOutput(BaseOutput): end_of_data_stream_called = False #: For testing. Contains the current position. - position = 0 + position = None #: For testing. Contains the current state. state = 'NULL' From 4515f0764e9082632893df9c042d95f54bd11223 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 3 Nov 2010 23:33:53 +0100 Subject: [PATCH 05/19] Use time position from output if provided, else internally calculated position --- mopidy/backends/base/playback.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mopidy/backends/base/playback.py b/mopidy/backends/base/playback.py index 8a3eeee5..8ab60470 100644 --- a/mopidy/backends/base/playback.py +++ b/mopidy/backends/base/playback.py @@ -282,6 +282,9 @@ class PlaybackController(object): @property def time_position(self): """Time position in milliseconds.""" + output_position = self.backend.output.get_position() + if output_position is not None: + return output_position if self.state == self.PLAYING: time_since_started = (self._current_wall_time - self._play_time_started) From 4b13dd9046b46d9fd04bfdc072c68dcf463d81c5 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 3 Nov 2010 23:34:08 +0100 Subject: [PATCH 06/19] Remove LocalPlaybackController which is now redundant --- mopidy/backends/local/__init__.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index 6a1d3c51..578e0b5e 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -39,7 +39,7 @@ class LocalBackend(Backend): provider=library_provider) playback_provider = LocalPlaybackProvider(backend=self) - self.playback = LocalPlaybackController(backend=self, + self.playback = PlaybackController(backend=self, provider=playback_provider) stored_playlists_provider = LocalStoredPlaylistsProvider(backend=self) @@ -49,12 +49,6 @@ class LocalBackend(Backend): self.uri_handlers = [u'file://'] -class LocalPlaybackController(PlaybackController): - @property - def time_position(self): - return self.backend.output.get_position() - - class LocalPlaybackProvider(BasePlaybackProvider): def pause(self): return self.backend.output.set_state('PAUSED') From 1f9414d2a77c98a2f23d18d60b87f3ca14892958 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 3 Nov 2010 23:38:27 +0100 Subject: [PATCH 07/19] Return None from GStreamerOutput.get_position() upon failure --- mopidy/outputs/gstreamer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index 3b037f62..3c2f5ea7 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -265,5 +265,5 @@ class GStreamerPlayerThread(BaseThread): position = self.gst_pipeline.query_position(gst.FORMAT_TIME)[0] return position // gst.MSECOND except gst.QueryError, e: - logger.error('time_position failed: %s', e) - return 0 + logger.debug(u'GStreamer time position: %s', e) + return None From f20b3b26f6e0631b3ef799a8170da7bf6147dcec Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 23 Apr 2011 15:02:40 +0200 Subject: [PATCH 08/19] Add platform and python information to startup logging (fixes #73) --- mopidy/__init__.py | 19 ++++++++++++++----- mopidy/core.py | 2 +- mopidy/utils/log.py | 6 ++++-- tests/version_test.py | 12 +++++++++++- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/mopidy/__init__.py b/mopidy/__init__.py index e9ced3ae..1fbf99c8 100644 --- a/mopidy/__init__.py +++ b/mopidy/__init__.py @@ -1,3 +1,4 @@ +import platform import sys if not (2, 6) <= sys.version_info < (3,): sys.exit(u'Mopidy requires Python >= 2.6, < 3') @@ -6,6 +7,12 @@ from subprocess import PIPE, Popen VERSION = (0, 4, 0) +def get_version(): + try: + return get_git_version() + except EnvironmentError: + return get_plain_version() + def get_git_version(): process = Popen(['git', 'describe'], stdout=PIPE, stderr=PIPE) if process.wait() != 0: @@ -18,11 +25,13 @@ def get_git_version(): def get_plain_version(): return '.'.join(map(str, VERSION)) -def get_version(): - try: - return get_git_version() - except EnvironmentError: - return get_plain_version() +def get_platform(): + return platform.platform() + +def get_python(): + implementation = platform.python_implementation() + version = platform.python_version() + return u' '.join([implementation, version]) class MopidyException(Exception): def __init__(self, message, *args, **kwargs): diff --git a/mopidy/core.py b/mopidy/core.py index a1c6b361..093f783d 100644 --- a/mopidy/core.py +++ b/mopidy/core.py @@ -29,7 +29,7 @@ def main(): ActorRegistry.stop_all() def parse_options(): - parser = optparse.OptionParser(version='Mopidy %s' % get_version()) + parser = optparse.OptionParser(version=u'Mopidy %s' % get_version()) parser.add_option('-q', '--quiet', action='store_const', const=0, dest='verbosity_level', help='less output (warning level)') diff --git a/mopidy/utils/log.py b/mopidy/utils/log.py index c74ff5ea..531b68b6 100644 --- a/mopidy/utils/log.py +++ b/mopidy/utils/log.py @@ -1,7 +1,8 @@ import logging import logging.handlers +import platform -from mopidy import get_version, settings +from mopidy import get_version, get_platform, get_python, settings def setup_logging(verbosity_level, save_debug_log): setup_root_logger() @@ -9,7 +10,8 @@ def setup_logging(verbosity_level, save_debug_log): if save_debug_log: setup_debug_logging_to_file() logger = logging.getLogger('mopidy.utils.log') - logger.info(u'-- Starting Mopidy %s --', get_version()) + logger.info(u'Starting Mopidy %s on %s %s', + get_version(), get_platform(), get_python()) def setup_root_logger(): root = logging.getLogger('') diff --git a/tests/version_test.py b/tests/version_test.py index f1f86b59..7f204283 100644 --- a/tests/version_test.py +++ b/tests/version_test.py @@ -1,7 +1,8 @@ from distutils.version import StrictVersion as SV import unittest +import platform -from mopidy import get_plain_version +from mopidy import get_version, get_plain_version, get_platform, get_python class VersionTest(unittest.TestCase): def test_current_version_is_parsable_as_a_strict_version_number(self): @@ -18,3 +19,12 @@ class VersionTest(unittest.TestCase): self.assert_(SV('0.3.0') < SV('0.3.1')) self.assert_(SV('0.3.1') < SV(get_plain_version())) self.assert_(SV(get_plain_version()) < SV('0.4.1')) + + def test_get_platform_contains_platform(self): + self.assert_(platform.platform() in get_platform()) + + def test_get_python_contains_python_implementation(self): + self.assert_(platform.python_implementation() in get_python()) + + def test_get_python_contains_python_version(self): + self.assert_(platform.python_version() in get_python()) From 0b91b26910d2404ab9282bd2c6387aff7d77e1b7 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 23 Apr 2011 17:55:53 +0200 Subject: [PATCH 09/19] Check if creation of IPv6 sockets works before using it (fixes #75) --- mopidy/frontends/mpd/server.py | 18 ++++++++++++++++-- tests/frontends/mpd/server_test.py | 6 ++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/mopidy/frontends/mpd/server.py b/mopidy/frontends/mpd/server.py index 231bdf40..8507e266 100644 --- a/mopidy/frontends/mpd/server.py +++ b/mopidy/frontends/mpd/server.py @@ -9,6 +9,20 @@ from .session import MpdSession logger = logging.getLogger('mopidy.frontends.mpd.server') +def _try_ipv6_socket(): + """Determine if system really supports IPv6""" + if not socket.has_ipv6: + return False + try: + socket.socket(socket.AF_INET6).close() + return True + except IOError, e: + logger.debug(u'Platform supports IPv6, but socket ' + 'creation failed, disabling: %s', e) + return False + +has_ipv6 = _try_ipv6_socket() + class MpdServer(asyncore.dispatcher): """ The MPD server. Creates a :class:`mopidy.frontends.mpd.session.MpdSession` @@ -21,7 +35,7 @@ class MpdServer(asyncore.dispatcher): def start(self): """Start MPD server.""" try: - if socket.has_ipv6: + if has_ipv6: self.create_socket(socket.AF_INET6, socket.SOCK_STREAM) # Explicitly configure socket to work for both IPv4 and IPv6 self.socket.setsockopt( @@ -53,7 +67,7 @@ class MpdServer(asyncore.dispatcher): self.close() def _format_hostname(self, hostname): - if (socket.has_ipv6 + if (has_ipv6 and re.match('\d+.\d+.\d+.\d+', hostname) is not None): hostname = '::ffff:%s' % hostname return hostname diff --git a/tests/frontends/mpd/server_test.py b/tests/frontends/mpd/server_test.py index ef963347..32e90450 100644 --- a/tests/frontends/mpd/server_test.py +++ b/tests/frontends/mpd/server_test.py @@ -10,20 +10,22 @@ class MpdServerTest(unittest.TestCase): self.backend = DummyBackend.start().proxy() self.mixer = DummyMixer.start().proxy() self.server = server.MpdServer() + self.has_ipv6 = server.has_ipv6 def tearDown(self): self.backend.stop().get() self.mixer.stop().get() + server.has_ipv6 = self.has_ipv6 def test_format_hostname_prefixes_ipv4_addresses_when_ipv6_available(self): - server.socket.has_ipv6 = True + server.has_ipv6 = True self.assertEqual(self.server._format_hostname('0.0.0.0'), '::ffff:0.0.0.0') self.assertEqual(self.server._format_hostname('127.0.0.1'), '::ffff:127.0.0.1') def test_format_hostname_does_nothing_when_only_ipv4_available(self): - server.socket.has_ipv6 = False + server.has_ipv6 = False self.assertEquals(self.server._format_hostname('0.0.0.0'), '0.0.0.0') class MpdSessionTest(unittest.TestCase): From 3e0a04ab9138bc948ae8750325c9022a2e501ab1 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 24 Apr 2011 01:25:10 +0200 Subject: [PATCH 10/19] Exit main thread if no actors are running --- mopidy/core.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mopidy/core.py b/mopidy/core.py index a1c6b361..5a83b1e6 100644 --- a/mopidy/core.py +++ b/mopidy/core.py @@ -23,9 +23,11 @@ def main(): setup_backend() setup_frontends() try: - time.sleep(10000*24*60*60) + while ActorRegistry.get_all(): + time.sleep(1) + logger.info(u'No actors left. Exiting...') except KeyboardInterrupt: - logger.info(u'Exiting...') + logger.info(u'User interrupt. Exiting...') ActorRegistry.stop_all() def parse_options(): From b5b2319ac1d83ebc5aad3a42baafb3560cdd2d11 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 24 Apr 2011 17:57:04 +0200 Subject: [PATCH 11/19] Use uridecodebin for all playblack --- mopidy/backends/spotify/playback.py | 2 +- mopidy/outputs/gstreamer.py | 28 +++++++--------------------- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/mopidy/backends/spotify/playback.py b/mopidy/backends/spotify/playback.py index a066d90e..b02c2d9f 100644 --- a/mopidy/backends/spotify/playback.py +++ b/mopidy/backends/spotify/playback.py @@ -20,7 +20,7 @@ class SpotifyPlaybackProvider(BasePlaybackProvider): self.backend.spotify.session.load( Link.from_string(track.uri).as_track()) self.backend.spotify.session.play(1) - self.backend.output.set_state('PLAYING') + self.backend.output.play_uri('appsrc://') return True except SpotifyError as e: logger.warning('Play %s failed: %s', track.uri, e) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index 0596addb..a6d1e9dd 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -46,23 +46,9 @@ class GStreamerOutput(ThreadingActor, BaseOutput): pad = self.gst_pipeline.get_by_name('convert').get_pad('sink') - if settings.BACKENDS[0] == 'mopidy.backends.local.LocalBackend': - uri_bin = gst.element_factory_make('uridecodebin', 'uri') - uri_bin.connect('pad-added', self._process_new_pad, pad) - self.gst_pipeline.add(uri_bin) - else: - app_src = gst.element_factory_make('appsrc', 'appsrc') - app_src_caps = gst.Caps(""" - audio/x-raw-int, - endianness=(int)1234, - channels=(int)2, - width=(int)16, - depth=(int)16, - signed=(boolean)true, - rate=(int)44100""") - app_src.set_property('caps', app_src_caps) - self.gst_pipeline.add(app_src) - app_src.get_pad('src').link(pad) + uridecodebin = gst.element_factory_make('uridecodebin', 'uri') + uridecodebin.connect('pad-added', self._process_new_pad, pad) + self.gst_pipeline.add(uridecodebin) # Setup bus and message processor gst_bus = self.gst_pipeline.get_bus() @@ -98,12 +84,12 @@ class GStreamerOutput(ThreadingActor, BaseOutput): def deliver_data(self, caps_string, data): """Deliver audio data to be played""" - app_src = self.gst_pipeline.get_by_name('appsrc') + source = self.gst_pipeline.get_by_name('source') caps = gst.caps_from_string(caps_string) buffer_ = gst.Buffer(buffer(data)) buffer_.set_caps(caps) - app_src.set_property('caps', caps) - app_src.emit('push-buffer', buffer_) + source.set_property('caps', caps) + source.emit('push-buffer', buffer_) def end_of_data_stream(self): """ @@ -112,7 +98,7 @@ class GStreamerOutput(ThreadingActor, BaseOutput): We will get a GStreamer message when the stream playback reaches the token, and can then do any end-of-stream related tasks. """ - self.gst_pipeline.get_by_name('appsrc').emit('end-of-stream') + self.gst_pipeline.get_by_name('source').emit('end-of-stream') def get_position(self): try: From aa0c309d233334b33740131a67b392845e211cc8 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 25 Apr 2011 00:47:25 +0200 Subject: [PATCH 12/19] Ignore tracks without uri as they are probably local files --- mopidy/backends/spotify/translator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mopidy/backends/spotify/translator.py b/mopidy/backends/spotify/translator.py index d81622f9..64422485 100644 --- a/mopidy/backends/spotify/translator.py +++ b/mopidy/backends/spotify/translator.py @@ -57,7 +57,9 @@ class SpotifyTranslator(object): return Playlist( uri=str(Link.from_playlist(spotify_playlist)), name=spotify_playlist.name().decode(ENCODING), - tracks=[cls.to_mopidy_track(t) for t in spotify_playlist], + # FIXME if check on link is a hackish workaround for is_local + tracks=[cls.to_mopidy_track(t) for t in spotify_playlist + if str(Link.from_track(t, 0))], ) except SpotifyError, e: logger.warning(u'Failed translating Spotify playlist ' From 043338d3af4b532e649174148214e0d225407374 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 25 Apr 2011 15:14:00 +0200 Subject: [PATCH 13/19] Revert "Merge branch 'feature/multi-backend' into develop" This reverts commit c8639f48dac5c057b87c10ab8a271c129f8668ec, reversing changes made to 3e0a04ab9138bc948ae8750325c9022a2e501ab1. --- docs/api/outputs.rst | 1 - docs/modules/outputs/dummy.rst | 10 ---------- mopidy/backends/base/playback.py | 11 ----------- mopidy/backends/local/__init__.py | 14 +++++++++++++- mopidy/outputs/base.py | 2 +- mopidy/outputs/dummy.py | 2 +- mopidy/outputs/gstreamer.py | 15 --------------- tests/frontends/mpd/music_db_test.py | 2 ++ 8 files changed, 17 insertions(+), 40 deletions(-) delete mode 100644 docs/modules/outputs/dummy.rst diff --git a/docs/api/outputs.rst b/docs/api/outputs.rst index 0650563e..5ef1606d 100644 --- a/docs/api/outputs.rst +++ b/docs/api/outputs.rst @@ -17,5 +17,4 @@ Outputs are responsible for playing audio. Output implementations ====================== -* :mod:`mopidy.outputs.dummy` * :mod:`mopidy.outputs.gstreamer` diff --git a/docs/modules/outputs/dummy.rst b/docs/modules/outputs/dummy.rst deleted file mode 100644 index 56436c94..00000000 --- a/docs/modules/outputs/dummy.rst +++ /dev/null @@ -1,10 +0,0 @@ -******************************************************* -:mod:`mopidy.outputs.dummy` -- Dummy output for testing -******************************************************* - -.. inheritance-diagram:: mopidy.outputs.dummy - -.. automodule:: mopidy.outputs.dummy - :synopsis: Dummy output for testing - :members: - :undoc-members: diff --git a/mopidy/backends/base/playback.py b/mopidy/backends/base/playback.py index 88aa0877..88ae141d 100644 --- a/mopidy/backends/base/playback.py +++ b/mopidy/backends/base/playback.py @@ -5,7 +5,6 @@ import time from pykka.registry import ActorRegistry from mopidy.frontends.base import BaseFrontend -from mopidy.outputs.base import BaseOutput logger = logging.getLogger('mopidy.backends.base') @@ -289,9 +288,6 @@ class PlaybackController(object): @property def time_position(self): """Time position in milliseconds.""" - output_position = self._time_position_from_output() - if output_position is not None: - return output_position if self.state == self.PLAYING: time_since_started = (self._current_wall_time - self.play_time_started) @@ -301,13 +297,6 @@ class PlaybackController(object): elif self.state == self.STOPPED: return 0 - def _time_position_from_output(self): - output_refs = ActorRegistry.get_by_class(BaseOutput) - if not output_refs: - return None - output = output_refs[0].proxy() - return output.get_position() - def _play_time_start(self): self.play_time_accumulated = 0 self.play_time_started = self._current_wall_time diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index fc7f170c..2fa96dab 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -41,7 +41,7 @@ class LocalBackend(ThreadingActor, Backend): provider=library_provider) playback_provider = LocalPlaybackProvider(backend=self) - self.playback = PlaybackController(backend=self, + self.playback = LocalPlaybackController(backend=self, provider=playback_provider) stored_playlists_provider = LocalStoredPlaylistsProvider(backend=self) @@ -58,6 +58,18 @@ class LocalBackend(ThreadingActor, Backend): self.output = output_refs[0].proxy() +class LocalPlaybackController(PlaybackController): + def __init__(self, *args, **kwargs): + super(LocalPlaybackController, self).__init__(*args, **kwargs) + + # XXX Why do we call stop()? Is it to set GStreamer state to 'READY'? + self.stop() + + @property + def time_position(self): + return self.backend.output.get_position().get() + + class LocalPlaybackProvider(BasePlaybackProvider): def pause(self): return self.backend.output.set_state('PAUSED').get() diff --git a/mopidy/outputs/base.py b/mopidy/outputs/base.py index 11c2f86e..fbc86688 100644 --- a/mopidy/outputs/base.py +++ b/mopidy/outputs/base.py @@ -40,7 +40,7 @@ class BaseOutput(object): *MUST be implemented by subclass.* - :rtype: int or :class:`None` if unknown + :rtype: int """ raise NotImplementedError diff --git a/mopidy/outputs/dummy.py b/mopidy/outputs/dummy.py index 02f1bfca..f09965f7 100644 --- a/mopidy/outputs/dummy.py +++ b/mopidy/outputs/dummy.py @@ -25,7 +25,7 @@ class DummyOutput(ThreadingActor, BaseOutput): end_of_data_stream_called = False #: For testing. Contains the current position. - position = None + position = 0 #: For testing. Contains the current state. state = 'NULL' diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index d7ff6e6d..0596addb 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -165,18 +165,3 @@ class GStreamerOutput(ThreadingActor, BaseOutput): gst_volume = self.gst_pipeline.get_by_name('volume') gst_volume.set_property('volume', volume / 100.0) return True - - def set_position(self, position): - self.gst_pipeline.get_state() # block until state changes are done - handeled = self.gst_pipeline.seek_simple(gst.Format(gst.FORMAT_TIME), - gst.SEEK_FLAG_FLUSH, position * gst.MSECOND) - self.gst_pipeline.get_state() # block until seek is done - return handeled - - def get_position(self): - try: - position = self.gst_pipeline.query_position(gst.FORMAT_TIME)[0] - return position // gst.MSECOND - except gst.QueryError, e: - logger.debug(u'GStreamer time position: %s', e) - return None diff --git a/tests/frontends/mpd/music_db_test.py b/tests/frontends/mpd/music_db_test.py index 28469136..fa5634be 100644 --- a/tests/frontends/mpd/music_db_test.py +++ b/tests/frontends/mpd/music_db_test.py @@ -386,3 +386,5 @@ class MusicDatabaseSearchTest(unittest.TestCase): def test_search_else_should_fail(self): result = self.h.handle_request(u'search "sometype" "something"') self.assertEqual(result[0], u'ACK [2@0] {search} incorrect arguments') + + From a7e6596578dfaaff3705c5cade28653debd35f7f Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 26 Apr 2011 23:07:36 +0200 Subject: [PATCH 14/19] Pause playback on Spoticy connection errors (fixes #65) --- docs/changes.rst | 2 ++ mopidy/backends/spotify/session_manager.py | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/changes.rst b/docs/changes.rst index fe7b9927..d00bde97 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -43,6 +43,8 @@ No description yet. - Reduce log level for trivial log messages from warning to info. (Fixes: :issue:`71`) + - Pause playback on network connection errors. (Fixes: :issue:`65`) + - Local backend: - Fix crash in :command:`mopidy-scan` if a track has no artist name. Thanks diff --git a/mopidy/backends/spotify/session_manager.py b/mopidy/backends/spotify/session_manager.py index e92fe89e..395f3f28 100644 --- a/mopidy/backends/spotify/session_manager.py +++ b/mopidy/backends/spotify/session_manager.py @@ -74,7 +74,11 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager): def connection_error(self, session, error): """Callback used by pyspotify""" - logger.error(u'Spotify connection error: %s', error) + if error is None: + logger.info(u'Spotify connection error resolved') + else: + logger.error(u'Spotify connection error: %s', error) + self.backend.playback.pause() def message_to_user(self, session, message): """Callback used by pyspotify""" From f35e1348bc71cbe9ed94a67960ff6222afc81742 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 26 Apr 2011 23:24:23 +0200 Subject: [PATCH 15/19] Update changelog with fixes from @adamcik --- docs/changes.rst | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/changes.rst b/docs/changes.rst index d00bde97..ecc474c3 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -30,7 +30,7 @@ No description yet. - Mopidy now use Pykka actors for thread management and inter-thread communication. The immediate advantage of this is that Mopidy now works on - Python 2.7. (Fixes: :issue:`66`) + Python 2.7, which is the default on e.g. Ubuntu 11.04. (Fixes: :issue:`66`) - Spotify backend: @@ -66,6 +66,14 @@ No description yet. - Fix bug where ``status`` returned ``song: None``, which caused MPDroid to crash. (Fixes: :issue:`69`) + - Gracefully fallback to IPv4 sockets on systems that supports IPv6, but has + turned it off. (Fixes: :issue:`75`) + +- GStreamer output: + + - Use ``uridecodebin`` for playing audio from both Spotify and the local + backend. This contributes to support for multiple backends simultaneously. + - Settings: - Fix crash on ``--list-settings`` on clean installation. Thanks to Martins @@ -76,6 +84,11 @@ No description yet. - Replace test data symlinks with real files to avoid symlink issues when installing with pip. (Fixes: :issue:`68`) +- Debugging: + + - Include platform, architecture, Linux distribution, and Python version in + the debug log, to ease debugging of issues with attached debug logs. + 0.3.1 (2010-01-22) ================== From b8a8da1cdc7e854cd2c4e6ba4f922156675e38ec Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 26 Apr 2011 23:47:38 +0200 Subject: [PATCH 16/19] Add description of 0.4.0 release --- docs/changes.rst | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index ecc474c3..5a7db6ad 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -5,10 +5,24 @@ Changes This change log is used to track all major changes to Mopidy. -0.4.0 (in development) -====================== +0.4.0 (2011-04-27) +================== -No description yet. +Mopidy 0.4.0 is another release without major feature additions. In 0.4.0 we've +fixed a bunch of issues and bugs, with the help of several new contributors +who are credited in the changelog below. The major change of 0.4.0 is an +internal refactoring which clears way for future features, and which also make +Mopidy work on Python 2.7. In other words, Mopidy 0.4.0 works on Ubuntu 11.04 +and Arch Linux. + +Please note that 0.4.0 requires some updated dependencies, as listed under +*Important changes* below. Also, the known bug in the Spotify playlist +loading from Mopidy 0.3.0 is still present. + +.. warning:: Known bug in Spotify playlist loading + + There is a known bug in the loading of Spotify playlists. To avoid the bug, + follow the simple workaround described at :issue:`59`. **Important changes** From 50aef50989ec5618e372fdb8a36f9cf82f06a88a Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 27 Apr 2011 00:20:08 +0200 Subject: [PATCH 17/19] Ready for 0.5.0 development --- mopidy/__init__.py | 2 +- tests/version_test.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/mopidy/__init__.py b/mopidy/__init__.py index 1fbf99c8..79a0aa29 100644 --- a/mopidy/__init__.py +++ b/mopidy/__init__.py @@ -5,7 +5,7 @@ if not (2, 6) <= sys.version_info < (3,): from subprocess import PIPE, Popen -VERSION = (0, 4, 0) +VERSION = (0, 5, 0) def get_version(): try: diff --git a/tests/version_test.py b/tests/version_test.py index 7f204283..b060a9c6 100644 --- a/tests/version_test.py +++ b/tests/version_test.py @@ -17,8 +17,9 @@ class VersionTest(unittest.TestCase): self.assert_(SV('0.1.0') < SV('1.0.0')) self.assert_(SV('0.2.0') < SV('0.3.0')) self.assert_(SV('0.3.0') < SV('0.3.1')) - self.assert_(SV('0.3.1') < SV(get_plain_version())) - self.assert_(SV(get_plain_version()) < SV('0.4.1')) + self.assert_(SV('0.3.1') < SV('0.4.0')) + self.assert_(SV('0.4.0') < SV(get_plain_version())) + self.assert_(SV(get_plain_version()) < SV('0.5.1')) def test_get_platform_contains_platform(self): self.assert_(platform.platform() in get_platform()) From 99ef5a3c7f79f5ff7dd7cf5688589cae4f21efba Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 27 Apr 2011 00:20:31 +0200 Subject: [PATCH 18/19] Include tox.ini in source distribution --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 1c126f85..033c51f2 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include LICENSE pylintrc *.rst data/mopidy.desktop +include LICENSE pylintrc *.rst *.ini data/mopidy.desktop include mopidy/backends/spotify/spotify_appkey.key recursive-include docs * prune docs/_build From 1a3fa4b80d09bc63036f1b1f32aa9441f3a98367 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 27 Apr 2011 00:21:39 +0200 Subject: [PATCH 19/19] Prepare changelog for 0.5.0 development --- docs/changes.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/changes.rst b/docs/changes.rst index 5a7db6ad..12da4e6d 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -5,6 +5,16 @@ Changes This change log is used to track all major changes to Mopidy. +0.5.0 (in development) +====================== + +No description yet. + +**Changes** + +No changes yet. + + 0.4.0 (2011-04-27) ==================