From 77fde2fce73571a6c602b1e97ba337891fe13532 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 14 Aug 2010 17:26:25 +0200 Subject: [PATCH 01/30] Remove skip test for local backend --- tests/backends/local_test.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/backends/local_test.py b/tests/backends/local_test.py index a5222276..ccc8bdd0 100644 --- a/tests/backends/local_test.py +++ b/tests/backends/local_test.py @@ -19,8 +19,6 @@ from tests import SkipTest, data_folder song = data_folder('song%s.wav') generate_song = lambda i: path_to_uri(song % i) -raise SkipTest - # FIXME can be switched to generic test class LocalCurrentPlaylistControllerTest(BaseCurrentPlaylistControllerTest, unittest.TestCase): From 8950385815210cbd89182eeda68f26a7ddd82cb0 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 14 Aug 2010 17:27:26 +0200 Subject: [PATCH 02/30] Update local backend tests to setup output and queues --- tests/backends/base.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/backends/base.py b/tests/backends/base.py index 64ca7797..d0389c0d 100644 --- a/tests/backends/base.py +++ b/tests/backends/base.py @@ -4,10 +4,12 @@ import shutil import tempfile import threading import time +import multiprocessing from mopidy import settings from mopidy.mixers.dummy import DummyMixer from mopidy.models import Playlist, Track, Album, Artist +from mopidy.utils import get_class from tests import SkipTest, data_folder @@ -32,7 +34,10 @@ class BaseCurrentPlaylistControllerTest(object): backend_class = None def setUp(self): - self.backend = self.backend_class(mixer_class=DummyMixer) + self.output_queue = multiprocessing.Queue() + self.core_queue = multiprocessing.Queue() + self.output = get_class(settings.OUTPUT)(self.core_queue, self.output_queue) + self.backend = self.backend_class(self.core_queue, self.output_queue, DummyMixer) self.controller = self.backend.current_playlist self.playback = self.backend.playback @@ -40,6 +45,7 @@ class BaseCurrentPlaylistControllerTest(object): def tearDown(self): self.backend.destroy() + self.output.destroy() def test_add(self): for track in self.tracks: @@ -281,7 +287,10 @@ class BasePlaybackControllerTest(object): backend_class = None def setUp(self): - self.backend = self.backend_class(mixer_class=DummyMixer) + self.output_queue = multiprocessing.Queue() + self.core_queue = multiprocessing.Queue() + self.output = get_class(settings.OUTPUT)(self.core_queue, self.output_queue) + self.backend = self.backend_class(self.core_queue, self.output_queue, DummyMixer) self.playback = self.backend.playback self.current_playlist = self.backend.current_playlist @@ -292,6 +301,7 @@ class BasePlaybackControllerTest(object): def tearDown(self): self.backend.destroy() + self.output.destroy() def test_initial_state_is_stopped(self): self.assertEqual(self.playback.state, self.playback.STOPPED) From 85100f21432d5dc70b2643ea85fb2d8f7b5e9372 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 14 Aug 2010 17:28:04 +0200 Subject: [PATCH 03/30] Remove appsrc from output until local backend tests are done --- mopidy/outputs/gstreamer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index c2547bdb..4cbeb205 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -43,7 +43,8 @@ class GStreamerProcess(BaseProcess): """ pipeline_description = ' ! '.join([ - 'appsrc name=src uridecodebin name=uri', + #'appsrc name=src uridecodebin name=uri', + 'uridecodebin name=uri', 'volume name=volume', 'autoaudiosink name=sink', ]) From a094c665465f2f54271e60b52db51c4af899ada5 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 14 Aug 2010 17:29:50 +0200 Subject: [PATCH 04/30] Strip out gst stuff from localbackend and start using output --- mopidy/backends/local/__init__.py | 87 +++++++------------------------ 1 file changed, 19 insertions(+), 68 deletions(-) diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index 87d2f7c0..658e73f9 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -1,34 +1,17 @@ -import gobject -gobject.threads_init() -# FIXME make sure we don't get hit by -# http://jameswestby.net/ -# weblog/tech/14-caution-python-multiprocessing-and-glib-dont-mix.html - -import pygst -pygst.require('0.10') - -import gst import logging import os import glob import shutil -import threading +import multiprocessing from mopidy.backends.base import * from mopidy.models import Playlist, Track, Album from mopidy import settings from mopidy.utils import parse_m3u, parse_mpd_tag_cache +from mopidy.process import pickle_connection logger = logging.getLogger(u'mopidy.backends.local') -class LocalMessages(threading.Thread): - def run(self): - gobject.MainLoop().run() - -message_thread = LocalMessages() -message_thread.daemon = True -message_thread.start() - class LocalBackend(BaseBackend): """ A backend for playing music from a local music archive. @@ -49,71 +32,39 @@ class LocalBackend(BaseBackend): class LocalPlaybackController(BasePlaybackController): def __init__(self, backend): super(LocalPlaybackController, self).__init__(backend) - - self._bin = gst.element_factory_make("playbin", "player") - self._bus = self._bin.get_bus() - sink = gst.element_factory_make("fakesink", "fakesink") - - # FIXME cleanup fakesink? - - self._bin.set_property("video-sink", sink) - self._bus.add_signal_watch() - self._bus_id = self._bus.connect('message', self._message) - self.stop() - def _set_state(self, state): - self._bin.set_state(state) - (_, new, _) = self._bin.get_state() - return new == state + def _send_recv(self, message): + (my_end, other_end) = multiprocessing.Pipe() + message.update({'reply_to': pickle_connection(other_end)}) + self.backend.output_queue.put(message) + my_end.poll(None) + return my_end.recv() - def _message(self, bus, message): - if message.type == gst.MESSAGE_EOS: - self.end_of_track_callback() - elif message.type == gst.MESSAGE_ERROR: - self._bin.set_state(gst.STATE_NULL) - error, debug = message.parse_error() - logger.error('%s %s', error, debug) + def _send(self, message): + self.backend.output_queue.put(message) + + def _set_state(self, state): + return self._send_recv({'command': 'set_state', 'state': state}) def _play(self, track): - self._bin.set_state(gst.STATE_READY) - self._bin.set_property('uri', track.uri) - return self._set_state(gst.STATE_PLAYING) + return self._send_recv({'command': 'play_uri', 'uri': track.uri}) def _stop(self): - return self._set_state(gst.STATE_READY) + return self._set_state('READY') def _pause(self): - return self._set_state(gst.STATE_PAUSED) + return self._set_state('PAUSED') def _resume(self): - return self._set_state(gst.STATE_PLAYING) + return self._set_state('PLAYING') def _seek(self, time_position): - self._bin.seek_simple(gst.Format(gst.FORMAT_TIME), - gst.SEEK_FLAG_FLUSH, time_position * gst.MSECOND) - self._set_state(gst.STATE_PLAYING) + pass @property def time_position(self): - try: - return self._bin.query_position(gst.FORMAT_TIME)[0] // gst.MSECOND - except gst.QueryError, e: - logger.error('time_position failed: %s', e) - return 0 - - def destroy(self): - playbin, self._bin = self._bin, None - bus, self._bus = self._bus, None - - bus.disconnect(self._bus_id) - bus.remove_signal_watch() - playbin.get_state() - playbin.set_state(gst.STATE_NULL) - bus.set_flushing(True) - - del bus - del playbin + pass class LocalStoredPlaylistsController(BaseStoredPlaylistsController): From 3dc9240ea4e61b64488c297c60c83736dc1a6762 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 14 Aug 2010 17:52:12 +0200 Subject: [PATCH 05/30] Add time position stuff to gstreamer output --- mopidy/backends/local/__init__.py | 4 ++-- mopidy/outputs/gstreamer.py | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index 658e73f9..967aab7a 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -60,11 +60,11 @@ class LocalPlaybackController(BasePlaybackController): return self._set_state('PLAYING') def _seek(self, time_position): - pass + self._send({'command': 'set_position', 'position': time_position}) @property def time_position(self): - pass + return self._send_recv({'command': 'get_position'}) class LocalStoredPlaylistsController(BaseStoredPlaylistsController): diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index 4cbeb205..2929007c 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -105,6 +105,12 @@ class GStreamerProcess(BaseProcess): connection.send(volume) elif message['command'] == 'set_volume': self.set_volume(message['volume']) + elif message['command'] == 'set_position': + self.set_position(message['position']) + elif message['command'] == 'get_position': + response = self.get_position() + connection = unpickle_connection(message['reply_to']) + connection.send(response) else: logger.warning(u'Cannot handle message: %s', message) @@ -180,3 +186,19 @@ class GStreamerProcess(BaseProcess): """Set volume in range [0..100]""" gst_volume = volume / 100.0 self.gst_volume.set_property('volume', gst_volume) + + def set_position(self, position): + logger.info('Seeking to %s' % position) + self.gst_pipeline.seek_simple(gst.Format(gst.FORMAT_TIME), + gst.SEEK_FLAG_FLUSH, position * gst.MSECOND) + self.set_state('PLAYING') + + def get_position(self): + try: + position = self.gst_pipeline.query_position(gst.FORMAT_TIME)[0] + return position // gst.MSECOND + except gst.QueryError, e: + logger.error('time_position failed: %s', e) + return 0 + + From f5b6d0e9944ea472c698bb892ad47d80d7169e1f Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 14 Aug 2010 18:29:33 +0200 Subject: [PATCH 06/30] Switch to using playbin in order to get simple base case working --- mopidy/outputs/gstreamer.py | 5 ++++- tests/outputs/gstreamer_test.py | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index 9ce9df15..da42a15b 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -48,6 +48,7 @@ class GStreamerProcess(BaseProcess): 'volume name=volume', 'autoaudiosink name=sink', ]) + pipeline_description = 'playbin' def __init__(self, core_queue, output_queue): super(GStreamerProcess, self).__init__() @@ -130,7 +131,7 @@ class GStreamerProcess(BaseProcess): def play_uri(self, uri): """Play audio at URI""" self.set_state('READY') - self.gst_uri_bin.set_property('uri', uri) + self.gst_pipeline.set_property('uri', uri) return self.set_state('PLAYING') def deliver_data(self, caps_string, data): @@ -178,10 +179,12 @@ class GStreamerProcess(BaseProcess): def get_volume(self): """Get volume in range [0..100]""" + return 0 gst_volume = self.gst_volume.get_property('volume') return int(gst_volume * 100) def set_volume(self, volume): + return """Set volume in range [0..100]""" gst_volume = volume / 100.0 self.gst_volume.set_property('volume', gst_volume) diff --git a/tests/outputs/gstreamer_test.py b/tests/outputs/gstreamer_test.py index f483a68a..1bdd35d9 100644 --- a/tests/outputs/gstreamer_test.py +++ b/tests/outputs/gstreamer_test.py @@ -35,18 +35,22 @@ class GStreamerOutputTest(unittest.TestCase): message = {'command': 'play_uri', 'uri': self.song_uri + 'bogus'} self.assertEqual(False, self.send_recv(message)) + @SkipTest def test_default_get_volume_result(self): message = {'command': 'get_volume'} self.assertEqual(100, self.send_recv(message)) + @SkipTest def test_set_volume(self): self.send({'command': 'set_volume', 'volume': 50}) self.assertEqual(50, self.send_recv({'command': 'get_volume'})) + @SkipTest def test_set_volume_to_zero(self): self.send({'command': 'set_volume', 'volume': 0}) self.assertEqual(0, self.send_recv({'command': 'get_volume'})) + @SkipTest def test_set_volume_to_one_hundred(self): self.send({'command': 'set_volume', 'volume': 100}) self.assertEqual(100, self.send_recv({'command': 'get_volume'})) From bb73f8ae5ff920178bd90f5b62a4369d21f57c8d Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 14 Aug 2010 18:53:31 +0200 Subject: [PATCH 07/30] Update set_state to block for async state changes --- mopidy/outputs/gstreamer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index da42a15b..13563a80 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -170,6 +170,10 @@ class GStreamerProcess(BaseProcess): """ result = self.gst_pipeline.set_state( getattr(gst, 'STATE_' + state_name)) + # Block until state change has occured, required for at least the local + # backends seek functionality. (Optional solution is to block in set + # position to ensure that change occours) + self.gst_pipeline.get_state() if result == gst.STATE_CHANGE_FAILURE: logger.warning('Setting GStreamer state to %s: failed', state_name) return False @@ -190,7 +194,6 @@ class GStreamerProcess(BaseProcess): self.gst_volume.set_property('volume', gst_volume) def set_position(self, position): - logger.info('Seeking to %s' % position) self.gst_pipeline.seek_simple(gst.Format(gst.FORMAT_TIME), gst.SEEK_FLAG_FLUSH, position * gst.MSECOND) self.set_state('PLAYING') From 504802b02da825089901e1cf3407e5e33592c2c9 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 14 Aug 2010 19:06:08 +0200 Subject: [PATCH 08/30] Update test_end_of_track_callback_gets_called to check that end_of_track is in core_queue --- tests/backends/base.py | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/tests/backends/base.py b/tests/backends/base.py index d0389c0d..582ccc9c 100644 --- a/tests/backends/base.py +++ b/tests/backends/base.py @@ -2,7 +2,6 @@ import os import random import shutil import tempfile -import threading import time import multiprocessing @@ -600,22 +599,10 @@ class BasePlaybackControllerTest(object): @populate_playlist def test_end_of_track_callback_gets_called(self): - end_of_track_callback = self.playback.end_of_track_callback - event = threading.Event() - - def wrapper(): - result = end_of_track_callback() - event.set() - return result - - self.playback.end_of_track_callback = wrapper - self.playback.play() self.playback.seek(self.tracks[0].length - 10) - - event.wait(5) - - self.assert_(event.is_set()) + message = self.core_queue.get() + self.assertEqual('end_of_track', message['command']) @populate_playlist def test_new_playlist_loaded_callback_when_playing(self): From cc9de4eff1092358f7eb2960ec1658250b294ef0 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 14 Aug 2010 19:14:52 +0200 Subject: [PATCH 09/30] Add extra backend test that check that pause resumes correct song --- tests/backends/base.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/backends/base.py b/tests/backends/base.py index 582ccc9c..97719c08 100644 --- a/tests/backends/base.py +++ b/tests/backends/base.py @@ -350,6 +350,17 @@ class BasePlaybackControllerTest(object): self.assertEqual(self.playback.state, self.playback.PLAYING) self.assertEqual(track, self.playback.current_track) + @populate_playlist + def test_play_when_pause_after_next(self): + self.playback.play() + self.playback.next() + self.playback.next() + track = self.playback.current_track + self.playback.pause() + self.playback.play() + self.assertEqual(self.playback.state, self.playback.PLAYING) + self.assertEqual(track, self.playback.current_track) + @populate_playlist def test_play_sets_current_track(self): self.playback.play() From e93e3d4265045a9eacf0d5a785086ba5a8b948b8 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 14 Aug 2010 22:26:32 +0200 Subject: [PATCH 10/30] Attempt to simplify gstreamer output with only playbin --- mopidy/outputs/gstreamer.py | 40 +++++++++++-------------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index 13563a80..b2012626 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -42,24 +42,11 @@ class GStreamerProcess(BaseProcess): http://jameswestby.net/weblog/tech/14-caution-python-multiprocessing-and-glib-dont-mix.html. """ - pipeline_description = ' ! '.join([ - #'appsrc name=src uridecodebin name=uri', - 'uridecodebin name=uri', - 'volume name=volume', - 'autoaudiosink name=sink', - ]) - pipeline_description = 'playbin' - def __init__(self, core_queue, output_queue): super(GStreamerProcess, self).__init__() self.core_queue = core_queue self.output_queue = output_queue self.gst_pipeline = None - self.gst_bus = None - self.gst_bus_id = None - self.gst_uri_bin = None - self.gst_data_src = None - self.gst_volume = None def run_inside_try(self): self.setup() @@ -75,16 +62,12 @@ class GStreamerProcess(BaseProcess): messages_thread.daemon = True messages_thread.start() - self.gst_pipeline = gst.parse_launch(self.pipeline_description) - self.gst_data_src = self.gst_pipeline.get_by_name('src') - self.gst_uri_bin = self.gst_pipeline.get_by_name('uri') - self.gst_volume = self.gst_pipeline.get_by_name('volume') + self.gst_pipeline = gst.parse_launch('playbin') # Setup bus and message processor - self.gst_bus = self.gst_pipeline.get_bus() - self.gst_bus.add_signal_watch() - self.gst_bus_id = self.gst_bus.connect('message', - self.process_gst_message) + gst_bus = self.gst_pipeline.get_bus() + gst_bus.add_signal_watch() + gst_bus.connect('message', self.process_gst_message) def process_mopidy_message(self, message): """Process messages from the rest of Mopidy.""" @@ -136,11 +119,12 @@ class GStreamerProcess(BaseProcess): def deliver_data(self, caps_string, data): """Deliver audio data to be played""" + data_src = self.gst_pipeline.get_by_name('src') caps = gst.caps_from_string(caps_string) buffer_ = gst.Buffer(buffer(data)) buffer_.set_caps(caps) - self.gst_data_src.set_property('caps', caps) - self.gst_data_src.emit('push-buffer', buffer_) + data_src.set_property('caps', caps) + data_src.emit('push-buffer', buffer_) def end_of_data_stream(self): """ @@ -149,7 +133,7 @@ class GStreamerProcess(BaseProcess): We will get a GStreamer message when the stream playback reaches the token, and can then do any end-of-stream related tasks. """ - self.gst_data_src.emit('end-of-stream') + self.gst_pipeline.get_by_name('src').emit('end-of-stream') def set_state(self, state_name): """ @@ -184,14 +168,14 @@ class GStreamerProcess(BaseProcess): def get_volume(self): """Get volume in range [0..100]""" return 0 - gst_volume = self.gst_volume.get_property('volume') - return int(gst_volume * 100) + gst_volume = self.gst_pipeline.get_by_name('volume') + return int(gst_volume.get_property('volume') * 100) def set_volume(self, volume): return """Set volume in range [0..100]""" - gst_volume = volume / 100.0 - self.gst_volume.set_property('volume', gst_volume) + gst_volume = self.gst_pipeline.get_by_name('volume') + gst_volume.set_property('volume', volume / 100.0) def set_position(self, position): self.gst_pipeline.seek_simple(gst.Format(gst.FORMAT_TIME), From b2b169eaa9a972f223079b73c0cf6b9413388627 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 14 Aug 2010 23:54:54 +0200 Subject: [PATCH 11/30] Use uridecodebin with auto connecting to play local music --- mopidy/outputs/gstreamer.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index b2012626..da4cbd20 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -47,6 +47,7 @@ class GStreamerProcess(BaseProcess): self.core_queue = core_queue self.output_queue = output_queue self.gst_pipeline = None + self.gst_decoder = None def run_inside_try(self): self.setup() @@ -62,13 +63,26 @@ class GStreamerProcess(BaseProcess): messages_thread.daemon = True messages_thread.start() - self.gst_pipeline = gst.parse_launch('playbin') + self.gst_pipeline = gst.parse_launch(' ! '.join([ + 'volume name=volume', + 'autoaudiosink' + ])) + + decode_bin = gst.element_factory_make('uridecodebin', 'uri') + decode_bin.connect('pad-added', self.process_new_pad, 'volume') + app_src = gst.element_factory_make('appsrc', 'src') + + self.gst_pipeline.add(decode_bin) + self.gst_pipeline.add(app_src) # Setup bus and message processor gst_bus = self.gst_pipeline.get_bus() gst_bus.add_signal_watch() gst_bus.connect('message', self.process_gst_message) + def process_new_pad(self, source, pad, target): + pad.link(self.gst_pipeline.get_by_name(target).get_pad('sink')) + def process_mopidy_message(self, message): """Process messages from the rest of Mopidy.""" if message['command'] == 'play_uri': @@ -114,7 +128,7 @@ class GStreamerProcess(BaseProcess): def play_uri(self, uri): """Play audio at URI""" self.set_state('READY') - self.gst_pipeline.set_property('uri', uri) + self.gst_pipeline.get_by_name('uri').set_property('uri', uri) return self.set_state('PLAYING') def deliver_data(self, caps_string, data): From 869e3cbc6796f3ea11ebc3938fc24abc819a472e Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 14 Aug 2010 23:55:21 +0200 Subject: [PATCH 12/30] Use alsasink ase there seems to be a bug with using pulse in this maner --- mopidy/outputs/gstreamer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index da4cbd20..cec27db0 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -65,7 +65,7 @@ class GStreamerProcess(BaseProcess): self.gst_pipeline = gst.parse_launch(' ! '.join([ 'volume name=volume', - 'autoaudiosink' + 'alsasink' ])) decode_bin = gst.element_factory_make('uridecodebin', 'uri') From fbe75e799a68892f0a9477ec85306f7e9cd33588 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 15 Aug 2010 00:25:39 +0200 Subject: [PATCH 13/30] Remove unused property --- mopidy/outputs/gstreamer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index cec27db0..292b5a2e 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -47,7 +47,6 @@ class GStreamerProcess(BaseProcess): self.core_queue = core_queue self.output_queue = output_queue self.gst_pipeline = None - self.gst_decoder = None def run_inside_try(self): self.setup() From da763be9d705b24abdcc0b81ae946f62ba8d95ca Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 15 Aug 2010 01:05:30 +0200 Subject: [PATCH 14/30] Add multiqueue to pipeline so appsrc in theory should work again --- mopidy/outputs/gstreamer.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index 292b5a2e..0b694aaf 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -63,24 +63,31 @@ class GStreamerProcess(BaseProcess): messages_thread.start() self.gst_pipeline = gst.parse_launch(' ! '.join([ + 'multiqueue name=queue', 'volume name=volume', 'alsasink' ])) decode_bin = gst.element_factory_make('uridecodebin', 'uri') - decode_bin.connect('pad-added', self.process_new_pad, 'volume') + decode_bin.connect('pad-added', self.process_new_pad) app_src = gst.element_factory_make('appsrc', 'src') + queue = self.gst_pipeline.get_by_name('queue') self.gst_pipeline.add(decode_bin) self.gst_pipeline.add(app_src) + self.gst_uri_pad = queue.get_request_pad('sink0') + self.gst_src_pad = queue.get_request_pad('sink1') + + app_src.get_pad('src').link(self.gst_src_pad) + # Setup bus and message processor gst_bus = self.gst_pipeline.get_bus() gst_bus.add_signal_watch() gst_bus.connect('message', self.process_gst_message) - def process_new_pad(self, source, pad, target): - pad.link(self.gst_pipeline.get_by_name(target).get_pad('sink')) + def process_new_pad(self, source, pad): + pad.link(self.gst_uri_pad) def process_mopidy_message(self, message): """Process messages from the rest of Mopidy.""" From 83fbc8ea221f57cec665ff9e28aeed7279e4a46e Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 15 Aug 2010 03:03:32 +0200 Subject: [PATCH 15/30] Start preprations for supporting switching between sources --- mopidy/outputs/gstreamer.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index 0b694aaf..aedc0eaa 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -47,6 +47,7 @@ class GStreamerProcess(BaseProcess): self.core_queue = core_queue self.output_queue = output_queue self.gst_pipeline = None + self.gst_target = None def run_inside_try(self): self.setup() @@ -63,23 +64,15 @@ class GStreamerProcess(BaseProcess): messages_thread.start() self.gst_pipeline = gst.parse_launch(' ! '.join([ - 'multiqueue name=queue', 'volume name=volume', 'alsasink' ])) + self.gst_target = self.gst_pipeline.get_by_name('volume') + decode_bin = gst.element_factory_make('uridecodebin', 'uri') decode_bin.connect('pad-added', self.process_new_pad) - app_src = gst.element_factory_make('appsrc', 'src') - queue = self.gst_pipeline.get_by_name('queue') - self.gst_pipeline.add(decode_bin) - self.gst_pipeline.add(app_src) - - self.gst_uri_pad = queue.get_request_pad('sink0') - self.gst_src_pad = queue.get_request_pad('sink1') - - app_src.get_pad('src').link(self.gst_src_pad) # Setup bus and message processor gst_bus = self.gst_pipeline.get_bus() @@ -87,7 +80,7 @@ class GStreamerProcess(BaseProcess): gst_bus.connect('message', self.process_gst_message) def process_new_pad(self, source, pad): - pad.link(self.gst_uri_pad) + pad.link(self.gst_target.get_pad('sink')) def process_mopidy_message(self, message): """Process messages from the rest of Mopidy.""" From 151d5db56684159cd37f4d3fe7b244669787114c Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 17 Aug 2010 01:32:06 +0200 Subject: [PATCH 16/30] Setup output based on which backend we are running --- mopidy/outputs/gstreamer.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index aedc0eaa..ff732448 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -8,6 +8,7 @@ import gst import logging import threading +from mopidy import settings from mopidy.process import BaseProcess, unpickle_connection logger = logging.getLogger('mopidy.outputs.gstreamer') @@ -47,7 +48,6 @@ class GStreamerProcess(BaseProcess): self.core_queue = core_queue self.output_queue = output_queue self.gst_pipeline = None - self.gst_target = None def run_inside_try(self): self.setup() @@ -68,19 +68,24 @@ class GStreamerProcess(BaseProcess): 'alsasink' ])) - self.gst_target = self.gst_pipeline.get_by_name('volume') + pad = self.gst_pipeline.get_by_name('volume').get_pad('sink') - decode_bin = gst.element_factory_make('uridecodebin', 'uri') - decode_bin.connect('pad-added', self.process_new_pad) - self.gst_pipeline.add(decode_bin) + if settings.BACKENDS[0] == 'mopidy.backends.local.LocalBackend': + uri_bin = gst.element_factory_make('uridecodebin', 'uri') + uri_bin.connect('pad-added', self.process_new_pad, pad) + self.gst_pipeline.add(uri_bin) + else: + app_src = gst.element_factory_make('appsrc', 'src') + self.gst_pipeline.add(app_src) + app_src.get_pad('src').link(pad) # Setup bus and message processor gst_bus = self.gst_pipeline.get_bus() gst_bus.add_signal_watch() gst_bus.connect('message', self.process_gst_message) - def process_new_pad(self, source, pad): - pad.link(self.gst_target.get_pad('sink')) + def process_new_pad(self, source, pad, target_pad): + pad.link(target_pad) def process_mopidy_message(self, message): """Process messages from the rest of Mopidy.""" From 4000fda43ff05e6591d5c921e453b9b09624bca2 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 17 Aug 2010 01:32:50 +0200 Subject: [PATCH 17/30] Reable get/set volume in output --- mopidy/outputs/gstreamer.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index ff732448..2f559f4f 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -185,12 +185,10 @@ class GStreamerProcess(BaseProcess): def get_volume(self): """Get volume in range [0..100]""" - return 0 gst_volume = self.gst_pipeline.get_by_name('volume') return int(gst_volume.get_property('volume') * 100) def set_volume(self, volume): - return """Set volume in range [0..100]""" gst_volume = self.gst_pipeline.get_by_name('volume') gst_volume.set_property('volume', volume / 100.0) @@ -207,5 +205,3 @@ class GStreamerProcess(BaseProcess): except gst.QueryError, e: logger.error('time_position failed: %s', e) return 0 - - From 77550ce2914f0eacc948b1fdde555e1dee14a720 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 17 Aug 2010 01:33:10 +0200 Subject: [PATCH 18/30] Use autoaudiosink --- mopidy/outputs/gstreamer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index 2f559f4f..898aa42b 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -65,7 +65,7 @@ class GStreamerProcess(BaseProcess): self.gst_pipeline = gst.parse_launch(' ! '.join([ 'volume name=volume', - 'alsasink' + 'autoaudiosink' ])) pad = self.gst_pipeline.get_by_name('volume').get_pad('sink') From a969daf046932cd77b353b647a58a1750e2d4160 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 18 Aug 2010 00:57:06 +0200 Subject: [PATCH 19/30] Add audioconvert to output pipeline to avoid gst.LinkError: --- mopidy/outputs/gstreamer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index 898aa42b..982c7a79 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -64,11 +64,12 @@ class GStreamerProcess(BaseProcess): messages_thread.start() self.gst_pipeline = gst.parse_launch(' ! '.join([ + 'audioconvert name=convert', 'volume name=volume', 'autoaudiosink' ])) - pad = self.gst_pipeline.get_by_name('volume').get_pad('sink') + pad = self.gst_pipeline.get_by_name('convert').get_pad('sink') if settings.BACKENDS[0] == 'mopidy.backends.local.LocalBackend': uri_bin = gst.element_factory_make('uridecodebin', 'uri') From 94ef06ac78e20910699b6e13db25a91028a6ab00 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 18 Aug 2010 23:41:13 +0200 Subject: [PATCH 20/30] Nuke local settings to ensure test consistency --- tests/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/__init__.py b/tests/__init__.py index b08afb01..c8618f3f 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -9,6 +9,11 @@ except ImportError: class SkipTest(Exception): pass +from mopidy import settings + +# Nuke any local settings to ensure same test env all over +settings.local.clear() + def data_folder(name): folder = os.path.dirname(__file__) folder = os.path.join(folder, 'data') From a20251daac86aef3ac5387d046562b5b55b28f83 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 18 Aug 2010 23:41:44 +0200 Subject: [PATCH 21/30] Ensure that backend tests setup right backend in setting --- tests/backends/local/backend_test.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/backends/local/backend_test.py b/tests/backends/local/backend_test.py index aff84658..b95c6dde 100644 --- a/tests/backends/local/backend_test.py +++ b/tests/backends/local/backend_test.py @@ -27,6 +27,15 @@ class LocalCurrentPlaylistControllerTest(BaseCurrentPlaylistControllerTest, backend_class = LocalBackend + def setUp(self): + self.original_backends = settings.BACKENDS + settings.BACKENDS = ('mopidy.backends.local.LocalBackend',) + super(LocalCurrentPlaylistControllerTest, self).setUp() + + def tearDown(self): + super(LocalCurrentPlaylistControllerTest, self).tearDown() + settings.BACKENDS = settings.original_backends + class LocalPlaybackControllerTest(BasePlaybackControllerTest, unittest.TestCase): @@ -35,10 +44,17 @@ class LocalPlaybackControllerTest(BasePlaybackControllerTest, backend_class = LocalBackend def setUp(self): + self.original_backends = settings.BACKENDS + settings.BACKENDS = ('mopidy.backends.local.LocalBackend',) + super(LocalPlaybackControllerTest, self).setUp() # Two tests does not work at all when using the fake sink #self.backend.playback.use_fake_sink() + def tearDown(self): + super(LocalPlaybackControllerTest, self).tearDown() + settings.BACKENDS = settings.original_backends + def add_track(self, path): uri = path_to_uri(data_folder(path)) track = Track(uri=uri, length=4464) From cf2408913775e416ea43dfc48e2a94939ed7d4e9 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Thu, 19 Aug 2010 00:52:42 +0200 Subject: [PATCH 22/30] Renable some output tests --- tests/outputs/gstreamer_test.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/outputs/gstreamer_test.py b/tests/outputs/gstreamer_test.py index e1081ca0..db15c952 100644 --- a/tests/outputs/gstreamer_test.py +++ b/tests/outputs/gstreamer_test.py @@ -1,6 +1,7 @@ import multiprocessing import unittest +from mopidy import settings from mopidy.outputs.gstreamer import GStreamerOutput from mopidy.process import pickle_connection from mopidy.utils.path import path_to_uri @@ -9,6 +10,8 @@ from tests import data_folder, SkipTest class GStreamerOutputTest(unittest.TestCase): def setUp(self): + self.original_backends = settings.BACKENDS + settings.BACKENDS = ('mopidy.backends.local.LocalBackend',) self.song_uri = path_to_uri(data_folder('song1.wav')) self.output_queue = multiprocessing.Queue() self.core_queue = multiprocessing.Queue() @@ -16,6 +19,7 @@ class GStreamerOutputTest(unittest.TestCase): def tearDown(self): self.output.destroy() + settings.BACKENDS = settings.original_backends def send_recv(self, message): (my_end, other_end) = multiprocessing.Pipe() @@ -24,35 +28,30 @@ class GStreamerOutputTest(unittest.TestCase): my_end.poll(None) return my_end.recv() + def send(self, message): self.output_queue.put(message) - @SkipTest def test_play_uri_existing_file(self): message = {'command': 'play_uri', 'uri': self.song_uri} self.assertEqual(True, self.send_recv(message)) - @SkipTest def test_play_uri_non_existing_file(self): message = {'command': 'play_uri', 'uri': self.song_uri + 'bogus'} self.assertEqual(False, self.send_recv(message)) - @SkipTest def test_default_get_volume_result(self): message = {'command': 'get_volume'} self.assertEqual(100, self.send_recv(message)) - @SkipTest def test_set_volume(self): self.send({'command': 'set_volume', 'volume': 50}) self.assertEqual(50, self.send_recv({'command': 'get_volume'})) - @SkipTest def test_set_volume_to_zero(self): self.send({'command': 'set_volume', 'volume': 0}) self.assertEqual(0, self.send_recv({'command': 'get_volume'})) - @SkipTest def test_set_volume_to_one_hundred(self): self.send({'command': 'set_volume', 'volume': 100}) self.assertEqual(100, self.send_recv({'command': 'get_volume'})) From 222bbf4998280a2c28c2594769e5d1e299ae7586 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Thu, 19 Aug 2010 00:56:24 +0200 Subject: [PATCH 23/30] Only block set state when handling async changes --- mopidy/outputs/gstreamer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index 982c7a79..61b4f19b 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -173,13 +173,13 @@ class GStreamerProcess(BaseProcess): """ result = self.gst_pipeline.set_state( getattr(gst, 'STATE_' + state_name)) - # Block until state change has occured, required for at least the local - # backends seek functionality. (Optional solution is to block in set - # position to ensure that change occours) - self.gst_pipeline.get_state() if result == gst.STATE_CHANGE_FAILURE: logger.warning('Setting GStreamer state to %s: failed', state_name) return False + elif result == gst.STATE_CHANGE_ASYNC: + # Block until ready + self.gst_pipeline.get_state() + return True else: logger.debug('Setting GStreamer state to %s: OK', state_name) return True From 1132f554c779fb0401d3fe14e06b5dfc7d22e763 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Thu, 19 Aug 2010 01:01:32 +0200 Subject: [PATCH 24/30] Only block after seek before starting to play to ensure that seek has happened --- mopidy/outputs/gstreamer.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index 61b4f19b..c1435c2c 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -176,10 +176,6 @@ class GStreamerProcess(BaseProcess): if result == gst.STATE_CHANGE_FAILURE: logger.warning('Setting GStreamer state to %s: failed', state_name) return False - elif result == gst.STATE_CHANGE_ASYNC: - # Block until ready - self.gst_pipeline.get_state() - return True else: logger.debug('Setting GStreamer state to %s: OK', state_name) return True @@ -197,6 +193,7 @@ class GStreamerProcess(BaseProcess): def set_position(self, position): self.gst_pipeline.seek_simple(gst.Format(gst.FORMAT_TIME), gst.SEEK_FLAG_FLUSH, position * gst.MSECOND) + self.gst_pipeline.get_state() # Block until state change self.set_state('PLAYING') def get_position(self): From 01b854b8913b5bfb61621bf5815c266b1ce1dc3c Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Thu, 19 Aug 2010 01:34:26 +0200 Subject: [PATCH 25/30] Flushing seek takes care of starting pipeline for us --- mopidy/outputs/gstreamer.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index c1435c2c..655487d5 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -193,8 +193,7 @@ class GStreamerProcess(BaseProcess): def set_position(self, position): self.gst_pipeline.seek_simple(gst.Format(gst.FORMAT_TIME), gst.SEEK_FLAG_FLUSH, position * gst.MSECOND) - self.gst_pipeline.get_state() # Block until state change - self.set_state('PLAYING') + self.gst_pipeline.get_state() def get_position(self): try: From 42bd13f30a159ea6601c1eaed83efab208f85a26 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Thu, 19 Aug 2010 19:50:08 +0200 Subject: [PATCH 26/30] Ensure that seek triggers playing --- mopidy/backends/local/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index 5199a9e1..d7257cad 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -63,6 +63,8 @@ class LocalPlaybackController(BasePlaybackController): def _seek(self, time_position): self._send({'command': 'set_position', 'position': time_position}) + if self.state == self.STOPPED: + self._set_state('PLAYING') @property def time_position(self): From ef03bbe19e2a49ff4a40098d0fecac7da032ab71 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 20 Aug 2010 00:17:08 +0200 Subject: [PATCH 27/30] Add tests to check return value of seek --- tests/backends/base.py | 44 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/tests/backends/base.py b/tests/backends/base.py index 005aadae..e6c10a5a 100644 --- a/tests/backends/base.py +++ b/tests/backends/base.py @@ -794,7 +794,8 @@ class BasePlaybackControllerTest(object): @populate_playlist def test_end_of_track_callback_gets_called(self): self.playback.play() - self.playback.seek(self.tracks[0].length - 10) + result = self.playback.seek(self.tracks[0].length - 10) + self.assert_(result, 'Seek failed') message = self.core_queue.get() self.assertEqual('end_of_track', message['command']) @@ -879,11 +880,20 @@ class BasePlaybackControllerTest(object): @populate_playlist def test_seek_when_stopped(self): + result = self.playback.seek(1000) + self.assert_(result, 'Seek return value was %s' % result) + + @populate_playlist + def test_seek_when_stopped_updates_position(self): self.playback.seek(1000) position = self.playback.time_position self.assert_(position >= 990, position) def test_seek_on_empty_playlist(self): + result = self.playback.seek(0) + self.assert_(not result, 'Seek return value was %s' % result) + + def test_seek_on_empty_playlist_updates_position(self): self.playback.seek(0) self.assertEqual(self.playback.state, self.playback.STOPPED) @@ -894,6 +904,12 @@ class BasePlaybackControllerTest(object): @populate_playlist def test_seek_when_playing(self): + self.playback.play() + result = self.playback.seek(self.tracks[0].length - 1000) + self.assert_(result, 'Seek return value was %s' % result) + + @populate_playlist + def test_seek_when_playing_updates_position(self): length = self.backend.current_playlist.tracks[0].length self.playback.play() self.playback.seek(length - 1000) @@ -902,6 +918,13 @@ class BasePlaybackControllerTest(object): @populate_playlist def test_seek_when_paused(self): + self.playback.play() + self.playback.pause() + result = self.playback.seek(self.tracks[0].length - 1000) + self.assert_(result, 'Seek return value was %s' % result) + + @populate_playlist + def test_seek_when_paused_updates_position(self): length = self.backend.current_playlist.tracks[0].length self.playback.play() self.playback.pause() @@ -918,6 +941,13 @@ class BasePlaybackControllerTest(object): @populate_playlist def test_seek_beyond_end_of_song(self): + raise SkipTest # FIXME need to decide return value + self.playback.play() + result = self.playback.seek(self.tracks[0].length*100) + self.assert_(not result, 'Seek return value was %s' % result) + + @populate_playlist + def test_seek_beyond_end_of_song_jumps_to_next_song(self): self.playback.play() self.playback.seek(self.tracks[0].length*100) self.assertEqual(self.playback.current_track, self.tracks[1]) @@ -930,17 +960,19 @@ class BasePlaybackControllerTest(object): @populate_playlist def test_seek_beyond_start_of_song(self): + raise SkipTest # FIXME need to decide return value + self.playback.play() + result = self.playback.seek(-1000) + self.assert_(not result, 'Seek return value was %s' % result) + + @populate_playlist + def test_seek_beyond_start_of_song_update_postion(self): self.playback.play() self.playback.seek(-1000) position = self.playback.time_position self.assert_(position >= 0, position) self.assertEqual(self.playback.state, self.playback.PLAYING) - @populate_playlist - def test_seek_return_value(self): - self.playback.play() - self.assertEqual(self.playback.seek(0), None) - @populate_playlist def test_stop_when_stopped(self): self.playback.stop() From dbdfb3a8c776e8e37b31e9efc3d850b8a13c5c7a Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 20 Aug 2010 00:17:34 +0200 Subject: [PATCH 28/30] Fix seek return value tests --- mopidy/backends/base/playback.py | 5 ++++- mopidy/backends/local/__init__.py | 5 ++--- mopidy/outputs/gstreamer.py | 10 +++++++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/mopidy/backends/base/playback.py b/mopidy/backends/base/playback.py index d1acc05a..2b5d7478 100644 --- a/mopidy/backends/base/playback.py +++ b/mopidy/backends/base/playback.py @@ -440,7 +440,10 @@ class BasePlaybackController(object): :param time_position: time position in milliseconds :type time_position: int + :rtype: :class:`True` if successful, else :class:`False` """ + # FIXME I think return value is only realy usefull for internal + # testing, as such it should probably not be exposed in api. if self.state == self.STOPPED: self.play() elif self.state == self.PAUSED: @@ -455,7 +458,7 @@ class BasePlaybackController(object): self._play_time_started = self._current_wall_time self._play_time_accumulated = time_position - self._seek(time_position) + return self._seek(time_position) def _seek(self, time_position): """ diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index d7257cad..bd8a4301 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -62,9 +62,8 @@ class LocalPlaybackController(BasePlaybackController): return self._set_state('PLAYING') def _seek(self, time_position): - self._send({'command': 'set_position', 'position': time_position}) - if self.state == self.STOPPED: - self._set_state('PLAYING') + return self._send_recv({'command': 'set_position', + 'position': time_position}) @property def time_position(self): diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index 655487d5..de5b7734 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -109,7 +109,9 @@ class GStreamerProcess(BaseProcess): elif message['command'] == 'set_volume': self.set_volume(message['volume']) elif message['command'] == 'set_position': - self.set_position(message['position']) + response = self.set_position(message['position']) + connection = unpickle_connection(message['reply_to']) + connection.send(response) elif message['command'] == 'get_position': response = self.get_position() connection = unpickle_connection(message['reply_to']) @@ -191,9 +193,11 @@ class GStreamerProcess(BaseProcess): gst_volume.set_property('volume', volume / 100.0) def set_position(self, position): - self.gst_pipeline.seek_simple(gst.Format(gst.FORMAT_TIME), + self.gst_pipeline.get_state() # block until state changes are done + handeled= self.gst_pipeline.seek_simple(gst.Format(gst.FORMAT_TIME), gst.SEEK_FLAG_FLUSH, position * gst.MSECOND) - self.gst_pipeline.get_state() + self.gst_pipeline.get_state() # block until seek is done + return handeled def get_position(self): try: From 7f25b0b5149339dac8cf688831fe6f4093a14fab Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 20 Aug 2010 00:56:28 +0200 Subject: [PATCH 29/30] Introduce GSTREAMER_AUDIO_SINK setting to control which sink we use --- mopidy/outputs/gstreamer.py | 2 +- mopidy/settings.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index de5b7734..8f321976 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -66,7 +66,7 @@ class GStreamerProcess(BaseProcess): self.gst_pipeline = gst.parse_launch(' ! '.join([ 'audioconvert name=convert', 'volume name=volume', - 'autoaudiosink' + settings.GSTREAMER_AUDIO_SINK, ])) pad = self.gst_pipeline.get_by_name('convert').get_pad('sink') diff --git a/mopidy/settings.py b/mopidy/settings.py index c9e3606e..3198ed93 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -167,3 +167,8 @@ SPOTIFY_USERNAME = u'' #: #: Used by :mod:`mopidy.backends.libspotify`. SPOTIFY_PASSWORD = u'' + +#: Which GStreamer audio sink to use in output pipeline. +#: +#: Default: autoaudiosink +GSTREAMER_AUDIO_SINK = u'autoaudiosink' From df22256f096ef3e541d1a5a07ca9250668d2647a Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 21 Aug 2010 23:25:38 +0200 Subject: [PATCH 30/30] Review gstreamer-local-backend branch --- mopidy/backends/base/playback.py | 4 ++-- mopidy/backends/local/__init__.py | 9 ++++----- mopidy/outputs/gstreamer.py | 2 +- mopidy/settings.py | 12 +++++++----- tests/backends/base.py | 2 +- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/mopidy/backends/base/playback.py b/mopidy/backends/base/playback.py index 2b5d7478..933424ad 100644 --- a/mopidy/backends/base/playback.py +++ b/mopidy/backends/base/playback.py @@ -442,8 +442,8 @@ class BasePlaybackController(object): :type time_position: int :rtype: :class:`True` if successful, else :class:`False` """ - # FIXME I think return value is only realy usefull for internal - # testing, as such it should probably not be exposed in api. + # FIXME I think return value is only really useful for internal + # testing, as such it should probably not be exposed in API. if self.state == self.STOPPED: self.play() elif self.state == self.PAUSED: diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index bd8a4301..e9e86f34 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -1,14 +1,13 @@ -import logging -import os import glob -import shutil +import logging import multiprocessing +import os +import shutil from mopidy import settings from mopidy.backends.base import * from mopidy.models import Playlist, Track, Album -from mopidy import settings -from mopidy.process import pickle_connection +from mopidy.utils.process import pickle_connection from .translator import parse_m3u, parse_mpd_tag_cache diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index 667b815b..453747d6 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -194,7 +194,7 @@ class GStreamerProcess(BaseProcess): def set_position(self, position): self.gst_pipeline.get_state() # block until state changes are done - handeled= self.gst_pipeline.seek_simple(gst.Format(gst.FORMAT_TIME), + handeled = self.gst_pipeline.seek_simple(gst.Format(gst.FORMAT_TIME), gst.SEEK_FLAG_FLUSH, position * gst.MSECOND) self.gst_pipeline.get_state() # block until seek is done return handeled diff --git a/mopidy/settings.py b/mopidy/settings.py index 4268f059..699eb16a 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -51,6 +51,13 @@ DUMP_LOG_FILENAME = u'dump.log' #: Currently only the first frontend in the list is used. FRONTENDS = (u'mopidy.frontends.mpd.MpdFrontend',) +#: Which GStreamer audio sink to use in :mod:`mopidy.outputs.gstreamer`. +#: +#: Default:: +#: +#: GSTREAMER_AUDIO_SINK = u'autoaudiosink' +GSTREAMER_AUDIO_SINK = u'autoaudiosink' + #: Path to folder with local music. #: #: Used by :mod:`mopidy.backends.local`. @@ -163,8 +170,3 @@ SPOTIFY_USERNAME = u'' #: #: Used by :mod:`mopidy.backends.libspotify`. SPOTIFY_PASSWORD = u'' - -#: Which GStreamer audio sink to use in output pipeline. -#: -#: Default: autoaudiosink -GSTREAMER_AUDIO_SINK = u'autoaudiosink' diff --git a/tests/backends/base.py b/tests/backends/base.py index e6c10a5a..e9b78453 100644 --- a/tests/backends/base.py +++ b/tests/backends/base.py @@ -1,9 +1,9 @@ +import multiprocessing import os import random import shutil import tempfile import time -import multiprocessing from mopidy import settings from mopidy.mixers.dummy import DummyMixer