diff --git a/docs/api/core.rst b/docs/api/core.rst index 21ff79f5..27ab2f57 100644 --- a/docs/api/core.rst +++ b/docs/api/core.rst @@ -64,6 +64,14 @@ Manages the music library, e.g. searching for tracks to be added to a playlist. :members: +Mixer controller +================ + +Manages volume and muting. + +.. autoclass:: mopidy.core.MixerController + :members: + Core listener ============= diff --git a/docs/changelog.rst b/docs/changelog.rst index 1a9eb33d..4f72ca30 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -17,6 +17,10 @@ v0.20.0 (UNRELEASED) - Added :class:`mopidy.core.HistoryController` which keeps track of what tracks have been played. (Fixes: :issue:`423`, PR: :issue:`803`) +- Added :class:`mopidy.core.MixerController` which keeps track of volume and + mute. The old methods on :class:`mopidy.core.PlaybackController` for volume + and mute management has been deprecated. (Fixes: :issue:`962`) + - Removed ``clear_current_track`` keyword argument to :meth:`mopidy.core.Playback.stop`. It was a leaky internal abstraction, which was never intended to be used externally. diff --git a/mopidy/core/__init__.py b/mopidy/core/__init__.py index 7fa7e299..720f9c38 100644 --- a/mopidy/core/__init__.py +++ b/mopidy/core/__init__.py @@ -5,6 +5,7 @@ from .actor import Core from .history import HistoryController from .library import LibraryController from .listener import CoreListener +from .mixer import MixerController from .playback import PlaybackController, PlaybackState from .playlists import PlaylistsController from .tracklist import TracklistController diff --git a/mopidy/core/actor.py b/mopidy/core/actor.py index cc1cdd9d..2f31c681 100644 --- a/mopidy/core/actor.py +++ b/mopidy/core/actor.py @@ -11,6 +11,7 @@ from mopidy.audio.utils import convert_tags_to_track from mopidy.core.history import HistoryController from mopidy.core.library import LibraryController from mopidy.core.listener import CoreListener +from mopidy.core.mixer import MixerController from mopidy.core.playback import PlaybackController from mopidy.core.playlists import PlaylistsController from mopidy.core.tracklist import TracklistController @@ -31,6 +32,10 @@ class Core( """The playback history controller. An instance of :class:`mopidy.core.HistoryController`.""" + mixer = None + """The mixer controller. An instance of + :class:`mopidy.core.MixerController`.""" + playback = None """The playback controller. An instance of :class:`mopidy.core.PlaybackController`.""" @@ -49,15 +54,10 @@ class Core( self.backends = Backends(backends) self.library = LibraryController(backends=self.backends, core=self) - self.history = HistoryController() - - self.playback = PlaybackController( - mixer=mixer, backends=self.backends, core=self) - - self.playlists = PlaylistsController( - backends=self.backends, core=self) - + self.mixer = MixerController(mixer=mixer) + self.playback = PlaybackController(backends=self.backends, core=self) + self.playlists = PlaylistsController(backends=self.backends, core=self) self.tracklist = TracklistController(core=self) self.audio = audio diff --git a/mopidy/core/mixer.py b/mopidy/core/mixer.py new file mode 100644 index 00000000..e6856f17 --- /dev/null +++ b/mopidy/core/mixer.py @@ -0,0 +1,64 @@ +from __future__ import absolute_import, unicode_literals + +import logging + + +logger = logging.getLogger(__name__) + + +class MixerController(object): + pykka_traversable = True + + def __init__(self, mixer): + self._mixer = mixer + self._volume = None + self._mute = False + + def get_volume(self): + """Get the volume. + + Integer in range [0..100] or :class:`None` if unknown. + + The volume scale is linear. + """ + if self._mixer: + return self._mixer.get_volume().get() + else: + # For testing + return self._volume + + def set_volume(self, volume): + """Set the volume. + + The volume is defined as an integer in range [0..100]. + + The volume scale is linear. + """ + if self._mixer: + self._mixer.set_volume(volume) + else: + # For testing + self._volume = volume + + def get_mute(self): + """Get mute state. + + :class:`True` if muted, :class:`False` otherwise. + """ + if self._mixer: + return self._mixer.get_mute().get() + else: + # For testing + return self._mute + + def set_mute(self, mute): + """Set mute state. + + :class:`True` to mute, :class:`False` to unmute. + """ + mute = bool(mute) + if self._mixer: + self._mixer.set_mute(mute) + else: + # For testing + self._mute = mute diff --git a/mopidy/core/playback.py b/mopidy/core/playback.py index fc273965..d4cdce0d 100644 --- a/mopidy/core/playback.py +++ b/mopidy/core/playback.py @@ -2,6 +2,7 @@ from __future__ import absolute_import, unicode_literals import logging import urlparse +import warnings from mopidy.audio import PlaybackState from mopidy.core import listener @@ -11,20 +12,16 @@ from mopidy.utils.deprecation import deprecated_property logger = logging.getLogger(__name__) -# TODO: split mixing out from playback? class PlaybackController(object): pykka_traversable = True - def __init__(self, mixer, backends, core): - self.mixer = mixer + def __init__(self, backends, core): self.backends = backends self.core = core self._current_tl_track = None self._current_metadata_track = None self._state = PlaybackState.STOPPED - self._volume = None - self._mute = False def _get_backend(self): # TODO: take in track instead @@ -139,64 +136,59 @@ class PlaybackController(object): """ def get_volume(self): - """Get the volume. - - Integer in range [0..100] or :class:`None` if unknown. - - The volume scale is linear. """ - if self.mixer: - return self.mixer.get_volume().get() - else: - # For testing - return self._volume + ... deprecated:: 0.20 + Use :meth:`core.mixer.get_volume() + ` instead. + """ + warnings.warn( + 'playback.get_volume() is deprecated', DeprecationWarning) + return self.core.mixer.get_volume() def set_volume(self, volume): - """Set the volume. - - The volume is defined as an integer in range [0..100]. - - The volume scale is linear. """ - if self.mixer: - self.mixer.set_volume(volume) - else: - # For testing - self._volume = volume + ... deprecated:: 0.20 + Use :meth:`core.mixer.set_volume() + ` instead. + """ + warnings.warn( + 'playback.set_volume() is deprecated', DeprecationWarning) + return self.core.mixer.set_volume(volume) volume = deprecated_property(get_volume, set_volume) """ .. deprecated:: 0.20 - Use :meth:`get_volume` and :meth:`set_volume` instead. + Use :meth:`core.mixer.get_volume() + ` and + :meth:`core.mixer.set_volume() + ` instead. """ def get_mute(self): - """Get mute state. - - :class:`True` if muted, :class:`False` otherwise. """ - if self.mixer: - return self.mixer.get_mute().get() - else: - # For testing - return self._mute - - def set_mute(self, value): - """Set mute state. - - :class:`True` to mute, :class:`False` to unmute. + ... deprecated:: 0.20 + Use :meth:`core.mixer.get_mute() + ` instead. """ - value = bool(value) - if self.mixer: - self.mixer.set_mute(value) - else: - # For testing - self._mute = value + warnings.warn('playback.get_mute() is deprecated', DeprecationWarning) + return self.core.mixer.get_mute() + + def set_mute(self, mute): + """ + ... deprecated:: 0.20 + Use :meth:`core.mixer.set_mute() + ` instead. + """ + warnings.warn('playback.set_mute() is deprecated', DeprecationWarning) + return self.core.mixer.set_mute(mute) mute = deprecated_property(get_mute, set_mute) """ .. deprecated:: 0.20 - Use :meth:`get_mute` and :meth:`set_mute` instead. + Use :meth:`core.mixer.get_mute() + ` and + :meth:`core.mixer.set_mute() + ` instead. """ # Methods diff --git a/mopidy/http/handlers.py b/mopidy/http/handlers.py index 721e419c..52bd8217 100644 --- a/mopidy/http/handlers.py +++ b/mopidy/http/handlers.py @@ -43,6 +43,7 @@ def make_jsonrpc_wrapper(core_actor): 'core.get_version': core.Core.get_version, 'core.history': core.HistoryController, 'core.library': core.LibraryController, + 'core.mixer': core.MixerController, 'core.playback': core.PlaybackController, 'core.playlists': core.PlaylistsController, 'core.tracklist': core.TracklistController, @@ -54,6 +55,7 @@ def make_jsonrpc_wrapper(core_actor): 'core.get_version': core_actor.get_version, 'core.history': core_actor.history, 'core.library': core_actor.library, + 'core.mixer': core_actor.mixer, 'core.playback': core_actor.playback, 'core.playlists': core_actor.playlists, 'core.tracklist': core_actor.tracklist, diff --git a/tests/core/test_mixer.py b/tests/core/test_mixer.py new file mode 100644 index 00000000..e3fa6be6 --- /dev/null +++ b/tests/core/test_mixer.py @@ -0,0 +1,28 @@ +from __future__ import absolute_import, unicode_literals + +import unittest + +from mopidy import core + + +class CoreMixerTest(unittest.TestCase): + def setUp(self): # noqa: N802 + self.core = core.Core(mixer=None, backends=[]) + + def test_volume(self): + self.assertEqual(self.core.mixer.get_volume(), None) + + self.core.mixer.set_volume(30) + + self.assertEqual(self.core.mixer.get_volume(), 30) + + self.core.mixer.set_volume(70) + + self.assertEqual(self.core.mixer.get_volume(), 70) + + def test_mute(self): + self.assertEqual(self.core.mixer.get_mute(), False) + + self.core.mixer.set_mute(True) + + self.assertEqual(self.core.mixer.get_mute(), True) diff --git a/tests/core/test_playback.py b/tests/core/test_playback.py index b9d19966..40741e23 100644 --- a/tests/core/test_playback.py +++ b/tests/core/test_playback.py @@ -426,21 +426,3 @@ class CorePlaybackTest(unittest.TestCase): self.assertFalse(self.playback2.get_time_position.called) # TODO Test on_tracklist_change - - def test_volume(self): - self.assertEqual(self.core.playback.volume, None) - - self.core.playback.volume = 30 - - self.assertEqual(self.core.playback.volume, 30) - - self.core.playback.volume = 70 - - self.assertEqual(self.core.playback.volume, 70) - - def test_mute(self): - self.assertEqual(self.core.playback.mute, False) - - self.core.playback.mute = True - - self.assertEqual(self.core.playback.mute, True)