#!/usr/bin/env python
-import utils
+import util
def get_properties(self):
return self._data
+ @property
+ def title(self):
+ raise NotImplementedError("On %s" % type(self))
+
def is_leaf(self):
raise NotImplementedError("")
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):
def __init__(self, connection):
ParentNode.__init__(self, connection, None, {})
+ @property
+ def title(self):
+ return "Radio"
+
def _get_func(self):
return "get_radio_channels", (), {}
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:
ParentNode.__init__(self, connection, None, {})
self._langId = langId
+ @property
+ def title(self):
+ return "Conferences"
+
def _get_func(self):
return "get_conferences", (self._langId, ), {}
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"], ), {}
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"], ), {}
@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"]
"""
@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
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)
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):
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":
@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()
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)
import gobject
+import util.misc as misc_utils
+import stream
+
_moduleLogger = logging.getLogger(__name__)
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):
@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")
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)
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):
--- /dev/null
+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)
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)
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):
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)
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")
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":
@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":
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):
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):
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):
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()
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)
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"]
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,
)
@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":
@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()