Add mixer API and rewrite ALSA mixer hack

This commit is contained in:
Stein Magnus Jodal 2010-03-07 23:48:50 +01:00
parent 0af7c52544
commit 523216d0fd
14 changed files with 156 additions and 38 deletions

8
docs/api/mixers.rst Normal file
View File

@ -0,0 +1,8 @@
********************************
:mod:`mopidy.mixer` -- Mixer API
********************************
.. automodule:: mopidy.mixers
:synopsis: Sound mixer interface.
:members:
:undoc-members:

View File

@ -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`.

View File

@ -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:

View File

@ -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."""

View File

@ -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)

View File

@ -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

View File

@ -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
View 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
View 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
View 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

View File

@ -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``

View File

@ -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
View 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)

View File

@ -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')