Add mixer API and rewrite ALSA mixer hack
This commit is contained in:
parent
0af7c52544
commit
523216d0fd
8
docs/api/mixers.rst
Normal file
8
docs/api/mixers.rst
Normal file
@ -0,0 +1,8 @@
|
||||
********************************
|
||||
:mod:`mopidy.mixer` -- Mixer API
|
||||
********************************
|
||||
|
||||
.. automodule:: mopidy.mixers
|
||||
:synopsis: Sound mixer interface.
|
||||
:members:
|
||||
:undoc-members:
|
||||
@ -160,6 +160,11 @@ libspotify backend, copy the Spotify application key to
|
||||
|
||||
BACKENDS = (u'mopidy.backends.libspotify.LibspotifyBackend',)
|
||||
|
||||
*OS X:* The default mixer does not work on OS X, so you must change to a dummy
|
||||
mixer::
|
||||
|
||||
MIXER = u'mopidy.mixers.dummy.DummyMixer'
|
||||
|
||||
For a full list of available settings, see :mod:`mopidy.settings.default`.
|
||||
|
||||
|
||||
|
||||
@ -14,7 +14,8 @@ logger = logging.getLogger('mopidy')
|
||||
|
||||
def main():
|
||||
_setup_logging(2)
|
||||
backend = _get_backend(settings.BACKENDS[0])
|
||||
mixer = _get_class(settings.MIXER)()
|
||||
backend = _get_class(settings.BACKENDS[0])(mixer=mixer)
|
||||
MpdServer(backend=backend)
|
||||
asyncore.loop()
|
||||
|
||||
@ -30,14 +31,13 @@ def _setup_logging(verbosity_level):
|
||||
level=level,
|
||||
)
|
||||
|
||||
def _get_backend(name):
|
||||
def _get_class(name):
|
||||
module_name = name[:name.rindex('.')]
|
||||
class_name = name[name.rindex('.') + 1:]
|
||||
logger.info('Loading: %s from %s', class_name, module_name)
|
||||
module = __import__(module_name, globals(), locals(), [class_name], -1)
|
||||
class_object = getattr(module, class_name)
|
||||
instance = class_object()
|
||||
return instance
|
||||
return class_object
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
|
||||
@ -3,13 +3,14 @@ import logging
|
||||
import random
|
||||
import time
|
||||
|
||||
import alsaaudio
|
||||
|
||||
from mopidy.models import Playlist
|
||||
|
||||
logger = logging.getLogger('backends.base')
|
||||
|
||||
class BaseBackend(object):
|
||||
def __init__(self, mixer=None):
|
||||
self.mixer = mixer
|
||||
|
||||
#: The current playlist controller. An instance of
|
||||
#: :class:`BaseCurrentPlaylistController`.
|
||||
current_playlist = None
|
||||
@ -17,6 +18,9 @@ class BaseBackend(object):
|
||||
#: The library controller. An instance of :class:`BaseLibraryController`.
|
||||
library = None
|
||||
|
||||
#: The sound mixer. An instance of :class:`mopidy.mixers.BaseMixer`.
|
||||
mixer = None
|
||||
|
||||
#: The playback controller. An instance of :class:`BasePlaybackController`.
|
||||
playback = None
|
||||
|
||||
@ -255,10 +259,9 @@ class BasePlaybackController(object):
|
||||
#: Playback continues after current song.
|
||||
single = False
|
||||
|
||||
def __init__(self, backend, mixer=alsaaudio.Mixer):
|
||||
def __init__(self, backend):
|
||||
self.backend = backend
|
||||
self._state = self.STOPPED
|
||||
self._mixer = mixer()
|
||||
|
||||
@property
|
||||
def next_track(self):
|
||||
@ -370,11 +373,11 @@ class BasePlaybackController(object):
|
||||
|
||||
:class:`None` if unknown.
|
||||
"""
|
||||
return self._mixer.getvolume()[0]
|
||||
return self.backend.mixer.volume
|
||||
|
||||
@volume.setter
|
||||
def volume(self, volume):
|
||||
self._mixer.setvolume(volume)
|
||||
self.backend.mixer.volume = volume
|
||||
|
||||
def new_playlist_loaded_callback(self):
|
||||
"""Tell the playback controller that a new playlist has been loaded."""
|
||||
|
||||
@ -14,7 +14,8 @@ logger = logging.getLogger(u'backends.despotify')
|
||||
ENCODING = 'utf-8'
|
||||
|
||||
class DespotifyBackend(BaseBackend):
|
||||
def __init__(self):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DespotifyBackend, self).__init__(*args, **kwargs)
|
||||
self.current_playlist = DespotifyCurrentPlaylistController(backend=self)
|
||||
self.library = DespotifyLibraryController(backend=self)
|
||||
self.playback = DespotifyPlaybackController(backend=self)
|
||||
|
||||
@ -4,10 +4,11 @@ from mopidy.backends import (BaseBackend, BaseCurrentPlaylistController,
|
||||
from mopidy.models import Playlist
|
||||
|
||||
class DummyBackend(BaseBackend):
|
||||
def __init__(self):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DummyBackend, self).__init__(*args, **kwargs)
|
||||
self.current_playlist = DummyCurrentPlaylistController(backend=self)
|
||||
self.library = DummyLibraryController(backend=self)
|
||||
self.playback = DummyPlaybackController(backend=self, mixer=DummyMixer)
|
||||
self.playback = DummyPlaybackController(backend=self)
|
||||
self.stored_playlists = DummyStoredPlaylistsController(backend=self)
|
||||
self.uri_handlers = [u'dummy:']
|
||||
|
||||
@ -46,12 +47,3 @@ class DummyPlaybackController(BasePlaybackController):
|
||||
class DummyStoredPlaylistsController(BaseStoredPlaylistsController):
|
||||
def search(self, query):
|
||||
return [Playlist(name=query)]
|
||||
|
||||
class DummyMixer(object):
|
||||
volume = 0
|
||||
|
||||
def getvolume(self):
|
||||
return [self.volume, self.volume]
|
||||
|
||||
def setvolume(self, volume):
|
||||
self.volume = volume
|
||||
|
||||
@ -17,7 +17,8 @@ logger = logging.getLogger(u'backends.libspotify')
|
||||
ENCODING = 'utf-8'
|
||||
|
||||
class LibspotifyBackend(BaseBackend):
|
||||
def __init__(self):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(LibspotifyBackend, self).__init__(*args, **kwargs)
|
||||
self.current_playlist = LibspotifyCurrentPlaylistController(
|
||||
backend=self)
|
||||
self.library = LibspotifyLibraryController(backend=self)
|
||||
|
||||
35
mopidy/mixers/__init__.py
Normal file
35
mopidy/mixers/__init__.py
Normal file
@ -0,0 +1,35 @@
|
||||
class BaseMixer(object):
|
||||
@property
|
||||
def volume(self):
|
||||
"""
|
||||
The audio volume
|
||||
|
||||
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()
|
||||
|
||||
@volume.setter
|
||||
def volume(self, volume):
|
||||
volume = int(volume)
|
||||
if volume < 0:
|
||||
volume = 0
|
||||
elif volume > 100:
|
||||
volume = 100
|
||||
self._set_volume(volume)
|
||||
|
||||
def _get_volume(self):
|
||||
"""
|
||||
Return volume as integer in range [0, 100]. :class:`None` if unknown.
|
||||
|
||||
*Must be implemented by subclass.*
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _set_volume(self, volume):
|
||||
"""
|
||||
Set volume as integer in range [0, 100].
|
||||
|
||||
*Must be implemented by subclass.*
|
||||
"""
|
||||
raise NotImplementedError
|
||||
13
mopidy/mixers/alsa.py
Normal file
13
mopidy/mixers/alsa.py
Normal file
@ -0,0 +1,13 @@
|
||||
import alsaaudio
|
||||
|
||||
from mopidy.mixers import BaseMixer
|
||||
|
||||
class AlsaMixer(BaseMixer):
|
||||
def __init__(self):
|
||||
self._mixer = alsaaudio.Mixer()
|
||||
|
||||
def _get_volume(self):
|
||||
return self._mixer.getvolume()[0]
|
||||
|
||||
def _set_volume(self, volume):
|
||||
self._mixer.setvolume(volume)
|
||||
11
mopidy/mixers/dummy.py
Normal file
11
mopidy/mixers/dummy.py
Normal file
@ -0,0 +1,11 @@
|
||||
from mopidy.mixers import BaseMixer
|
||||
|
||||
class DummyMixer(BaseMixer):
|
||||
def __init__(self):
|
||||
self._volume = None
|
||||
|
||||
def _get_volume(self):
|
||||
return self._volume
|
||||
|
||||
def _set_volume(self, volume):
|
||||
self._volume = volume
|
||||
@ -13,7 +13,6 @@ Available settings and their default values.
|
||||
#:
|
||||
#: .. note::
|
||||
#: Currently only the first backend in the list is used.
|
||||
#:
|
||||
BACKENDS = (
|
||||
u'mopidy.backends.despotify.DespotifyBackend',
|
||||
#u'mopidy.backends.libspotify.LibspotifyBackend',
|
||||
@ -24,6 +23,11 @@ BACKENDS = (
|
||||
#: the format.
|
||||
CONSOLE_LOG_FORMAT = u'%(levelname)-8s %(asctime)s [%(threadName)s] %(name)s\n %(message)s'
|
||||
|
||||
#: Sound mixer to use. Default::
|
||||
#:
|
||||
#: MIXER = u'mopidy.mixers.alsa.AlsaMixer'
|
||||
MIXER = u'mopidy.mixers.alsa.AlsaMixer'
|
||||
|
||||
#: Which address Mopidy should bind to. Examples:
|
||||
#:
|
||||
#: ``localhost``
|
||||
|
||||
@ -9,6 +9,7 @@ def main():
|
||||
sys.path.insert(0,
|
||||
os.path.abspath(os.path.join(os.path.dirname(__file__), '../')))
|
||||
r = CoverageTestRunner()
|
||||
r.add_pair('mopidy/mixers/dummy.py', 'tests/mixers/dummytest.py')
|
||||
r.add_pair('mopidy/models.py', 'tests/modelstest.py')
|
||||
r.add_pair('mopidy/mpd/handler.py', 'tests/mpd/handlertest.py')
|
||||
r.run()
|
||||
|
||||
26
tests/mixers/dummytest.py
Normal file
26
tests/mixers/dummytest.py
Normal file
@ -0,0 +1,26 @@
|
||||
import unittest
|
||||
|
||||
from mopidy.mixers.dummy import DummyMixer
|
||||
|
||||
class BaseMixerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.m = DummyMixer()
|
||||
|
||||
def test_volume_is_None_initially(self):
|
||||
self.assertEqual(self.m.volume, None)
|
||||
|
||||
def test_volume_set_to_min(self):
|
||||
self.m.volume = 0
|
||||
self.assertEqual(self.m.volume, 0)
|
||||
|
||||
def test_volume_set_to_max(self):
|
||||
self.m.volume = 100
|
||||
self.assertEqual(self.m.volume, 100)
|
||||
|
||||
def test_volume_set_to_below_min_results_in_min(self):
|
||||
self.m.volume = -10
|
||||
self.assertEqual(self.m.volume, 0)
|
||||
|
||||
def test_volume_set_to_above_max_results_in_max(self):
|
||||
self.m.volume = 110
|
||||
self.assertEqual(self.m.volume, 100)
|
||||
@ -3,6 +3,7 @@ import unittest
|
||||
|
||||
from mopidy.backends.dummy import DummyBackend
|
||||
from mopidy.exceptions import MpdAckError
|
||||
from mopidy.mixers.dummy import DummyMixer
|
||||
from mopidy.models import Track, Playlist
|
||||
from mopidy.mpd import handler
|
||||
|
||||
@ -19,7 +20,9 @@ class DummySession(object):
|
||||
|
||||
class RequestHandlerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.h = handler.MpdHandler(backend=DummyBackend())
|
||||
self.m = DummyMixer()
|
||||
self.b = DummyBackend(mixer=self.m)
|
||||
self.h = handler.MpdHandler(backend=self.b)
|
||||
|
||||
def test_register_same_pattern_twice_fails(self):
|
||||
func = lambda: None
|
||||
@ -46,7 +49,9 @@ class RequestHandlerTest(unittest.TestCase):
|
||||
|
||||
class CommandListsTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.h = handler.MpdHandler(backend=DummyBackend())
|
||||
self.m = DummyMixer()
|
||||
self.b = DummyBackend(mixer=self.m)
|
||||
self.h = handler.MpdHandler(backend=self.b)
|
||||
|
||||
def test_command_list_begin(self):
|
||||
result = self.h.handle_request(u'command_list_begin')
|
||||
@ -92,7 +97,8 @@ class CommandListsTest(unittest.TestCase):
|
||||
|
||||
class StatusHandlerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.b = DummyBackend()
|
||||
self.m = DummyMixer()
|
||||
self.b = DummyBackend(mixer=self.m)
|
||||
self.s = DummySession()
|
||||
self.h = handler.MpdHandler(backend=self.b, session=self.s)
|
||||
|
||||
@ -158,7 +164,6 @@ class StatusHandlerTest(unittest.TestCase):
|
||||
self.assert_(u'OK' in result)
|
||||
|
||||
def test_status_method_contains_volume_which_defaults_to_0(self):
|
||||
self.b.playback.volume = None
|
||||
result = dict(self.h._status_status())
|
||||
self.assert_('volume' in result)
|
||||
self.assertEquals(int(result['volume']), 0)
|
||||
@ -302,7 +307,8 @@ class StatusHandlerTest(unittest.TestCase):
|
||||
|
||||
class PlaybackOptionsHandlerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.b = DummyBackend()
|
||||
self.m = DummyMixer()
|
||||
self.b = DummyBackend(mixer=self.m)
|
||||
self.h = handler.MpdHandler(backend=self.b)
|
||||
|
||||
def test_consume_off(self):
|
||||
@ -421,7 +427,8 @@ class PlaybackOptionsHandlerTest(unittest.TestCase):
|
||||
|
||||
class PlaybackControlHandlerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.b = DummyBackend()
|
||||
self.m = DummyMixer()
|
||||
self.b = DummyBackend(mixer=self.m)
|
||||
self.h = handler.MpdHandler(backend=self.b)
|
||||
|
||||
def test_next(self):
|
||||
@ -499,7 +506,8 @@ class PlaybackControlHandlerTest(unittest.TestCase):
|
||||
|
||||
class CurrentPlaylistHandlerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.b = DummyBackend()
|
||||
self.m = DummyMixer()
|
||||
self.b = DummyBackend(mixer=self.m)
|
||||
self.h = handler.MpdHandler(backend=self.b)
|
||||
|
||||
def test_add(self):
|
||||
@ -791,7 +799,8 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
|
||||
|
||||
class StoredPlaylistsHandlerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.b = DummyBackend()
|
||||
self.m = DummyMixer()
|
||||
self.b = DummyBackend(mixer=self.m)
|
||||
self.h = handler.MpdHandler(backend=self.b)
|
||||
|
||||
def test_listplaylist(self):
|
||||
@ -863,7 +872,8 @@ class StoredPlaylistsHandlerTest(unittest.TestCase):
|
||||
|
||||
class MusicDatabaseHandlerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.b = DummyBackend()
|
||||
self.m = DummyMixer()
|
||||
self.b = DummyBackend(mixer=self.m)
|
||||
self.h = handler.MpdHandler(backend=self.b)
|
||||
|
||||
def test_count(self):
|
||||
@ -1018,7 +1028,9 @@ class MusicDatabaseHandlerTest(unittest.TestCase):
|
||||
|
||||
class StickersHandlerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.h = handler.MpdHandler(backend=DummyBackend())
|
||||
self.m = DummyMixer()
|
||||
self.b = DummyBackend(mixer=self.m)
|
||||
self.h = handler.MpdHandler(backend=self.b)
|
||||
|
||||
def test_sticker_get(self):
|
||||
result = self.h.handle_request(
|
||||
@ -1053,8 +1065,10 @@ class StickersHandlerTest(unittest.TestCase):
|
||||
|
||||
class ConnectionHandlerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.h = handler.MpdHandler(session=DummySession(),
|
||||
backend=DummyBackend())
|
||||
self.m = DummyMixer()
|
||||
self.b = DummyBackend(mixer=self.m)
|
||||
self.s = DummySession()
|
||||
self.h = handler.MpdHandler(backend=self.b, session=self.s)
|
||||
|
||||
def test_close(self):
|
||||
result = self.h.handle_request(u'close')
|
||||
@ -1079,7 +1093,9 @@ class ConnectionHandlerTest(unittest.TestCase):
|
||||
|
||||
class AudioOutputHandlerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.h = handler.MpdHandler(backend=DummyBackend())
|
||||
self.m = DummyMixer()
|
||||
self.b = DummyBackend(mixer=self.m)
|
||||
self.h = handler.MpdHandler(backend=self.b)
|
||||
|
||||
def test_enableoutput(self):
|
||||
result = self.h.handle_request(u'enableoutput "0"')
|
||||
@ -1099,7 +1115,9 @@ class AudioOutputHandlerTest(unittest.TestCase):
|
||||
|
||||
class ReflectionHandlerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.h = handler.MpdHandler(backend=DummyBackend())
|
||||
self.m = DummyMixer()
|
||||
self.b = DummyBackend(mixer=self.m)
|
||||
self.h = handler.MpdHandler(backend=self.b)
|
||||
|
||||
def test_commands(self):
|
||||
result = self.h.handle_request(u'commands')
|
||||
|
||||
Loading…
Reference in New Issue
Block a user