diff -Naur trunk/configure.in totem/configure.in
--- trunk/configure.in	2009-06-21 22:30:47.639272301 +0200
+++ totem/configure.in	2009-06-21 22:29:42.956534516 +0200
@@ -48,7 +48,7 @@
 AC_SUBST(TOTEM_VERSION_MICRO)
 
 # The full list of plugins
-allowed_plugins="thumbnail screensaver ontop galago gromit lirc media-player-keys mythtv properties sidebar-test skipto sample-python sample-vala bemused youtube publish tracker pythonconsole jamendo opensubtitles screenshot brasero-disc-recorder coherence_upnp dbus-service"
+allowed_plugins="thumbnail screensaver ontop galago gromit lirc media-player-keys mythtv properties sidebar-test skipto sample-python sample-vala bemused youtube publish tracker pythonconsole jamendo mediathek opensubtitles screenshot brasero-disc-recorder coherence_upnp dbus-service"
 
 PLUGINDIR='${libdir}/totem/plugins'
 AC_SUBST(PLUGINDIR)
@@ -897,6 +897,7 @@
 src/plugins/pythonconsole/Makefile
 src/plugins/publish/Makefile
 src/plugins/jamendo/Makefile
+src/plugins/mediathek/Makefile
 src/plugins/brasero-disc-recorder/Makefile
 src/backend/Makefile
 browser-plugin/Makefile
diff -Naur trunk/po/POTFILES.in totem/po/POTFILES.in
--- trunk/po/POTFILES.in	2009-06-21 22:30:47.565941514 +0200
+++ totem/po/POTFILES.in	2009-06-21 23:12:36.119034475 +0200
@@ -55,6 +55,9 @@
 [type: gettext/ini]src/plugins/lirc/lirc.totem-plugin.in
 src/plugins/lirc/totem-lirc.c
 [type: gettext/ini]src/plugins/media-player-keys/media-player-keys.totem-plugin.in
+[type: gettext/glade]src/plugins/mediathek/mediathek.ui
+[type: gettext/ini]src/plugins/mediathek/mediathek.totem-plugin.in
+src/plugins/mediathek/mediathek.py
 src/plugins/mythtv/totem-mythtv.c
 [type: gettext/glade]src/plugins/opensubtitles/opensubtitles.ui
 [type: gettext/ini]src/plugins/opensubtitles/opensubtitles.totem-plugin.in
diff -Naur trunk/src/plugins/mediathek/htmlparser.py totem/src/plugins/mediathek/htmlparser.py
--- trunk/src/plugins/mediathek/htmlparser.py	1970-01-01 01:00:00.000000000 +0100
+++ totem/src/plugins/mediathek/htmlparser.py	2009-06-21 22:24:07.866866408 +0200
@@ -0,0 +1,88 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2009 Christian Kornacker <christian.kornacker@gmx.net>
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+#
+# Provides a simple and very quick way to parse list feeds
+#
+
+import re
+
+def xmlunescape(data):
+	data = data.replace('&amp;', '&')
+	data = data.replace('&gt;', '>')
+	data = data.replace('&lt;', '<')
+	data = data.replace('\\u00F6', 'ö')
+	data = data.replace('\\u00FC', 'ü')
+	data = data.replace('\\u00E4', 'ä')
+	data = data.replace('\\u00DF', 'ß')
+	return data
+
+class indexentry(object):
+	 def __init__(self, title=None, id=None):
+		 self.title	= title
+		 self.id	= id
+
+class listentry(object):
+	 def __init__(self, title=None, pubDate=None, description=None, streams=None):
+		 self.title	  = title
+		 self.pubDate	  = pubDate
+		 self.description = description
+		 self.streams	  = streams
+
+class listentries(object):
+	 def __init__(self):
+		 self.entries = []
+				  
+def parse_index(htmlSource):
+	elist = listentries()
+	entriesSrc = re.findall( "/ZDFmediathek/content/[^/]*/(\d*\?reset=[^>]*>[^<]*</a>)", htmlSource, re.DOTALL)
+
+	for entrySrc in entriesSrc:
+		entry={}
+		id = re.findall( "(\d*)\?reset=", entrySrc,re.DOTALL)[0]
+		title = re.findall( "reset=[^>]*>([^<]*)</a>", entrySrc, re.DOTALL)[0]
+
+		elist.entries.append(indexentry(xmlunescape(title), id))
+
+	return elist
+
+def parse_rss(rssSource):  
+	try:
+		encoding = re.findall( "<\?xml version=\"[^\"]*\" encoding=\"([^\"]*)\"\?>", rssSource )[ 0 ]
+	except:
+		return None
+
+	elist=listentries()
+	# gather all list entries 
+	entriesSrc = re.findall( "<item>(.*?)</item>", rssSource, re.DOTALL)
+	
+	# enumerate thru the element list and gather info
+	for entrySrc in entriesSrc:
+		entry={}
+		title	   = re.findall( "<title[^>]*>(.*?)</title>", entrySrc, re.DOTALL )[0]
+		pubDate	 = re.findall( "<pubDate[^>]*>(.*?)</pubDate>", entrySrc, re.DOTALL )[0]
+		description = re.findall( "<description[^>]*>(.*?)</description>", entrySrc, re.DOTALL )[0]
+		streams	 = re.findall( "<media:content url=\"(.*?)\"[^>]*>", entrySrc, re.DOTALL )
+
+		if streams:
+			elist.entries.append(listentry(xmlunescape(title), xmlunescape(pubDate), xmlunescape(description), streams))
+
+	return elist   
diff -Naur trunk/src/plugins/mediathek/Makefile.am totem/src/plugins/mediathek/Makefile.am
--- trunk/src/plugins/mediathek/Makefile.am	1970-01-01 01:00:00.000000000 +0100
+++ totem/src/plugins/mediathek/Makefile.am	2009-06-21 22:28:16.549290024 +0200
@@ -0,0 +1,14 @@
+plugindir = $(PLUGINDIR)/mediathek
+uidir = $(plugindir)
+plugin_PYTHON = mediathek.py htmlparser.py
+plugin_in_files = mediathek.totem-plugin.in
+
+%.totem-plugin: %.totem-plugin.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+plugin_DATA = $(plugin_in_files:.totem-plugin.in=.totem-plugin)
+ui_DATA = mediathek.ui
+
+EXTRA_DIST = $(plugin_in_files) $(ui_DATA) mediathek.py htmlparser.py
+
+CLEANFILES = $(plugin_DATA)
+DISTCLEANFILES = $(plugin_DATA)
diff -Naur trunk/src/plugins/mediathek/mediathek.py totem/src/plugins/mediathek/mediathek.py
--- trunk/src/plugins/mediathek/mediathek.py	1970-01-01 01:00:00.000000000 +0100
+++ totem/src/plugins/mediathek/mediathek.py	2009-06-21 22:24:07.866866408 +0200
@@ -0,0 +1,240 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2009 Christian Kornacker <christian.kornacker@gmx.net>
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+#
+# This is based on the BBC iPlayer Totem Plugin
+#
+
+import time
+import locale
+import threading
+
+import urllib2
+import httplib2
+import htmlparser
+
+import totem
+import gtk
+import gobject
+
+from xml.sax.saxutils import escape
+
+http = httplib2.Http()
+
+class MediathekPlugin (totem.Plugin):
+	def __init__ (self):
+		totem.Plugin.__init__ (self)
+		self.debug = False
+		self.totem = None
+		self.feed_download_lock = threading.Lock ()
+
+	def activate (self, totem_object):
+		"""
+		Build the interface
+		"""
+		builder = self.load_interface ("mediathek.ui", True, totem_object.get_main_window (), self)
+		container = builder.get_object ('mediathek_vbox')
+
+		self.tv_tree_store = builder.get_object ('mediathek_feed_store')
+		feed_list = builder.get_object ('mediathek_feed_list')
+		feed_list.connect ('row-expanded', self._row_expanded_cb)
+		feed_list.connect ('row-activated', self._row_activated_cb)
+
+		self.totem = totem_object
+		container.show_all ()
+
+		self.tv = feed ()
+
+		# Add the interface to Totem's sidebar
+		self.totem.add_sidebar_page ("mediathek", _("ZDF Mediathek"), container)
+
+		# Get the channel category listings
+		self.populate_channel_list (self.tv, self.tv_tree_store)
+
+	def deactivate (self, totem_object):
+		totem_object.remove_sidebar_page ("mediathek")
+
+	def populate_channel_list (self, feed, tree_store):
+		"""
+		Add all the channels as top-level rows in the tree store
+		"""
+		programm = feed.get_programm ()
+		for entry in programm.entries:
+			parent_iter = tree_store.append (None, [entry.title, entry.id, None])
+			tree_store.append (parent_iter, [_('Loading…'), None, None])
+
+	def _row_expanded_cb (self, tree_view, row_iter, path):
+		tree_model = tree_view.get_model ()
+
+		# 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) != 0 or tree_model.iter_n_children (row_iter) != 1:
+			return
+
+		# Populate it with feeds asynchronously
+		self.populate_feed_list (self.tv, tree_model, row_iter)
+
+	def _row_activated_cb (self, tree_view, path, view_column):
+		tree_store = tree_view.get_model ()
+		tree_iter = tree_store.get_iter (path)
+		mrl = tree_store.get_value (tree_iter, 1)
+
+		# Only allow feed rows to be activated, not channel or category rows
+		if mrl == None:
+			return
+
+		# Add the feed to the playlist and play it
+		self.totem.add_to_playlist_and_play (mrl, tree_store.get_value (tree_iter, 0), True)
+
+	def populate_feed_list (self, feed, tree_store, category_iter):
+		category_path = tree_store.get_path (category_iter)
+		thread = PopulateFeedsThread (self, feed, tree_store, category_path)
+		thread.start ()
+
+	def _populate_feed_list_cb (self, tree_store, category_path, values, remove_placeholder):
+		# Callback from PopulateFeedsThread to add stuff to the tree store
+		if values == None:
+			self.totem.action_error (_('Error getting programm feed'), _('There was an unknown error getting the list of feeds for this channel.'))
+			return False
+
+		if self.debug:
+			print "Title: %s" % values[0]
+			print "Description: %s" % values[1]
+			print "Stream: %s" % values[2]
+
+		category_iter = tree_store.get_iter (category_path)
+
+		tree_store.append (category_iter, values)
+
+		# Remove the placeholder row
+		if remove_placeholder:
+			tree_store.remove (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
+
+class PopulateFeedsThread (threading.Thread):
+	"""
+	Class to populate the feed 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
+		threading.Thread.__init__ (self)
+
+	def run (self):
+		self.plugin.feed_download_lock.acquire ()
+
+		remove_placeholder = True
+		tree_iter = self.tree_model.get_iter (self.category_path)
+		channel_id = self.tree_model.get_value (tree_iter, 1)
+
+		# Retrieve the feeds and return them
+		feeds = get_feeds (channel_id)
+
+		if feeds.entries == []:
+			gobject.idle_add (self.plugin._populate_feed_list_cb, self.tree_model, self.category_path, None, False)
+			self.plugin.feed_download_lock.release ()
+			return
+
+		# Add the feeds to the tree store
+		for feed in feeds.entries:
+			try:
+				media = feed.streams[1]
+			except:
+				# Not worth displaying an error in the interface for this
+				print "Programme has no streams"
+				continue
+
+			tip = '\n'.join([
+			  '<b>%s</b>' % format_str(feed.title),
+			  _('<b>Description:</b> %s') % format_str(feed.description),
+			  _('<b>Date:</b> %s') % format_time(feed.pubDate),
+			])
+
+			gobject.idle_add (self.plugin._populate_feed_list_cb, self.tree_model, self.category_path,
+				  [feed.title, media, tip], remove_placeholder)
+			remove_placeholder = False
+
+		self.plugin.feed_download_lock.release ()
+
+class feed(object):
+	"""
+	Get available Programms
+	"""
+	def __init__(self, programm=None):
+		url = "http://www.zdf.de/ZDFmediathek/inhalt?view=navJson";
+		index = httpget(url);
+
+		self.programm = htmlparser.parse_index(index);
+
+	def get_programm(self):
+		return self.programm
+
+def get_feeds(id):
+	"""
+	Get Video Feeds from RSS Data
+	"""
+	rss = "http://www.zdf.de/ZDFmediathek/content/%s?view=rss" % id
+	index = httpget(rss);
+	feeds = htmlparser.parse_rss(index);
+
+	return feeds
+
+def httpget(url):
+	resp, data = http.request(url, 'GET')
+	return data
+
+def format_str(st, truncate=False):
+	"""
+	Escape entities for pango markup and force the string to utf-8.
+	"""
+	if not st:
+		return ''
+	try:
+		return escape(st.encode('utf8'))
+	except:
+		return st
+
+def format_time(st):
+	"""
+	Convert date string to localized date
+	"""
+	if not st:
+		return strftime("%a, %d %b %Y %H:%M:%S", gmtime())
+	try:
+		loc = locale.getlocale()
+		locale.setlocale(locale.LC_ALL, 'C')
+		release = time.strptime(st, "%a, %d %b %Y %H:%M:%S %Z")
+		locale.setlocale(locale.LC_ALL, loc[0])
+
+		return time.strftime("%a, %x %X", release)
+	except:
+		return st
+
diff -Naur trunk/src/plugins/mediathek/mediathek.totem-plugin.in totem/src/plugins/mediathek/mediathek.totem-plugin.in
--- trunk/src/plugins/mediathek/mediathek.totem-plugin.in	1970-01-01 01:00:00.000000000 +0100
+++ totem/src/plugins/mediathek/mediathek.totem-plugin.in	2009-06-21 23:11:20.120428049 +0200
@@ -0,0 +1,9 @@
+[Totem Plugin]
+Loader=python
+Module=mediathek
+IAge=1
+_Name=ZDF Mediathek
+_Description=Stream ZDF programs from the Mediathek service.
+Authors=Christian Kornacker <christian.kornacker@gmx.net>
+Copyright=Copyright © 2009 Christian Kornacker
+Website=http://projects.gnome.org/totem/
diff -Naur trunk/src/plugins/mediathek/mediathek.ui totem/src/plugins/mediathek/mediathek.ui
--- trunk/src/plugins/mediathek/mediathek.ui	1970-01-01 01:00:00.000000000 +0100
+++ totem/src/plugins/mediathek/mediathek.ui	2009-06-21 22:24:07.866866408 +0200
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<!--*- mode: xml -*-->
+<interface>
+<object class="GtkTreeStore" id="mediathek_feed_store">
+	<columns>
+		<column type="gchararray"/><!--Title-->
+		<column type="gchararray"/><!--Summary-->
+		<column type="gchararray"/><!--MRL-->
+	</columns>
+</object>
+
+<object class="GtkVBox" id="mediathek_vbox">
+	<property name="border-width">5</property>
+	<property name="homogeneous">False</property>
+	<property name="spacing">6</property>
+	<child>
+		<object class="GtkScrolledWindow" id="mediathek_scrolled_window">
+			<property name="hscrollbar-policy">GTK_POLICY_AUTOMATIC</property>
+			<property name="vscrollbar-policy">GTK_POLICY_AUTOMATIC</property>
+			<property name="shadow-type">GTK_SHADOW_IN</property>
+			<child>
+				<object class="GtkTreeView" id="mediathek_feed_list">
+					<property name="model">mediathek_feed_store</property>
+					<property name="expander-column">mediathek_title_column</property>
+					<property name="headers-visible">False</property>
+					<property name="tooltip_column">2</property>
+					<child>
+						<object class="GtkTreeViewColumn" id="mediathek_title_column">
+							<child>
+								<object class="GtkCellRendererText" id="mediathek_title_cell_renderer"/>
+								<attributes>
+									<attribute name="text">0</attribute>
+								</attributes>
+							</child>
+						</object>
+					</child>
+				</object>
+			</child>
+		</object>
+	</child>
+</object>
+</interface>
