[totem] Added support for listing and playing programmes



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]