From abce165aa3b356081503d032db16d92b6c2f6b37 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 24 Aug 2010 01:06:20 +0200 Subject: [PATCH] Extend output API with all methods needed for GStreamerOutput --- mopidy/outputs/base.py | 69 +++++++++++++++++++++++++++++++++ mopidy/outputs/dummy.py | 56 ++++++++++++++++++++++++-- mopidy/outputs/gstreamer.py | 48 +++++++++++++++++++++-- tests/outputs/gstreamer_test.py | 49 +++++++++++------------ 4 files changed, 187 insertions(+), 35 deletions(-) diff --git a/mopidy/outputs/base.py b/mopidy/outputs/base.py index b6b024a6..bb312323 100644 --- a/mopidy/outputs/base.py +++ b/mopidy/outputs/base.py @@ -17,3 +17,72 @@ class BaseOutput(object): def process_message(self, message): """Process messages with the output as destination.""" raise NotImplementedError + + def play_uri(self, uri): + """ + Play URI. + + :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. + + :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.""" + raise NotImplementedError + + def get_position(self): + """ + Get position in milliseconds. + + :rtype: int + """ + raise NotImplementedError + + def set_position(self, position): + """ + Set position in milliseconds. + + :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. + + :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. + + :rtype: int in range [0..100] + """ + raise NotImplementedError + + def set_volume(self, volume): + """ + Set volume level for software mixer. + + :param volume: the volume in the range [0..100] + :type volume: int + :rtype: :class:`True` if successful, else :class:`False` + """ + raise NotImplementedError diff --git a/mopidy/outputs/dummy.py b/mopidy/outputs/dummy.py index b6a42a29..a98e38af 100644 --- a/mopidy/outputs/dummy.py +++ b/mopidy/outputs/dummy.py @@ -15,6 +15,29 @@ class DummyOutput(BaseOutput): #: For testing. Contains all messages :meth:`process_message` has received. messages = [] + #: 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 start(self): self.start_called = True @@ -23,7 +46,32 @@ class DummyOutput(BaseOutput): def process_message(self, message): self.messages.append(message) - if 'reply_to' in message: - connection = unpickle_connection(message['reply_to']) - # FIXME This is too simple. Some callers expect something else. - connection.send(True) + + 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 diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index 85e86171..e2aa6436 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -11,7 +11,8 @@ import threading from mopidy import settings from mopidy.outputs.base import BaseOutput -from mopidy.utils.process import BaseProcess, unpickle_connection +from mopidy.utils.process import (BaseProcess, pickle_connection, + unpickle_connection) logger = logging.getLogger('mopidy.outputs.gstreamer') @@ -38,17 +39,56 @@ class GStreamerOutput(BaseOutput): self.process.terminate() def process_message(self, message): - """ - Processes messages with the GStreamer output as destination. - """ assert message['to'] == 'output', \ u'Message recipient must be "output".' self.output_queue.put(message) + def _send_recv(self, message): + (my_end, other_end) = multiprocessing.Pipe() + message['to'] = 'output' + message['reply_to'] = pickle_connection(other_end) + self.process_message(message) + my_end.poll(None) + return my_end.recv() + + def _send(self, message): + message['to'] = 'output' + self.process_message(message) + + def play_uri(self, uri): + return self._send_recv({'command': 'play_uri', 'uri': uri}) + + def deliver_data(self, capabilities, data): + return self._send({ + 'command': 'deliver_data', + 'caps': capabilities, + 'data': data, + }) + + def end_of_data_stream(self): + return self._send({'command': 'end_of_data_stream'}) + + def get_position(self): + return self._send_recv({'command': 'get_position'}) + + def set_position(self, position): + return self._send_recv({'command': 'set_position'}) + + def set_state(self, state): + return self._send_recv({'command': 'set_state', 'state': state}) + + def get_volume(self): + return self._send_recv({'command': 'get_volume'}) + + def set_volume(self, volume): + return self._send_recv({'command': 'set_volume', 'volume': volume}) + + class GStreamerMessagesThread(threading.Thread): def run(self): gobject.MainLoop().run() + class GStreamerProcess(BaseProcess): """ A process for all work related to GStreamer. diff --git a/tests/outputs/gstreamer_test.py b/tests/outputs/gstreamer_test.py index 15260ee0..3f18739d 100644 --- a/tests/outputs/gstreamer_test.py +++ b/tests/outputs/gstreamer_test.py @@ -28,44 +28,39 @@ class GStreamerOutputTest(unittest.TestCase): self.output.destroy() settings.BACKENDS = settings.original_backends - def send_recv(self, message): - (my_end, other_end) = multiprocessing.Pipe() - message.update({ - 'to': 'output', - 'reply_to': pickle_connection(other_end), - }) - self.output.process_message(message) - my_end.poll(None) - return my_end.recv() - - def send(self, message): - message.update({'to': 'output'}) - self.output.process_message(message) - def test_play_uri_existing_file(self): - message = {'command': 'play_uri', 'uri': self.song_uri} - self.assertEqual(True, self.send_recv(message)) + self.assertTrue(self.output.play_uri(self.song_uri)) def test_play_uri_non_existing_file(self): - message = {'command': 'play_uri', 'uri': self.song_uri + 'bogus'} - self.assertEqual(False, self.send_recv(message)) + self.assertFalse(self.output.play_uri(self.song_uri + 'bogus')) + + @SkipTest + def test_deliver_data(self): + pass # TODO + + @SkipTest + def test_end_of_data_stream(self): + pass # TODO def test_default_get_volume_result(self): - message = {'command': 'get_volume'} - self.assertEqual(100, self.send_recv(message)) + self.assertEqual(100, self.output.get_volume()) def test_set_volume(self): - self.send({'command': 'set_volume', 'volume': 50}) - self.assertEqual(50, self.send_recv({'command': 'get_volume'})) + self.assertTrue(self.output.set_volume(50)) + self.assertEqual(50, self.output.get_volume()) def test_set_volume_to_zero(self): - self.send({'command': 'set_volume', 'volume': 0}) - self.assertEqual(0, self.send_recv({'command': 'get_volume'})) + self.assertTrue(self.output.set_volume(0)) + self.assertEqual(0, self.output.get_volume()) def test_set_volume_to_one_hundred(self): - self.send({'command': 'set_volume', 'volume': 100}) - self.assertEqual(100, self.send_recv({'command': 'get_volume'})) + self.assertTrue(self.output.set_volume(100)) + self.assertEqual(100, self.output.get_volume()) @SkipTest def test_set_state(self): - raise NotImplementedError + pass # TODO + + @SkipTest + def test_set_position(self): + pass # TODO