diff --git a/docs/changes.rst b/docs/changes.rst index d3ed3db2..12028a17 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -34,6 +34,7 @@ greatly improved MPD client support. ``SPOTIFY_LIB_APPKEY`` setting is thus removed. - Added new :mod:`mopidy.mixers.GStreamerSoftwareMixer` which now is the default mixer on all platforms. +- New setting ``MIXER_MAX_VOLUME`` for capping the maximum output volume. - MPD frontend: - Relocate from :mod:`mopidy.mpd` to :mod:`mopidy.frontends.mpd`. diff --git a/mopidy/mixers/__init__.py b/mopidy/mixers/__init__.py index 3ef1b645..c9543863 100644 --- a/mopidy/mixers/__init__.py +++ b/mopidy/mixers/__init__.py @@ -1,3 +1,5 @@ +from mopidy import settings + class BaseMixer(object): """ :param backend: a backend instance @@ -6,6 +8,7 @@ class BaseMixer(object): def __init__(self, backend, *args, **kwargs): self.backend = backend + self.amplification_factor = settings.MIXER_MAX_VOLUME / 100.0 @property def volume(self): @@ -15,11 +18,13 @@ class BaseMixer(object): Integer in range [0, 100]. :class:`None` if unknown. Values below 0 is equal to 0. Values above 100 is equal to 100. """ - return self._get_volume() + if self._get_volume() is None: + return None + return int(self._get_volume() / self.amplification_factor) @volume.setter def volume(self, volume): - volume = int(volume) + volume = int(int(volume) * self.amplification_factor) if volume < 0: volume = 0 elif volume > 100: diff --git a/mopidy/mixers/nad.py b/mopidy/mixers/nad.py index 56958005..d78863aa 100644 --- a/mopidy/mixers/nad.py +++ b/mopidy/mixers/nad.py @@ -22,10 +22,7 @@ class NadMixer(BaseMixer): currently used by this mixer. Sadly, this means that if you use the remote control to change the volume - on the amplifier, Mopidy will no longer report the correct volume. To - recalibrate the mixer, set the volume to 0 through Mopidy. This will reset - the amplifier to a known state, including powering on the device, selecting - the configured speakers and input sources. + on the amplifier, Mopidy will no longer report the correct volume. **Dependencies** @@ -51,8 +48,6 @@ class NadMixer(BaseMixer): def _set_volume(self, volume): self._volume = volume - if volume == 0: - self._pipe.send({'command': 'reset_device'}) self._pipe.send({'command': 'set_volume', 'volume': volume}) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index 2929007c..9ce9df15 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -167,15 +167,14 @@ class GStreamerProcess(BaseProcess): :type state_name: string :rtype: :class:`True` or :class:`False` """ - state = getattr(gst, 'STATE_' + state_name) - self.gst_pipeline.set_state(state) - new_state = self.gst_pipeline.get_state()[1] - if new_state == state: - logger.debug('Setting GStreamer state to %s: OK', state_name) - return True - else: + result = self.gst_pipeline.set_state( + getattr(gst, 'STATE_' + state_name)) + if result == gst.STATE_CHANGE_FAILURE: logger.warning('Setting GStreamer state to %s: failed', state_name) return False + else: + logger.debug('Setting GStreamer state to %s: OK', state_name) + return True def get_volume(self): """Get volume in range [0..100]""" diff --git a/mopidy/settings.py b/mopidy/settings.py index 232adda8..67b0c24f 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -115,6 +115,16 @@ MIXER_EXT_SPEAKERS_A = None #: Default: :class:`None`. MIXER_EXT_SPEAKERS_B = None +#: The maximum volume. Integer in the range 0 to 100. +#: +#: If this settings is set to 80, the mixer will set the actual volume to 80 +#: when asked to set it to 100. +#: +#: Default:: +#: +#: MIXER_MAX_VOLUME = 100 +MIXER_MAX_VOLUME = 100 + #: Audio output handler to use. #: #: Default:: diff --git a/tests/frontends/mpd/playback_test.py b/tests/frontends/mpd/playback_test.py index 42525d90..a1331bb3 100644 --- a/tests/frontends/mpd/playback_test.py +++ b/tests/frontends/mpd/playback_test.py @@ -271,18 +271,32 @@ class PlaybackControlHandlerTest(unittest.TestCase): self.assert_(u'OK' in result) def test_seek(self): - self.b.current_playlist.load([Track()]) + self.b.current_playlist.load([Track(length=40000)]) self.h.handle_request(u'seek "0"') result = self.h.handle_request(u'seek "0" "30"') self.assert_(u'OK' in result) - self.assert_(self.b.playback.time_position > 30000) + self.assert_(self.b.playback.time_position >= 30000) + + def test_seek_with_songpos(self): + seek_track = Track(uri='2', length=40000) + self.b.current_playlist.load( + [Track(uri='1', length=40000), seek_track]) + result = self.h.handle_request(u'seek "1" "30"') + self.assertEqual(self.b.playback.current_track, seek_track) def test_seekid(self): - self.b.current_playlist.load([Track()]) - result = self.h.handle_request(u'seekid "0" "30"') + self.b.current_playlist.load([Track(length=40000)]) + result = self.h.handle_request(u'seekid "1" "30"') self.assert_(u'OK' in result) - self.assert_(self.b.playback.time_position > 30000) + self.assert_(self.b.playback.time_position >= 30000) + def test_seekid_with_cpid(self): + seek_track = Track(uri='2', length=40000) + self.b.current_playlist.load( + [Track(length=40000), seek_track]) + result = self.h.handle_request(u'seekid "2" "30"') + self.assertEqual(self.b.playback.current_cpid, 2) + self.assertEqual(self.b.playback.current_track, seek_track) def test_stop(self): result = self.h.handle_request(u'stop') diff --git a/tests/mixers/base_test.py b/tests/mixers/base_test.py index 292edcda..d6129ad5 100644 --- a/tests/mixers/base_test.py +++ b/tests/mixers/base_test.py @@ -1,18 +1,16 @@ -import unittest - -from mopidy.mixers.dummy import DummyMixer - -class BaseMixerTest(unittest.TestCase): +class BaseMixerTest(object): MIN = 0 MAX = 100 - ACTUAL_MIN = MIN ACTUAL_MAX = MAX - INITIAL = None + mixer_class = None + def setUp(self): - self.mixer = DummyMixer(None) + assert self.mixer_class is not None, \ + "mixer_class must be set in subclass" + self.mixer = self.mixer_class(None) def test_initial_volume(self): self.assertEqual(self.mixer.volume, self.INITIAL) diff --git a/tests/mixers/denon_test.py b/tests/mixers/denon_test.py index e0f59705..5370f155 100644 --- a/tests/mixers/denon_test.py +++ b/tests/mixers/denon_test.py @@ -1,3 +1,5 @@ +import unittest + from mopidy.mixers.denon import DenonMixer from tests.mixers.base_test import BaseMixerTest @@ -22,11 +24,12 @@ class DenonMixerDeviceMock(object): def open(self): self._open = True -class DenonMixerTest(BaseMixerTest): +class DenonMixerTest(BaseMixerTest, unittest.TestCase): ACTUAL_MAX = 99 - INITIAL = 1 + mixer_class = DenonMixer + def setUp(self): self.device = DenonMixerDeviceMock() self.mixer = DenonMixer(None, device=self.device) diff --git a/tests/mixers/dummy_test.py b/tests/mixers/dummy_test.py new file mode 100644 index 00000000..334dc8a1 --- /dev/null +++ b/tests/mixers/dummy_test.py @@ -0,0 +1,17 @@ +import unittest + +from mopidy.mixers.dummy import DummyMixer +from tests.mixers.base_test import BaseMixerTest + +class DenonMixerTest(BaseMixerTest, unittest.TestCase): + mixer_class = DummyMixer + + def test_set_volume_is_capped(self): + self.mixer.amplification_factor = 0.5 + self.mixer.volume = 100 + self.assertEquals(self.mixer._volume, 50) + + def test_get_volume_does_not_show_that_the_volume_is_capped(self): + self.mixer.amplification_factor = 0.5 + self.mixer._volume = 50 + self.assertEquals(self.mixer.volume, 100)