Integrating and testing of playback
authorEd Page <eopage@byu.net>
Wed, 12 May 2010 03:05:38 +0000 (22:05 -0500)
committerEd Page <eopage@byu.net>
Wed, 12 May 2010 03:11:33 +0000 (22:11 -0500)
src/__init__.py
src/index.py
src/mormonchannel_gtk.py
src/playcontrol.py
src/player.py
src/presenter.py
src/stream.py [new file with mode: 0644]
src/windows.py

index 7132119..d3a51da 100644 (file)
@@ -1,3 +1,3 @@
 #!/usr/bin/env python
 
-import utils
+import util
index e8deff6..2ffb6c2 100644 (file)
@@ -120,6 +120,10 @@ class Node(object):
        def get_properties(self):
                return self._data
 
+       @property
+       def title(self):
+               raise NotImplementedError("On %s" % type(self))
+
        def is_leaf(self):
                raise NotImplementedError("")
 
@@ -193,11 +197,11 @@ class LeafNode(Node):
                raise NotImplementedError("On %s" % type(self))
 
        @property
-       def title(self):
+       def subtitle(self):
                raise NotImplementedError("On %s" % type(self))
 
        @property
-       def subtitle(self):
+       def uri(self):
                raise NotImplementedError("On %s" % type(self))
 
        def _get_children(self, on_success, on_error):
@@ -209,6 +213,10 @@ class RadioNode(ParentNode):
        def __init__(self, connection):
                ParentNode.__init__(self, connection, None, {})
 
+       @property
+       def title(self):
+               return "Radio"
+
        def _get_func(self):
                return "get_radio_channels", (), {}
 
@@ -235,6 +243,10 @@ class RadioChannelNode(LeafNode):
        def subtitle(self):
                return ""
 
+       @property
+       def uri(self):
+               return self._data["url"]
+
        def get_programming(self, date, on_success, on_error):
                date = date.strftime("%Y-%m-%d")
                try:
@@ -287,6 +299,10 @@ class ConferencesNode(ParentNode):
                ParentNode.__init__(self, connection, None, {})
                self._langId = langId
 
+       @property
+       def title(self):
+               return "Conferences"
+
        def _get_func(self):
                return "get_conferences", (self._langId, ), {}
 
@@ -299,6 +315,10 @@ class ConferenceNode(ParentNode):
        def __init__(self, connection, parent, data):
                ParentNode.__init__(self, connection, parent, data)
 
+       @property
+       def title(self):
+               return self._data["title"]
+
        def _get_func(self):
                return "get_conference_sessions", (self._data["id"], ), {}
 
@@ -311,6 +331,10 @@ class SessionNode(ParentNode):
        def __init__(self, connection, parent, data):
                ParentNode.__init__(self, connection, parent, data)
 
+       @property
+       def title(self):
+               return self._data["title"]
+
        def _get_func(self):
                return "get_conference_talks", (self._data["id"], ), {}
 
@@ -329,8 +353,12 @@ class TalkNode(LeafNode):
 
        @property
        def title(self):
-               return self._date["title"]
+               return self._data["title"]
 
        @property
        def subtitle(self):
-               return self._date["speaker"]
+               return self._data["speaker"]
+
+       @property
+       def uri(self):
+               return self._data["url"]
index 6484fff..85d068e 100755 (executable)
@@ -4,6 +4,7 @@
 """
 @todo Reverse order option.  Toggle between playing ascending/descending chronological order
 @todo Track recent
+@bug All connect's need disconnects or else we will leak a bunch of objects
 """
 
 from __future__ import with_statement
index 9f55bd9..8576237 100644 (file)
@@ -45,7 +45,7 @@ class NavControl(gobject.GObject):
                self._controlBox.connect("action", self._on_nav_action)
                self._controlBox.connect("navigating", self._on_navigating)
 
-               self._titleButton = gtk.Label("")
+               self._titleButton = gtk.Label(self._player.title)
 
                self._displayBox = presenter.NavigationBox()
                self._displayBox.toplevel.add(self._titleButton)
@@ -57,8 +57,25 @@ class NavControl(gobject.GObject):
                self._layout.pack_start(self._displayBox.toplevel, True, True)
 
        def refresh(self):
-               if not self._player.title:
+               self._titleButton.set_label(self._player.title)
+               self._set_context(self._player.state)
+
+       def _set_context(self, state):
+               if state == self._player.STATE_PLAY:
+                       stateImage = self._store.STORE_LOOKUP["small_pause"]
+                       self._store.set_image_from_store(self._controlButton, stateImage)
+                       self.toplevel.show()
+               elif state == self._player.STATE_PAUSE:
+                       stateImage = self._store.STORE_LOOKUP["small_play"]
+                       self._store.set_image_from_store(self._controlButton, stateImage)
+                       self.toplevel.show()
+               elif state == self._player.STATE_STOP:
+                       self._titleButton.set_label("")
                        self.toplevel.hide()
+               else:
+                       _moduleLogger.info("Unhandled player state %s" % state)
+                       stateImage = self._store.STORE_LOOKUP["small_pause"]
+                       self._store.set_image_from_store(self._controlButton, stateImage)
 
        @property
        def toplevel(self):
@@ -72,45 +89,32 @@ class NavControl(gobject.GObject):
                if self._controlBox.is_active() or self._displayBox.is_active():
                        return
 
-               if newState == "play":
-                       stateImage = self._store.STORE_LOOKUP["small_play"]
-                       self._store.set_image_from_store(self._controlButton, stateImage)
-                       self.toplevel.show()
-               elif newState == "pause":
-                       stateImage = self._store.STORE_LOOKUP["small_pause"]
-                       self._store.set_image_from_store(self._controlButton, stateImage)
-                       self.toplevel.show()
-               elif newState == "stop":
-                       self._titleButton.set_label("")
-                       self.toplevel.hide()
-               else:
-                       _moduleLogger.info("Unhandled player state %s" % newState)
-                       stateImage = self._store.STORE_LOOKUP["small_pause"]
-                       self._store.set_image_from_store(self._controlButton, stateImage)
+               self._set_context(newState)
 
        @misc_utils.log_exception(_moduleLogger)
-       def _on_player_title_change(self, player, newState):
+       def _on_player_title_change(self, player, node):
+               _moduleLogger.info("Title change: %s" % self._player.title)
                self._titleButton.set_label(self._player.title)
 
        @misc_utils.log_exception(_moduleLogger)
        def _on_navigating(self, widget, navState):
                if navState == "down":
                        imageName = "small_home"
-               elif navState == "up":
-                       imageName = "small_play"
                elif navState == "clicking" or not self._player.can_navigate:
                        if widget is self._controlBox:
                                if self._player.state == "play":
-                                       imageName = "small_pause"
-                               else:
-                                       imageName = "small_play"
-                       elif widget is self._displayBox:
-                               if self._player.state == "play":
                                        imageName = "small_play"
                                else:
                                        imageName = "small_pause"
+                       elif widget is self._displayBox:
+                               if self._player.state == self._player.STATE_PLAY:
+                                       imageName = "small_pause"
+                               else:
+                                       imageName = "small_play"
                        else:
                                raise NotImplementedError()
+               elif navState == "up":
+                       imageName = "small_play"
                elif navState == "left":
                        imageName = "small_next"
                elif navState == "right":
@@ -121,16 +125,11 @@ class NavControl(gobject.GObject):
 
        @misc_utils.log_exception(_moduleLogger)
        def _on_nav_action(self, widget, navState):
-               if self._player.state == "play":
-                       imageName = "small_play"
-               else:
-                       imageName = "small_pause"
-               imagePath = self._store.STORE_LOOKUP[imageName]
-               self._store.set_image_from_store(self._controlButton, imagePath)
+               self._set_context(self._player.state)
 
                if navState == "clicking":
                        if widget is self._controlBox:
-                               if self._player.state == "play":
+                               if self._player.state == self._player.STATE_PLAY:
                                        self._player.pause()
                                else:
                                        self._player.play()
@@ -276,7 +275,7 @@ class PlayControl(object):
                self._set_state(newState)
 
        @misc_utils.log_exception(_moduleLogger)
-       def _on_player_nav_change(self, player, newState):
+       def _on_player_nav_change(self, player, node):
                self._set_navigate(player.can_navigate)
 
        @misc_utils.log_exception(_moduleLogger)
index c4a06de..9288470 100644 (file)
@@ -2,6 +2,9 @@ import logging
 
 import gobject
 
+import util.misc as misc_utils
+import stream
+
 
 _moduleLogger = logging.getLogger(__name__)
 
@@ -19,20 +22,35 @@ class Player(gobject.GObject):
                        gobject.TYPE_NONE,
                        (gobject.TYPE_PYOBJECT, ),
                ),
+               'error' : (
+                       gobject.SIGNAL_RUN_LAST,
+                       gobject.TYPE_NONE,
+                       (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT),
+               ),
        }
 
+       STATE_PLAY = stream.GSTStream.STATE_PLAY
+       STATE_PAUSE = stream.GSTStream.STATE_PAUSE
+       STATE_STOP = stream.GSTStream.STATE_STOP
+
        def __init__(self, index):
                gobject.GObject.__init__(self)
                self._index = index
                self._node = None
-               self._state = "play"
+               self._stream = stream.GSTStream()
+               self._stream.connect("state-change", self._on_stream_state)
+               self._stream.connect("eof", self._on_stream_eof)
+               self._stream.connect("error", self._on_stream_error)
 
        def set_piece_by_node(self, node):
-               assert node.is_leaf() or node is None
+               assert node is None or node.is_leaf(), node
                if self._node is node:
                        return
                self._node = node
-               self.emit("title_change", self._state)
+               if self._node is not None:
+                       self._stream.set_file(self._node.uri)
+               _moduleLogger.info("New node %r" % self._node)
+               self.emit("title_change", self._node)
 
        @property
        def node(self):
@@ -58,29 +76,20 @@ class Player(gobject.GObject):
 
        @property
        def state(self):
-               return self._state
+               return self._stream.state
 
        def play(self):
-               if self._state == "play":
-                       return
-               self._state = "play"
-               self.emit("state_change", self._state)
                _moduleLogger.info("play")
+               self._stream.play()
 
        def pause(self):
-               if self._state == "pause":
-                       return
-               self._state = "pause"
-               self.emit("state_change", self._state)
                _moduleLogger.info("pause")
+               self._stream.pause()
 
        def stop(self):
-               if self._state == "stop":
-                       return
-               self._state = "stop"
-               self.set_piece_by_node(None)
-               self.emit("state_change", self._state)
                _moduleLogger.info("stop")
+               self._stream.stop()
+               self.set_piece_by_node(None)
 
        def back(self):
                _moduleLogger.info("back")
@@ -88,5 +97,20 @@ class Player(gobject.GObject):
        def next(self):
                _moduleLogger.info("next")
 
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_stream_state(self, s, state):
+               _moduleLogger.info("State change %r" % state)
+               self.emit("state_change", state)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_stream_eof(self, s, uri):
+               _moduleLogger.info("EOF %s" % uri)
+               self.next()
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_stream_error(self, s, error, debug):
+               _moduleLogger.info("Error %s %s" % (error, debug))
+               self.emit("error", error, debug)
+
 
 gobject.type_register(Player)
index 65899b7..fd57533 100644 (file)
@@ -99,9 +99,9 @@ class NavigationBox(gobject.GObject):
                        mousePosition = event.get_coords()
                        state = self.get_state(mousePosition)
                        assert state
-                       self.emit("action", state)
                finally:
                        self._clickPosition = self._NO_POSITION
+               self.emit("action", state)
 
        @misc_utils.log_exception(_moduleLogger)
        def _on_motion_notify(self, widget, event):
diff --git a/src/stream.py b/src/stream.py
new file mode 100644 (file)
index 0000000..5ae4313
--- /dev/null
@@ -0,0 +1,144 @@
+import logging
+
+import gobject
+import gst
+
+import util.misc as misc_utils
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+class GSTStream(gobject.GObject):
+
+       STATE_PLAY = "play"
+       STATE_PAUSE = "pause"
+       STATE_STOP = "stop"
+
+       __gsignals__ = {
+               'state-change' : (
+                       gobject.SIGNAL_RUN_LAST,
+                       gobject.TYPE_NONE,
+                       (gobject.TYPE_STRING, ),
+               ),
+               'eof' : (
+                       gobject.SIGNAL_RUN_LAST,
+                       gobject.TYPE_NONE,
+                       (gobject.TYPE_STRING, ),
+               ),
+               'error' : (
+                       gobject.SIGNAL_RUN_LAST,
+                       gobject.TYPE_NONE,
+                       (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT),
+               ),
+       }
+
+
+       def __init__(self):
+               gobject.GObject.__init__(self)
+               #Fields
+               self._state = self.STATE_STOP
+               self._uri = ""
+               self._elapsed = 0
+               self._duration = 0
+
+               #Set up GStreamer
+               self._player = gst.element_factory_make("playbin2", "player")
+               bus = self._player.get_bus()
+               bus.add_signal_watch()
+               bus.connect("message", self._on_message)
+
+               #Constants
+               self._timeFormat = gst.Format(gst.FORMAT_TIME)
+               self._seekFlag = gst.SEEK_FLAG_FLUSH
+
+       @property
+       def playing(self):
+               return self.state == self.STATE_PLAY
+
+       @property
+       def has_file(self):
+               return 0 < len(self._uri)
+
+       @property
+       def state(self):
+               state = self._player.get_state()[1]
+               return self._translate_state(state)
+
+       def set_file(self, uri):
+               if self._uri != file:
+                       self._invalidate_cache()
+               if self.playing:
+                       self.stop()
+
+               self._uri = uri
+               self._player.set_property("uri", uri)
+
+       def play(self):
+               if self.state == self.STATE_PLAY:
+                       _moduleLogger.info("Already play")
+                       return
+               _moduleLogger.info("Play")
+               self._player.set_state(gst.STATE_PLAYING)
+               self.emit("state-change", self.STATE_PLAY)
+
+       def pause(self):
+               if self.state == self.STATE_PAUSE:
+                       _moduleLogger.info("Already pause")
+                       return
+               _moduleLogger.info("Pause")
+               self._player.set_state(gst.STATE_PAUSED)
+               self.emit("state-change", self.STATE_PAUSE)
+
+       def stop(self):
+               if self.state == self.STATE_STOP:
+                       _moduleLogger.info("Already stop")
+                       return
+               self._player.set_state(gst.STATE_NULL)
+               _moduleLogger.info("Stopped")
+               self.emit("state-change", self.STATE_STOP)
+
+       def elapsed(self):
+               try:
+                       self._elapsed = self._player.query_position(self._timeFormat, None)[0]
+               except:
+                       pass
+               return self._elapsed
+
+       def duration(self):
+               try:
+                       self._duration = self._player.query_duration(self._timeFormat, None)[0]
+               except:
+                       _moduleLogger.exception("Query failed")
+               return self._duration
+
+       def seek_time(self, ns):
+               _moduleLogger.debug("Seeking to: %s", ns)
+               self._elapsed = ns
+               self._player.seek_simple(self._timeFormat, self._seekFlag, ns)
+
+       def _invalidate_cache(self):
+               self._elapsed = 0
+               self._duration = 0
+
+       def _translate_state(self, gstState):
+               return {
+                       gst.STATE_NULL: self.STATE_STOP,
+                       gst.STATE_PAUSED: self.STATE_PAUSE,
+                       gst.STATE_PLAYING: self.STATE_PLAY,
+               }.get(gstState, self.STATE_STOP)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_message(self, bus, message):
+               t = message.type
+               if t == gst.MESSAGE_EOS:
+                       self._player.set_state(gst.STATE_NULL)
+                       self.emit("eof", self._uri)
+               elif t == gst.MESSAGE_ERROR:
+                       self._player.set_state(gst.STATE_NULL)
+                       err, debug = message.parse_error()
+                       _moduleLogger.error("Error: %s, (%s)" % (err, debug))
+                       self.emit("error", err, debug)
+
+
+gobject.type_register(GSTStream)
index 0a493d7..fd7ece3 100644 (file)
@@ -310,10 +310,6 @@ class RadioWindow(BasicWindow):
                self._treeScroller.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
 
                self._presenter = presenter.StreamMiniPresenter(self._store)
-               if self._player.state == "play":
-                       self._presenter.set_state(self._store.STORE_LOOKUP["play"])
-               else:
-                       self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
                self._presenterNavigation = presenter.NavigationBox()
                self._presenterNavigation.toplevel.add(self._presenter.toplevel)
                self._presenterNavigation.connect("action", self._on_nav_action)
@@ -327,7 +323,7 @@ class RadioWindow(BasicWindow):
                self._layout.pack_start(self._loadingBanner.toplevel, False, False)
                self._layout.pack_start(self._radioLayout, True, True)
 
-               self._window.set_title("Radio")
+               self._window.set_title(self._node.title)
                self._dateShown = datetime.datetime.now()
 
        def show(self):
@@ -338,6 +334,24 @@ class RadioWindow(BasicWindow):
 
                self._refresh()
 
+       @property
+       def _active(self):
+               return self._player.node is self._childNode
+
+       def _set_context(self, state):
+               if state == self._player.STATE_PLAY:
+                       if self._active:
+                               self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
+                       else:
+                               self._presenter.set_state(self._store.STORE_LOOKUP["stop"])
+               elif state == self._player.STATE_PAUSE:
+                       self._presenter.set_state(self._store.STORE_LOOKUP["play"])
+               elif state == self._player.STATE_STOP:
+                       self._presenter.set_state(self._store.STORE_LOOKUP["play"])
+               else:
+                       _moduleLogger.info("Unhandled player state %s" % state)
+                       self._presenter.set_state(self._store.STORE_LOOKUP["play"])
+
        def _show_loading(self):
                animationPath = self._store.STORE_LOOKUP["loading"]
                animation = self._store.get_pixbuf_animation_from_store(animationPath)
@@ -353,6 +367,7 @@ class RadioWindow(BasicWindow):
                        self._on_channels,
                        self._on_load_error,
                )
+               self._set_context(self._player.state)
 
        def _get_current_row(self):
                nowTime = self._dateShown.strftime("%H:%M:%S")
@@ -371,26 +386,29 @@ class RadioWindow(BasicWindow):
                if self._headerNavigation.is_active() or self._presenterNavigation.is_active():
                        return
 
-               if newState == "play":
-                       self._presenter.set_state(self._store.STORE_LOOKUP["play"])
-               elif newState == "pause":
-                       self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
-               else:
-                       _moduleLogger.info("Unhandled player state %s" % newState)
-                       self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
+               self._set_context(newState)
 
        @misc_utils.log_exception(_moduleLogger)
-       def _on_player_title_change(self, player, newState):
-               _moduleLogger.info("Player title magically changed to %s" % player.title)
-               self._destroy()
+       def _on_player_title_change(self, player, node):
+               if node is not self._childNode or node is None:
+                       _moduleLogger.info("Player title magically changed to %s" % player.title)
+                       return
 
        @misc_utils.log_exception(_moduleLogger)
        def _on_navigating(self, widget, navState):
                if navState == "clicking":
-                       if self._player.state == "play":
-                               imageName = "pause"
+                       if self._player.state == self._player.STATE_PLAY:
+                               if self._active:
+                                       imageName = "pause"
+                               else:
+                                       imageName = "stop"
+                       elif self._player.state == self._player.STATE_PAUSE:
+                               imageName = "play"
+                       elif self._player.state == self._player.STATE_STOP:
+                               imageName = "play"
                        else:
                                imageName = "play"
+                               _moduleLogger.info("Unhandled player state %s" % self._player.state)
                elif navState == "down":
                        imageName = "home"
                elif navState == "up":
@@ -404,16 +422,21 @@ class RadioWindow(BasicWindow):
 
        @misc_utils.log_exception(_moduleLogger)
        def _on_nav_action(self, widget, navState):
-               if self._player.state == "play":
-                       self._presenter.set_state(self._store.STORE_LOOKUP["play"])
-               else:
-                       self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
+               self._set_context(self._player.state)
 
                if navState == "clicking":
-                       if self._player.state == "play":
-                               self._player.pause()
-                       else:
+                       if self._player.state == self._player.STATE_PLAY:
+                               if self._active:
+                                       self._player.pause()
+                               else:
+                                       self._player.stop()
+                       elif self._player.state == self._player.STATE_PAUSE:
+                               self._player.play()
+                       elif self._player.state == self._player.STATE_STOP:
+                               self._player.set_piece_by_node(self._childNode)
                                self._player.play()
+                       else:
+                               _moduleLogger.info("Unhandled player state %s" % self._player.state)
                elif navState == "down":
                        self.window.destroy()
                elif navState == "up":
@@ -555,7 +578,7 @@ class ConferencesWindow(ListWindow):
 
        def __init__(self, player, store, node):
                ListWindow.__init__(self, player, store, node)
-               self._window.set_title("Conferences")
+               self._window.set_title(self._node.title)
 
        @classmethod
        def _get_columns(cls):
@@ -626,7 +649,7 @@ class ConferenceSessionsWindow(ListWindow):
 
        def __init__(self, player, store, node):
                ListWindow.__init__(self, player, store, node)
-               self._window.set_title("Sessions")
+               self._window.set_title(self._node.title)
 
        @classmethod
        def _get_columns(cls):
@@ -691,7 +714,7 @@ class ConferenceTalksWindow(ListWindow):
 
        def __init__(self, player, store, node):
                ListWindow.__init__(self, player, store, node)
-               self._window.set_title("Talks")
+               self._window.set_title(self._node.title)
 
        @classmethod
        def _get_columns(cls):
@@ -756,9 +779,11 @@ class ConferenceTalkWindow(BasicWindow):
 
        def __init__(self, player, store, node):
                BasicWindow.__init__(self, player, store)
+               self._node = node
 
                self._player.connect("state-change", self._on_player_state_change)
                self._player.connect("title-change", self._on_player_title_change)
+               self._player.connect("error", self._on_player_error)
 
                self._loadingBanner = banners.GenericBanner()
 
@@ -771,7 +796,7 @@ class ConferenceTalkWindow(BasicWindow):
                self._layout.pack_start(self._loadingBanner.toplevel, False, False)
                self._layout.pack_start(self._presenterNavigation.toplevel, True, True)
 
-               self._window.set_title("Talk")
+               self._window.set_title(self._node.title)
 
        def show(self):
                BasicWindow.show(self)
@@ -784,10 +809,11 @@ class ConferenceTalkWindow(BasicWindow):
                        self._player.title,
                        self._player.subtitle,
                )
-               if self._player.state == "play":
-                       self._presenter.set_state(self._store.STORE_LOOKUP["play"])
-               else:
-                       self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
+               self._set_context(self._player.state)
+
+       @property
+       def _active(self):
+               return self._player.node is self._node
 
        def _show_loading(self):
                animationPath = self._store.STORE_LOOKUP["loading"]
@@ -797,21 +823,31 @@ class ConferenceTalkWindow(BasicWindow):
        def _hide_loading(self):
                self._loadingBanner.hide()
 
+       def _set_context(self, state):
+               if state == self._player.STATE_PLAY:
+                       if self._active:
+                               self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
+                       else:
+                               self._presenter.set_state(self._store.STORE_LOOKUP["stop"])
+               elif state == self._player.STATE_PAUSE:
+                       self._presenter.set_state(self._store.STORE_LOOKUP["play"])
+               elif state == self._player.STATE_STOP:
+                       self._presenter.set_state(self._store.STORE_LOOKUP["play"])
+               else:
+                       _moduleLogger.info("Unhandled player state %s" % state)
+
        @misc_utils.log_exception(_moduleLogger)
        def _on_player_state_change(self, player, newState):
                if self._presenterNavigation.is_active():
                        return
 
-               if newState == "play":
-                       self._presenter.set_state(self._store.STORE_LOOKUP["play"])
-               elif newState == "pause":
-                       self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
-               else:
-                       _moduleLogger.info("Unhandled player state %s" % newState)
-                       self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
+               self._set_context(newState)
 
        @misc_utils.log_exception(_moduleLogger)
-       def _on_player_title_change(self, player, newState):
+       def _on_player_title_change(self, player, node):
+               if node is not self._node or node is None:
+                       _moduleLogger.info("Player title magically changed to %s" % player.title)
+                       return
                self._presenter.set_context(
                        self._store.STORE_LOOKUP["conference_background"],
                        self._player.title,
@@ -819,12 +855,23 @@ class ConferenceTalkWindow(BasicWindow):
                )
 
        @misc_utils.log_exception(_moduleLogger)
+       def _on_player_error(self, player, err, debug):
+               _moduleLogger.error("%r - %r" % (err, debug))
+
+       @misc_utils.log_exception(_moduleLogger)
        def _on_navigating(self, widget, navState):
                if navState == "clicking":
-                       if self._player.state == "play":
-                               imageName = "pause"
-                       else:
+                       if self._player.state == self._player.STATE_PLAY:
+                               if self._active:
+                                       imageName = "pause"
+                               else:
+                                       imageName = "stop"
+                       elif self._player.state == self._player.STATE_PAUSE:
                                imageName = "play"
+                       elif self._player.state == self._player.STATE_STOP:
+                               imageName = "play"
+                       else:
+                               _moduleLogger.info("Unhandled player state %s" % self._player.state)
                elif navState == "down":
                        imageName = "home"
                elif navState == "up":
@@ -838,16 +885,21 @@ class ConferenceTalkWindow(BasicWindow):
 
        @misc_utils.log_exception(_moduleLogger)
        def _on_nav_action(self, widget, navState):
-               if self._player.state == "play":
-                       self._presenter.set_state(self._store.STORE_LOOKUP["play"])
-               else:
-                       self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
+               self._set_context(self._player.state)
 
                if navState == "clicking":
-                       if self._player.state == "play":
-                               self._player.pause()
-                       else:
+                       if self._player.state == self._player.STATE_PLAY:
+                               if self._active:
+                                       self._player.pause()
+                               else:
+                                       self._player.stop()
+                       elif self._player.state == self._player.STATE_PAUSE:
                                self._player.play()
+                       elif self._player.state == self._player.STATE_STOP:
+                               self._player.set_piece_by_node(self._node)
+                               self._player.play()
+                       else:
+                               _moduleLogger.info("Unhandled player state %s" % self._player.state)
                elif navState == "down":
                        self.emit("home")
                        self._window.destroy()