diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index 2fa96dab..126034f1 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -12,7 +12,7 @@ from mopidy.backends.base import (Backend, CurrentPlaylistController, BasePlaybackProvider, StoredPlaylistsController, BaseStoredPlaylistsProvider) from mopidy.models import Playlist, Track, Album -from mopidy.outputs.base import BaseOutput +from mopidy.gstreamer import GStreamer from .translator import parse_m3u, parse_mpd_tag_cache @@ -50,12 +50,12 @@ class LocalBackend(ThreadingActor, Backend): self.uri_handlers = [u'file://'] - self.output = None + self.gstreamer = None def on_start(self): - output_refs = ActorRegistry.get_by_class(BaseOutput) - assert len(output_refs) == 1, 'Expected exactly one running output.' - self.output = output_refs[0].proxy() + gstreamer_refs = ActorRegistry.get_by_class(GStreamer) + assert len(gstreamer_refs) == 1, 'Expected exactly one running gstreamer.' + self.gstreamer = gstreamer_refs[0].proxy() class LocalPlaybackController(PlaybackController): @@ -67,24 +67,24 @@ class LocalPlaybackController(PlaybackController): @property def time_position(self): - return self.backend.output.get_position().get() + return self.backend.gstreamer.get_position().get() class LocalPlaybackProvider(BasePlaybackProvider): def pause(self): - return self.backend.output.set_state('PAUSED').get() + return self.backend.gstreamer.set_state('PAUSED').get() def play(self, track): - return self.backend.output.play_uri(track.uri).get() + return self.backend.gstreamer.play_uri(track.uri).get() def resume(self): - return self.backend.output.set_state('PLAYING').get() + return self.backend.gstreamer.set_state('PLAYING').get() def seek(self, time_position): - return self.backend.output.set_position(time_position).get() + return self.backend.gstreamer.set_position(time_position).get() def stop(self): - return self.backend.output.set_state('READY').get() + return self.backend.gstreamer.set_state('READY').get() class LocalStoredPlaylistsProvider(BaseStoredPlaylistsProvider): diff --git a/mopidy/backends/spotify/__init__.py b/mopidy/backends/spotify/__init__.py index 1ac5f0be..9dababc0 100644 --- a/mopidy/backends/spotify/__init__.py +++ b/mopidy/backends/spotify/__init__.py @@ -6,7 +6,7 @@ from pykka.registry import ActorRegistry from mopidy import settings from mopidy.backends.base import (Backend, CurrentPlaylistController, LibraryController, PlaybackController, StoredPlaylistsController) -from mopidy.outputs.base import BaseOutput +from mopidy.gstreamer import GStreamer logger = logging.getLogger('mopidy.backends.spotify') @@ -63,13 +63,13 @@ class SpotifyBackend(ThreadingActor, Backend): self.uri_handlers = [u'spotify:', u'http://open.spotify.com/'] - self.output = None + self.gstreamer = None self.spotify = None def on_start(self): - output_refs = ActorRegistry.get_by_class(BaseOutput) - assert len(output_refs) == 1, 'Expected exactly one running output.' - self.output = output_refs[0].proxy() + gstreamer_refs = ActorRegistry.get_by_class(GStreamer) + assert len(gstreamer_refs) == 1, 'Expected exactly one running gstreamer.' + self.gstreamer = gstreamer_refs[0].proxy() self.spotify = self._connect() diff --git a/mopidy/backends/spotify/playback.py b/mopidy/backends/spotify/playback.py index 3721fe9c..3f2a157b 100644 --- a/mopidy/backends/spotify/playback.py +++ b/mopidy/backends/spotify/playback.py @@ -8,10 +8,10 @@ logger = logging.getLogger('mopidy.backends.spotify.playback') class SpotifyPlaybackProvider(BasePlaybackProvider): def pause(self): - return self.backend.output.set_state('PAUSED') + return self.backend.gstreamer.set_state('PAUSED') def play(self, track): - self.backend.output.set_state('READY') + self.backend.gstreamer.set_state('READY') if self.backend.playback.state == self.backend.playback.PLAYING: self.backend.spotify.session.play(0) if track.uri is None: @@ -20,7 +20,8 @@ class SpotifyPlaybackProvider(BasePlaybackProvider): self.backend.spotify.session.load( Link.from_string(track.uri).as_track()) self.backend.spotify.session.play(1) - self.backend.output.play_uri('appsrc://') + self.backend.gstreamer.play_uri('appsrc://') + self.backend.gstreamer.set_metadata(track) return True except SpotifyError as e: logger.info('Playback of %s failed: %s', track.uri, e) @@ -30,12 +31,12 @@ class SpotifyPlaybackProvider(BasePlaybackProvider): return self.seek(self.backend.playback.time_position) def seek(self, time_position): - self.backend.output.set_state('READY') + self.backend.gstreamer.set_state('READY') self.backend.spotify.session.seek(time_position) - self.backend.output.set_state('PLAYING') + self.backend.gstreamer.set_state('PLAYING') return True def stop(self): - result = self.backend.output.set_state('READY') + result = self.backend.gstreamer.set_state('READY') self.backend.spotify.session.play(0) return result diff --git a/mopidy/backends/spotify/session_manager.py b/mopidy/backends/spotify/session_manager.py index 395f3f28..09064db2 100644 --- a/mopidy/backends/spotify/session_manager.py +++ b/mopidy/backends/spotify/session_manager.py @@ -10,7 +10,7 @@ from mopidy import get_version, settings from mopidy.backends.base import Backend from mopidy.backends.spotify.translator import SpotifyTranslator from mopidy.models import Playlist -from mopidy.outputs.base import BaseOutput +from mopidy.gstreamer import GStreamer from mopidy.utils.process import BaseThread logger = logging.getLogger('mopidy.backends.spotify.session_manager') @@ -29,7 +29,7 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager): BaseThread.__init__(self) self.name = 'SpotifySMThread' - self.output = None + self.gstreamer = None self.backend = None self.connected = threading.Event() @@ -40,9 +40,9 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager): self.connect() def setup(self): - output_refs = ActorRegistry.get_by_class(BaseOutput) - assert len(output_refs) == 1, 'Expected exactly one running output.' - self.output = output_refs[0].proxy() + gstreamer_refs = ActorRegistry.get_by_class(GStreamer) + assert len(gstreamer_refs) == 1, 'Expected exactly one running gstreamer.' + self.gstreamer = gstreamer_refs[0].proxy() backend_refs = ActorRegistry.get_by_class(Backend) assert len(backend_refs) == 1, 'Expected exactly one running backend.' @@ -106,7 +106,7 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager): 'sample_rate': sample_rate, 'channels': channels, } - self.output.deliver_data(capabilites, bytes(frames)) + self.gstreamer.deliver_data(capabilites, bytes(frames)) def play_token_lost(self, session): """Callback used by pyspotify""" @@ -120,7 +120,7 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager): def end_of_track(self, session): """Callback used by pyspotify""" logger.debug(u'End of data stream reached') - self.output.end_of_data_stream() + self.gstreamer.end_of_data_stream() def refresh_stored_playlists(self): """Refresh the stored playlists in the backend with fresh meta data diff --git a/mopidy/core.py b/mopidy/core.py index f1a9dc36..4ca6ec29 100644 --- a/mopidy/core.py +++ b/mopidy/core.py @@ -5,6 +5,7 @@ import time from pykka.registry import ActorRegistry from mopidy import get_version, settings, OptionalDependencyError +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 @@ -18,7 +19,7 @@ def main(): setup_logging(options.verbosity_level, options.save_debug_log) setup_settings() setup_gobject_loop() - setup_output() + setup_gstreamer() setup_mixer() setup_backend() setup_frontends() @@ -56,8 +57,8 @@ def setup_gobject_loop(): gobject_loop.start() return gobject_loop -def setup_output(): - return get_class(settings.OUTPUT).start().proxy() +def setup_gstreamer(): + return GStreamer().start().proxy() def setup_mixer(): return get_class(settings.MIXER).start().proxy() diff --git a/mopidy/outputs/gstreamer.py b/mopidy/gstreamer.py similarity index 67% rename from mopidy/outputs/gstreamer.py rename to mopidy/gstreamer.py index 11cddc42..6a00ca37 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/gstreamer.py @@ -8,10 +8,10 @@ from pykka.actor import ThreadingActor from pykka.registry import ActorRegistry from mopidy import settings +from mopidy.utils import get_class from mopidy.backends.base import Backend -from mopidy.outputs.base import BaseOutput -logger = logging.getLogger('mopidy.outputs.gstreamer') +logger = logging.getLogger('mopidy.gstreamer') default_caps = gst.Caps(""" audio/x-raw-int, @@ -22,7 +22,48 @@ default_caps = gst.Caps(""" signed=(boolean)true, rate=(int)44100""") -class GStreamerOutput(ThreadingActor, BaseOutput): +class BaseOutput(object): + def connect_bin(self, pipeline, element_to_link_to): + """ + Connect output bin to pipeline and given element. + """ + description = 'queue ! %s' % self.describe_bin() + logger.debug('Adding new output to tee: %s', description) + + output = self.parse_bin(description) + self.modify_bin(output) + + pipeline.add(output) + output.sync_state_with_parent() + gst.element_link_many(element_to_link_to, output) + + def parse_bin(self, description): + return gst.parse_bin_from_description(description, True) + + def modify_bin(self, output): + """ + Modifies bin before it is installed if needed + """ + pass + + def describe_bin(self): + """ + Describe bin to be parsed. + + Must be implemented by subclasses. + """ + raise NotImplementedError + + def set_properties(self, element, properties): + """ + Set properties on element if they have a value. + """ + for key, value in properties.items(): + if value: + element.set_property(key, value) + + +class GStreamer(ThreadingActor): """ Audio output through `GStreamer `_. @@ -44,22 +85,32 @@ class GStreamerOutput(ThreadingActor, BaseOutput): :class:`mopidy.utils.process.GObjectEventThread` to be running. This is not enforced by :class:`GStreamerOutput` itself. """ - - logger.debug(u'Setting up GStreamer pipeline') - - self.gst_pipeline = gst.parse_launch(' ! '.join([ + base_pipeline = ' ! '.join([ 'audioconvert name=convert', 'volume name=volume', - settings.GSTREAMER_AUDIO_SINK, - ])) + 'taginject name=tag', + 'tee name=tee', + ]) - pad = self.gst_pipeline.get_by_name('convert').get_pad('sink') + logger.debug(u'Setting up base GStreamer pipeline: %s', base_pipeline) + + self.gst_pipeline = gst.parse_launch(base_pipeline) + + self.gst_tee = self.gst_pipeline.get_by_name('tee') + self.gst_convert = self.gst_pipeline.get_by_name('convert') + self.gst_volume = self.gst_pipeline.get_by_name('volume') + self.gst_taginject = self.gst_pipeline.get_by_name('tag') uridecodebin = gst.element_factory_make('uridecodebin', 'uri') - uridecodebin.connect('pad-added', self._process_new_pad, pad) uridecodebin.connect('notify::source', self._process_new_source) + uridecodebin.connect('pad-added', self._process_new_pad, + self.gst_convert.get_pad('sink')) self.gst_pipeline.add(uridecodebin) + for output in settings.OUTPUTS: + output_cls = get_class(output)() + output_cls.connect_bin(self.gst_pipeline, self.gst_tee) + # Setup bus and message processor gst_bus = self.gst_pipeline.get_bus() gst_bus.add_signal_watch() @@ -96,7 +147,7 @@ class GStreamerOutput(ThreadingActor, BaseOutput): def play_uri(self, uri): """Play audio at URI""" self.set_state('READY') - self.gst_pipeline.get_by_name('uri').set_property('uri', uri) + self.gst_uridecodebin.set_property('uri', uri) return self.set_state('PLAYING') def deliver_data(self, caps_string, data): @@ -160,11 +211,17 @@ class GStreamerOutput(ThreadingActor, BaseOutput): def get_volume(self): """Get volume in range [0..100]""" - gst_volume = self.gst_pipeline.get_by_name('volume') - return int(gst_volume.get_property('volume') * 100) + return int(self.gst_volume.get_property('volume') * 100) def set_volume(self, volume): """Set volume in range [0..100]""" - gst_volume = self.gst_pipeline.get_by_name('volume') - gst_volume.set_property('volume', volume / 100.0) + self.gst_volume.set_property('volume', volume / 100.0) return True + + def set_metadata(self, track): + tags = u'artist="%(artist)s",title="%(title)s"' % { + 'artist': u', '.join([a.name for a in track.artists]), + 'title': track.name, + } + logger.debug('Setting tags to: %s', tags) + self.gst_taginject.set_property('tags', tags) diff --git a/mopidy/mixers/gstreamer_software.py b/mopidy/mixers/gstreamer_software.py index d6365b4b..87602772 100644 --- a/mopidy/mixers/gstreamer_software.py +++ b/mopidy/mixers/gstreamer_software.py @@ -2,7 +2,7 @@ from pykka.actor import ThreadingActor from pykka.registry import ActorRegistry from mopidy.mixers.base import BaseMixer -from mopidy.outputs.base import BaseOutput +from mopidy.gstreamer import GStreamer class GStreamerSoftwareMixer(ThreadingActor, BaseMixer): """Mixer which uses GStreamer to control volume in software.""" @@ -11,7 +11,7 @@ class GStreamerSoftwareMixer(ThreadingActor, BaseMixer): self.output = None def on_start(self): - output_refs = ActorRegistry.get_by_class(BaseOutput) + output_refs = ActorRegistry.get_by_class(GStreamer) assert len(output_refs) == 1, 'Expected exactly one running output.' self.output = output_refs[0].proxy() diff --git a/mopidy/outputs.py b/mopidy/outputs.py new file mode 100644 index 00000000..5a57f446 --- /dev/null +++ b/mopidy/outputs.py @@ -0,0 +1,31 @@ +from mopidy import settings +from mopidy.gstreamer import BaseOutput + +class LocalOutput(BaseOutput): + def describe_bin(self): + if settings.LOCAL_OUTPUT_OVERRIDE: + return settings.LOCAL_OUTPUT_OVERRIDE + return 'autoaudiosink' + +class NullOutput(BaseOutput): + def describe_bin(self): + return 'fakesink' + +class ShoutcastOutput(BaseOutput): + def describe_bin(self): + if settings.SHOUTCAST_OUTPUT_OVERRIDE: + return settings.SHOUTCAST_OUTPUT_OVERRIDE + return 'audioconvert ! %s ! shout2send name=shoutcast' \ + % settings.SHOUTCAST_OUTPUT_ENCODER + + def modify_bin(self, output): + if settings.SHOUTCAST_OUTPUT_OVERRIDE: + return + + self.set_properties(output.get_by_name('shoutcast'), { + u'ip': settings.SHOUTCAST_OUTPUT_SERVER, + u'mount': settings.SHOUTCAST_OUTPUT_MOUNT, + u'port': settings.SHOUTCAST_OUTPUT_PORT, + u'username': settings.SHOUTCAST_OUTPUT_USERNAME, + u'password': settings.SHOUTCAST_OUTPUT_PASSWORD, + }) diff --git a/mopidy/outputs/__init__.py b/mopidy/outputs/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/mopidy/outputs/base.py b/mopidy/outputs/base.py deleted file mode 100644 index fbc86688..00000000 --- a/mopidy/outputs/base.py +++ /dev/null @@ -1,91 +0,0 @@ -class BaseOutput(object): - """ - Base class for audio outputs. - """ - - def play_uri(self, uri): - """ - Play URI. - - *MUST be implemented by subclass.* - - :param uri: the URI to play - :type uri: string - :rtype: :class:`True` if successful, else :class:`False` - """ - raise NotImplementedError - - def deliver_data(self, capabilities, data): - """ - Deliver audio data to be played. - - *MUST be implemented by subclass.* - - :param capabilities: a GStreamer capabilities string - :type capabilities: string - """ - raise NotImplementedError - - def end_of_data_stream(self): - """ - Signal that the last audio data has been delivered. - - *MUST be implemented by subclass.* - """ - raise NotImplementedError - - def get_position(self): - """ - Get position in milliseconds. - - *MUST be implemented by subclass.* - - :rtype: int - """ - raise NotImplementedError - - def set_position(self, position): - """ - Set position in milliseconds. - - *MUST be implemented by subclass.* - - :param position: the position in milliseconds - :type volume: int - :rtype: :class:`True` if successful, else :class:`False` - """ - raise NotImplementedError - - def set_state(self, state): - """ - Set playback state. - - *MUST be implemented by subclass.* - - :param state: the state - :type state: string - :rtype: :class:`True` if successful, else :class:`False` - """ - raise NotImplementedError - - def get_volume(self): - """ - Get volume level for software mixer. - - *MUST be implemented by subclass.* - - :rtype: int in range [0..100] - """ - raise NotImplementedError - - def set_volume(self, volume): - """ - Set volume level for software mixer. - - *MUST be implemented by subclass.* - - :param volume: the volume in the range [0..100] - :type volume: int - :rtype: :class:`True` if successful, else :class:`False` - """ - raise NotImplementedError diff --git a/mopidy/outputs/dummy.py b/mopidy/outputs/dummy.py deleted file mode 100644 index f09965f7..00000000 --- a/mopidy/outputs/dummy.py +++ /dev/null @@ -1,63 +0,0 @@ -from pykka.actor import ThreadingActor - -from mopidy.outputs.base import BaseOutput - -class DummyOutput(ThreadingActor, BaseOutput): - """ - Audio output used for testing. - """ - - # pylint: disable = R0902 - # Too many instance attributes (9/7) - - #: For testing. Contains the last URI passed to :meth:`play_uri`. - uri = None - - #: For testing. Contains the last capabilities passed to - #: :meth:`deliver_data`. - capabilities = None - - #: For testing. Contains the last data passed to :meth:`deliver_data`. - data = None - - #: For testing. :class:`True` if :meth:`end_of_data_stream` has been - #: called. - end_of_data_stream_called = False - - #: For testing. Contains the current position. - position = 0 - - #: For testing. Contains the current state. - state = 'NULL' - - #: For testing. Contains the current volume. - volume = 100 - - def play_uri(self, uri): - self.uri = uri - return True - - def deliver_data(self, capabilities, data): - self.capabilities = capabilities - self.data = data - - def end_of_data_stream(self): - self.end_of_data_stream_called = True - - def get_position(self): - return self.position - - def set_position(self, position): - self.position = position - return True - - def set_state(self, state): - self.state = state - return True - - def get_volume(self): - return self.volume - - def set_volume(self, volume): - self.volume = volume - return True diff --git a/mopidy/settings.py b/mopidy/settings.py index 6e33ffaa..c0ee3569 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -54,12 +54,12 @@ FRONTENDS = ( u'mopidy.frontends.lastfm.LastfmFrontend', ) -#: Which GStreamer audio sink to use in :mod:`mopidy.outputs.gstreamer`. +#: Which GStreamer bin description to use in :mod:`mopidy.outputs.CustomOutput`. #: #: Default:: #: -#: GSTREAMER_AUDIO_SINK = u'autoaudiosink' -GSTREAMER_AUDIO_SINK = u'autoaudiosink' +#: LOCAL_OUTPUT_OVERRIDE = None +LOCAL_OUTPUT_OVERRIDE = None #: Your `Last.fm `_ username. #: @@ -143,13 +143,6 @@ MIXER_EXT_SPEAKERS_B = None #: MIXER_MAX_VOLUME = 100 MIXER_MAX_VOLUME = 100 -#: Audio output handler to use. -#: -#: Default:: -#: -#: OUTPUT = u'mopidy.outputs.gstreamer.GStreamerOutput' -OUTPUT = u'mopidy.outputs.gstreamer.GStreamerOutput' - #: Which address Mopidy's MPD server should bind to. #: #:Examples: @@ -174,6 +167,77 @@ MPD_SERVER_PASSWORD = None #: Default: 6600 MPD_SERVER_PORT = 6600 +#: List of outputs to use. See :mod:`mopidy.outputs` for all available +#: backends +#: +#: Default:: +#: +#: OUTPUTS = ( +#: u'mopidy.outputs.LocalOutput', +#: ) +OUTPUTS = ( + u'mopidy.outputs.LocalOutput', +) + +#: Servar that runs Shoutcast server to send stream to. +#: +#: Default:: +#: +#: SHOUTCAST_OUTPUT_SERVER = u'127.0.0.1' +SHOUTCAST_OUTPUT_SERVER = u'127.0.0.1' + +#: User to authenticate as against Shoutcast server. +#: +#: Default:: +#: +#: SHOUTCAST_OUTPUT_USERNAME = u'source' +SHOUTCAST_OUTPUT_USERNAME = u'source' + +#: Password to authenticate with against Shoutcast server. +#: +#: Default:: +#: +#: SHOUTCAST_OUTPUT_PASSWORD = u'hackme' +SHOUTCAST_OUTPUT_PASSWORD = u'hackme' + +#: Port to use for streaming to Shoutcast server. +#: +#: Default:: +#: +#: SHOUTCAST_OUTPUT_PORT = 8000 +SHOUTCAST_OUTPUT_PORT = 8000 + +#: Mountpoint to use for the stream on the Shoutcast server. +#: +#: Default:: +#: +#: SHOUTCAST_OUTPUT_MOUNT = u'/stream' +SHOUTCAST_OUTPUT_MOUNT = u'/stream' + +#: Encoder to use to process audio data before streaming. +#: +#: Default:: +#: +#: SHOUTCAST_OUTPUT_ENCODER = u'lame mode=stereo bitrate=320' +SHOUTCAST_OUTPUT_ENCODER = u'lame mode=stereo bitrate=320' + +#: Overrides to allow advanced setup of shoutcast. Using this settings implies +#: that all other SHOUTCAST_OUTPUT_* settings will be ignored. +#: +#: Examples: +#: +#: ``vorbisenc ! oggmux ! shout2send mount=/stream port=8000`` +#: Encode with vorbis and use ogg mux. +#: ``lame bitrate=320 ! shout2send mount=/stream port=8000`` +#: Encode with lame to bitrate=320. +#: +#: For all options see gst-inspect-0.10 lame, vorbisenc and shout2send. +#: +#: Default:: +#: +#: SHOUTCAST_OUTPUT_OVERRIDE = None +SHOUTCAST_OUTPUT_OVERRIDE = None + #: Path to the Spotify cache. #: #: Used by :mod:`mopidy.backends.spotify`. diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py index 529c6fb1..0dc6b4cb 100644 --- a/mopidy/utils/settings.py +++ b/mopidy/utils/settings.py @@ -97,9 +97,11 @@ def validate_settings(defaults, settings): 'DUMP_LOG_FILENAME': 'DEBUG_LOG_FILENAME', 'DUMP_LOG_FORMAT': 'DEBUG_LOG_FORMAT', 'FRONTEND': 'FRONTENDS', + 'GSTREAMER_AUDIO_SINK': 'LOCAL_OUTPUT_OVERRIDE', 'LOCAL_MUSIC_FOLDER': 'LOCAL_MUSIC_PATH', 'LOCAL_PLAYLIST_FOLDER': 'LOCAL_PLAYLIST_PATH', 'LOCAL_TAG_CACHE': 'LOCAL_TAG_CACHE_FILE', + 'OUTPUT': None, 'SERVER': None, 'SERVER_HOSTNAME': 'MPD_SERVER_HOSTNAME', 'SERVER_PORT': 'MPD_SERVER_PORT', diff --git a/tests/backends/base/current_playlist.py b/tests/backends/base/current_playlist.py index ee5e1111..427ce76d 100644 --- a/tests/backends/base/current_playlist.py +++ b/tests/backends/base/current_playlist.py @@ -3,7 +3,7 @@ import multiprocessing import random from mopidy.models import Playlist, Track -from mopidy.outputs.base import BaseOutput +from mopidy.gstreamer import GStreamer from tests.backends.base import populate_playlist @@ -12,7 +12,7 @@ class CurrentPlaylistControllerTest(object): def setUp(self): self.backend = self.backend_class() - self.backend.output = mock.Mock(spec=BaseOutput) + self.backend.gstreamer = mock.Mock(spec=GStreamer) self.controller = self.backend.current_playlist self.playback = self.backend.playback diff --git a/tests/backends/base/playback.py b/tests/backends/base/playback.py index 8ea48a3a..2d455225 100644 --- a/tests/backends/base/playback.py +++ b/tests/backends/base/playback.py @@ -4,7 +4,7 @@ import random import time from mopidy.models import Track -from mopidy.outputs.base import BaseOutput +from mopidy.gstreamer import GStreamer from tests import SkipTest from tests.backends.base import populate_playlist @@ -16,7 +16,7 @@ class PlaybackControllerTest(object): def setUp(self): self.backend = self.backend_class() - self.backend.output = mock.Mock(spec=BaseOutput) + self.backend.gstreamer = mock.Mock(spec=GStreamer) self.playback = self.backend.playback self.current_playlist = self.backend.current_playlist @@ -520,7 +520,7 @@ class PlaybackControllerTest(object): self.assert_(wrapper.called) - @SkipTest # Blocks for 10ms and does not work with DummyOutput + @SkipTest # Blocks for 10ms @populate_playlist def test_end_of_track_callback_gets_called(self): self.playback.play() @@ -599,7 +599,7 @@ class PlaybackControllerTest(object): self.playback.pause() self.assertEqual(self.playback.resume(), None) - @SkipTest # Uses sleep and does not work with DummyOutput+LocalBackend + @SkipTest # Uses sleep and might not work with LocalBackend @populate_playlist def test_resume_continues_from_right_position(self): self.playback.play() @@ -729,7 +729,7 @@ class PlaybackControllerTest(object): def test_time_position_when_stopped(self): future = mock.Mock() future.get = mock.Mock(return_value=0) - self.backend.output.get_position = mock.Mock(return_value=future) + self.backend.gstreamer.get_position = mock.Mock(return_value=future) self.assertEqual(self.playback.time_position, 0) @@ -737,11 +737,11 @@ class PlaybackControllerTest(object): def test_time_position_when_stopped_with_playlist(self): future = mock.Mock() future.get = mock.Mock(return_value=0) - self.backend.output.get_position = mock.Mock(return_value=future) + self.backend.gstreamer.get_position = mock.Mock(return_value=future) self.assertEqual(self.playback.time_position, 0) - @SkipTest # Uses sleep and does not work with LocalBackend+DummyOutput + @SkipTest # Uses sleep and does might not work with LocalBackend @populate_playlist def test_time_position_when_playing(self): self.playback.play() diff --git a/tests/outputs/gstreamer_test.py b/tests/gstreamer_test.py similarity index 59% rename from tests/outputs/gstreamer_test.py rename to tests/gstreamer_test.py index 31a16756..9087e0db 100644 --- a/tests/outputs/gstreamer_test.py +++ b/tests/gstreamer_test.py @@ -9,26 +9,28 @@ if sys.platform == 'win32': raise SkipTest from mopidy import settings -from mopidy.outputs.gstreamer import GStreamerOutput +from mopidy.gstreamer import GStreamer from mopidy.utils.path import path_to_uri from tests import path_to_data_dir -class GStreamerOutputTest(unittest.TestCase): +# TODO BaseOutputTest? + +class GStreamerTest(unittest.TestCase): def setUp(self): settings.BACKENDS = ('mopidy.backends.local.LocalBackend',) self.song_uri = path_to_uri(path_to_data_dir('song1.wav')) - self.output = GStreamerOutput() - self.output.on_start() + self.gstreamer = GStreamer() + self.gstreamer.on_start() def tearDown(self): settings.runtime.clear() def test_play_uri_existing_file(self): - self.assertTrue(self.output.play_uri(self.song_uri)) + self.assertTrue(self.gstreamer.play_uri(self.song_uri)) def test_play_uri_non_existing_file(self): - self.assertFalse(self.output.play_uri(self.song_uri + 'bogus')) + self.assertFalse(self.gstreamer.play_uri(self.song_uri + 'bogus')) @SkipTest def test_deliver_data(self): @@ -39,19 +41,19 @@ class GStreamerOutputTest(unittest.TestCase): pass # TODO def test_default_get_volume_result(self): - self.assertEqual(100, self.output.get_volume()) + self.assertEqual(100, self.gstreamer.get_volume()) def test_set_volume(self): - self.assertTrue(self.output.set_volume(50)) - self.assertEqual(50, self.output.get_volume()) + self.assertTrue(self.gstreamer.set_volume(50)) + self.assertEqual(50, self.gstreamer.get_volume()) def test_set_volume_to_zero(self): - self.assertTrue(self.output.set_volume(0)) - self.assertEqual(0, self.output.get_volume()) + self.assertTrue(self.gstreamer.set_volume(0)) + self.assertEqual(0, self.gstreamer.get_volume()) def test_set_volume_to_one_hundred(self): - self.assertTrue(self.output.set_volume(100)) - self.assertEqual(100, self.output.get_volume()) + self.assertTrue(self.gstreamer.set_volume(100)) + self.assertEqual(100, self.gstreamer.get_volume()) @SkipTest def test_set_state(self):