diff --git a/mopidy/core.py b/mopidy/core.py index 701c8144..8067d3ea 100644 --- a/mopidy/core.py +++ b/mopidy/core.py @@ -7,7 +7,7 @@ from mopidy import get_version, settings, OptionalDependencyError 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 BaseThread +from mopidy.utils.process import BaseThread, GObjectEventThread from mopidy.utils.settings import list_settings_optparse_callback logger = logging.getLogger('mopidy.core') @@ -47,6 +47,7 @@ class CoreProcess(BaseThread): def setup(self): self.setup_logging() self.setup_settings() + self.gobject_loop = self.setup_gobject_loop(self.core_queue) self.output = self.setup_output(self.core_queue) self.backend = self.setup_backend(self.core_queue, self.output) self.frontends = self.setup_frontends(self.core_queue, self.backend) @@ -61,6 +62,11 @@ class CoreProcess(BaseThread): get_or_create_file('~/.mopidy/settings.py') settings.validate() + def setup_gobject_loop(self, core_queue): + gobject_loop = GObjectEventThread(core_queue) + gobject_loop.start() + return gobject_loop + def setup_output(self, core_queue): output = get_class(settings.OUTPUT)(core_queue) output.start() diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index 52bd302d..3b037f62 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -1,6 +1,3 @@ -import gobject -gobject.threads_init() - import pygst pygst.require('0.10') import gst @@ -28,20 +25,14 @@ class GStreamerOutput(BaseOutput): def __init__(self, *args, **kwargs): super(GStreamerOutput, self).__init__(*args, **kwargs) - # Start a helper thread that can run the gobject.MainLoop - self.messages_thread = GStreamerMessagesThread(self.core_queue) - - # Start a helper thread that can process the output_queue self.output_queue = multiprocessing.Queue() self.player_thread = GStreamerPlayerThread(self.core_queue, self.output_queue) def start(self): - self.messages_thread.start() self.player_thread.start() def destroy(self): - self.messages_thread.destroy() self.player_thread.destroy() def process_message(self, message): @@ -91,21 +82,15 @@ class GStreamerOutput(BaseOutput): return self._send_recv({'command': 'set_volume', 'volume': volume}) -class GStreamerMessagesThread(BaseThread): - def __init__(self, core_queue): - super(GStreamerMessagesThread, self).__init__(core_queue) - self.name = u'GStreamerMessagesThread' - - def run_inside_try(self): - gobject.MainLoop().run() - - class GStreamerPlayerThread(BaseThread): """ A process for all work related to GStreamer. The main loop processes events from both Mopidy and GStreamer. + This thread requires :class:`mopidy.utils.process.GObjectEventThread` to be + running too. This is not enforced in any way by the code. + Make sure this subprocess is started by the MainThread in the top-most parent process, and not some other thread. If not, we can get into the problems described at diff --git a/mopidy/utils/process.py b/mopidy/utils/process.py index c34d018c..11dafa8a 100644 --- a/mopidy/utils/process.py +++ b/mopidy/utils/process.py @@ -4,6 +4,9 @@ import multiprocessing.dummy from multiprocessing.reduction import reduce_connection import pickle +import gobject +gobject.threads_init() + from mopidy import SettingsError logger = logging.getLogger('mopidy.utils.process') @@ -84,3 +87,25 @@ class BaseThread(multiprocessing.dummy.Process): self.core_queue.put({'to': 'core', 'command': 'exit', 'status': status, 'reason': reason}) self.destroy() + + +class GObjectEventThread(BaseThread): + """ + A GObject event loop which is shared by all Mopidy components that uses + libraries that need a GObject event loop, like GStreamer and D-Bus. + + Should be started by Mopidy's core and used by + :mod:`mopidy.output.gstreamer`, :mod:`mopidy.frontend.mpris`, etc. + """ + + def __init__(self, core_queue): + super(GObjectEventThread, self).__init__(core_queue) + self.name = u'GObjectEventThread' + self.loop = None + + def run_inside_try(self): + self.loop = gobject.MainLoop().run() + + def destroy(self): + self.loop.quit() + super(GObjectEventThread, self).destroy()