Extend output API with all methods needed for GStreamerOutput

This commit is contained in:
Stein Magnus Jodal 2010-08-24 01:06:20 +02:00
parent 9fe5874069
commit abce165aa3
4 changed files with 187 additions and 35 deletions

View File

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

View File

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

View File

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

View File

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