Merge pull request #196 from jodal/feature/audio-module

Add mopidy.audio module
This commit is contained in:
Thomas Adamcik 2012-09-13 16:07:41 -07:00
commit 8802ea56f1
15 changed files with 85 additions and 90 deletions

7
docs/modules/audio.rst Normal file
View File

@ -0,0 +1,7 @@
*************************************
:mod:`mopidy.audio` -- Audio playback
*************************************
.. automodule:: mopidy.audio
:synopsis: Audio playback through GStreamer
:members:

View File

@ -1,7 +0,0 @@
********************************************
:mod:`mopidy.gstreamer` -- GStreamer adapter
********************************************
.. automodule:: mopidy.gstreamer
:synopsis: GStreamer adapter
:members:

View File

@ -30,7 +30,7 @@ sys.path.insert(0,
from mopidy import (get_version, settings, OptionalDependencyError,
SettingsError, DATA_PATH, SETTINGS_PATH, SETTINGS_FILE)
from mopidy.gstreamer import GStreamer
from mopidy.audio import Audio
from mopidy.utils import get_class
from mopidy.utils.deps import list_deps_optparse_callback
from mopidy.utils.log import setup_logging
@ -51,7 +51,7 @@ def main():
setup_logging(options.verbosity_level, options.save_debug_log)
check_old_folders()
setup_settings(options.interactive)
setup_gstreamer()
setup_audio()
setup_backend()
setup_frontends()
loop.run()
@ -65,7 +65,7 @@ def main():
loop.quit()
stop_frontends()
stop_backend()
stop_gstreamer()
stop_audio()
stop_remaining_actors()
@ -117,12 +117,12 @@ def setup_settings(interactive):
sys.exit(1)
def setup_gstreamer():
GStreamer.start()
def setup_audio():
Audio.start()
def stop_gstreamer():
stop_actors_by_class(GStreamer)
def stop_audio():
stop_actors_by_class(Audio)
def setup_backend():
get_class(settings.BACKENDS[0]).start()

View File

@ -13,12 +13,12 @@ from mopidy.backends.base import Backend
from mopidy.utils import process
# Trigger install of gst mixer plugins
from mopidy import mixers
from mopidy.audio import mixers
logger = logging.getLogger('mopidy.gstreamer')
logger = logging.getLogger('mopidy.audio')
class GStreamer(ThreadingActor):
class Audio(ThreadingActor):
"""
Audio output through `GStreamer <http://gstreamer.freedesktop.org/>`_.
@ -31,7 +31,7 @@ class GStreamer(ThreadingActor):
"""
def __init__(self):
super(GStreamer, self).__init__()
super(Audio, self).__init__()
self._default_caps = gst.Caps("""
audio/x-raw-int,

View File

@ -38,6 +38,6 @@ def create_track(label, initial_volume, min_volume, max_volume,
#
# Keep these imports at the bottom of the file to avoid cyclic import problems
# when mixers use the above code.
from mopidy.mixers.auto import AutoAudioMixer
from mopidy.mixers.fake import FakeMixer
from mopidy.mixers.nad import NadMixer
from .auto import AutoAudioMixer
from .fake import FakeMixer
from .nad import NadMixer

View File

@ -5,7 +5,7 @@ import gst
import logging
logger = logging.getLogger('mopidy.mixers.auto')
logger = logging.getLogger('mopidy.audio.mixers.auto')
# TODO: we might want to add some ranking to the mixers we know about?

View File

@ -3,7 +3,7 @@ pygst.require('0.10')
import gobject
import gst
from mopidy.mixers import create_track
from mopidy.audio.mixers import create_track
class FakeMixer(gst.Element, gst.ImplementsInterface, gst.interfaces.Mixer):

View File

@ -12,10 +12,10 @@ except ImportError:
from pykka.actor import ThreadingActor
from mopidy.mixers import create_track
from mopidy.audio.mixers import create_track
logger = logging.getLogger('mopidy.mixers.nad')
logger = logging.getLogger('mopidy.audio.mixers.nad')
class NadMixer(gst.Element, gst.ImplementsInterface, gst.interfaces.Mixer):

View File

@ -7,10 +7,9 @@ import shutil
from pykka.actor import ThreadingActor
from pykka.registry import ActorRegistry
from mopidy import core, settings, DATA_PATH
from mopidy import audio, core, settings, DATA_PATH
from mopidy.backends import base
from mopidy.models import Playlist, Track, Album
from mopidy.gstreamer import GStreamer
from .translator import parse_m3u, parse_mpd_tag_cache
@ -58,13 +57,13 @@ class LocalBackend(ThreadingActor, base.Backend):
self.uri_schemes = [u'file']
self.gstreamer = None
self.audio = None
def on_start(self):
gstreamer_refs = ActorRegistry.get_by_class(GStreamer)
assert len(gstreamer_refs) == 1, \
'Expected exactly one running GStreamer.'
self.gstreamer = gstreamer_refs[0].proxy()
audio_refs = ActorRegistry.get_by_class(audio.Audio)
assert len(audio_refs) == 1, \
'Expected exactly one running Audio instance.'
self.audio = audio_refs[0].proxy()
class LocalPlaybackController(core.PlaybackController):
@ -76,32 +75,32 @@ class LocalPlaybackController(core.PlaybackController):
@property
def time_position(self):
return self.backend.gstreamer.get_position().get()
return self.backend.audio.get_position().get()
class LocalPlaybackProvider(base.BasePlaybackProvider):
def pause(self):
return self.backend.gstreamer.pause_playback().get()
return self.backend.audio.pause_playback().get()
def play(self, track):
self.backend.gstreamer.prepare_change()
self.backend.gstreamer.set_uri(track.uri).get()
return self.backend.gstreamer.start_playback().get()
self.backend.audio.prepare_change()
self.backend.audio.set_uri(track.uri).get()
return self.backend.audio.start_playback().get()
def resume(self):
return self.backend.gstreamer.start_playback().get()
return self.backend.audio.start_playback().get()
def seek(self, time_position):
return self.backend.gstreamer.set_position(time_position).get()
return self.backend.audio.set_position(time_position).get()
def stop(self):
return self.backend.gstreamer.stop_playback().get()
return self.backend.audio.stop_playback().get()
def get_volume(self):
return self.backend.gstreamer.get_volume().get()
return self.backend.audio.get_volume().get()
def set_volume(self, volume):
self.backend.gstreamer.set_volume(volume).get()
self.backend.audio.set_volume(volume).get()
class LocalStoredPlaylistsProvider(base.BaseStoredPlaylistsProvider):

View File

@ -3,9 +3,8 @@ import logging
from pykka.actor import ThreadingActor
from pykka.registry import ActorRegistry
from mopidy import core, settings
from mopidy import audio, core, settings
from mopidy.backends import base
from mopidy.gstreamer import GStreamer
logger = logging.getLogger('mopidy.backends.spotify')
@ -67,7 +66,7 @@ class SpotifyBackend(ThreadingActor, base.Backend):
self.uri_schemes = [u'spotify']
self.gstreamer = None
self.audio = None
self.spotify = None
# Fail early if settings are not present
@ -75,10 +74,10 @@ class SpotifyBackend(ThreadingActor, base.Backend):
self.password = settings.SPOTIFY_PASSWORD
def on_start(self):
gstreamer_refs = ActorRegistry.get_by_class(GStreamer)
assert len(gstreamer_refs) == 1, \
'Expected exactly one running GStreamer.'
self.gstreamer = gstreamer_refs[0].proxy()
audio_refs = ActorRegistry.get_by_class(audio.Audio)
assert len(audio_refs) == 1, \
'Expected exactly one running Audio instance.'
self.audio = audio_refs[0].proxy()
logger.info(u'Mopidy uses SPOTIFY(R) CORE')
self.spotify = self._connect()

View File

@ -8,7 +8,7 @@ logger = logging.getLogger('mopidy.backends.spotify.playback')
class SpotifyPlaybackProvider(BasePlaybackProvider):
def pause(self):
return self.backend.gstreamer.pause_playback()
return self.backend.audio.pause_playback()
def play(self, track):
if self.backend.playback.state == self.backend.playback.PLAYING:
@ -19,10 +19,10 @@ class SpotifyPlaybackProvider(BasePlaybackProvider):
self.backend.spotify.session.load(
Link.from_string(track.uri).as_track())
self.backend.spotify.session.play(1)
self.backend.gstreamer.prepare_change()
self.backend.gstreamer.set_uri('appsrc://')
self.backend.gstreamer.start_playback()
self.backend.gstreamer.set_metadata(track)
self.backend.audio.prepare_change()
self.backend.audio.set_uri('appsrc://')
self.backend.audio.start_playback()
self.backend.audio.set_metadata(track)
return True
except SpotifyError as e:
logger.info('Playback of %s failed: %s', track.uri, e)
@ -32,18 +32,18 @@ class SpotifyPlaybackProvider(BasePlaybackProvider):
return self.seek(self.backend.playback.time_position)
def seek(self, time_position):
self.backend.gstreamer.prepare_change()
self.backend.audio.prepare_change()
self.backend.spotify.session.seek(time_position)
self.backend.gstreamer.start_playback()
self.backend.audio.start_playback()
return True
def stop(self):
result = self.backend.gstreamer.stop_playback()
result = self.backend.audio.stop_playback()
self.backend.spotify.session.play(0)
return result
def get_volume(self):
return self.backend.gstreamer.get_volume().get()
return self.backend.audio.get_volume().get()
def set_volume(self, volume):
self.backend.gstreamer.set_volume(volume)
self.backend.audio.set_volume(volume)

View File

@ -6,14 +6,13 @@ from spotify.manager import SpotifySessionManager as PyspotifySessionManager
from pykka.registry import ActorRegistry
from mopidy import get_version, settings, CACHE_PATH
from mopidy import audio, get_version, settings, CACHE_PATH
from mopidy.backends.base import Backend
from mopidy.backends.spotify import BITRATES
from mopidy.backends.spotify.container_manager import SpotifyContainerManager
from mopidy.backends.spotify.playlist_manager import SpotifyPlaylistManager
from mopidy.backends.spotify.translator import SpotifyTranslator
from mopidy.models import Playlist
from mopidy.gstreamer import GStreamer
from mopidy.utils.process import BaseThread
logger = logging.getLogger('mopidy.backends.spotify.session_manager')
@ -34,7 +33,7 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager):
BaseThread.__init__(self)
self.name = 'SpotifyThread'
self.gstreamer = None
self.audio = None
self.backend = None
self.connected = threading.Event()
@ -50,10 +49,10 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager):
self.connect()
def setup(self):
gstreamer_refs = ActorRegistry.get_by_class(GStreamer)
assert len(gstreamer_refs) == 1, \
'Expected exactly one running gstreamer.'
self.gstreamer = gstreamer_refs[0].proxy()
audio_refs = ActorRegistry.get_by_class(audio.Audio)
assert len(audio_refs) == 1, \
'Expected exactly one running Audio instance.'
self.audio = audio_refs[0].proxy()
backend_refs = ActorRegistry.get_by_class(Backend)
assert len(backend_refs) == 1, 'Expected exactly one running backend.'
@ -117,7 +116,7 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager):
'sample_rate': sample_rate,
'channels': channels,
}
self.gstreamer.emit_data(capabilites, bytes(frames))
self.audio.emit_data(capabilites, bytes(frames))
return num_frames
def play_token_lost(self, session):
@ -143,7 +142,7 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager):
def end_of_track(self, session):
"""Callback used by pyspotify"""
logger.debug(u'End of data stream reached')
self.gstreamer.emit_end_of_stream()
self.audio.emit_end_of_stream()
def refresh_stored_playlists(self):
"""Refresh the stored playlists in the backend with fresh meta data

View File

@ -1,7 +1,6 @@
import sys
from mopidy import settings
from mopidy.gstreamer import GStreamer
from mopidy import audio, settings
from mopidy.utils.path import path_to_uri
from tests import unittest, path_to_data_dir
@ -9,38 +8,38 @@ from tests import unittest, path_to_data_dir
@unittest.skipIf(sys.platform == 'win32',
'Our Windows build server does not support GStreamer yet')
class GStreamerTest(unittest.TestCase):
class AudioTest(unittest.TestCase):
def setUp(self):
settings.MIXER = 'fakemixer track_max_volume=65536'
settings.OUTPUT = 'fakesink'
self.song_uri = path_to_uri(path_to_data_dir('song1.wav'))
self.gstreamer = GStreamer.start().proxy()
self.audio = audio.Audio.start().proxy()
def tearDown(self):
self.gstreamer.stop()
self.audio.stop()
settings.runtime.clear()
def prepare_uri(self, uri):
self.gstreamer.prepare_change()
self.gstreamer.set_uri(uri)
self.audio.prepare_change()
self.audio.set_uri(uri)
def test_start_playback_existing_file(self):
self.prepare_uri(self.song_uri)
self.assertTrue(self.gstreamer.start_playback().get())
self.assertTrue(self.audio.start_playback().get())
def test_start_playback_non_existing_file(self):
self.prepare_uri(self.song_uri + 'bogus')
self.assertFalse(self.gstreamer.start_playback().get())
self.assertFalse(self.audio.start_playback().get())
def test_pause_playback_while_playing(self):
self.prepare_uri(self.song_uri)
self.gstreamer.start_playback()
self.assertTrue(self.gstreamer.pause_playback().get())
self.audio.start_playback()
self.assertTrue(self.audio.pause_playback().get())
def test_stop_playback_while_playing(self):
self.prepare_uri(self.song_uri)
self.gstreamer.start_playback()
self.assertTrue(self.gstreamer.stop_playback().get())
self.audio.start_playback()
self.assertTrue(self.audio.stop_playback().get())
@unittest.SkipTest
def test_deliver_data(self):
@ -52,8 +51,8 @@ class GStreamerTest(unittest.TestCase):
def test_set_volume(self):
for value in range(0, 101):
self.assertTrue(self.gstreamer.set_volume(value).get())
self.assertEqual(value, self.gstreamer.get_volume().get())
self.assertTrue(self.audio.set_volume(value).get())
self.assertEqual(value, self.audio.get_volume().get())
@unittest.SkipTest
def test_set_state_encapsulation(self):
@ -66,4 +65,3 @@ class GStreamerTest(unittest.TestCase):
@unittest.SkipTest
def test_invalid_output_raises_error(self):
pass # TODO

View File

@ -1,8 +1,8 @@
import mock
import random
from mopidy import audio
from mopidy.models import CpTrack, Playlist, Track
from mopidy.gstreamer import GStreamer
from tests.backends.base import populate_playlist
@ -12,7 +12,7 @@ class CurrentPlaylistControllerTest(object):
def setUp(self):
self.backend = self.backend_class()
self.backend.gstreamer = mock.Mock(spec=GStreamer)
self.backend.audio = mock.Mock(spec=audio.Audio)
self.controller = self.backend.current_playlist
self.playback = self.backend.playback

View File

@ -2,8 +2,8 @@ import mock
import random
import time
from mopidy import audio
from mopidy.models import Track
from mopidy.gstreamer import GStreamer
from tests import unittest
from tests.backends.base import populate_playlist
@ -16,7 +16,7 @@ class PlaybackControllerTest(object):
def setUp(self):
self.backend = self.backend_class()
self.backend.gstreamer = mock.Mock(spec=GStreamer)
self.backend.audio = mock.Mock(spec=audio.Audio)
self.playback = self.backend.playback
self.current_playlist = self.backend.current_playlist
@ -729,7 +729,7 @@ class PlaybackControllerTest(object):
def test_time_position_when_stopped(self):
future = mock.Mock()
future.get = mock.Mock(return_value=0)
self.backend.gstreamer.get_position = mock.Mock(return_value=future)
self.backend.audio.get_position = mock.Mock(return_value=future)
self.assertEqual(self.playback.time_position, 0)
@ -737,7 +737,7 @@ class PlaybackControllerTest(object):
def test_time_position_when_stopped_with_playlist(self):
future = mock.Mock()
future.get = mock.Mock(return_value=0)
self.backend.gstreamer.get_position = mock.Mock(return_value=future)
self.backend.audio.get_position = mock.Mock(return_value=future)
self.assertEqual(self.playback.time_position, 0)