audio: Maintain state and trigger events based on GStreamer state changes

This commit is contained in:
Stein Magnus Jodal 2012-11-14 01:49:05 +01:00
parent f9bd0d00b3
commit 87ce7bbe11
2 changed files with 88 additions and 5 deletions

View File

@ -13,6 +13,7 @@ from mopidy import settings
from mopidy.utils import process
from . import mixers
from .constants import PlaybackState
from .listener import AudioListener
logger = logging.getLogger('mopidy.audio')
@ -29,9 +30,11 @@ class Audio(pykka.ThreadingActor):
- :attr:`mopidy.settings.OUTPUT`
- :attr:`mopidy.settings.MIXER`
- :attr:`mopidy.settings.MIXER_TRACK`
"""
#: The GStreamer state mapped to :class:`mopidy.audio.PlaybackState`
state = PlaybackState.STOPPED
def __init__(self):
super(Audio, self).__init__()
@ -160,8 +163,12 @@ class Audio(pykka.ThreadingActor):
bus.remove_signal_watch()
def _on_message(self, bus, message):
if message.type == gst.MESSAGE_EOS:
self._trigger_reached_end_of_stream_event()
if (message.type == gst.MESSAGE_STATE_CHANGED
and message.src == self._playbin):
old_state, new_state, pending_state = message.parse_state_changed()
self._on_playbin_state_changed(old_state, new_state, pending_state)
elif message.type == gst.MESSAGE_EOS:
self._on_end_of_stream()
elif message.type == gst.MESSAGE_ERROR:
error, debug = message.parse_error()
logger.error('%s %s', error, debug)
@ -170,8 +177,35 @@ class Audio(pykka.ThreadingActor):
error, debug = message.parse_warning()
logger.warning('%s %s', error, debug)
def _trigger_reached_end_of_stream_event(self):
logger.debug('Triggering reached end of stream event')
def _on_playbin_state_changed(self, old_state, new_state, pending_state):
if new_state == gst.STATE_READY and pending_state == gst.STATE_NULL:
# XXX: We're not called on the last state cheng when going down to
# NULL, so we rewrite the second to last call to get the expected
# behavior.
new_state = gst.STATE_NULL
pending_state = gst.STATE_VOID_PENDING
if pending_state != gst.STATE_VOID_PENDING:
return # Ignore intermediate state changes
if new_state == gst.STATE_READY:
return # Ignore READY state as it's GStreamer specific
if new_state == gst.STATE_PLAYING:
new_state = PlaybackState.PLAYING
elif new_state == gst.STATE_PAUSED:
new_state = PlaybackState.PAUSED
elif new_state == gst.STATE_NULL:
new_state = PlaybackState.STOPPED
old_state, self.state = self.state, new_state
logger.debug('Triggering state_changed event')
AudioListener.send('state_changed',
old_state=old_state, new_state=new_state)
def _on_end_of_stream(self):
logger.debug('Triggering reached_end_of_stream event')
AudioListener.send('reached_end_of_stream')
def set_uri(self, uri):

View File

@ -1,5 +1,9 @@
from __future__ import unicode_literals
import pygst
pygst.require('0.10')
import gst
from mopidy import audio, settings
from mopidy.utils.path import path_to_uri
@ -63,3 +67,48 @@ class AudioTest(unittest.TestCase):
@unittest.SkipTest
def test_invalid_output_raises_error(self):
pass # TODO
class AudioStateTest(unittest.TestCase):
def setUp(self):
self.audio = audio.Audio()
def test_state_starts_as_stopped(self):
self.assertEqual(audio.PlaybackState.STOPPED, self.audio.state)
def test_state_does_not_change_when_in_gst_ready_state(self):
self.audio._on_playbin_state_changed(
gst.STATE_NULL, gst.STATE_READY, gst.STATE_VOID_PENDING)
self.assertEqual(audio.PlaybackState.STOPPED, self.audio.state)
def test_state_changes_from_stopped_to_playing_on_play(self):
self.audio._on_playbin_state_changed(
gst.STATE_NULL, gst.STATE_READY, gst.STATE_PLAYING)
self.audio._on_playbin_state_changed(
gst.STATE_READY, gst.STATE_PAUSED, gst.STATE_PLAYING)
self.audio._on_playbin_state_changed(
gst.STATE_PAUSED, gst.STATE_PLAYING, gst.STATE_VOID_PENDING)
self.assertEqual(audio.PlaybackState.PLAYING, self.audio.state)
def test_state_changes_from_playing_to_paused_on_pause(self):
self.audio.state = audio.PlaybackState.PLAYING
self.audio._on_playbin_state_changed(
gst.STATE_PLAYING, gst.STATE_PAUSED, gst.STATE_VOID_PENDING)
self.assertEqual(audio.PlaybackState.PAUSED, self.audio.state)
def test_state_changes_from_playing_to_stopped_on_stop(self):
self.audio.state = audio.PlaybackState.PLAYING
self.audio._on_playbin_state_changed(
gst.STATE_PLAYING, gst.STATE_PAUSED, gst.STATE_NULL)
self.audio._on_playbin_state_changed(
gst.STATE_PAUSED, gst.STATE_READY, gst.STATE_NULL)
# We never get the following call, so the logic must work without it
#self.audio._on_playbin_state_changed(
# gst.STATE_READY, gst.STATE_NULL, gst.STATE_VOID_PENDING)
self.assertEqual(audio.PlaybackState.STOPPED, self.audio.state)