Merge branch 'feature/modularised-output' into develop
This commit is contained in:
commit
0031239357
@ -12,7 +12,7 @@ from mopidy.backends.base import (Backend, CurrentPlaylistController,
|
||||
BasePlaybackProvider, StoredPlaylistsController,
|
||||
BaseStoredPlaylistsProvider)
|
||||
from mopidy.models import Playlist, Track, Album
|
||||
from mopidy.outputs.base import BaseOutput
|
||||
from mopidy.gstreamer import GStreamer
|
||||
|
||||
from .translator import parse_m3u, parse_mpd_tag_cache
|
||||
|
||||
@ -50,12 +50,12 @@ class LocalBackend(ThreadingActor, Backend):
|
||||
|
||||
self.uri_handlers = [u'file://']
|
||||
|
||||
self.output = None
|
||||
self.gstreamer = None
|
||||
|
||||
def on_start(self):
|
||||
output_refs = ActorRegistry.get_by_class(BaseOutput)
|
||||
assert len(output_refs) == 1, 'Expected exactly one running output.'
|
||||
self.output = output_refs[0].proxy()
|
||||
gstreamer_refs = ActorRegistry.get_by_class(GStreamer)
|
||||
assert len(gstreamer_refs) == 1, 'Expected exactly one running gstreamer.'
|
||||
self.gstreamer = gstreamer_refs[0].proxy()
|
||||
|
||||
|
||||
class LocalPlaybackController(PlaybackController):
|
||||
@ -67,24 +67,24 @@ class LocalPlaybackController(PlaybackController):
|
||||
|
||||
@property
|
||||
def time_position(self):
|
||||
return self.backend.output.get_position().get()
|
||||
return self.backend.gstreamer.get_position().get()
|
||||
|
||||
|
||||
class LocalPlaybackProvider(BasePlaybackProvider):
|
||||
def pause(self):
|
||||
return self.backend.output.set_state('PAUSED').get()
|
||||
return self.backend.gstreamer.set_state('PAUSED').get()
|
||||
|
||||
def play(self, track):
|
||||
return self.backend.output.play_uri(track.uri).get()
|
||||
return self.backend.gstreamer.play_uri(track.uri).get()
|
||||
|
||||
def resume(self):
|
||||
return self.backend.output.set_state('PLAYING').get()
|
||||
return self.backend.gstreamer.set_state('PLAYING').get()
|
||||
|
||||
def seek(self, time_position):
|
||||
return self.backend.output.set_position(time_position).get()
|
||||
return self.backend.gstreamer.set_position(time_position).get()
|
||||
|
||||
def stop(self):
|
||||
return self.backend.output.set_state('READY').get()
|
||||
return self.backend.gstreamer.set_state('READY').get()
|
||||
|
||||
|
||||
class LocalStoredPlaylistsProvider(BaseStoredPlaylistsProvider):
|
||||
|
||||
@ -6,7 +6,7 @@ from pykka.registry import ActorRegistry
|
||||
from mopidy import settings
|
||||
from mopidy.backends.base import (Backend, CurrentPlaylistController,
|
||||
LibraryController, PlaybackController, StoredPlaylistsController)
|
||||
from mopidy.outputs.base import BaseOutput
|
||||
from mopidy.gstreamer import GStreamer
|
||||
|
||||
logger = logging.getLogger('mopidy.backends.spotify')
|
||||
|
||||
@ -63,13 +63,13 @@ class SpotifyBackend(ThreadingActor, Backend):
|
||||
|
||||
self.uri_handlers = [u'spotify:', u'http://open.spotify.com/']
|
||||
|
||||
self.output = None
|
||||
self.gstreamer = None
|
||||
self.spotify = None
|
||||
|
||||
def on_start(self):
|
||||
output_refs = ActorRegistry.get_by_class(BaseOutput)
|
||||
assert len(output_refs) == 1, 'Expected exactly one running output.'
|
||||
self.output = output_refs[0].proxy()
|
||||
gstreamer_refs = ActorRegistry.get_by_class(GStreamer)
|
||||
assert len(gstreamer_refs) == 1, 'Expected exactly one running gstreamer.'
|
||||
self.gstreamer = gstreamer_refs[0].proxy()
|
||||
|
||||
self.spotify = self._connect()
|
||||
|
||||
|
||||
@ -8,10 +8,10 @@ logger = logging.getLogger('mopidy.backends.spotify.playback')
|
||||
|
||||
class SpotifyPlaybackProvider(BasePlaybackProvider):
|
||||
def pause(self):
|
||||
return self.backend.output.set_state('PAUSED')
|
||||
return self.backend.gstreamer.set_state('PAUSED')
|
||||
|
||||
def play(self, track):
|
||||
self.backend.output.set_state('READY')
|
||||
self.backend.gstreamer.set_state('READY')
|
||||
if self.backend.playback.state == self.backend.playback.PLAYING:
|
||||
self.backend.spotify.session.play(0)
|
||||
if track.uri is None:
|
||||
@ -20,7 +20,8 @@ class SpotifyPlaybackProvider(BasePlaybackProvider):
|
||||
self.backend.spotify.session.load(
|
||||
Link.from_string(track.uri).as_track())
|
||||
self.backend.spotify.session.play(1)
|
||||
self.backend.output.play_uri('appsrc://')
|
||||
self.backend.gstreamer.play_uri('appsrc://')
|
||||
self.backend.gstreamer.set_metadata(track)
|
||||
return True
|
||||
except SpotifyError as e:
|
||||
logger.info('Playback of %s failed: %s', track.uri, e)
|
||||
@ -30,12 +31,12 @@ class SpotifyPlaybackProvider(BasePlaybackProvider):
|
||||
return self.seek(self.backend.playback.time_position)
|
||||
|
||||
def seek(self, time_position):
|
||||
self.backend.output.set_state('READY')
|
||||
self.backend.gstreamer.set_state('READY')
|
||||
self.backend.spotify.session.seek(time_position)
|
||||
self.backend.output.set_state('PLAYING')
|
||||
self.backend.gstreamer.set_state('PLAYING')
|
||||
return True
|
||||
|
||||
def stop(self):
|
||||
result = self.backend.output.set_state('READY')
|
||||
result = self.backend.gstreamer.set_state('READY')
|
||||
self.backend.spotify.session.play(0)
|
||||
return result
|
||||
|
||||
@ -10,7 +10,7 @@ from mopidy import get_version, settings
|
||||
from mopidy.backends.base import Backend
|
||||
from mopidy.backends.spotify.translator import SpotifyTranslator
|
||||
from mopidy.models import Playlist
|
||||
from mopidy.outputs.base import BaseOutput
|
||||
from mopidy.gstreamer import GStreamer
|
||||
from mopidy.utils.process import BaseThread
|
||||
|
||||
logger = logging.getLogger('mopidy.backends.spotify.session_manager')
|
||||
@ -29,7 +29,7 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager):
|
||||
BaseThread.__init__(self)
|
||||
self.name = 'SpotifySMThread'
|
||||
|
||||
self.output = None
|
||||
self.gstreamer = None
|
||||
self.backend = None
|
||||
|
||||
self.connected = threading.Event()
|
||||
@ -40,9 +40,9 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager):
|
||||
self.connect()
|
||||
|
||||
def setup(self):
|
||||
output_refs = ActorRegistry.get_by_class(BaseOutput)
|
||||
assert len(output_refs) == 1, 'Expected exactly one running output.'
|
||||
self.output = output_refs[0].proxy()
|
||||
gstreamer_refs = ActorRegistry.get_by_class(GStreamer)
|
||||
assert len(gstreamer_refs) == 1, 'Expected exactly one running gstreamer.'
|
||||
self.gstreamer = gstreamer_refs[0].proxy()
|
||||
|
||||
backend_refs = ActorRegistry.get_by_class(Backend)
|
||||
assert len(backend_refs) == 1, 'Expected exactly one running backend.'
|
||||
@ -106,7 +106,7 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager):
|
||||
'sample_rate': sample_rate,
|
||||
'channels': channels,
|
||||
}
|
||||
self.output.deliver_data(capabilites, bytes(frames))
|
||||
self.gstreamer.deliver_data(capabilites, bytes(frames))
|
||||
|
||||
def play_token_lost(self, session):
|
||||
"""Callback used by pyspotify"""
|
||||
@ -120,7 +120,7 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager):
|
||||
def end_of_track(self, session):
|
||||
"""Callback used by pyspotify"""
|
||||
logger.debug(u'End of data stream reached')
|
||||
self.output.end_of_data_stream()
|
||||
self.gstreamer.end_of_data_stream()
|
||||
|
||||
def refresh_stored_playlists(self):
|
||||
"""Refresh the stored playlists in the backend with fresh meta data
|
||||
|
||||
@ -5,6 +5,7 @@ import time
|
||||
from pykka.registry import ActorRegistry
|
||||
|
||||
from mopidy import get_version, settings, OptionalDependencyError
|
||||
from mopidy.gstreamer import GStreamer
|
||||
from mopidy.utils import get_class
|
||||
from mopidy.utils.log import setup_logging
|
||||
from mopidy.utils.path import get_or_create_folder, get_or_create_file
|
||||
@ -18,7 +19,7 @@ def main():
|
||||
setup_logging(options.verbosity_level, options.save_debug_log)
|
||||
setup_settings()
|
||||
setup_gobject_loop()
|
||||
setup_output()
|
||||
setup_gstreamer()
|
||||
setup_mixer()
|
||||
setup_backend()
|
||||
setup_frontends()
|
||||
@ -56,8 +57,8 @@ def setup_gobject_loop():
|
||||
gobject_loop.start()
|
||||
return gobject_loop
|
||||
|
||||
def setup_output():
|
||||
return get_class(settings.OUTPUT).start().proxy()
|
||||
def setup_gstreamer():
|
||||
return GStreamer().start().proxy()
|
||||
|
||||
def setup_mixer():
|
||||
return get_class(settings.MIXER).start().proxy()
|
||||
|
||||
@ -8,10 +8,10 @@ from pykka.actor import ThreadingActor
|
||||
from pykka.registry import ActorRegistry
|
||||
|
||||
from mopidy import settings
|
||||
from mopidy.utils import get_class
|
||||
from mopidy.backends.base import Backend
|
||||
from mopidy.outputs.base import BaseOutput
|
||||
|
||||
logger = logging.getLogger('mopidy.outputs.gstreamer')
|
||||
logger = logging.getLogger('mopidy.gstreamer')
|
||||
|
||||
default_caps = gst.Caps("""
|
||||
audio/x-raw-int,
|
||||
@ -22,7 +22,48 @@ default_caps = gst.Caps("""
|
||||
signed=(boolean)true,
|
||||
rate=(int)44100""")
|
||||
|
||||
class GStreamerOutput(ThreadingActor, BaseOutput):
|
||||
class BaseOutput(object):
|
||||
def connect_bin(self, pipeline, element_to_link_to):
|
||||
"""
|
||||
Connect output bin to pipeline and given element.
|
||||
"""
|
||||
description = 'queue ! %s' % self.describe_bin()
|
||||
logger.debug('Adding new output to tee: %s', description)
|
||||
|
||||
output = self.parse_bin(description)
|
||||
self.modify_bin(output)
|
||||
|
||||
pipeline.add(output)
|
||||
output.sync_state_with_parent()
|
||||
gst.element_link_many(element_to_link_to, output)
|
||||
|
||||
def parse_bin(self, description):
|
||||
return gst.parse_bin_from_description(description, True)
|
||||
|
||||
def modify_bin(self, output):
|
||||
"""
|
||||
Modifies bin before it is installed if needed
|
||||
"""
|
||||
pass
|
||||
|
||||
def describe_bin(self):
|
||||
"""
|
||||
Describe bin to be parsed.
|
||||
|
||||
Must be implemented by subclasses.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def set_properties(self, element, properties):
|
||||
"""
|
||||
Set properties on element if they have a value.
|
||||
"""
|
||||
for key, value in properties.items():
|
||||
if value:
|
||||
element.set_property(key, value)
|
||||
|
||||
|
||||
class GStreamer(ThreadingActor):
|
||||
"""
|
||||
Audio output through `GStreamer <http://gstreamer.freedesktop.org/>`_.
|
||||
|
||||
@ -44,22 +85,32 @@ class GStreamerOutput(ThreadingActor, BaseOutput):
|
||||
:class:`mopidy.utils.process.GObjectEventThread` to be running. This is
|
||||
not enforced by :class:`GStreamerOutput` itself.
|
||||
"""
|
||||
|
||||
logger.debug(u'Setting up GStreamer pipeline')
|
||||
|
||||
self.gst_pipeline = gst.parse_launch(' ! '.join([
|
||||
base_pipeline = ' ! '.join([
|
||||
'audioconvert name=convert',
|
||||
'volume name=volume',
|
||||
settings.GSTREAMER_AUDIO_SINK,
|
||||
]))
|
||||
'taginject name=tag',
|
||||
'tee name=tee',
|
||||
])
|
||||
|
||||
pad = self.gst_pipeline.get_by_name('convert').get_pad('sink')
|
||||
logger.debug(u'Setting up base GStreamer pipeline: %s', base_pipeline)
|
||||
|
||||
self.gst_pipeline = gst.parse_launch(base_pipeline)
|
||||
|
||||
self.gst_tee = self.gst_pipeline.get_by_name('tee')
|
||||
self.gst_convert = self.gst_pipeline.get_by_name('convert')
|
||||
self.gst_volume = self.gst_pipeline.get_by_name('volume')
|
||||
self.gst_taginject = self.gst_pipeline.get_by_name('tag')
|
||||
|
||||
uridecodebin = gst.element_factory_make('uridecodebin', 'uri')
|
||||
uridecodebin.connect('pad-added', self._process_new_pad, pad)
|
||||
uridecodebin.connect('notify::source', self._process_new_source)
|
||||
uridecodebin.connect('pad-added', self._process_new_pad,
|
||||
self.gst_convert.get_pad('sink'))
|
||||
self.gst_pipeline.add(uridecodebin)
|
||||
|
||||
for output in settings.OUTPUTS:
|
||||
output_cls = get_class(output)()
|
||||
output_cls.connect_bin(self.gst_pipeline, self.gst_tee)
|
||||
|
||||
# Setup bus and message processor
|
||||
gst_bus = self.gst_pipeline.get_bus()
|
||||
gst_bus.add_signal_watch()
|
||||
@ -96,7 +147,7 @@ class GStreamerOutput(ThreadingActor, BaseOutput):
|
||||
def play_uri(self, uri):
|
||||
"""Play audio at URI"""
|
||||
self.set_state('READY')
|
||||
self.gst_pipeline.get_by_name('uri').set_property('uri', uri)
|
||||
self.gst_uridecodebin.set_property('uri', uri)
|
||||
return self.set_state('PLAYING')
|
||||
|
||||
def deliver_data(self, caps_string, data):
|
||||
@ -160,11 +211,17 @@ class GStreamerOutput(ThreadingActor, BaseOutput):
|
||||
|
||||
def get_volume(self):
|
||||
"""Get volume in range [0..100]"""
|
||||
gst_volume = self.gst_pipeline.get_by_name('volume')
|
||||
return int(gst_volume.get_property('volume') * 100)
|
||||
return int(self.gst_volume.get_property('volume') * 100)
|
||||
|
||||
def set_volume(self, volume):
|
||||
"""Set volume in range [0..100]"""
|
||||
gst_volume = self.gst_pipeline.get_by_name('volume')
|
||||
gst_volume.set_property('volume', volume / 100.0)
|
||||
self.gst_volume.set_property('volume', volume / 100.0)
|
||||
return True
|
||||
|
||||
def set_metadata(self, track):
|
||||
tags = u'artist="%(artist)s",title="%(title)s"' % {
|
||||
'artist': u', '.join([a.name for a in track.artists]),
|
||||
'title': track.name,
|
||||
}
|
||||
logger.debug('Setting tags to: %s', tags)
|
||||
self.gst_taginject.set_property('tags', tags)
|
||||
@ -2,7 +2,7 @@ from pykka.actor import ThreadingActor
|
||||
from pykka.registry import ActorRegistry
|
||||
|
||||
from mopidy.mixers.base import BaseMixer
|
||||
from mopidy.outputs.base import BaseOutput
|
||||
from mopidy.gstreamer import GStreamer
|
||||
|
||||
class GStreamerSoftwareMixer(ThreadingActor, BaseMixer):
|
||||
"""Mixer which uses GStreamer to control volume in software."""
|
||||
@ -11,7 +11,7 @@ class GStreamerSoftwareMixer(ThreadingActor, BaseMixer):
|
||||
self.output = None
|
||||
|
||||
def on_start(self):
|
||||
output_refs = ActorRegistry.get_by_class(BaseOutput)
|
||||
output_refs = ActorRegistry.get_by_class(GStreamer)
|
||||
assert len(output_refs) == 1, 'Expected exactly one running output.'
|
||||
self.output = output_refs[0].proxy()
|
||||
|
||||
|
||||
31
mopidy/outputs.py
Normal file
31
mopidy/outputs.py
Normal file
@ -0,0 +1,31 @@
|
||||
from mopidy import settings
|
||||
from mopidy.gstreamer import BaseOutput
|
||||
|
||||
class LocalOutput(BaseOutput):
|
||||
def describe_bin(self):
|
||||
if settings.LOCAL_OUTPUT_OVERRIDE:
|
||||
return settings.LOCAL_OUTPUT_OVERRIDE
|
||||
return 'autoaudiosink'
|
||||
|
||||
class NullOutput(BaseOutput):
|
||||
def describe_bin(self):
|
||||
return 'fakesink'
|
||||
|
||||
class ShoutcastOutput(BaseOutput):
|
||||
def describe_bin(self):
|
||||
if settings.SHOUTCAST_OUTPUT_OVERRIDE:
|
||||
return settings.SHOUTCAST_OUTPUT_OVERRIDE
|
||||
return 'audioconvert ! %s ! shout2send name=shoutcast' \
|
||||
% settings.SHOUTCAST_OUTPUT_ENCODER
|
||||
|
||||
def modify_bin(self, output):
|
||||
if settings.SHOUTCAST_OUTPUT_OVERRIDE:
|
||||
return
|
||||
|
||||
self.set_properties(output.get_by_name('shoutcast'), {
|
||||
u'ip': settings.SHOUTCAST_OUTPUT_SERVER,
|
||||
u'mount': settings.SHOUTCAST_OUTPUT_MOUNT,
|
||||
u'port': settings.SHOUTCAST_OUTPUT_PORT,
|
||||
u'username': settings.SHOUTCAST_OUTPUT_USERNAME,
|
||||
u'password': settings.SHOUTCAST_OUTPUT_PASSWORD,
|
||||
})
|
||||
@ -1,91 +0,0 @@
|
||||
class BaseOutput(object):
|
||||
"""
|
||||
Base class for audio outputs.
|
||||
"""
|
||||
|
||||
def play_uri(self, uri):
|
||||
"""
|
||||
Play URI.
|
||||
|
||||
*MUST be implemented by subclass.*
|
||||
|
||||
:param uri: the URI to play
|
||||
:type uri: string
|
||||
:rtype: :class:`True` if successful, else :class:`False`
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def deliver_data(self, capabilities, data):
|
||||
"""
|
||||
Deliver audio data to be played.
|
||||
|
||||
*MUST be implemented by subclass.*
|
||||
|
||||
:param capabilities: a GStreamer capabilities string
|
||||
:type capabilities: string
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def end_of_data_stream(self):
|
||||
"""
|
||||
Signal that the last audio data has been delivered.
|
||||
|
||||
*MUST be implemented by subclass.*
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_position(self):
|
||||
"""
|
||||
Get position in milliseconds.
|
||||
|
||||
*MUST be implemented by subclass.*
|
||||
|
||||
:rtype: int
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def set_position(self, position):
|
||||
"""
|
||||
Set position in milliseconds.
|
||||
|
||||
*MUST be implemented by subclass.*
|
||||
|
||||
:param position: the position in milliseconds
|
||||
:type volume: int
|
||||
:rtype: :class:`True` if successful, else :class:`False`
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def set_state(self, state):
|
||||
"""
|
||||
Set playback state.
|
||||
|
||||
*MUST be implemented by subclass.*
|
||||
|
||||
:param state: the state
|
||||
:type state: string
|
||||
:rtype: :class:`True` if successful, else :class:`False`
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_volume(self):
|
||||
"""
|
||||
Get volume level for software mixer.
|
||||
|
||||
*MUST be implemented by subclass.*
|
||||
|
||||
:rtype: int in range [0..100]
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def set_volume(self, volume):
|
||||
"""
|
||||
Set volume level for software mixer.
|
||||
|
||||
*MUST be implemented by subclass.*
|
||||
|
||||
:param volume: the volume in the range [0..100]
|
||||
:type volume: int
|
||||
:rtype: :class:`True` if successful, else :class:`False`
|
||||
"""
|
||||
raise NotImplementedError
|
||||
@ -1,63 +0,0 @@
|
||||
from pykka.actor import ThreadingActor
|
||||
|
||||
from mopidy.outputs.base import BaseOutput
|
||||
|
||||
class DummyOutput(ThreadingActor, BaseOutput):
|
||||
"""
|
||||
Audio output used for testing.
|
||||
"""
|
||||
|
||||
# pylint: disable = R0902
|
||||
# Too many instance attributes (9/7)
|
||||
|
||||
#: For testing. Contains the last URI passed to :meth:`play_uri`.
|
||||
uri = None
|
||||
|
||||
#: For testing. Contains the last capabilities passed to
|
||||
#: :meth:`deliver_data`.
|
||||
capabilities = None
|
||||
|
||||
#: For testing. Contains the last data passed to :meth:`deliver_data`.
|
||||
data = None
|
||||
|
||||
#: For testing. :class:`True` if :meth:`end_of_data_stream` has been
|
||||
#: called.
|
||||
end_of_data_stream_called = False
|
||||
|
||||
#: For testing. Contains the current position.
|
||||
position = 0
|
||||
|
||||
#: For testing. Contains the current state.
|
||||
state = 'NULL'
|
||||
|
||||
#: For testing. Contains the current volume.
|
||||
volume = 100
|
||||
|
||||
def play_uri(self, uri):
|
||||
self.uri = uri
|
||||
return True
|
||||
|
||||
def deliver_data(self, capabilities, data):
|
||||
self.capabilities = capabilities
|
||||
self.data = data
|
||||
|
||||
def end_of_data_stream(self):
|
||||
self.end_of_data_stream_called = True
|
||||
|
||||
def get_position(self):
|
||||
return self.position
|
||||
|
||||
def set_position(self, position):
|
||||
self.position = position
|
||||
return True
|
||||
|
||||
def set_state(self, state):
|
||||
self.state = state
|
||||
return True
|
||||
|
||||
def get_volume(self):
|
||||
return self.volume
|
||||
|
||||
def set_volume(self, volume):
|
||||
self.volume = volume
|
||||
return True
|
||||
@ -54,12 +54,12 @@ FRONTENDS = (
|
||||
u'mopidy.frontends.lastfm.LastfmFrontend',
|
||||
)
|
||||
|
||||
#: Which GStreamer audio sink to use in :mod:`mopidy.outputs.gstreamer`.
|
||||
#: Which GStreamer bin description to use in :mod:`mopidy.outputs.CustomOutput`.
|
||||
#:
|
||||
#: Default::
|
||||
#:
|
||||
#: GSTREAMER_AUDIO_SINK = u'autoaudiosink'
|
||||
GSTREAMER_AUDIO_SINK = u'autoaudiosink'
|
||||
#: LOCAL_OUTPUT_OVERRIDE = None
|
||||
LOCAL_OUTPUT_OVERRIDE = None
|
||||
|
||||
#: Your `Last.fm <http://www.last.fm/>`_ username.
|
||||
#:
|
||||
@ -143,13 +143,6 @@ MIXER_EXT_SPEAKERS_B = None
|
||||
#: MIXER_MAX_VOLUME = 100
|
||||
MIXER_MAX_VOLUME = 100
|
||||
|
||||
#: Audio output handler to use.
|
||||
#:
|
||||
#: Default::
|
||||
#:
|
||||
#: OUTPUT = u'mopidy.outputs.gstreamer.GStreamerOutput'
|
||||
OUTPUT = u'mopidy.outputs.gstreamer.GStreamerOutput'
|
||||
|
||||
#: Which address Mopidy's MPD server should bind to.
|
||||
#:
|
||||
#:Examples:
|
||||
@ -174,6 +167,77 @@ MPD_SERVER_PASSWORD = None
|
||||
#: Default: 6600
|
||||
MPD_SERVER_PORT = 6600
|
||||
|
||||
#: List of outputs to use. See :mod:`mopidy.outputs` for all available
|
||||
#: backends
|
||||
#:
|
||||
#: Default::
|
||||
#:
|
||||
#: OUTPUTS = (
|
||||
#: u'mopidy.outputs.LocalOutput',
|
||||
#: )
|
||||
OUTPUTS = (
|
||||
u'mopidy.outputs.LocalOutput',
|
||||
)
|
||||
|
||||
#: Servar that runs Shoutcast server to send stream to.
|
||||
#:
|
||||
#: Default::
|
||||
#:
|
||||
#: SHOUTCAST_OUTPUT_SERVER = u'127.0.0.1'
|
||||
SHOUTCAST_OUTPUT_SERVER = u'127.0.0.1'
|
||||
|
||||
#: User to authenticate as against Shoutcast server.
|
||||
#:
|
||||
#: Default::
|
||||
#:
|
||||
#: SHOUTCAST_OUTPUT_USERNAME = u'source'
|
||||
SHOUTCAST_OUTPUT_USERNAME = u'source'
|
||||
|
||||
#: Password to authenticate with against Shoutcast server.
|
||||
#:
|
||||
#: Default::
|
||||
#:
|
||||
#: SHOUTCAST_OUTPUT_PASSWORD = u'hackme'
|
||||
SHOUTCAST_OUTPUT_PASSWORD = u'hackme'
|
||||
|
||||
#: Port to use for streaming to Shoutcast server.
|
||||
#:
|
||||
#: Default::
|
||||
#:
|
||||
#: SHOUTCAST_OUTPUT_PORT = 8000
|
||||
SHOUTCAST_OUTPUT_PORT = 8000
|
||||
|
||||
#: Mountpoint to use for the stream on the Shoutcast server.
|
||||
#:
|
||||
#: Default::
|
||||
#:
|
||||
#: SHOUTCAST_OUTPUT_MOUNT = u'/stream'
|
||||
SHOUTCAST_OUTPUT_MOUNT = u'/stream'
|
||||
|
||||
#: Encoder to use to process audio data before streaming.
|
||||
#:
|
||||
#: Default::
|
||||
#:
|
||||
#: SHOUTCAST_OUTPUT_ENCODER = u'lame mode=stereo bitrate=320'
|
||||
SHOUTCAST_OUTPUT_ENCODER = u'lame mode=stereo bitrate=320'
|
||||
|
||||
#: Overrides to allow advanced setup of shoutcast. Using this settings implies
|
||||
#: that all other SHOUTCAST_OUTPUT_* settings will be ignored.
|
||||
#:
|
||||
#: Examples:
|
||||
#:
|
||||
#: ``vorbisenc ! oggmux ! shout2send mount=/stream port=8000``
|
||||
#: Encode with vorbis and use ogg mux.
|
||||
#: ``lame bitrate=320 ! shout2send mount=/stream port=8000``
|
||||
#: Encode with lame to bitrate=320.
|
||||
#:
|
||||
#: For all options see gst-inspect-0.10 lame, vorbisenc and shout2send.
|
||||
#:
|
||||
#: Default::
|
||||
#:
|
||||
#: SHOUTCAST_OUTPUT_OVERRIDE = None
|
||||
SHOUTCAST_OUTPUT_OVERRIDE = None
|
||||
|
||||
#: Path to the Spotify cache.
|
||||
#:
|
||||
#: Used by :mod:`mopidy.backends.spotify`.
|
||||
|
||||
@ -97,9 +97,11 @@ def validate_settings(defaults, settings):
|
||||
'DUMP_LOG_FILENAME': 'DEBUG_LOG_FILENAME',
|
||||
'DUMP_LOG_FORMAT': 'DEBUG_LOG_FORMAT',
|
||||
'FRONTEND': 'FRONTENDS',
|
||||
'GSTREAMER_AUDIO_SINK': 'LOCAL_OUTPUT_OVERRIDE',
|
||||
'LOCAL_MUSIC_FOLDER': 'LOCAL_MUSIC_PATH',
|
||||
'LOCAL_PLAYLIST_FOLDER': 'LOCAL_PLAYLIST_PATH',
|
||||
'LOCAL_TAG_CACHE': 'LOCAL_TAG_CACHE_FILE',
|
||||
'OUTPUT': None,
|
||||
'SERVER': None,
|
||||
'SERVER_HOSTNAME': 'MPD_SERVER_HOSTNAME',
|
||||
'SERVER_PORT': 'MPD_SERVER_PORT',
|
||||
|
||||
@ -3,7 +3,7 @@ import multiprocessing
|
||||
import random
|
||||
|
||||
from mopidy.models import Playlist, Track
|
||||
from mopidy.outputs.base import BaseOutput
|
||||
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.output = mock.Mock(spec=BaseOutput)
|
||||
self.backend.gstreamer = mock.Mock(spec=GStreamer)
|
||||
self.controller = self.backend.current_playlist
|
||||
self.playback = self.backend.playback
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import random
|
||||
import time
|
||||
|
||||
from mopidy.models import Track
|
||||
from mopidy.outputs.base import BaseOutput
|
||||
from mopidy.gstreamer import GStreamer
|
||||
|
||||
from tests import SkipTest
|
||||
from tests.backends.base import populate_playlist
|
||||
@ -16,7 +16,7 @@ class PlaybackControllerTest(object):
|
||||
|
||||
def setUp(self):
|
||||
self.backend = self.backend_class()
|
||||
self.backend.output = mock.Mock(spec=BaseOutput)
|
||||
self.backend.gstreamer = mock.Mock(spec=GStreamer)
|
||||
self.playback = self.backend.playback
|
||||
self.current_playlist = self.backend.current_playlist
|
||||
|
||||
@ -520,7 +520,7 @@ class PlaybackControllerTest(object):
|
||||
|
||||
self.assert_(wrapper.called)
|
||||
|
||||
@SkipTest # Blocks for 10ms and does not work with DummyOutput
|
||||
@SkipTest # Blocks for 10ms
|
||||
@populate_playlist
|
||||
def test_end_of_track_callback_gets_called(self):
|
||||
self.playback.play()
|
||||
@ -599,7 +599,7 @@ class PlaybackControllerTest(object):
|
||||
self.playback.pause()
|
||||
self.assertEqual(self.playback.resume(), None)
|
||||
|
||||
@SkipTest # Uses sleep and does not work with DummyOutput+LocalBackend
|
||||
@SkipTest # Uses sleep and might not work with LocalBackend
|
||||
@populate_playlist
|
||||
def test_resume_continues_from_right_position(self):
|
||||
self.playback.play()
|
||||
@ -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.output.get_position = mock.Mock(return_value=future)
|
||||
self.backend.gstreamer.get_position = mock.Mock(return_value=future)
|
||||
|
||||
self.assertEqual(self.playback.time_position, 0)
|
||||
|
||||
@ -737,11 +737,11 @@ class PlaybackControllerTest(object):
|
||||
def test_time_position_when_stopped_with_playlist(self):
|
||||
future = mock.Mock()
|
||||
future.get = mock.Mock(return_value=0)
|
||||
self.backend.output.get_position = mock.Mock(return_value=future)
|
||||
self.backend.gstreamer.get_position = mock.Mock(return_value=future)
|
||||
|
||||
self.assertEqual(self.playback.time_position, 0)
|
||||
|
||||
@SkipTest # Uses sleep and does not work with LocalBackend+DummyOutput
|
||||
@SkipTest # Uses sleep and does might not work with LocalBackend
|
||||
@populate_playlist
|
||||
def test_time_position_when_playing(self):
|
||||
self.playback.play()
|
||||
|
||||
@ -9,26 +9,28 @@ if sys.platform == 'win32':
|
||||
raise SkipTest
|
||||
|
||||
from mopidy import settings
|
||||
from mopidy.outputs.gstreamer import GStreamerOutput
|
||||
from mopidy.gstreamer import GStreamer
|
||||
from mopidy.utils.path import path_to_uri
|
||||
|
||||
from tests import path_to_data_dir
|
||||
|
||||
class GStreamerOutputTest(unittest.TestCase):
|
||||
# TODO BaseOutputTest?
|
||||
|
||||
class GStreamerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
settings.BACKENDS = ('mopidy.backends.local.LocalBackend',)
|
||||
self.song_uri = path_to_uri(path_to_data_dir('song1.wav'))
|
||||
self.output = GStreamerOutput()
|
||||
self.output.on_start()
|
||||
self.gstreamer = GStreamer()
|
||||
self.gstreamer.on_start()
|
||||
|
||||
def tearDown(self):
|
||||
settings.runtime.clear()
|
||||
|
||||
def test_play_uri_existing_file(self):
|
||||
self.assertTrue(self.output.play_uri(self.song_uri))
|
||||
self.assertTrue(self.gstreamer.play_uri(self.song_uri))
|
||||
|
||||
def test_play_uri_non_existing_file(self):
|
||||
self.assertFalse(self.output.play_uri(self.song_uri + 'bogus'))
|
||||
self.assertFalse(self.gstreamer.play_uri(self.song_uri + 'bogus'))
|
||||
|
||||
@SkipTest
|
||||
def test_deliver_data(self):
|
||||
@ -39,19 +41,19 @@ class GStreamerOutputTest(unittest.TestCase):
|
||||
pass # TODO
|
||||
|
||||
def test_default_get_volume_result(self):
|
||||
self.assertEqual(100, self.output.get_volume())
|
||||
self.assertEqual(100, self.gstreamer.get_volume())
|
||||
|
||||
def test_set_volume(self):
|
||||
self.assertTrue(self.output.set_volume(50))
|
||||
self.assertEqual(50, self.output.get_volume())
|
||||
self.assertTrue(self.gstreamer.set_volume(50))
|
||||
self.assertEqual(50, self.gstreamer.get_volume())
|
||||
|
||||
def test_set_volume_to_zero(self):
|
||||
self.assertTrue(self.output.set_volume(0))
|
||||
self.assertEqual(0, self.output.get_volume())
|
||||
self.assertTrue(self.gstreamer.set_volume(0))
|
||||
self.assertEqual(0, self.gstreamer.get_volume())
|
||||
|
||||
def test_set_volume_to_one_hundred(self):
|
||||
self.assertTrue(self.output.set_volume(100))
|
||||
self.assertEqual(100, self.output.get_volume())
|
||||
self.assertTrue(self.gstreamer.set_volume(100))
|
||||
self.assertEqual(100, self.gstreamer.get_volume())
|
||||
|
||||
@SkipTest
|
||||
def test_set_state(self):
|
||||
Loading…
Reference in New Issue
Block a user