From b8a0ca59cdb0bcbdac734bb242e5b81f952ffac0 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 3 Aug 2014 23:46:05 +0200 Subject: [PATCH] audio: Refactor softwaremixer and audio interactions This rips the mixer bits and pieces that have been hiding in the audio actor to it's own class. The software mixer now only knows about this and nothing else from audio. --- mopidy/audio/actor.py | 127 +++++++++++++++------------------- mopidy/softwaremixer/mixer.py | 50 +++++++------ 2 files changed, 86 insertions(+), 91 deletions(-) diff --git a/mopidy/audio/actor.py b/mopidy/audio/actor.py index f854ff15..520ac41c 100644 --- a/mopidy/audio/actor.py +++ b/mopidy/audio/actor.py @@ -192,6 +192,53 @@ class _Outputs(gst.Bin): self._tee.link(queue) +class SoftwareMixer(object): + pykka_traversable = True + + def __init__(self, mixer): + self._mixer = mixer + self._element = None + self._last_volume = None + self._last_mute = None + self._signals = _Signals() + + def setup(self, element, mixer_ref): + self._element = element + + self._signals.connect(element, 'notify::volume', self._volume_changed) + self._signals.connect(element, 'notify::mute', self._mute_changed) + + self._mixer.setup(mixer_ref) + + def teardown(self): + self._signals.clear() + self._mixer.teardown() + + def get_volume(self): + return int(round(self._element.get_property('volume') * 100)) + + def set_volume(self, volume): + self._element.set_property('volume', volume / 100.0) + + def get_mute(self): + return self._element.get_property('mute') + + def set_mute(self, mute): + return self._element.set_property('mute', bool(mute)) + + def _volume_changed(self, element, property_): + old_volume, self._last_volume = self._last_volume, self.get_volume() + if old_volume != self._last_volume: + gst_logger.debug('Notify volume: %s', self._last_volume / 100.0) + self._mixer.trigger_volume_changed(self._last_volume) + + def _mute_changed(self, element, property_): + old_mute, self._last_mute = self._last_mute, self.get_mute() + if old_mute != self._last_mute: + gst_logger.debug('Notify mute: %s', self._last_mute) + self._mixer.trigger_mute_changed(self._last_mute) + + def setup_proxy(element, config): # TODO: reuse in scanner code if not config.get('hostname'): @@ -215,11 +262,13 @@ class Audio(pykka.ThreadingActor): #: The GStreamer state mapped to :class:`mopidy.audio.PlaybackState` state = PlaybackState.STOPPED + #: The software mixing interface :class:`mopidy.audio.actor.SoftwareMixer` + mixer = None + def __init__(self, config, mixer): super(Audio, self).__init__() self._config = config - self._mixer = mixer self._target_state = gst.STATE_NULL self._buffering = False @@ -230,6 +279,9 @@ class Audio(pykka.ThreadingActor): self._appsrc = _Appsrc() self._signals = _Signals() + if mixer and self._config['audio']['mixer'] == 'software': + self.mixer = SoftwareMixer(mixer) + def on_start(self): try: self._setup_preferences() @@ -300,31 +352,12 @@ class Audio(pykka.ThreadingActor): self._playbin.set_property('audio-sink', self._outputs) def _setup_mixer(self): - if self._config['audio']['mixer'] != 'software': - return - self._mixer.audio = self.actor_ref.proxy() - self._signals.connect( - self._playbin, 'notify::volume', self._on_mixer_change) - self._signals.connect( - self._playbin, 'notify::mute', self._on_mixer_change) - - # The Mopidy startup procedure will set the initial volume of a mixer, - # but this happens before the audio actor is injected into the software - # mixer and has no effect. Thus, we need to set the initial volume - # again. - initial_volume = self._config['audio']['mixer_volume'] - if initial_volume is not None: - self._mixer.set_volume(initial_volume) - - def _on_mixer_change(self, element, gparamspec): - self._mixer.trigger_events_for_changed_values() + if self.mixer: + self.mixer.setup(self._playbin, self.actor_ref.proxy().mixer) def _teardown_mixer(self): - if self._config['audio']['mixer'] != 'software': - return - self._signals.disconnect(self._playbin, 'notify::volume') - self._signals.disconnect(self._playbin, 'notify::mute') - self._mixer.audio = None + if self.mixer: + self.mixer.teardown() def _setup_visualizer(self): visualizer_element = self._config['audio']['visualizer'] @@ -647,52 +680,6 @@ class Audio(pykka.ThreadingActor): # of faking it in the message handling when result=OK return True - def get_volume(self): - """ - Get volume level of the software mixer. - - Example values: - - 0: - Minimum volume. - 100: - Maximum volume. - - :rtype: int in range [0..100] - """ - return int(round(self._playbin.get_property('volume') * 100)) - - def set_volume(self, volume): - """ - Set volume level of the software mixer. - - :param volume: the volume in the range [0..100] - :type volume: int - :rtype: :class:`True` if successful, else :class:`False` - """ - self._playbin.set_property('volume', volume / 100.0) - return True - - def get_mute(self): - """ - Get mute status of the software mixer. - - :rtype: :class:`True` if muted, :class:`False` if unmuted, - :class:`None` if no mixer is installed. - """ - return self._playbin.get_property('mute') - - def set_mute(self, mute): - """ - Mute or unmute of the software mixer. - - :param mute: Whether to mute the mixer or not. - :type mute: bool - :rtype: :class:`True` if successful, else :class:`False` - """ - self._playbin.set_property('mute', bool(mute)) - return True - def set_metadata(self, track): """ Set track metadata for currently playing song. diff --git a/mopidy/softwaremixer/mixer.py b/mopidy/softwaremixer/mixer.py index 0ebbfeb7..71d178f5 100644 --- a/mopidy/softwaremixer/mixer.py +++ b/mopidy/softwaremixer/mixer.py @@ -17,40 +17,48 @@ class SoftwareMixer(pykka.ThreadingActor, mixer.Mixer): def __init__(self, config): super(SoftwareMixer, self).__init__(config) - self.audio = None - self._last_volume = None - self._last_mute = None + self._audio_mixer = None + self._initial_volume = None + self._initial_mute = None + # TODO: shouldn't this be logged by thing that choose us? logger.info('Mixing using GStreamer software mixing') + def setup(self, mixer_ref): + self._audio_mixer = mixer_ref + + # The Mopidy startup procedure will set the initial volume of a + # mixer, but this happens before the audio actor is injected into the + # software mixer and has no effect. Thus, we need to set the initial + # volume again. + if self._initial_volume is not None: + self.set_volume(self._initial_volume) + if self._initial_mute is not None: + self.set_mute(self._initial_mute) + + def teardown(self): + self._audio_mixer = None + def get_volume(self): - if self.audio is None: + if self._audio_mixer is None: return None - return self.audio.get_volume().get() + return self._audio_mixer.get_volume().get() def set_volume(self, volume): - if self.audio is None: + if self._audio_mixer is None: + self._initial_volume = volume return False - self.audio.set_volume(volume) + self._audio_mixer.set_volume(volume) return True def get_mute(self): - if self.audio is None: + if self._audio_mixer is None: return None - return self.audio.get_mute().get() + return self._audio_mixer.get_mute().get() def set_mute(self, mute): - if self.audio is None: + if self._audio_mixer is None: + self._initial_mute = mute return False - self.audio.set_mute(mute) + self._audio_mixer.set_mute(mute) return True - - def trigger_events_for_changed_values(self): - old_volume, self._last_volume = self._last_volume, self.get_volume() - old_mute, self._last_mute = self._last_mute, self.get_mute() - - if old_volume != self._last_volume: - self.trigger_volume_changed(self._last_volume) - - if old_mute != self._last_mute: - self.trigger_mute_changed(self._last_mute)