audio: Add tags changed event to audio.

Current version simply emits the keys of the changed tags to the audio
listener. Following change will add support for storing the actual data.
This commit is contained in:
Thomas Adamcik 2014-12-17 23:59:54 +01:00
parent de6bd63481
commit 6c62252919
5 changed files with 57 additions and 17 deletions

View File

@ -303,6 +303,8 @@ class _Handler(object):
self.on_warning(*msg.parse_warning())
elif msg.type == gst.MESSAGE_ASYNC_DONE:
self.on_async_done()
elif msg.type == gst.MESSAGE_TAG:
self.on_tag(msg.parse_tag())
elif msg.type == gst.MESSAGE_ELEMENT:
if gst.pbutils.is_missing_plugin_message(msg):
self.on_missing_plugin(_get_missing_description(msg),
@ -387,6 +389,12 @@ class _Handler(object):
def on_async_done(self):
gst_logger.debug('Got async-done.')
def on_tag(self, taglist):
# TODO: store current tags and reset on stream changes.
tags = taglist.keys()
logger.debug('Audio event: tags_changed(tags=%r)', tags)
AudioListener.send('tags_changed', tags=tags)
def on_missing_plugin(self, msg):
desc = gst.pbutils.missing_plugin_message_get_description(msg)
debug = gst.pbutils.missing_plugin_message_get_installer_detail(msg)

View File

@ -91,6 +91,9 @@ class DummyAudio(pykka.ThreadingActor):
AudioListener.send('state_changed', old_state=old_state,
new_state=new_state, target_state=None)
if new_state == PlaybackState.PLAYING:
AudioListener.send('tags_changed', tags=[])
return self._state_change_result
def trigger_fake_playback_failure(self):

View File

@ -75,3 +75,21 @@ class AudioListener(listener.Listener):
field or :class:`None` if this is a final state.
"""
pass
def tags_changed(self, tags):
"""
Called whenever the current audio streams tags changes.
This event signals that some track metadata has been updated. This can
be metadata such as artists, titles, organization, or details about the
actual audio such as bit-rates, numbers of channels etc.
For the available tag keys please refer to GStreamer documenation for
tags.
*MAY* be implemented by actor.
:param tags: The tags that have just been updated.
:type tags: :class:`set` of strings
"""
pass

View File

@ -42,7 +42,7 @@ class BaseTest(unittest.TestCase):
audio_class = audio.Audio
def setUp(self):
def setUp(self): # noqa
config = {
'audio': {
'mixer': 'foomixer',
@ -57,7 +57,7 @@ class BaseTest(unittest.TestCase):
self.song_uri = path_to_uri(path_to_data_dir('song1.wav'))
self.audio = self.audio_class.start(config=config, mixer=None).proxy()
def tearDown(self):
def tearDown(self): # noqa
pykka.ActorRegistry.stop_all()
def possibly_trigger_fake_playback_error(self):
@ -135,7 +135,7 @@ class AudioDummyTest(DummyMixin, AudioTest):
@mock.patch.object(audio.AudioListener, 'send')
class AudioEventTest(BaseTest):
def setUp(self):
def setUp(self): # noqa
super(AudioEventTest, self).setUp()
self.audio.enable_sync_handler().get()
@ -292,6 +292,14 @@ class AudioEventTest(BaseTest):
call = mock.call('position_changed', position=2000)
self.assertIn(call, send_mock.call_args_list)
def test_tags_changed_on_playback(self, send_mock):
self.audio.prepare_change()
self.audio.set_uri(self.uris[0])
self.audio.start_playback()
self.audio.wait_for_state_change().get()
send_mock.assert_any_call('tags_changed', tags=mock.ANY)
# Unlike the other events, having the state changed done is not
# enough to ensure our event is called. So we setup a threading
# event that we can wait for with a timeout while the track playback
@ -361,20 +369,20 @@ class AudioEventTest(BaseTest):
if not done.wait(timeout=1.0):
self.fail('EOS not received')
excepted = [
('position_changed', {'position': 0}),
('stream_changed', {'uri': self.uris[0]}),
('state_changed', {'old_state': PlaybackState.STOPPED,
'new_state': PlaybackState.PLAYING,
'target_state': None}),
('position_changed', {'position': 0}),
('stream_changed', {'uri': self.uris[1]}),
('reached_end_of_stream', {})]
self.assertEqual(excepted, events)
# Check that both uris got played
self.assertIn(('stream_changed', {'uri': self.uris[0]}), events)
self.assertIn(('stream_changed', {'uri': self.uris[1]}), events)
# Check that events counts check out.
keys = [k for k, v in events]
self.assertEqual(2, keys.count('stream_changed'))
self.assertEqual(2, keys.count('position_changed'))
self.assertEqual(1, keys.count('state_changed'))
self.assertEqual(1, keys.count('reached_end_of_stream'))
class AudioDummyEventTest(DummyMixin, AudioEventTest):
pass
"""Exercise the AudioEventTest against our mock audio classes."""
# TODO: move to mixer tests...
@ -399,7 +407,7 @@ class MixerTest(BaseTest):
class AudioStateTest(unittest.TestCase):
def setUp(self):
def setUp(self): # noqa
self.audio = audio.Audio(config=None, mixer=None)
def test_state_starts_as_stopped(self):
@ -444,7 +452,7 @@ class AudioStateTest(unittest.TestCase):
class AudioBufferingTest(unittest.TestCase):
def setUp(self):
def setUp(self): # noqa
self.audio = audio.Audio(config=None, mixer=None)
self.audio._playbin = mock.Mock(spec=['set_state'])

View File

@ -8,7 +8,7 @@ from mopidy import audio
class AudioListenerTest(unittest.TestCase):
def setUp(self):
def setUp(self): # noqa
self.listener = audio.AudioListener()
def test_on_event_forwards_to_specific_handler(self):
@ -32,3 +32,6 @@ class AudioListenerTest(unittest.TestCase):
def test_listener_has_default_impl_for_position_changed(self):
self.listener.position_changed(None)
def test_listener_has_default_impl_for_tags_changed(self):
self.listener.tags_changed([])