diff --git a/docs/api/mixer.rst b/docs/api/mixer.rst index 0428dab6..4697a9d5 100644 --- a/docs/api/mixer.rst +++ b/docs/api/mixer.rst @@ -10,6 +10,9 @@ Audio mixer API .. autoclass:: mopidy.mixer.Mixer :members: +.. autoclass:: mopidy.mixer.MixerListener + :members: + Mixer implementations ===================== diff --git a/mopidy/mixer.py b/mopidy/mixer.py index 6b15efb2..c000eb41 100644 --- a/mopidy/mixer.py +++ b/mopidy/mixer.py @@ -1,8 +1,16 @@ from __future__ import unicode_literals +import logging + +from mopidy import listener + + +logger = logging.getLogger(__name__) + class Mixer(object): - """Audio mixer API + """ + Audio mixer API If the mixer has problems during initialization it should raise :exc:`~mopidy.exceptions.MixerError` with a descriptive error message. This @@ -16,7 +24,8 @@ class Mixer(object): """ name = None - """Name of the mixer. + """ + Name of the mixer. Used when configuring what mixer to use. Should match the :attr:`~mopidy.ext.Extension.ext_name` of the extension providing the @@ -54,9 +63,20 @@ class Mixer(object): """ return False + def trigger_volume_changed(self, volume): + """ + Send ``volume_changed`` event to all mixer listeners. + + This method should be called by subclasses when the volume is changed, + either because of a call to :meth:`set_volume` or because or any + external entity changing the volume. + """ + logger.debug('Mixer event: volume_changed(volume=%d)', volume) + MixerListener.send('volume_changed', volume=volume) + def get_mute(self): """ - Get mute status of the mixer. + Get mute state of the mixer. *MAY be implemented by subclass.* @@ -76,3 +96,53 @@ class Mixer(object): :rtype: :class:`True` if success, :class:`False` if failure """ return False + + def trigger_mute_changed(self, muted): + """ + Send ``mute_changed`` event to all mixer listeners. + + This method should be called by subclasses when the mute state is + changed, either because of a call to :meth:`set_mute` or because or + any external entity changing the mute state. + """ + logger.debug('Mixer event: mute_changed(muted=%s)', muted) + MixerListener.send('mute_changed', muted=muted) + + +class MixerListener(listener.Listener): + """ + Marker interface for recipients of events sent by the mixer actor. + + Any Pykka actor that mixes in this class will receive calls to the methods + defined here when the corresponding events happen in the mixer actor. This + interface is used both for looking up what actors to notify of the events, + and for providing default implementations for those listeners that are not + interested in all events. + """ + + @staticmethod + def send(event, **kwargs): + """Helper to allow calling of audio listener events""" + listener.send_async(MixerListener, event, **kwargs) + + def volume_changed(self, volume): + """ + Called after the volume has changed. + + *MAY* be implemented by actor. + + :param volume: the new volume + :type volume: int in range [0..100] + """ + pass + + def mute_changed(self, muted): + """ + Called after the mute state has changed. + + *MAY* be implemented by actor. + + :param muted: :class:`True` if muted, :class:`False` if not muted + :type muted: bool + """ + pass diff --git a/tests/test_mixer.py b/tests/test_mixer.py new file mode 100644 index 00000000..a214eba7 --- /dev/null +++ b/tests/test_mixer.py @@ -0,0 +1,26 @@ +from __future__ import unicode_literals + +import unittest + +import mock + +from mopidy import mixer + + +class MixerListenerTest(unittest.TestCase): + def setUp(self): + self.listener = mixer.MixerListener() + + def test_on_event_forwards_to_specific_handler(self): + self.listener.volume_changed = mock.Mock() + + self.listener.on_event( + 'volume_changed', volume=60) + + self.listener.volume_changed.assert_called_with(volume=60) + + def test_listener_has_default_impl_for_volume_changed(self): + self.listener.volume_changed(volume=60) + + def test_listener_has_default_impl_for_mute_changed(self): + self.listener.mute_changed(muted=True)