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:
parent
bc347f1650
commit
983148a9a4
@ -372,6 +372,7 @@ class _Handler(object):
|
|||||||
def on_end_of_stream(self):
|
def on_end_of_stream(self):
|
||||||
gst_logger.debug('Got end-of-stream message.')
|
gst_logger.debug('Got end-of-stream message.')
|
||||||
logger.debug('Audio event: reached_end_of_stream()')
|
logger.debug('Audio event: reached_end_of_stream()')
|
||||||
|
self._audio._tags = {}
|
||||||
AudioListener.send('reached_end_of_stream')
|
AudioListener.send('reached_end_of_stream')
|
||||||
|
|
||||||
def on_error(self, error, debug):
|
def on_error(self, error, debug):
|
||||||
@ -390,10 +391,10 @@ class _Handler(object):
|
|||||||
gst_logger.debug('Got async-done.')
|
gst_logger.debug('Got async-done.')
|
||||||
|
|
||||||
def on_tag(self, taglist):
|
def on_tag(self, taglist):
|
||||||
# TODO: store current tags and reset on stream changes.
|
tags = utils.convert_taglist(taglist)
|
||||||
tags = taglist.keys()
|
self._audio._tags.update(tags)
|
||||||
logger.debug('Audio event: tags_changed(tags=%r)', tags)
|
logger.debug('Audio event: tags_changed(tags=%r)', tags.keys())
|
||||||
AudioListener.send('tags_changed', tags=tags)
|
AudioListener.send('tags_changed', tags=tags.keys())
|
||||||
|
|
||||||
def on_missing_plugin(self, msg):
|
def on_missing_plugin(self, msg):
|
||||||
desc = gst.pbutils.missing_plugin_message_get_description(msg)
|
desc = gst.pbutils.missing_plugin_message_get_description(msg)
|
||||||
@ -440,6 +441,7 @@ class Audio(pykka.ThreadingActor):
|
|||||||
self._config = config
|
self._config = config
|
||||||
self._target_state = gst.STATE_NULL
|
self._target_state = gst.STATE_NULL
|
||||||
self._buffering = False
|
self._buffering = False
|
||||||
|
self._tags = {}
|
||||||
|
|
||||||
self._playbin = None
|
self._playbin = None
|
||||||
self._outputs = None
|
self._outputs = None
|
||||||
@ -546,6 +548,7 @@ class Audio(pykka.ThreadingActor):
|
|||||||
:param uri: the URI to play
|
:param uri: the URI to play
|
||||||
:type uri: string
|
:type uri: string
|
||||||
"""
|
"""
|
||||||
|
self._tags = {} # TODO: add test for this somehow
|
||||||
self._playbin.set_property('uri', uri)
|
self._playbin.set_property('uri', uri)
|
||||||
|
|
||||||
def set_appsrc(
|
def set_appsrc(
|
||||||
@ -733,6 +736,7 @@ class Audio(pykka.ThreadingActor):
|
|||||||
# of faking it in the message handling when result=OK
|
# of faking it in the message handling when result=OK
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
# TODO: bake this into setup appsrc perhaps?
|
||||||
def set_metadata(self, track):
|
def set_metadata(self, track):
|
||||||
"""
|
"""
|
||||||
Set track metadata for currently playing song.
|
Set track metadata for currently playing song.
|
||||||
@ -763,5 +767,22 @@ class Audio(pykka.ThreadingActor):
|
|||||||
taglist[gst.TAG_ALBUM] = track.album.name
|
taglist[gst.TAG_ALBUM] = track.album.name
|
||||||
|
|
||||||
event = gst.event_new_tag(taglist)
|
event = gst.event_new_tag(taglist)
|
||||||
|
# TODO: check if we get this back on our own bus?
|
||||||
self._playbin.send_event(event)
|
self._playbin.send_event(event)
|
||||||
gst_logger.debug('Sent tag event: track=%s', track.uri)
|
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
|
||||||
|
|||||||
@ -21,9 +21,11 @@ class DummyAudio(pykka.ThreadingActor):
|
|||||||
self._callback = None
|
self._callback = None
|
||||||
self._uri = None
|
self._uri = None
|
||||||
self._state_change_result = True
|
self._state_change_result = True
|
||||||
|
self._tags = {}
|
||||||
|
|
||||||
def set_uri(self, uri):
|
def set_uri(self, uri):
|
||||||
assert self._uri is None, 'prepare change not called before set'
|
assert self._uri is None, 'prepare change not called before set'
|
||||||
|
self._tags = {}
|
||||||
self._uri = uri
|
self._uri = uri
|
||||||
|
|
||||||
def set_appsrc(self, *args, **kwargs):
|
def set_appsrc(self, *args, **kwargs):
|
||||||
@ -66,6 +68,9 @@ class DummyAudio(pykka.ThreadingActor):
|
|||||||
def set_metadata(self, track):
|
def set_metadata(self, track):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def get_current_tags(self):
|
||||||
|
return self._tags
|
||||||
|
|
||||||
def set_about_to_finish_callback(self, callback):
|
def set_about_to_finish_callback(self, callback):
|
||||||
self._callback = callback
|
self._callback = callback
|
||||||
|
|
||||||
@ -92,7 +97,8 @@ class DummyAudio(pykka.ThreadingActor):
|
|||||||
new_state=new_state, target_state=None)
|
new_state=new_state, target_state=None)
|
||||||
|
|
||||||
if new_state == PlaybackState.PLAYING:
|
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
|
return self._state_change_result
|
||||||
|
|
||||||
@ -107,6 +113,7 @@ class DummyAudio(pykka.ThreadingActor):
|
|||||||
self._callback()
|
self._callback()
|
||||||
|
|
||||||
if not self._uri or not self._callback:
|
if not self._uri or not self._callback:
|
||||||
|
self._tags = {}
|
||||||
AudioListener.send('reached_end_of_stream')
|
AudioListener.send('reached_end_of_stream')
|
||||||
else:
|
else:
|
||||||
AudioListener.send('position_changed', position=0)
|
AudioListener.send('position_changed', position=0)
|
||||||
|
|||||||
@ -338,7 +338,7 @@ class AudioEventTest(BaseTest):
|
|||||||
if not event.wait(timeout=1.0):
|
if not event.wait(timeout=1.0):
|
||||||
self.fail('End of stream not reached within deadline')
|
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):
|
def test_gapless(self, send_mock):
|
||||||
uris = self.uris[1:]
|
uris = self.uris[1:]
|
||||||
@ -380,6 +380,60 @@ class AudioEventTest(BaseTest):
|
|||||||
self.assertEqual(1, keys.count('state_changed'))
|
self.assertEqual(1, keys.count('state_changed'))
|
||||||
self.assertEqual(1, keys.count('reached_end_of_stream'))
|
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):
|
class AudioDummyEventTest(DummyMixin, AudioEventTest):
|
||||||
"""Exercise the AudioEventTest against our mock audio classes."""
|
"""Exercise the AudioEventTest against our mock audio classes."""
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user