audio: Start storing the tags we find in audio

Adds a new get_currents_tags method for fetching the full set of current tags.
There are still some untested cases for this, and I also suspect we still want
some API refinements one core starts using this.
This commit is contained in:
Thomas Adamcik 2014-12-18 23:25:24 +01:00
parent bc347f1650
commit 983148a9a4
3 changed files with 88 additions and 6 deletions

View File

@ -372,6 +372,7 @@ class _Handler(object):
def on_end_of_stream(self):
gst_logger.debug('Got end-of-stream message.')
logger.debug('Audio event: reached_end_of_stream()')
self._audio._tags = {}
AudioListener.send('reached_end_of_stream')
def on_error(self, error, debug):
@ -390,10 +391,10 @@ class _Handler(object):
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)
tags = utils.convert_taglist(taglist)
self._audio._tags.update(tags)
logger.debug('Audio event: tags_changed(tags=%r)', tags.keys())
AudioListener.send('tags_changed', tags=tags.keys())
def on_missing_plugin(self, msg):
desc = gst.pbutils.missing_plugin_message_get_description(msg)
@ -440,6 +441,7 @@ class Audio(pykka.ThreadingActor):
self._config = config
self._target_state = gst.STATE_NULL
self._buffering = False
self._tags = {}
self._playbin = None
self._outputs = None
@ -546,6 +548,7 @@ class Audio(pykka.ThreadingActor):
:param uri: the URI to play
:type uri: string
"""
self._tags = {} # TODO: add test for this somehow
self._playbin.set_property('uri', uri)
def set_appsrc(
@ -733,6 +736,7 @@ class Audio(pykka.ThreadingActor):
# of faking it in the message handling when result=OK
return True
# TODO: bake this into setup appsrc perhaps?
def set_metadata(self, track):
"""
Set track metadata for currently playing song.
@ -763,5 +767,22 @@ class Audio(pykka.ThreadingActor):
taglist[gst.TAG_ALBUM] = track.album.name
event = gst.event_new_tag(taglist)
# TODO: check if we get this back on our own bus?
self._playbin.send_event(event)
gst_logger.debug('Sent tag event: track=%s', track.uri)
def get_current_tags(self):
"""
Get the currently playing media's tags.
If no tags have been found, or nothing is playing this returns an empty
dictionary. For each set of tags we collect a tags_changed event is
emitted with the keys of the changes tags. After such calls users may
call this function to get the updated values.
:rtype: {key: [values]} dict for the current media.
"""
# TODO: should this be a (deep) copy? most likely yes
# TODO: should we return None when stopped?
# TODO: support only fetching keys we care about?
return self._tags

View File

@ -21,9 +21,11 @@ class DummyAudio(pykka.ThreadingActor):
self._callback = None
self._uri = None
self._state_change_result = True
self._tags = {}
def set_uri(self, uri):
assert self._uri is None, 'prepare change not called before set'
self._tags = {}
self._uri = uri
def set_appsrc(self, *args, **kwargs):
@ -66,6 +68,9 @@ class DummyAudio(pykka.ThreadingActor):
def set_metadata(self, track):
pass
def get_current_tags(self):
return self._tags
def set_about_to_finish_callback(self, callback):
self._callback = callback
@ -92,7 +97,8 @@ class DummyAudio(pykka.ThreadingActor):
new_state=new_state, target_state=None)
if new_state == PlaybackState.PLAYING:
AudioListener.send('tags_changed', tags=[])
self._tags['audio-codec'] = [u'fake info...']
AudioListener.send('tags_changed', tags=['audio-codec'])
return self._state_change_result
@ -107,6 +113,7 @@ class DummyAudio(pykka.ThreadingActor):
self._callback()
if not self._uri or not self._callback:
self._tags = {}
AudioListener.send('reached_end_of_stream')
else:
AudioListener.send('position_changed', position=0)

View File

@ -338,7 +338,7 @@ class AudioEventTest(BaseTest):
if not event.wait(timeout=1.0):
self.fail('End of stream not reached within deadline')
# Make sure that gapless really works:
self.assertFalse(self.audio.get_current_tags().get())
def test_gapless(self, send_mock):
uris = self.uris[1:]
@ -380,6 +380,60 @@ class AudioEventTest(BaseTest):
self.assertEqual(1, keys.count('state_changed'))
self.assertEqual(1, keys.count('reached_end_of_stream'))
# TODO: test tag states within gaples
def test_current_tags_are_blank_to_begin_with(self, send_mock):
self.assertFalse(self.audio.get_current_tags().get())
def test_current_tags_blank_after_end_of_stream(self, send_mock):
done = threading.Event()
def send(name, **kwargs):
if name == 'reached_end_of_stream':
done.set()
send_mock.side_effect = send
self.audio.prepare_change()
self.audio.set_uri(self.uris[0])
self.audio.start_playback()
self.possibly_trigger_fake_about_to_finish()
self.audio.wait_for_state_change().get()
if not done.wait(timeout=1.0):
self.fail('EOS not received')
self.assertFalse(self.audio.get_current_tags().get())
def test_current_tags_stored(self, send_mock):
done = threading.Event()
tags = []
def callback():
tags.append(self.audio.get_current_tags().get())
def send(name, **kwargs):
if name == 'reached_end_of_stream':
done.set()
send_mock.side_effect = send
self.audio.set_about_to_finish_callback(callback).get()
self.audio.prepare_change()
self.audio.set_uri(self.uris[0])
self.audio.start_playback()
self.possibly_trigger_fake_about_to_finish()
self.audio.wait_for_state_change().get()
if not done.wait(timeout=1.0):
self.fail('EOS not received')
self.assertTrue(tags[0])
# TODO: test that we reset when we expect between songs
class AudioDummyEventTest(DummyMixin, AudioEventTest):
"""Exercise the AudioEventTest against our mock audio classes."""