diff --git a/mopidy/audio/actor.py b/mopidy/audio/actor.py index 867382c5..97660f1d 100644 --- a/mopidy/audio/actor.py +++ b/mopidy/audio/actor.py @@ -11,6 +11,7 @@ import gst.pbutils import pykka +from mopidy import exceptions from mopidy.audio import playlists, utils from mopidy.audio.constants import PlaybackState from mopidy.audio.listener import AudioListener @@ -141,6 +142,56 @@ class _Appsrc(object): return True +class _Outputs(gst.Bin): + def __init__(self): + gst.Bin.__init__(self) + + self._tee = gst.element_factory_make('tee') + self.add(self._tee) + + # Queue element to buy us time between the about to finish event and + # the actual switch, i.e. about to switch can block for longer thanks + # to this queue. + # TODO: make the min-max values a setting? + # TODO: move out of this class? + queue = gst.element_factory_make('queue') + queue.set_property('max-size-buffers', 0) + queue.set_property('max-size-bytes', 0) + queue.set_property('max-size-time', 5 * gst.SECOND) + queue.set_property('min-threshold-time', 3 * gst.SECOND) + self.add(queue) + + queue.link(self._tee) + + ghost_pad = gst.GhostPad('sink', queue.get_pad('sink')) + self.add_pad(ghost_pad) + + # Add an always connected fakesink so the tee doesn't fail. + # XXX disabled for now as we get one stream changed per sink... + # self._add(gst.element_factory_make('fakesink')) + + def add_output(self, description): + # XXX This only works for pipelines not in use until #790 gets done. + try: + output = gst.parse_bin_from_description( + description, ghost_unconnected_pads=True) + except gobject.GError as ex: + logger.error( + 'Failed to create audio output "%s": %s', description, ex) + raise exceptions.AudioException(bytes(ex)) + + self._add(output) + logger.info('Audio output set to "%s"', description) + + def _add(self, element): + # All tee branches need a queue in front of them. + queue = gst.element_factory_make('queue') + self.add(element) + self.add(queue) + queue.link(element) + self._tee.link(queue) + + # TODO: create a player class which replaces the actors internals class Audio(pykka.ThreadingActor): """ @@ -159,6 +210,7 @@ class Audio(pykka.ThreadingActor): self._buffering = False self._playbin = None + self._outputs = None self._about_to_finish_callback = None self._appsrc = _Appsrc() @@ -241,38 +293,13 @@ class Audio(pykka.ThreadingActor): self._playbin.set_state(gst.STATE_NULL) def _setup_output(self): - output_desc = self._config['audio']['output'] + self._outputs = _Outputs() + self._outputs.get_pad('sink').add_event_probe(self._on_pad_event) try: - user_output = gst.parse_bin_from_description( - output_desc, ghost_unconnected_pads=True) - except gobject.GError as ex: - logger.error( - 'Failed to create audio output "%s": %s', output_desc, ex) - process.exit_process() - - output = gst.Bin('output') - - # Queue element to buy us time between the about to finish event and - # the actual switch, i.e. about to switch can block for longer thanks - # to this queue. - # TODO: make the min-max values a setting? - queue = gst.element_factory_make('queue') - queue.set_property('max-size-buffers', 0) - queue.set_property('max-size-bytes', 0) - queue.set_property('max-size-time', 5 * gst.SECOND) - queue.set_property('min-threshold-time', 3 * gst.SECOND) - - queue.get_pad('src').add_event_probe(self._on_pad_event) - - output.add(user_output) - output.add(queue) - - queue.link(user_output) - ghost_pad = gst.GhostPad('sink', queue.get_pad('sink')) - output.add_pad(ghost_pad) - - logger.info('Audio output set to "%s"', output_desc) - self._playbin.set_property('audio-sink', output) + self._outputs.add_output(self._config['audio']['output']) + except exceptions.AudioException: + process.exit_process() # TODO: move this up the chain + self._playbin.set_property('audio-sink', self._outputs) def _setup_mixer(self): if self._config['audio']['mixer'] != 'software': diff --git a/mopidy/exceptions.py b/mopidy/exceptions.py index bf9b6dd9..532f6853 100644 --- a/mopidy/exceptions.py +++ b/mopidy/exceptions.py @@ -34,3 +34,7 @@ class MixerError(MopidyException): class ScannerError(MopidyException): pass + + +class AudioException(MopidyException): + pass diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 47b3080d..19127aaa 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -31,3 +31,7 @@ class ExceptionsTest(unittest.TestCase): def test_scanner_error_is_a_mopidy_exception(self): self.assert_(issubclass( exceptions.ScannerError, exceptions.MopidyException)) + + def test_audio_error_is_a_mopidy_exception(self): + self.assert_(issubclass( + exceptions.AudioException, exceptions.MopidyException))