[totem] Added support for listing and playing programmes
- From: pwithnall src gnome org
- To: svn-commits-list gnome org
- Subject: [totem] Added support for listing and playing programmes
- Date: Tue, 5 May 2009 14:39:30 -0400 (EDT)
commit 771b7790ec88ef1c8424de6cd06d3214b5556b28
Author: Philip Withnall <philip tecnocode co uk>
Date: Sun Apr 26 15:09:27 2009 +0100
Added support for listing and playing programmes
Programmes are now listed asynchronously beneath their categories. This
required a few fixes to the iPlayer library, especially re. category
handling.
---
src/plugins/iplayer/iplayer.py | 119 ++++++++++++++++++++++++++++++++++++++-
src/plugins/iplayer/iplayer2.py | 16 +++--
2 files changed, 126 insertions(+), 9 deletions(-)
diff --git a/src/plugins/iplayer/iplayer.py b/src/plugins/iplayer/iplayer.py
index 111ef40..e22431b 100644
--- a/src/plugins/iplayer/iplayer.py
+++ b/src/plugins/iplayer/iplayer.py
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
import totem
import gobject
import gtk
@@ -7,7 +9,7 @@ import threading
class IplayerPlugin (totem.Plugin):
def __init__ (self):
totem.Plugin.__init__ (self)
- self.debug = True
+ self.debug = False
self.totem = None
def activate (self, totem_object):
@@ -15,6 +17,9 @@ class IplayerPlugin (totem.Plugin):
builder = self.load_interface ("iplayer.ui", True, totem_object.get_main_window (), self)
container = builder.get_object ('iplayer_vbox')
self.tree_store = builder.get_object ('iplayer_programme_store')
+ programme_list = builder.get_object ('iplayer_programme_list')
+ programme_list.connect ('row-expanded', self._row_expanded_cb)
+ programme_list.connect ('row-activated', self._row_activated_cb)
self.totem = totem_object
container.show_all ()
@@ -31,6 +36,9 @@ class IplayerPlugin (totem.Plugin):
totem_object.remove_sidebar_page ("iplayer")
def populate_channel_list (self):
+ if self.debug:
+ print "Populating channel listâ?¦"
+
# Add all the channels as top-level rows in the tree store
channels = self.tv.channels ()
for channel_id, title in channels.items ():
@@ -44,9 +52,69 @@ class IplayerPlugin (totem.Plugin):
def _populate_channel_list_cb (self, parent_path, values):
# Callback from PopulateChannelsThread to add stuff to the tree store
parent_iter = self.tree_store.get_iter (parent_path)
- self.tree_store.append (parent_iter, values)
+ category_iter = self.tree_store.append (parent_iter, values)
+
+ # Append a dummy child row so that the expander's visible; we can
+ # then queue off the expander to load the programme listing for this category
+ self.tree_store.append (category_iter, [_('Loadingâ?¦'), None, None])
+
+ return False
+
+ def _row_expanded_cb (self, tree_view, row_iter, path):
+ tree_model = tree_view.get_model ()
+
+ if self.debug:
+ print "_row_expanded_cb called."
+
+ # We only care about the category level (level 1), and only when
+ # it has the "Loading..." placeholder child row
+ if get_iter_level (tree_model, row_iter) != 1 or tree_model.iter_n_children (row_iter) != 1:
+ return
+
+ # Populate it with programmes asynchronously
+ self.populate_programme_list (row_iter)
+
+ def _row_activated_cb (self, tree_view, path, view_column):
+ tree_iter = self.tree_store.get_iter (path)
+ mrl = self.tree_store.get_value (tree_iter, 2)
+
+ # Only allow programme rows to be activated, not channel or category rows
+ if mrl == None:
+ return
+
+ # Add the programme to the playlist and play it
+ self.totem.add_to_playlist_and_play (mrl, self.tree_store.get_value (tree_iter, 0), True)
+
+ def populate_programme_list (self, category_iter):
+ if self.debug:
+ print "Populating programme listâ?¦"
+
+ category_path = self.tree_store.get_path (category_iter)
+ thread = PopulateProgrammesThread (self, self.tv, self.tree_store, category_path)
+ thread.start ()
+
+ def _populate_programme_list_cb (self, category_path, values, remove_placeholder):
+ # Callback from PopulateProgrammesThread to add stuff to the tree store
+ category_iter = self.tree_store.get_iter (category_path)
+
+ self.tree_store.append (category_iter, values)
+
+ # Remove the placeholder row
+ if remove_placeholder:
+ self.tree_store.remove (self.tree_store.iter_children (category_iter))
+
return False
+def get_iter_level (tree_model, tree_iter):
+ i = -1;
+ while (tree_iter != None):
+ tree_iter = tree_model.iter_parent (tree_iter)
+ i += 1
+ return i
+
+def category_name_to_id (category_name):
+ return category_name.lower ().replace (' ', '_').replace ('&', 'and')
+
class PopulateChannelsThread (threading.Thread):
# Class to populate the channel list from the Internet
def __init__ (self, plugin, parent_path, feed, tree_model):
@@ -64,8 +132,53 @@ class PopulateChannelsThread (threading.Thread):
# Add this channel's categories as sub-rows
# We have to pass a path because the model could theoretically be modified
# while the idle function is waiting in the queue, invalidating an iter
- for name, category_id in self.feed.get (channel_id).categories ():
+ for name, count in self.feed.get (channel_id).categories ():
+ category_id = category_name_to_id (name)
gobject.idle_add (self.plugin._populate_channel_list_cb, parent_path, [name, category_id, None])
tree_iter = self.tree_model.iter_next (tree_iter)
+class PopulateProgrammesThread (threading.Thread):
+ # Class to populate the programme list for a channel/category combination from the Internet
+ def __init__ (self, plugin, feed, tree_model, category_path):
+ self.plugin = plugin
+ self.feed = feed
+ self.tree_model = tree_model
+ self.category_path = category_path
+ self.lock = threading.Lock ()
+ threading.Thread.__init__ (self)
+
+ def run (self):
+ self.lock.acquire ()
+
+ category_iter = self.tree_model.get_iter (self.category_path)
+ category_id = self.tree_model.get_value (category_iter, 1)
+ channel_id = self.tree_model.get_value (self.tree_model.iter_parent (category_iter), 1)
+
+ # Retrieve the programmes and return them
+ feed = self.feed.get (channel_id).get (category_id)
+ if feed == None:
+ totem.action_error (_('Error getting programme feed'), _('There was an unknown error getting the list of programmes for this channel and category combination.'))
+ gobject.idle_add (self.plugin._populate_programme_list_cb, self.category_path, None)
+ self.lock.release ()
+ return
+
+ # Get the programmes and add them to the tree store
+ programmes = feed.list ()
+ remove_placeholder = True
+ for programme in programmes:
+ programme_item = programme.programme
+
+ # Get the media, which gives the stream URI.
+ # We go for mobile quality, since the higher-quality streams are RTMP-only
+ # which isn't currently supported by GStreamer or xine
+ media = programme_item.get_media_for ('mobile')
+ if media == None:
+ print "Programme has no HTTP streams"
+ continue
+
+ gobject.idle_add (self.plugin._populate_programme_list_cb, self.category_path,
+ [programme.get_title (), programme.get_summary (), media.url], remove_placeholder)
+ remove_placeholder = False
+
+ self.lock.release ()
diff --git a/src/plugins/iplayer/iplayer2.py b/src/plugins/iplayer/iplayer2.py
index 610c4d2..7d67dcd 100644
--- a/src/plugins/iplayer/iplayer2.py
+++ b/src/plugins/iplayer/iplayer2.py
@@ -810,6 +810,13 @@ class feed(object):
path = [self.channel]
if self.atoz:
path += ['atoz', self.atoz]
+ if self.category:
+ path += [self.category]
+ path += [listing]
+ elif self.category:
+ path = [self.category]
+ if self.atoz:
+ path += ['atoz', self.atoz]
path += [listing]
elif self.atoz:
path = ['atoz', self.atoz, listing]
@@ -929,14 +936,11 @@ class feed(object):
Returns a child/subfeed of this feed.
child: can be channel/cat/subcat/letter, e.g. 'bbc_one'
"""
- if self.channel and subfeed in categories:
- # no children: channel feeds don't support categories
- return None
+ if self.channel:
+ return self.sub(category=subfeed)
elif self.category:
# no children: TODO support subcategories
return None
- elif subfeed in categories:
- return self.sub(category=subfeed)
elif self.is_atoz(subfeed):
return self.sub(atoz=self.is_atoz(subfeed))
else:
@@ -956,7 +960,7 @@ class feed(object):
d = []
for entry in progs.entries:
pid = parse_entry_id(entry.id)
- p = programme_simple(pid, entry)
+ p = programme(pid)
d.append(p)
logging.info('Found %d entries', len(d))
rss_cache[url] = d
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]