[rygel] core,plugins: Add gst-based MediaRenderer



commit a1bbaa24fa4925f885dbfd44e921ce4c9e00fbbd
Author: Zeeshan Ali (Khattak) <zeeshanak gnome org>
Date:   Tue Jun 16 22:33:32 2009 +0300

    core,plugins: Add gst-based MediaRenderer
    
    This is mostly code stolen (and heavily addapted) from
    gupnp-media-renderer and libowl-av.

 configure.ac                                       |    1 +
 data/xml/AVTransport2.xml                          |  788 ++++++++++
 data/xml/Makefile.am                               |    7 +-
 data/xml/MediaRenderer2.xml                        |   29 +
 data/xml/RenderingControl2.xml                     |  953 +++++++++++++
 src/plugins/Makefile.am                            |    7 +-
 src/plugins/gst-renderer/Makefile.am               |   55 +
 src/plugins/gst-renderer/owl-video-widget.c        | 1503 ++++++++++++++++++++
 src/plugins/gst-renderer/owl-video-widget.h        |  128 ++
 src/plugins/gst-renderer/owl-video-widget.vapi     |   31 +
 .../gst-renderer/rygel-gst-av-transport.vala       |  415 ++++++
 src/plugins/gst-renderer/rygel-gst-changelog.vala  |   92 ++
 .../gst-renderer/rygel-gst-connection-manager.vala |   98 ++
 src/plugins/gst-renderer/rygel-gst-plugin.vala     |   53 +
 .../gst-renderer/rygel-gst-rendering-control.vala  |  251 ++++
 .../gst-renderer/rygel-gst-video-window.vala       |  197 +++
 16 files changed, 4604 insertions(+), 4 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index f09425c..1d4c7b1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -239,6 +239,7 @@ src/plugins/external/Makefile
 src/plugins/gst-launch/Makefile
 src/plugins/mediathek/Makefile
 src/plugins/tracker/Makefile
+src/plugins/gst-renderer/Makefile
 src/plugins/test/Makefile
 data/Makefile
 data/xml/Makefile
diff --git a/data/xml/AVTransport2.xml b/data/xml/AVTransport2.xml
new file mode 100644
index 0000000..902a775
--- /dev/null
+++ b/data/xml/AVTransport2.xml
@@ -0,0 +1,788 @@
+<!--============================================================
+Title: UPnP AV AV-Transport Service (AVT) Template
+
+Purpose:
+To identify the required/optional actions and state variables
+and the required allowed values defined by this service type.
+
+Note:
+This file uses tabs (not spaces) for block indentation.
+Any updates to this file should maintain this convention.
+This includes disabling any automatic tab-to-space conversion
+feature provided by your editor.
+================================================================-->
+<scpd>
+	<serviceStateTable>
+		<stateVariable>
+			<name>TransportState</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+			<allowedValueList>
+				<allowedValue>STOPPED</allowedValue>
+				<allowedValue>PLAYING</allowedValue>
+			</allowedValueList>
+		</stateVariable>
+
+		<stateVariable>
+			<name>TransportStatus</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+			<allowedValueList>
+				<allowedValue>OK</allowedValue>
+				<allowedValue>ERROR_OCCURRED</allowedValue>
+			</allowedValueList>
+		</stateVariable>
+
+		<stateVariable>
+			<name>CurrentMediaCategory</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+			<allowedValueList>
+				<allowedValue>NO_MEDIA</allowedValue>
+				<allowedValue>TRACK_AWARE</allowedValue>
+				<allowedValue>TRACK_UNAWARE</allowedValue>
+			</allowedValueList>
+		</stateVariable>
+
+		<stateVariable>
+			<name>PlaybackStorageMedium</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<name>RecordStorageMedium</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<name>PossiblePlaybackStorageMedia</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<name>PossibleRecordStorageMedia</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<name>CurrentPlayMode</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+			<allowedValueList>
+				<allowedValue>NORMAL</allowedValue>
+			</allowedValueList>
+			<defaultValue>NORMAL</defaultValue>
+		</stateVariable>
+
+		<stateVariable>
+			<name>TransportPlaySpeed</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+			<allowedValueList>
+				<allowedValue>1</allowedValue>
+			</allowedValueList>
+			<defaultValue>1</defaultValue>
+		</stateVariable>
+
+		<stateVariable>
+			<name>RecordMediumWriteStatus</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<name>CurrentRecordQualityMode</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<name>PossibleRecordQualityModes</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<name>NumberOfTracks</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>ui4</dataType>
+			<allowedValueRange>
+				<minimum>0</minimum>
+			</allowedValueRange>
+		</stateVariable>
+
+		<stateVariable>
+			<name>CurrentTrack</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>ui4</dataType>
+			<allowedValueRange>
+				<minimum>0</minimum>
+				<step>1</step>
+			</allowedValueRange>
+		</stateVariable>
+
+		<stateVariable>
+			<name>CurrentTrackDuration</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<name>CurrentMediaDuration</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<name>CurrentTrackMetaData</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<name>CurrentTrackURI</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<name>AVTransportURI</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<name>AVTransportURIMetaData</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<name>NextAVTransportURI</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<name>NextAVTransportURIMetaData</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<name>RelativeTimePosition</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<name>AbsoluteTimePosition</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<name>RelativeCounterPosition</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>i4</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<name>AbsoluteCounterPosition</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>i4</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>CurrentTransportActions</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<name>LastChange</name>
+			<sendEventsAttribute>yes</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>DRMState</name>
+			<sendEventsAttribute>yes</sendEventsAttribute>
+			<dataType>string</dataType>
+			<allowedValueList>
+				<allowedValue>OK</allowedValue>
+			</allowedValueList>
+			<defaultValue>UNKNOWN</defaultValue>
+		</stateVariable>
+
+		<stateVariable>
+			<name>A_ARG_TYPE_SeekMode</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+			<allowedValueList>
+				<allowedValue>TRACK_NR</allowedValue>
+			</allowedValueList>
+		</stateVariable>
+
+		<stateVariable>
+			<name>A_ARG_TYPE_SeekTarget</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<name>A_ARG_TYPE_InstanceID</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>ui4</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>A_ARG_TYPE_DeviceUDN</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>A_ARG_TYPE_ServiceType</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>A_ARG_TYPE_ServiceID</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>A_ARG_TYPE_StateVariableValuePairs</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>A_ARG_TYPE_StateVariableList</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+	</serviceStateTable>
+
+	<actionList>
+		<action>
+			<name>SetAVTransportURI</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentURI</name>
+					<direction>in</direction>
+					<relatedStateVariable>AVTransportURI</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentURIMetaData</name>
+					<direction>in</direction>
+					<relatedStateVariable>AVTransportURIMetaData</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>SetNextAVTransportURI</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>NextURI</name>
+					<direction>in</direction>
+					<relatedStateVariable>NextAVTransportURI</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>NextURIMetaData</name>
+					<direction>in</direction>
+					<relatedStateVariable>NextAVTransportURIMetaData</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<name>GetMediaInfo</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>NrTracks</name>
+					<direction>out</direction>
+					<relatedStateVariable>NumberOfTracks</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>MediaDuration</name>
+					<direction>out</direction>
+					<relatedStateVariable>CurrentMediaDuration</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentURI</name>
+					<direction>out</direction>
+					<relatedStateVariable>AVTransportURI</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentURIMetaData</name>
+					<direction>out</direction>
+					<relatedStateVariable>AVTransportURIMetaData</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>NextURI</name>
+					<direction>out</direction>
+					<relatedStateVariable>NextAVTransportURI</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>NextURIMetaData</name>
+					<direction>out</direction>
+					<relatedStateVariable>NextAVTransportURIMetaData</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>PlayMedium</name>
+					<direction>out</direction>
+					<relatedStateVariable>PlaybackStorageMedium</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>RecordMedium</name>
+					<direction>out</direction>
+					<relatedStateVariable>RecordStorageMedium</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>WriteStatus</name>
+					<direction>out</direction>
+					<relatedStateVariable>RecordMediumWriteStatus</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<name>GetMediaInfo_Ext</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentType</name>
+					<direction>out</direction>
+					<relatedStateVariable>CurrentMediaCategory</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>NrTracks</name>
+					<direction>out</direction>
+					<relatedStateVariable>NumberOfTracks</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>MediaDuration</name>
+					<direction>out</direction>
+					<relatedStateVariable>CurrentMediaDuration</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentURI</name>
+					<direction>out</direction>
+					<relatedStateVariable>AVTransportURI</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentURIMetaData</name>
+					<direction>out</direction>
+					<relatedStateVariable>AVTransportURIMetaData</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>NextURI</name>
+					<direction>out</direction>
+					<relatedStateVariable>NextAVTransportURI</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>NextURIMetaData</name>
+					<direction>out</direction>
+					<relatedStateVariable>NextAVTransportURIMetaData</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>PlayMedium</name>
+					<direction>out</direction>
+					<relatedStateVariable>PlaybackStorageMedium</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>RecordMedium</name>
+					<direction>out</direction>
+					<relatedStateVariable>RecordStorageMedium</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>WriteStatus</name>
+					<direction>out</direction>
+					<relatedStateVariable>RecordMediumWriteStatus</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<name>GetTransportInfo</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentTransportState</name>
+					<direction>out</direction>
+					<relatedStateVariable>TransportState</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentTransportStatus</name>
+					<direction>out</direction>
+					<relatedStateVariable>TransportStatus</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentSpeed</name>
+					<direction>out</direction>
+					<relatedStateVariable>TransportPlaySpeed</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<name>GetPositionInfo</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>Track</name>
+					<direction>out</direction>
+					<relatedStateVariable>CurrentTrack</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>TrackDuration</name>
+					<direction>out</direction>
+					<relatedStateVariable>CurrentTrackDuration</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>TrackMetaData</name>
+					<direction>out</direction>
+					<relatedStateVariable>CurrentTrackMetaData</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>TrackURI</name>
+					<direction>out</direction>
+					<relatedStateVariable>CurrentTrackURI</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>RelTime</name>
+					<direction>out</direction>
+					<relatedStateVariable>RelativeTimePosition</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>AbsTime</name>
+					<direction>out</direction>
+					<relatedStateVariable>AbsoluteTimePosition</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>RelCount</name>
+					<direction>out</direction>
+					<relatedStateVariable>RelativeCounterPosition</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>AbsCount</name>
+					<direction>out</direction>
+					<relatedStateVariable>AbsoluteCounterPosition</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<name>GetDeviceCapabilities</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>PlayMedia</name>
+					<direction>out</direction>
+					<relatedStateVariable>PossiblePlaybackStorageMedia</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>RecMedia</name>
+					<direction>out</direction>
+					<relatedStateVariable>PossibleRecordStorageMedia</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>RecQualityModes</name>
+					<direction>out</direction>
+					<relatedStateVariable>PossibleRecordQualityModes</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<name>GetTransportSettings</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>PlayMode</name>
+					<direction>out</direction>
+					<relatedStateVariable>CurrentPlayMode</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>RecQualityMode</name>
+					<direction>out</direction>
+					<relatedStateVariable>CurrentRecordQualityMode</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<name>Stop</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<name>Play</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>Speed</name>
+					<direction>in</direction>
+					<relatedStateVariable>TransportPlaySpeed</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>Pause</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>Record</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<name>Seek</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>Unit</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_SeekMode</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>Target</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_SeekTarget</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<name>Next</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<name>Previous</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>SetPlayMode</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>NewPlayMode</name>
+					<direction>in</direction>
+					<relatedStateVariable>CurrentPlayMode</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>SetRecordQualityMode</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>NewRecordQualityMode</name>
+					<direction>in</direction>
+					<relatedStateVariable>CurrentRecordQualityMode</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>GetCurrentTransportActions</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>Actions</name>
+					<direction>out</direction>
+					<relatedStateVariable>CurrentTransportActions</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>GetDRMState</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentDRMState</name>
+					<direction>out</direction>
+					<relatedStateVariable>DRMState</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>GetStateVariables</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>StateVariableList</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_StateVariableList</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>StateVariableValuePairs</name>
+					<direction>out</direction>
+					<relatedStateVariable>A_ARG_TYPE_StateVariableValuePairs</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>SetStateVariables</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>AVTransportUDN</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_DeviceUDN</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>ServiceType</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_ServiceType</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>ServiceId</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_ServiceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>StateVariableValuePairs</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_StateVariableValuePairs</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>StateVariableList</name>
+					<direction>out</direction>
+					<relatedStateVariable>A_ARG_TYPE_StateVariableList</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+	</actionList>
+</scpd>
+
diff --git a/data/xml/Makefile.am b/data/xml/Makefile.am
index 3c3d864..1883aee 100644
--- a/data/xml/Makefile.am
+++ b/data/xml/Makefile.am
@@ -1,11 +1,12 @@
 xml_DATA = MediaServer2.xml \
+	   MediaRenderer2.xml \
 	   ContentDirectory.xml \
-	   ConnectionManager.xml
+	   ConnectionManager.xml \
+	   AVTransport2.xml \
+	   RenderingControl2.xml
 
 xmldir = $(datadir)/rygel/xml
 
 EXTRA_DIST = $(xml_DATA)
 
 MAINTAINERCLEANFILES = Makefile.in
-
-
diff --git a/data/xml/MediaRenderer2.xml b/data/xml/MediaRenderer2.xml
new file mode 100644
index 0000000..7eb9d12
--- /dev/null
+++ b/data/xml/MediaRenderer2.xml
@@ -0,0 +1,29 @@
+<!--============================================================
+Title: UPnP AV MediaRenderer:2 Device Template
+
+Purpose:
+To identify the required and optional services
+defined by this device type.
+
+NOTE: This file uses tabs (not spaces) for block indentation.
+Any updates to this file should maintain this convention.
+This includes disabling any automatic tab-to-space conversion
+feature enabled by your editor.
+============================================================-->
+<device>
+	<serviceList>
+		<service>
+		<serviceType>urn:schemas-upnp-org:service:RenderingControl:2</serviceType>
+			<serviceId>RenderingControl</serviceId>
+		</service>
+		<service>
+			<serviceType>urn:schemas-upnp-org:service:ConnectionManager:2</serviceType>
+			<serviceId>ConnectionManager</serviceId>
+		</service>
+		<service>
+			<Optional/>
+			<serviceType>urn:schemas-upnp-org:service:AVTransport:2</serviceType>
+			<serviceId>AVTransport</serviceId>
+		</service>
+	</serviceList>
+</device>
\ No newline at end of file
diff --git a/data/xml/RenderingControl2.xml b/data/xml/RenderingControl2.xml
new file mode 100644
index 0000000..2918f8a
--- /dev/null
+++ b/data/xml/RenderingControl2.xml
@@ -0,0 +1,953 @@
+<!--============================================================
+Title: UPnP AV Rendering Control Service (RCS) Template
+
+Purpose:
+To identify the required/optional actions and state variables
+and the required allowed values defined by this service type.
+
+Note:
+This file uses tabs (not spaces) for block indentation.
+Any updates to this file should maintain this convention.
+This includes disabling any automatic tab-to-space conversion
+feature provided by your editor.
+================================================================-->
+<scpd>
+	<serviceStateTable>
+		<stateVariable>
+			<name>LastChange</name>
+			<sendEventsAttribute>yes</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<name>PresetNameList</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>Brightness</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>ui2</dataType>
+			<allowedValueRange>
+				<minimum>0</minimum>
+				<step>1</step>
+			</allowedValueRange>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>Contrast</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>ui2</dataType>
+			<allowedValueRange>
+				<minimum>0</minimum>
+				<step>1</step>
+			</allowedValueRange>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>Sharpness</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>ui2</dataType>
+			<allowedValueRange>
+				<minimum>0</minimum>
+				<step>1</step>
+			</allowedValueRange>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>RedVideoGain</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>ui2</dataType>
+			<allowedValueRange>
+				<minimum>0</minimum>
+				<step>1</step>
+			</allowedValueRange>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>GreenVideoGain</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>ui2</dataType>
+			<allowedValueRange>
+				<minimum>0</minimum>
+				<step>1</step>
+			</allowedValueRange>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>BlueVideoGain</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>ui2</dataType>
+			<allowedValueRange>
+				<minimum>0</minimum>
+				<step>1</step>
+			</allowedValueRange>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>RedVideoBlackLevel</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>ui2</dataType>
+			<allowedValueRange>
+				<minimum>0</minimum>
+				<step>1</step>
+			</allowedValueRange>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>GreenVideoBlackLevel</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>ui2</dataType>
+			<allowedValueRange>
+				<minimum>0</minimum>
+				<step>1</step>
+			</allowedValueRange>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>BlueVideoBlackLevel</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>ui2</dataType>
+			<allowedValueRange>
+				<minimum>0</minimum>
+				<step>1</step>
+			</allowedValueRange>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>ColorTemperature</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>ui2</dataType>
+			<allowedValueRange>
+				<minimum>0</minimum>
+				<step>1</step>
+			</allowedValueRange>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>HorizontalKeystone</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>i2</dataType>
+			<allowedValueRange>
+				<step>1</step>
+			</allowedValueRange>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>VerticalKeystone</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>i2</dataType>
+			<allowedValueRange>
+				<step>1</step>
+			</allowedValueRange>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>Mute</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>boolean</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>Volume</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>ui2</dataType>
+			<allowedValueRange>
+				<minimum>0</minimum>
+				<step>1</step>
+			</allowedValueRange>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>VolumeDB</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>i2</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>Loudness</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>boolean</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>A_ARG_TYPE_Channel</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+			<allowedValueList>
+				<allowedValue>Master</allowedValue>
+			</allowedValueList>
+		</stateVariable>
+
+		<stateVariable>
+			<name>A_ARG_TYPE_InstanceID</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>ui4</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<name>A_ARG_TYPE_PresetName</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+			<allowedValueList>
+				<allowedValue>FactoryDefaults</allowedValue>
+			</allowedValueList>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>A_ARG_TYPE_DeviceUDN</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>A_ARG_TYPE_ServiceType</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>A_ARG_TYPE_ServiceID</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>A_ARG_TYPE_StateVariableValuePairs</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+
+		<stateVariable>
+			<Optional/>
+			<name>A_ARG_TYPE_StateVariableList</name>
+			<sendEventsAttribute>no</sendEventsAttribute>
+			<dataType>string</dataType>
+		</stateVariable>
+	</serviceStateTable>
+
+	<actionList>
+		<action>
+			<name>ListPresets</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentPresetNameList</name>
+					<direction>out</direction>
+					<relatedStateVariable>PresetNameList</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<name>SelectPreset</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>PresetName</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_PresetName</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>GetBrightness</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentBrightness</name>
+					<direction>out</direction>
+					<relatedStateVariable>Brightness</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>SetBrightness</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>DesiredBrightness</name>
+					<direction>in</direction>
+					<relatedStateVariable>Brightness</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>GetContrast</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentContrast</name>
+					<direction>out</direction>
+					<relatedStateVariable>Contrast</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action><Optional/>
+		<name>SetContrast</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>DesiredContrast</name>
+					<direction>in</direction>
+					<relatedStateVariable>Contrast</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>GetSharpness</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentSharpness</name>
+					<direction>out</direction>
+					<relatedStateVariable>Sharpness</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>SetSharpness</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>DesiredSharpness</name>
+					<direction>in</direction>
+					<relatedStateVariable>Sharpness</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>GetRedVideoGain</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentRedVideoGain</name>
+					<direction>out</direction>
+					<relatedStateVariable>RedVideoGain</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>SetRedVideoGain</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>DesiredRedVideoGain</name>
+					<direction>in</direction>
+					<relatedStateVariable>RedVideoGain</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>GetGreenVideoGain</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentGreenVideoGain</name>
+					<direction>out</direction>
+					<relatedStateVariable>GreenVideoGain</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>SetGreenVideoGain</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>DesiredGreenVideoGain</name>
+					<direction>in</direction>
+					<relatedStateVariable>GreenVideoGain</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>GetBlueVideoGain</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentBlueVideoGain</name>
+					<direction>out</direction>
+					<relatedStateVariable>BlueVideoGain</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>SetBlueVideoGain</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>DesiredBlueVideoGain</name>
+					<direction>in</direction>
+					<relatedStateVariable>BlueVideoGain</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>GetRedVideoBlackLevel</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentRedVideoBlackLevel</name>
+					<direction>out</direction>
+					<relatedStateVariable>RedVideoBlackLevel</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>SetRedVideoBlackLevel</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>DesiredRedVideoBlackLevel</name>
+					<direction>in</direction>
+					<relatedStateVariable>RedVideoBlackLevel</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>GetGreenVideoBlackLevel</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentGreenVideoBlackLevel</name>
+					<direction>out</direction>
+					<relatedStateVariable>GreenVideoBlackLevel</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>SetGreenVideoBlackLevel</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>DesiredGreenVideoBlackLevel</name>
+					<direction>in</direction>
+					<relatedStateVariable>GreenVideoBlackLevel</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>GetBlueVideoBlackLevel</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentBlueVideoBlackLevel</name>
+					<direction>out</direction>
+					<relatedStateVariable>BlueVideoBlackLevel</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>SetBlueVideoBlackLevel</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>DesiredBlueVideoBlackLevel</name>
+					<direction>in</direction>
+					<relatedStateVariable>BlueVideoBlackLevel</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>GetColorTemperature</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentColorTemperature</name>
+					<direction>out</direction>
+					<relatedStateVariable>ColorTemperature</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>SetColorTemperature</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>DesiredColorTemperature</name>
+					<direction>in</direction>
+					<relatedStateVariable>ColorTemperature</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>GetHorizontalKeystone</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentHorizontalKeystone</name>
+					<direction>out</direction>
+					<relatedStateVariable>HorizontalKeystone</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>SetHorizontalKeystone</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>DesiredHorizontalKeystone</name>
+					<direction>in</direction>
+					<relatedStateVariable>HorizontalKeystone</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>GetVerticalKeystone</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentVerticalKeystone</name>
+					<direction>out</direction>
+					<relatedStateVariable>VerticalKeystone</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>SetVerticalKeystone</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>DesiredVerticalKeystone</name>
+					<direction>in</direction>
+					<relatedStateVariable>VerticalKeystone</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>GetMute</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>Channel</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentMute</name>
+					<direction>out</direction>
+					<relatedStateVariable>Mute</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>SetMute</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>Channel</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>DesiredMute</name>
+					<direction>in</direction>
+					<relatedStateVariable>Mute</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>GetVolume</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>Channel</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentVolume</name>
+					<direction>out</direction>
+					<relatedStateVariable>Volume</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>SetVolume</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>Channel</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>DesiredVolume</name>
+					<direction>in</direction>
+					<relatedStateVariable>Volume</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>GetVolumeDB</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>Channel</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentVolume</name>
+					<direction>out</direction>
+					<relatedStateVariable>VolumeDB</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>SetVolumeDB</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>Channel</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>DesiredVolume</name>
+					<direction>in</direction>
+					<relatedStateVariable>VolumeDB</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>GetVolumeDBRange</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>Channel</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>MinValue</name>
+					<direction>out</direction>
+					<relatedStateVariable>VolumeDB</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>MaxValue</name>
+					<direction>out</direction>
+					<relatedStateVariable>VolumeDB</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>GetLoudness</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>Channel</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>CurrentLoudness</name>
+					<direction>out</direction>
+					<relatedStateVariable>Loudness</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>SetLoudness</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>Channel</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>DesiredLoudness</name>
+					<direction>in</direction>
+					<relatedStateVariable>Loudness</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>GetStateVariables</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>StateVariableList</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_StateVariableList</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>StateVariableValuePairs</name>
+					<direction>out</direction>
+					<relatedStateVariable>A_ARG_TYPE_StateVariableValuePairs</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+
+		<action>
+			<Optional/>
+			<name>SetStateVariables</name>
+			<argumentList>
+				<argument>
+					<name>InstanceID</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>RenderingControlUDN</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_DeviceUDN</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>ServiceType</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_ServiceType</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>ServiceId</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_ServiceID</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>StateVariableValuePairs</name>
+					<direction>in</direction>
+					<relatedStateVariable>A_ARG_TYPE_StateVariableValuePairs</relatedStateVariable>
+				</argument>
+				<argument>
+					<name>StateVariableList</name>
+					<direction>out</direction>
+					<relatedStateVariable>A_ARG_TYPE_StateVariableList</relatedStateVariable>
+				</argument>
+			</argumentList>
+		</action>
+	</actionList>
+</scpd>
\ No newline at end of file
diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am
index e7affd8..4472416 100644
--- a/src/plugins/Makefile.am
+++ b/src/plugins/Makefile.am
@@ -22,11 +22,16 @@ if BUILD_GST_LAUNCH_PLUGIN
 GST_LAUNCH_PLUGIN = gst-launch
 endif
 
+if BUILD_UI
+GST_RENDERER = gst-renderer
+endif
+
 SUBDIRS = $(TEST_PLUGIN) \
 	  $(TRACKER_PLUGIN) \
 	  $(MEDIATHEK_PLUGIN) \
 	  $(MEDIA_EXPORT_PLUGIN) \
 	  $(EXTERNAL_PLUGIN) \
-	  $(GST_LAUNCH_PLUGIN)
+	  $(GST_LAUNCH_PLUGIN) \
+          $(GST_RENDERER)
 
 MAINTAINERCLEANFILES = Makefile.in
diff --git a/src/plugins/gst-renderer/Makefile.am b/src/plugins/gst-renderer/Makefile.am
new file mode 100644
index 0000000..79f0594
--- /dev/null
+++ b/src/plugins/gst-renderer/Makefile.am
@@ -0,0 +1,55 @@
+plugindir = $(libdir)/rygel-1.0
+
+plugin_LTLIBRARIES = librygel-gst.la
+
+AM_CFLAGS = $(LIBGUPNP_CFLAGS) \
+	    $(LIBGUPNP_AV_CFLAGS) \
+	    $(GEE_CFLAGS) \
+	    $(GTK_CFLAGS) \
+	    $(LIBGSTREAMER_CFLAGS) \
+	    $(LIBGCONF_CFLAGS) \
+	    -I$(top_srcdir)/src/rygel -DDATA_DIR='"$(datadir)"'
+
+BUILT_SOURCES = rygel-gst-connection-manager.c \
+		rygel-gst-rendering-control.c \
+		rygel-gst-av-transport.c \
+		rygel-gst-video-window.c \
+		rygel-gst-changelog.c \
+		rygel-gst-plugin.c
+
+$(BUILT_SOURCES) : rygel-gst.stamp
+
+librygel_gst_la_SOURCES = rygel-gst-connection-manager.c \
+			  rygel-gst-connection-manager.vala \
+			  rygel-gst-rendering-control.c \
+			  rygel-gst-rendering-control.vala \
+			  rygel-gst-av-transport.c \
+			  rygel-gst-av-transport.vala \
+			  rygel-gst-video-window.c \
+			  rygel-gst-video-window.vala \
+			  rygel-gst-changelog.c \
+			  rygel-gst-changelog.vala \
+			  rygel-gst-plugin.c \
+			  rygel-gst-plugin.vala \
+			  owl-video-widget.c \
+			  owl-video-widget.h
+
+rygel-gst.stamp: $(filter %.vala,$(librygel_gst_la_SOURCES))
+	$(VALAC) -C --vapidir=$(srcdir) --vapidir=$(top_srcdir)/src/rygel \
+	--pkg rygel-1.0 --pkg cstuff --pkg gupnp-1.0 --pkg gupnp-av-1.0 \
+	--pkg owl-video-widget --pkg gee-1.0 --pkg gstreamer-0.10 \
+	--pkg gconf-2.0 --pkg gtk+-2.0 \
+	$^
+	touch $@
+
+librygel_gst_la_LIBADD = $(LIBGUPNP_LIBS) \
+                         $(LIBGUPNP_AV_LIBS) \
+	                 $(LIBGSTREAMER_LIBS) \
+                         $(GEE_LIBS) \
+                         $(GTK_LIBS) \
+	    		 $(LIBGCONF_LIBS)
+librygel_gst_la_LDFLAGS = -shared -fPIC -module -avoid-version
+
+CLEANFILES = $(BUILT_SOURCES) rygel-gst.stamp
+MAINTAINERCLEANFILES = Makefile.in
+EXTRA_DIST = $(BUILT_SOURCES) rygel-gst.stamp owl-video-widget.vapi
diff --git a/src/plugins/gst-renderer/owl-video-widget.c b/src/plugins/gst-renderer/owl-video-widget.c
new file mode 100644
index 0000000..e200155
--- /dev/null
+++ b/src/plugins/gst-renderer/owl-video-widget.c
@@ -0,0 +1,1503 @@
+/*
+ * Copyright (C) 2006, 2008 OpenedHand Ltd.
+ *
+ * OpenedHand Widget Library Video Widget - A GStreamer video GTK+ widget
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Author: Jorn Baayen <jorn openedhand com>
+ */
+
+#include <gdk/gdkx.h>
+#include <gst/gst.h>
+#include <gst/interfaces/xoverlay.h>
+
+#include "owl-video-widget.h"
+
+/** TODO
+ * o Possibly implement colour balance properties.
+ *   xvimagesink supports the following, on a range -1000 - 1000:
+ *     - contrast
+ *     - brightness
+ *     - hue
+ *     - saturation
+ **/
+
+G_DEFINE_TYPE (OwlVideoWidget,
+               owl_video_widget,
+               GTK_TYPE_BIN);
+
+struct _OwlVideoWidgetPrivate {
+        GstElement *playbin;
+        GstXOverlay *overlay;
+
+        GMutex *overlay_lock;
+
+        GdkWindow *dummy_window;
+
+        char *uri;
+
+        gboolean can_seek;
+
+        int buffer_percent;
+
+        int duration;
+
+        gboolean force_aspect_ratio;
+
+        guint tick_timeout_id;
+};
+
+enum {
+        PROP_0,
+        PROP_URI,
+        PROP_PLAYING,
+        PROP_POSITION,
+        PROP_VOLUME,
+        PROP_CAN_SEEK,
+        PROP_BUFFER_PERCENT,
+        PROP_DURATION,
+        PROP_FORCE_ASPECT_RATIO
+};
+
+enum {
+        TAG_LIST_AVAILABLE,
+        EOS,
+        ERROR,
+        LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+#define TICK_TIMEOUT 0.5
+
+/* TODO: Possibly retrieve these through introspection. The problem is that we
+ * need them in class_init already. */
+#define GST_VOL_DEFAULT 1.0
+#define GST_VOL_MAX     4.0
+
+/**
+ * Synchronise the force-aspect-ratio property with the videosink.
+ **/
+static void
+sync_force_aspect_ratio (OwlVideoWidget *video_widget)
+{
+        GObjectClass *class;
+
+        class = G_OBJECT_GET_CLASS (video_widget->priv->overlay);
+        
+        if (!g_object_class_find_property (class, "force-aspect-ratio")) {
+                g_warning ("Unable to find 'force-aspect-ratio' "
+                           "property.");
+
+                return;
+        } 
+
+        g_object_set (video_widget->priv->overlay,
+                      "force-aspect-ratio",
+                      video_widget->priv->force_aspect_ratio,
+                      NULL);
+}
+
+/**
+ * Ensures the existance of a dummy window and returns its XID.
+ **/
+static XID
+create_dummy_window (OwlVideoWidget *video_widget)
+{
+        GdkWindowAttr attributes;
+
+        if (video_widget->priv->dummy_window)
+                return GDK_WINDOW_XID (video_widget->priv->dummy_window);
+
+        attributes.width = 0;
+        attributes.height = 0;
+        attributes.window_type = GDK_WINDOW_TOPLEVEL;
+        attributes.wclass = GDK_INPUT_OUTPUT;
+        attributes.event_mask = 0;
+
+        video_widget->priv->dummy_window = gdk_window_new (NULL,
+                                                           &attributes,
+                                                           0);
+
+        /**
+         * Sync, so that the window is definetely there when the videosink
+         * starts looking at it.
+         **/
+        XSync (GDK_WINDOW_XDISPLAY (video_widget->priv->dummy_window), FALSE);
+
+        return GDK_WINDOW_XID (video_widget->priv->dummy_window);
+}
+
+/**
+ * Destroys the dummy window, if any.
+ **/
+static void
+destroy_dummy_window (OwlVideoWidget *video_widget)
+{
+        if (video_widget->priv->dummy_window) {
+                g_object_unref (video_widget->priv->dummy_window);
+                video_widget->priv->dummy_window = NULL;
+        }
+}
+
+/**
+ * A message arrived synchronously on the bus: See if the overlay becomes available.
+ **/
+static GstBusSyncReply
+bus_sync_handler_cb (GstBus            *bus,
+                     GstMessage        *message,
+                     OwlVideoWidget *video_widget)
+{
+
+        const GstStructure *str;
+        XID xid;
+
+        str = gst_message_get_structure (message);
+        if (!str)
+                return GST_BUS_PASS;
+        
+        if (!gst_structure_has_name (str, "prepare-xwindow-id"))
+                return GST_BUS_PASS;
+
+        /**
+         * Lock.
+         **/
+        g_mutex_lock (video_widget->priv->overlay_lock);
+
+        gdk_threads_enter ();
+
+        /**
+         * Take in the new overlay.
+         **/
+        if (video_widget->priv->overlay) {
+                g_object_remove_weak_pointer
+                        (G_OBJECT (video_widget->priv->overlay),
+                         (gpointer) &video_widget->priv->overlay);
+        }
+        
+        video_widget->priv->overlay = GST_X_OVERLAY (GST_MESSAGE_SRC (message));
+
+        g_object_add_weak_pointer (G_OBJECT (video_widget->priv->overlay),
+                                   (gpointer) &video_widget->priv->overlay);
+
+        g_object_set (video_widget->priv->overlay,
+                      "handle-expose", FALSE,
+                      NULL);
+
+        sync_force_aspect_ratio (video_widget);
+
+        /**
+         * Connect the new overlay to our window.
+         **/
+        if (GTK_WIDGET_REALIZED (video_widget))
+                xid = GDK_WINDOW_XID (GTK_WIDGET (video_widget)->window);
+        else
+                xid = create_dummy_window (video_widget);
+
+        gst_x_overlay_set_xwindow_id (video_widget->priv->overlay, xid);
+
+        /**
+         * And expose.
+         **/
+        if (GTK_WIDGET_REALIZED (video_widget))
+                gst_x_overlay_expose (video_widget->priv->overlay);
+        
+        /**
+         * Unlock.
+         **/
+        gdk_threads_leave ();
+        
+        g_mutex_unlock (video_widget->priv->overlay_lock);
+
+        /**
+         * Drop this message.
+         **/
+        gst_message_unref (message);
+        
+        return GST_BUS_DROP;
+}
+
+/**
+ * An error occured.
+ **/
+static void
+bus_message_error_cb (GstBus         *bus,
+                      GstMessage     *message,
+                      OwlVideoWidget *video_widget)
+{
+        GError *error;
+
+        error = NULL;
+        gst_message_parse_error (message,
+                                 &error,
+                                 NULL);
+        
+        g_signal_emit (video_widget,
+                       signals[ERROR],
+                       0,
+                       error);
+
+        g_error_free (error);
+}
+
+/**
+ * End of stream reached.
+ **/
+static void
+bus_message_eos_cb (GstBus         *bus,
+                    GstMessage     *message,
+                    OwlVideoWidget *video_widget)
+{
+        /**
+         * Make sure UI is in sync.
+         **/
+        g_object_notify (G_OBJECT (video_widget), "position");
+
+        /**
+         * Emit EOS signal.
+         **/
+        g_signal_emit (video_widget,
+                       signals[EOS],
+                       0);
+}
+
+/**
+ * Tag list available.
+ **/
+static void
+bus_message_tag_cb (GstBus         *bus,
+                    GstMessage     *message,
+                    OwlVideoWidget *video_widget)
+{
+        GstTagList *tag_list;
+
+        gst_message_parse_tag (message, &tag_list);
+
+        g_signal_emit (video_widget,
+                       signals[TAG_LIST_AVAILABLE],
+                       0,
+                       tag_list);
+
+        gst_tag_list_free (tag_list);
+}
+
+/**
+ * Buffering information available.
+ **/
+static void
+bus_message_buffering_cb (GstBus         *bus,
+                          GstMessage     *message,
+                          OwlVideoWidget *video_widget)
+{
+        const GstStructure *str;
+
+        str = gst_message_get_structure (message);
+        if (!str)
+                return;
+
+        if (!gst_structure_get_int (str,
+                                    "buffer-percent",
+                                    &video_widget->priv->buffer_percent))
+                return;
+        
+        g_object_notify (G_OBJECT (video_widget), "buffer-percent");
+}
+
+/**
+ * Duration information available.
+ **/
+static void
+bus_message_duration_cb (GstBus         *bus,
+                         GstMessage     *message,
+                         OwlVideoWidget *video_widget)
+{
+        GstFormat format;
+        gint64 duration;
+
+        gst_message_parse_duration (message,
+                                    &format,
+                                    &duration);
+
+        if (format != GST_FORMAT_TIME)
+                return;
+
+        video_widget->priv->duration = duration / GST_SECOND;
+
+        g_object_notify (G_OBJECT (video_widget), "duration");
+}
+
+/**
+ * A state change occured.
+ **/
+static void
+bus_message_state_change_cb (GstBus         *bus,
+                             GstMessage     *message,
+                             OwlVideoWidget *video_widget)
+{
+        gpointer src;
+        GstState old_state, new_state;
+
+        src = GST_MESSAGE_SRC (message);
+        
+        if (src != video_widget->priv->playbin)
+                return;
+
+        gst_message_parse_state_changed (message,
+                                         &old_state,
+                                         &new_state,
+                                         NULL);
+
+        if (old_state == GST_STATE_READY &&
+            new_state == GST_STATE_PAUSED) {
+                GstQuery *query;
+
+                /**
+                 * Determine whether we can seek.
+                 **/
+                query = gst_query_new_seeking (GST_FORMAT_TIME);
+
+                if (gst_element_query (video_widget->priv->playbin, query)) {
+                        gst_query_parse_seeking (query,
+                                                 NULL,
+                                                 &video_widget->priv->can_seek,
+                                                 NULL,
+                                                 NULL);
+                } else {
+                        /**
+                         * Could not query for ability to seek. Assume
+                         * seek is supported.
+                         **/
+
+                        video_widget->priv->can_seek = TRUE;
+                }
+
+                gst_query_unref (query);
+                
+                g_object_notify (G_OBJECT (video_widget), "can-seek");
+
+                /**
+                 * Determine the duration.
+                 **/
+                query = gst_query_new_duration (GST_FORMAT_TIME);
+
+                if (gst_element_query (video_widget->priv->playbin, query)) {
+                        gint64 duration;
+
+                        gst_query_parse_duration (query,
+                                                  NULL,
+                                                  &duration);
+
+                        video_widget->priv->duration = duration / GST_SECOND;
+                        
+                        g_object_notify (G_OBJECT (video_widget), "duration");
+                }
+
+                gst_query_unref (query);
+        }
+}
+
+/**
+ * Called every TICK_TIMEOUT secs to notify of a position change.
+ **/
+static gboolean
+tick_timeout (OwlVideoWidget *video_widget)
+{
+        g_object_notify (G_OBJECT (video_widget), "position");
+
+        return TRUE;
+}
+
+/**
+ * Constructs the GStreamer pipeline.
+ **/
+static void
+construct_pipeline (OwlVideoWidget *video_widget)
+{
+
+        GstElement *videosink, *audiosink;
+        GstBus *bus;
+
+        /**
+         * playbin.
+         **/
+        video_widget->priv->playbin =
+                gst_element_factory_make ("playbin2", "playbin2");
+        if (!video_widget->priv->playbin) {
+                /* Try playbin if playbin2 isn't available */
+                video_widget->priv->playbin =
+                        gst_element_factory_make ("playbin", "playbin");
+        }
+
+        if (!video_widget->priv->playbin) {
+                g_warning ("No playbin found. Playback will not work.");
+
+                return;
+        }
+
+        /**
+         * A videosink.
+         **/
+        videosink = gst_element_factory_make ("gconfvideosink", "videosink");
+        if (!videosink) {
+                g_warning ("No gconfvideosink found. Trying autovideosink ...");
+
+                videosink = gst_element_factory_make ("autovideosink",
+                                                      "videosink");
+                if (!videosink) {
+                        g_warning ("No autovideosink found. "
+                                   "Trying ximagesink ...");
+
+                        videosink = gst_element_factory_make ("ximagesink",
+                                                              "videosink");
+                        if (!videosink) {
+                                g_warning ("No videosink could be found. "
+                                           "Video will not be available.");
+                        }
+                }
+        }
+
+        /**
+         * An audiosink.
+         **/
+        audiosink = gst_element_factory_make ("gconfaudiosink", "audiosink");
+        if (!audiosink) {
+                g_warning ("No gconfaudiosink found. Trying autoaudiosink ...");
+
+                audiosink = gst_element_factory_make ("autoaudiosink",
+                                                      "audiosink");
+                if (!audiosink) {
+                        g_warning ("No autoaudiosink found. "
+                                   "Trying alsasink ...");
+
+                        audiosink = gst_element_factory_make ("alsasink",
+                                                              "audiosink");
+                        if (!audiosink) {
+                                g_warning ("No audiosink could be found. "
+                                           "Audio will not be available.");
+                        }
+                }
+        }
+
+        /**
+         * Click sinks into playbin.
+         **/
+        g_object_set (G_OBJECT (video_widget->priv->playbin),
+                      "video-sink", videosink,
+                      "audio-sink", audiosink,
+                      NULL);
+
+        /**
+         * Connect to signals on bus.
+         **/
+        bus = gst_pipeline_get_bus (GST_PIPELINE (video_widget->priv->playbin));
+
+        gst_bus_add_signal_watch (bus);
+
+        gst_bus_set_sync_handler (bus,
+                                  (GstBusSyncHandler) bus_sync_handler_cb,
+                                  video_widget);
+        
+        g_signal_connect_object (bus,
+                                 "message::error",
+                                 G_CALLBACK (bus_message_error_cb),
+                                 video_widget,
+                                 0);
+        g_signal_connect_object (bus,
+                                 "message::eos",
+                                 G_CALLBACK (bus_message_eos_cb),
+                                 video_widget,
+                                 0);
+        g_signal_connect_object (bus,
+                                 "message::tag",
+                                 G_CALLBACK (bus_message_tag_cb),
+                                 video_widget,
+                                 0);
+        g_signal_connect_object (bus,
+                                 "message::buffering",
+                                 G_CALLBACK (bus_message_buffering_cb),
+                                 video_widget,
+                                 0);
+        g_signal_connect_object (bus,
+                                 "message::duration",
+                                 G_CALLBACK (bus_message_duration_cb),
+                                 video_widget,
+                                 0);
+ 
+        g_signal_connect_object (bus,
+                                 "message::state-changed",
+                                 G_CALLBACK (bus_message_state_change_cb),
+                                 video_widget,
+                                 0);
+
+        gst_object_unref (GST_OBJECT (bus));
+}
+
+static void
+owl_video_widget_init (OwlVideoWidget *video_widget)
+{
+        /**
+         * We do have our own GdkWindow.
+         **/
+        GTK_WIDGET_UNSET_FLAGS (video_widget, GTK_NO_WINDOW);
+        GTK_WIDGET_UNSET_FLAGS (video_widget, GTK_DOUBLE_BUFFERED);
+
+        /**
+         * Create pointer to private data.
+         **/
+        video_widget->priv =
+                G_TYPE_INSTANCE_GET_PRIVATE (video_widget,
+                                             OWL_TYPE_VIDEO_WIDGET,
+                                             OwlVideoWidgetPrivate);
+
+        /**
+         * Initialize defaults.
+         **/
+        video_widget->priv->force_aspect_ratio = TRUE;
+
+        /**
+         * Create lock.
+         **/
+        video_widget->priv->overlay_lock = g_mutex_new ();
+
+        /**
+         * Construct GStreamer pipeline: playbin with sinks from GConf.
+         **/
+        construct_pipeline (video_widget);
+}
+
+static void
+owl_video_widget_set_property (GObject      *object,
+                               guint         property_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+        OwlVideoWidget *video_widget;
+
+        video_widget = OWL_VIDEO_WIDGET (object);
+
+        switch (property_id) {
+        case PROP_URI:
+                owl_video_widget_set_uri (video_widget,
+                                             g_value_get_string (value));
+                break;
+        case PROP_PLAYING:
+                owl_video_widget_set_playing (video_widget,
+                                                 g_value_get_boolean (value));
+                break;
+        case PROP_POSITION:
+                owl_video_widget_set_position (video_widget,
+                                                  g_value_get_int (value));
+                break;
+        case PROP_VOLUME:
+                owl_video_widget_set_volume (video_widget,
+                                                g_value_get_double (value));
+                break;
+        case PROP_FORCE_ASPECT_RATIO:
+                owl_video_widget_set_force_aspect_ratio
+                                               (video_widget,
+                                                g_value_get_boolean (value));
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+                break;
+        }
+}
+
+static void
+owl_video_widget_get_property (GObject    *object,
+                               guint       property_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+        OwlVideoWidget *video_widget;
+
+        video_widget = OWL_VIDEO_WIDGET (object);
+
+        switch (property_id) {
+        case PROP_URI:
+                g_value_set_string
+                        (value,
+                         owl_video_widget_get_uri (video_widget));
+                break;
+        case PROP_PLAYING:
+                g_value_set_boolean
+                        (value,
+                         owl_video_widget_get_playing (video_widget));
+                break;
+        case PROP_POSITION:
+                g_value_set_int
+                        (value,
+                         owl_video_widget_get_position (video_widget));
+                break;
+        case PROP_VOLUME:
+                g_value_set_double
+                        (value,
+                         owl_video_widget_get_volume (video_widget));
+                break;
+        case PROP_CAN_SEEK:
+                g_value_set_boolean
+                        (value,
+                         owl_video_widget_get_can_seek (video_widget));
+                break;
+        case PROP_BUFFER_PERCENT:
+                g_value_set_int
+                        (value,
+                         owl_video_widget_get_buffer_percent (video_widget));
+                break;
+        case PROP_DURATION:
+                g_value_set_int
+                        (value,
+                         owl_video_widget_get_duration (video_widget));
+                break;
+        case PROP_FORCE_ASPECT_RATIO:
+                g_value_set_boolean
+                        (value,
+                         owl_video_widget_get_force_aspect_ratio
+                                                        (video_widget));
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+                break;
+        }
+}
+
+static void
+owl_video_widget_dispose (GObject *object)
+{
+        OwlVideoWidget *video_widget;
+        GObjectClass *object_class;
+
+        video_widget = OWL_VIDEO_WIDGET (object);
+
+        if (video_widget->priv->playbin) {
+                gst_element_set_state (video_widget->priv->playbin,
+                                       GST_STATE_NULL);
+
+                gst_object_unref (GST_OBJECT (video_widget->priv->playbin));
+                video_widget->priv->playbin = NULL;
+        }
+
+        if (video_widget->priv->tick_timeout_id > 0) {
+                g_source_remove (video_widget->priv->tick_timeout_id);
+                video_widget->priv->tick_timeout_id = 0;
+        }
+
+        destroy_dummy_window (video_widget);
+
+        object_class = G_OBJECT_CLASS (owl_video_widget_parent_class);
+        object_class->dispose (object);
+}
+
+static void
+owl_video_widget_finalize (GObject *object)
+{
+        OwlVideoWidget *video_widget;
+        GObjectClass *object_class;
+
+        video_widget = OWL_VIDEO_WIDGET (object);
+
+        g_mutex_free (video_widget->priv->overlay_lock);
+
+        g_free (video_widget->priv->uri);
+
+        object_class = G_OBJECT_CLASS (owl_video_widget_parent_class);
+        object_class->finalize (object);
+}
+
+static void
+owl_video_widget_realize (GtkWidget *widget)
+{
+        OwlVideoWidget *video_widget;
+        GdkWindow *parent_window;
+        GdkWindowAttr attributes;
+        guint attributes_mask;
+        int border_width;
+
+        video_widget = OWL_VIDEO_WIDGET (widget);
+
+        /**
+         * Mark widget as realized.
+         **/
+        GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+
+        /**
+         * Lock.
+         **/
+        g_mutex_lock (video_widget->priv->overlay_lock);
+
+        /**
+         * Create our GdkWindow.
+         **/
+        border_width = GTK_CONTAINER (widget)->border_width;
+
+        attributes.x = widget->allocation.x + border_width;
+        attributes.y = widget->allocation.y + border_width;
+        attributes.width = widget->allocation.width - border_width * 2;
+        attributes.height = widget->allocation.height - border_width * 2;
+        attributes.window_type = GDK_WINDOW_CHILD;
+        attributes.wclass = GDK_INPUT_OUTPUT;
+        attributes.visual = gtk_widget_get_visual (widget);
+        attributes.colormap = gtk_widget_get_colormap (widget);
+        attributes.event_mask = gtk_widget_get_events (widget);
+        attributes.event_mask |= GDK_EXPOSURE_MASK;
+
+        attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
+
+        parent_window = gtk_widget_get_parent_window (widget);
+        widget->window = gdk_window_new (parent_window,
+                                         &attributes,
+                                         attributes_mask);
+        gdk_window_set_user_data (widget->window, widget);
+
+        gdk_window_set_back_pixmap (widget->window, NULL, FALSE);
+
+        /**
+         * Sync, so that the window is definitely there when the videosink
+         * starts looking at it.
+         **/
+        XSync (GDK_WINDOW_XDISPLAY (widget->window), FALSE);
+
+        /**
+         * Connect overlay, if available, to window.
+         **/
+        if (video_widget->priv->overlay) {
+                XID xid;
+                
+                xid = GDK_WINDOW_XID (widget->window);
+                gst_x_overlay_set_xwindow_id (video_widget->priv->overlay, xid);
+                gst_x_overlay_expose (video_widget->priv->overlay);
+
+                /**
+                 * Destroy dummy window if it was there.
+                 **/
+                destroy_dummy_window (video_widget);
+        }
+        
+        /**
+         * Unlock.
+         **/
+        g_mutex_unlock (video_widget->priv->overlay_lock);
+
+        /**
+         * Attach GtkStyle.
+         **/
+        widget->style = gtk_style_attach (widget->style, widget->window);
+}
+
+static void
+owl_video_widget_unrealize (GtkWidget *widget)
+{
+        OwlVideoWidget *video_widget;
+        GtkWidgetClass *widget_class;
+
+        video_widget = OWL_VIDEO_WIDGET (widget);
+
+        /**
+         * Lock.
+         **/
+        g_mutex_lock (video_widget->priv->overlay_lock);
+
+        /**
+         * Connect overlay, if available, to hidden window.
+         **/
+        if (video_widget->priv->overlay) {
+                XID xid;
+
+                xid = create_dummy_window (video_widget);
+                
+                gst_x_overlay_set_xwindow_id (video_widget->priv->overlay, xid);
+        }
+
+        /**
+         * Unlock.
+         **/
+        g_mutex_unlock (video_widget->priv->overlay_lock);
+
+        /**
+         * Call parent class.
+         **/
+        widget_class = GTK_WIDGET_CLASS (owl_video_widget_parent_class);
+        widget_class->unrealize (widget);
+}
+
+static gboolean
+owl_video_widget_expose (GtkWidget      *widget,
+                         GdkEventExpose *event)
+{
+        OwlVideoWidget *video_widget;
+        GtkWidgetClass *widget_class;
+
+        /* Perform extra exposure compression */
+        if (event && event->count > 0)
+          return TRUE;
+        
+        video_widget = OWL_VIDEO_WIDGET (widget);
+
+        /**
+         * Only draw if we are drawable.
+         **/
+        if (!GTK_WIDGET_DRAWABLE (widget))
+                return FALSE;
+
+        gdk_draw_rectangle (widget->window, widget->style->black_gc, TRUE,
+                            event->area.x, event->area.y,
+                            event->area.width, event->area.height);
+
+        /**
+         * Lock.
+         **/
+        g_mutex_lock (video_widget->priv->overlay_lock);
+
+        /**
+         * If we have an overlay, forward the expose to GStreamer.
+         **/
+        if (video_widget->priv->overlay)
+                gst_x_overlay_expose (video_widget->priv->overlay);
+
+        /**
+         * Unlock.
+         **/
+        g_mutex_unlock (video_widget->priv->overlay_lock);
+
+        /**
+         * Call parent class.
+         **/
+        widget_class = GTK_WIDGET_CLASS (owl_video_widget_parent_class);
+        widget_class->expose_event (widget, event);
+
+        return TRUE;
+}
+
+static void
+owl_video_widget_size_request (GtkWidget      *widget,
+                               GtkRequisition *requisition)
+{
+        int border_width;
+        GtkWidget *child;
+
+        border_width = GTK_CONTAINER (widget)->border_width;
+
+        /**
+         * Request width from child.
+         **/
+        child = GTK_BIN (widget)->child;
+        if (child && GTK_WIDGET_VISIBLE (child))
+                gtk_widget_size_request (child, requisition);
+
+        requisition->width  += border_width * 2;
+        requisition->height += border_width * 2;
+}
+
+static void
+owl_video_widget_size_allocate (GtkWidget     *widget,
+                                GtkAllocation *allocation)
+{
+        OwlVideoWidget *video_widget;
+        int border_width;
+        GtkAllocation child_allocation;
+        GtkWidget *child;
+
+        video_widget = OWL_VIDEO_WIDGET (widget);
+
+        /**
+         * Cache the allocation.
+         **/
+        widget->allocation = *allocation;
+
+        /**
+         * Calculate the size for our GdkWindow and for the child.
+         **/
+        border_width = GTK_CONTAINER (widget)->border_width;
+
+        child_allocation.x      = allocation->x + border_width;
+        child_allocation.y      = allocation->y + border_width;
+        child_allocation.width  = allocation->width - border_width * 2;
+        child_allocation.height = allocation->height - border_width * 2;
+
+        /**
+         * Resize our GdkWindow.
+         **/
+        if (GTK_WIDGET_REALIZED (widget)) {
+                gdk_window_move_resize (widget->window,
+                                        child_allocation.x,
+                                        child_allocation.y,
+                                        child_allocation.width,
+                                        child_allocation.height);
+        }
+
+        /**
+         * Forward the size allocation to our child.
+         **/
+        child = GTK_BIN (widget)->child;
+        if (child && GTK_WIDGET_VISIBLE (child)) {
+                /**
+                 * The child is positioned relative to its parent.
+                 **/
+                child_allocation.x = 0;
+                child_allocation.y = 0;
+
+                gtk_widget_size_allocate (child, &child_allocation);
+        }
+}
+
+static void
+owl_video_widget_class_init (OwlVideoWidgetClass *klass)
+{
+        GObjectClass *object_class;
+        GtkWidgetClass *widget_class;
+
+	object_class = G_OBJECT_CLASS (klass);
+
+	object_class->set_property = owl_video_widget_set_property;
+	object_class->get_property = owl_video_widget_get_property;
+	object_class->dispose      = owl_video_widget_dispose;
+	object_class->finalize     = owl_video_widget_finalize;
+
+        widget_class = GTK_WIDGET_CLASS (klass);
+
+        widget_class->realize       = owl_video_widget_realize;
+        widget_class->unrealize     = owl_video_widget_unrealize;
+        widget_class->expose_event  = owl_video_widget_expose;
+        widget_class->size_request  = owl_video_widget_size_request;
+        widget_class->size_allocate = owl_video_widget_size_allocate;
+
+        g_type_class_add_private (klass, sizeof (OwlVideoWidgetPrivate));
+
+        g_object_class_install_property
+                (object_class,
+                 PROP_URI,
+                 g_param_spec_string
+                         ("uri",
+                          "URI",
+                          "The loaded URI.",
+                          NULL,
+                          G_PARAM_READWRITE |
+                          G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                          G_PARAM_STATIC_BLURB));
+
+        g_object_class_install_property
+                (object_class,
+                 PROP_PLAYING,
+                 g_param_spec_boolean
+                         ("playing",
+                          "Playing",
+                          "TRUE if playing.",
+                          FALSE,
+                          G_PARAM_READWRITE |
+                          G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                          G_PARAM_STATIC_BLURB));
+
+        g_object_class_install_property
+                (object_class,
+                 PROP_POSITION,
+                 g_param_spec_int
+                         ("position",
+                          "Position",
+                          "The position in the current stream in seconds.",
+                          0, G_MAXINT, 0,
+                          G_PARAM_READWRITE |
+                          G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                          G_PARAM_STATIC_BLURB));
+
+        g_object_class_install_property
+                (object_class,
+                 PROP_VOLUME,
+                 g_param_spec_double
+                         ("volume",
+                          "Volume",
+                          "The audio volume.",
+                          0, GST_VOL_MAX, GST_VOL_DEFAULT,
+                          G_PARAM_READWRITE |
+                          G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                          G_PARAM_STATIC_BLURB));
+
+        g_object_class_install_property
+                (object_class,
+                 PROP_CAN_SEEK,
+                 g_param_spec_boolean
+                         ("can-seek",
+                          "Can seek",
+                          "TRUE if the current stream is seekable.",
+                          FALSE,
+                          G_PARAM_READABLE |
+                          G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                          G_PARAM_STATIC_BLURB));
+
+        g_object_class_install_property
+                (object_class,
+                 PROP_BUFFER_PERCENT,
+                 g_param_spec_int
+                         ("buffer-percent",
+                          "Buffer percent",
+                          "The percentage the current stream buffer is filled.",
+                          0, 100, 0,
+                          G_PARAM_READABLE |
+                          G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                          G_PARAM_STATIC_BLURB));
+
+        g_object_class_install_property
+                (object_class,
+                 PROP_DURATION,
+                 g_param_spec_int
+                         ("duration",
+                          "Duration",
+                          "The duration of the current stream in seconds.",
+                          0, G_MAXINT, 0,
+                          G_PARAM_READABLE |
+                          G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                          G_PARAM_STATIC_BLURB));
+
+        g_object_class_install_property
+                (object_class,
+                 PROP_FORCE_ASPECT_RATIO,
+                 g_param_spec_boolean
+                         ("force-aspect-ratio",
+                          "Force aspect ratio",
+                          "TRUE to force the image's aspect ratio to be "
+                          "honoured.",
+                          TRUE,
+                          G_PARAM_READWRITE |
+                          G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                          G_PARAM_STATIC_BLURB));
+
+        signals[TAG_LIST_AVAILABLE] =
+                g_signal_new ("tag-list-available",
+                              OWL_TYPE_VIDEO_WIDGET,
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (OwlVideoWidgetClass,
+                                               tag_list_available),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__POINTER,
+                              G_TYPE_NONE, 1, G_TYPE_POINTER);
+
+        signals[EOS] =
+                g_signal_new ("eos",
+                              OWL_TYPE_VIDEO_WIDGET,
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (OwlVideoWidgetClass,
+                                               eos),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE, 0);
+
+        signals[ERROR] =
+                g_signal_new ("error",
+                              OWL_TYPE_VIDEO_WIDGET,
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (OwlVideoWidgetClass,
+                                               error),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__POINTER,
+                              G_TYPE_NONE, 1, G_TYPE_POINTER);
+}
+
+/**
+ * owl_video_widget_new
+ *
+ * Return value: A new #OwlVideoWidget.
+ **/
+GtkWidget *
+owl_video_widget_new (void)
+{
+        return g_object_new (OWL_TYPE_VIDEO_WIDGET, NULL);
+}
+
+/**
+ * owl_video_widget_set_uri
+ * @video_widget: A #OwlVideoWidget
+ * @uri: A URI
+ *
+ * Loads @uri.
+ **/
+void
+owl_video_widget_set_uri (OwlVideoWidget *video_widget,
+                          const char     *uri)
+{
+        GstState state, pending;
+
+        g_return_if_fail (OWL_IS_VIDEO_WIDGET (video_widget));
+
+        if (!video_widget->priv->playbin)
+                return;
+
+        g_free (video_widget->priv->uri);
+
+        if (uri) {
+                video_widget->priv->uri = g_strdup (uri);
+
+                /**
+                 * Ensure the tick timeout is installed.
+                 * 
+                 * We also have it installed in PAUSED state, because
+                 * seeks etc may have a delayed effect on the position.
+                 **/
+                if (video_widget->priv->tick_timeout_id == 0) {
+                        video_widget->priv->tick_timeout_id =
+                                g_timeout_add (TICK_TIMEOUT * 1000,
+                                               (GSourceFunc) tick_timeout,
+                                               video_widget);
+                }
+        } else {
+                video_widget->priv->uri = NULL;
+
+                /**
+                 * Remove tick timeout.
+                 **/
+                if (video_widget->priv->tick_timeout_id > 0) {
+                        g_source_remove (video_widget->priv->tick_timeout_id);
+                        video_widget->priv->tick_timeout_id = 0;
+                }
+        }
+
+        /**
+         * Reset properties.
+         **/
+        video_widget->priv->can_seek = FALSE;
+        video_widget->priv->duration = 0;
+
+        /**
+         * Store old state.
+         **/
+        gst_element_get_state (video_widget->priv->playbin,
+                               &state,
+                               &pending,
+                               0);
+        if (pending)
+                state = pending;
+
+        /**
+         * State to NULL.
+         **/
+        gst_element_set_state (video_widget->priv->playbin, GST_STATE_NULL);
+
+        /**
+         * Set new URI.
+         **/
+        g_object_set (video_widget->priv->playbin,
+                      "uri", uri,
+                      NULL);
+        
+        /**
+         * Restore state.
+         **/
+        if (uri)
+                gst_element_set_state (video_widget->priv->playbin, state);
+
+        /**
+         * Emit notififications for all these to make sure UI is not showing
+         * any properties of the old URI.
+         **/
+        g_object_notify (G_OBJECT (video_widget), "uri");
+        g_object_notify (G_OBJECT (video_widget), "can-seek");
+        g_object_notify (G_OBJECT (video_widget), "duration");
+        g_object_notify (G_OBJECT (video_widget), "position");
+}
+
+/**
+ * owl_video_widget_get_uri
+ * @video_widget: A #OwlVideoWidget
+ *
+ * Return value: The loaded URI, or NULL if none set.
+ **/
+const char *
+owl_video_widget_get_uri (OwlVideoWidget *video_widget)
+{
+        g_return_val_if_fail (OWL_IS_VIDEO_WIDGET (video_widget), NULL);
+
+        return video_widget->priv->uri;
+}
+
+/**
+ * owl_video_widget_set_playing
+ * @video_widget: A #OwlVideoWidget
+ * @playing: TRUE if @video_widget should be playing, FALSE otherwise
+ *
+ * Sets the playback state of @video_widget to @playing.
+ **/
+void
+owl_video_widget_set_playing (OwlVideoWidget *video_widget,
+                              gboolean        playing)
+{
+        g_return_if_fail (OWL_IS_VIDEO_WIDGET (video_widget));
+
+        if (!video_widget->priv->playbin)
+                return;
+        
+        /**
+         * Choose the correct state for the pipeline.
+         **/
+        if (video_widget->priv->uri) {
+                GstState state;
+
+                if (playing)
+                        state = GST_STATE_PLAYING;
+                else
+                        state = GST_STATE_PAUSED;
+
+                gst_element_set_state (video_widget->priv->playbin, state);
+        } else {
+                if (playing)
+                        g_warning ("Tried to play, but no URI is loaded.");
+
+                /**
+                 * Do nothing.
+                 **/
+        }
+
+        g_object_notify (G_OBJECT (video_widget), "playing");
+
+        /**
+         * Make sure UI is in sync.
+         **/
+        g_object_notify (G_OBJECT (video_widget), "position");
+}
+
+/**
+ * owl_video_widget_get_playing
+ * @video_widget: A #OwlVideoWidget
+ *
+ * Return value: TRUE if @video_widget is playing.
+ **/
+gboolean
+owl_video_widget_get_playing (OwlVideoWidget *video_widget)
+{
+        GstState state, pending;
+
+        g_return_val_if_fail (OWL_IS_VIDEO_WIDGET (video_widget), FALSE);
+
+        if (!video_widget->priv->playbin)
+                return FALSE;
+
+        gst_element_get_state (video_widget->priv->playbin,
+                               &state,
+                               &pending,
+                               0);
+
+        if (pending)
+                return (pending == GST_STATE_PLAYING);
+        else
+                return (state == GST_STATE_PLAYING);
+}
+
+/**
+ * owl_video_widget_set_position
+ * @video_widget: A #OwlVideoWidget
+ * @position: The position in the current stream in seconds.
+ *
+ * Sets the position in the current stream to @position.
+ **/
+void
+owl_video_widget_set_position (OwlVideoWidget *video_widget,
+                               int             position)
+{
+        GstState state, pending;
+
+        g_return_if_fail (OWL_IS_VIDEO_WIDGET (video_widget));
+
+        if (!video_widget->priv->playbin)
+                return;
+
+        /**
+         * Store old state.
+         **/
+        gst_element_get_state (video_widget->priv->playbin,
+                               &state,
+                               &pending,
+                               0);
+        if (pending)
+                state = pending;
+
+        /**
+         * State to PAUSED.
+         **/
+        gst_element_set_state (video_widget->priv->playbin, GST_STATE_PAUSED);
+        
+        /**
+         * Perform the seek.
+         **/
+        gst_element_seek (video_widget->priv->playbin,
+                          1.0, GST_FORMAT_TIME,
+                          GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
+                          GST_SEEK_TYPE_SET, position * GST_SECOND,
+                          GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
+        /**
+         * Restore state.
+         **/
+        gst_element_set_state (video_widget->priv->playbin, state);
+}
+
+/**
+ * owl_video_widget_get_position
+ * @video_widget: A #OwlVideoWidget
+ *
+ * Return value: The position in the current file in seconds.
+ **/
+int
+owl_video_widget_get_position (OwlVideoWidget *video_widget)
+{
+        GstQuery *query;
+        gint64 position;
+       
+        g_return_val_if_fail (OWL_IS_VIDEO_WIDGET (video_widget), -1);
+
+        if (!video_widget->priv->playbin)
+                return -1;
+
+        query = gst_query_new_position (GST_FORMAT_TIME);
+
+        if (gst_element_query (video_widget->priv->playbin, query)) {
+                gst_query_parse_position (query,
+                                          NULL,
+                                          &position);
+        } else
+                position = 0;
+
+        gst_query_unref (query);
+
+        return (position / GST_SECOND);
+}
+
+/**
+ * owl_video_widget_set_volume
+ * @video_widget: A #OwlVideoWidget
+ * @volume: The audio volume to set, in the range 0.0 - 4.0.
+ *
+ * Sets the current audio volume to @volume.
+ **/
+void
+owl_video_widget_set_volume (OwlVideoWidget *video_widget,
+                             double          volume)
+{
+        g_return_if_fail (OWL_IS_VIDEO_WIDGET (video_widget));
+        g_return_if_fail (volume >= 0.0 && volume <= GST_VOL_MAX);
+
+        if (!video_widget->priv->playbin)
+                return;
+
+        g_object_set (G_OBJECT (video_widget->priv->playbin),
+                      "volume", volume,
+                      NULL);
+        
+        g_object_notify (G_OBJECT (video_widget), "volume");
+}
+
+/**
+ * owl_video_widget_get_volume
+ * @video_widget: A #OwlVideoWidget
+ *
+ * Return value: The current audio volume, in the range 0.0 - 4.0.
+ **/
+double
+owl_video_widget_get_volume (OwlVideoWidget *video_widget)
+{
+        double volume;
+
+        g_return_val_if_fail (OWL_IS_VIDEO_WIDGET (video_widget), 0);
+
+        if (!video_widget->priv->playbin)
+                return 0.0;
+
+        g_object_get (video_widget->priv->playbin,
+                      "volume", &volume,
+                      NULL);
+
+        return volume;
+}
+
+/**
+ * owl_video_widget_get_can_seek
+ * @video_widget: A #OwlVideoWidget
+ *
+ * Return value: TRUE if the current stream is seekable.
+ **/
+gboolean
+owl_video_widget_get_can_seek (OwlVideoWidget *video_widget)
+{
+        g_return_val_if_fail (OWL_IS_VIDEO_WIDGET (video_widget), FALSE);
+
+        return video_widget->priv->can_seek;
+}
+
+/**
+ * owl_video_widget_get_buffer_percent
+ * @video_widget: A #OwlVideoWidget
+ *
+ * Return value: Percentage the current stream buffer is filled.
+ **/
+int
+owl_video_widget_get_buffer_percent (OwlVideoWidget *video_widget)
+{
+        g_return_val_if_fail (OWL_IS_VIDEO_WIDGET (video_widget), -1);
+
+        return video_widget->priv->buffer_percent;
+}
+
+/**
+ * owl_video_widget_get_duration
+ * @video_widget: A #OwlVideoWidget
+ *
+ * Return value: The duration of the current stream in seconds.
+ **/
+int
+owl_video_widget_get_duration (OwlVideoWidget *video_widget)
+{
+        g_return_val_if_fail (OWL_IS_VIDEO_WIDGET (video_widget), -1);
+
+        return video_widget->priv->duration;
+}
+
+/**
+ * owl_video_widget_set_force_aspect_ratio
+ * @video_widget: A #OwlVideoWidget
+ * @force_aspect_ratio: TRUE to force the image's aspect ratio to be
+ * honoured.
+ *
+ * If @force_aspect_ratio is TRUE, sets the image's aspect ratio to be
+ * honoured.
+ **/
+void
+owl_video_widget_set_force_aspect_ratio (OwlVideoWidget *video_widget,
+                                         gboolean        force_aspect_ratio)
+{
+        g_return_if_fail (OWL_IS_VIDEO_WIDGET (video_widget));
+
+        if (video_widget->priv->force_aspect_ratio == force_aspect_ratio)
+                return;
+
+        video_widget->priv->force_aspect_ratio = force_aspect_ratio;
+
+        g_mutex_lock (video_widget->priv->overlay_lock);
+
+        if (video_widget->priv->overlay)
+                sync_force_aspect_ratio (video_widget);
+
+        g_mutex_unlock (video_widget->priv->overlay_lock);
+
+        g_object_notify (G_OBJECT (video_widget), "force-aspect-ratio");
+}
+
+/**
+ * owl_video_widget_get_force_aspect_ratio
+ * @video_widget: A #OwlVideoWidget
+ * 
+ * Return value: TRUE if the image's aspect ratio is being honoured.
+ **/
+gboolean
+owl_video_widget_get_force_aspect_ratio (OwlVideoWidget *video_widget)
+{
+        g_return_val_if_fail (OWL_IS_VIDEO_WIDGET (video_widget), FALSE);
+
+        return video_widget->priv->force_aspect_ratio;
+}
diff --git a/src/plugins/gst-renderer/owl-video-widget.h b/src/plugins/gst-renderer/owl-video-widget.h
new file mode 100644
index 0000000..7bb936f
--- /dev/null
+++ b/src/plugins/gst-renderer/owl-video-widget.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2006 OpenedHand Ltd.
+ *
+ * OpenedHand Widget Library Video Widget - A GStreamer video GTK+ widget
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Author: Jorn Baayen <jorn openedhand com>
+ */
+
+#ifndef __OWL_VIDEO_WIDGET_H__
+#define __OWL_VIDEO_WIDGET_H__
+
+#include <gtk/gtkbin.h>
+#include <gst/gsttaglist.h>
+
+G_BEGIN_DECLS
+
+#define OWL_TYPE_VIDEO_WIDGET \
+                (owl_video_widget_get_type ())
+#define OWL_VIDEO_WIDGET(obj) \
+                (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+                 OWL_TYPE_VIDEO_WIDGET, \
+                 OwlVideoWidget))
+#define OWL_VIDEO_WIDGET_CLASS(klass) \
+                (G_TYPE_CHECK_CLASS_CAST ((klass), \
+                 OWL_TYPE_VIDEO_WIDGET, \
+                 OwlVideoWidgetClass))
+#define OWL_IS_VIDEO_WIDGET(obj) \
+                (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+                 OWL_TYPE_VIDEO_WIDGET))
+#define OWL_IS_VIDEO_WIDGET_CLASS(klass) \
+                (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+                 OWL_TYPE_VIDEO_WIDGET))
+#define OWL_VIDEO_WIDGET_GET_CLASS(obj) \
+                (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+                 OWL_TYPE_VIDEO_WIDGET, \
+                 OwlVideoWidgetClass))
+
+typedef struct _OwlVideoWidgetPrivate OwlVideoWidgetPrivate;
+
+typedef struct {
+        GtkBin parent;
+
+        OwlVideoWidgetPrivate *priv;
+} OwlVideoWidget;
+
+typedef struct {
+        GtkBinClass parent_class;
+
+        /* Signals */
+        void (* tag_list_available) (OwlVideoWidget *video_widget,
+                                     GstTagList     *tag_list);
+        void (* eos)                (OwlVideoWidget *video_widget);
+        void (* error)              (OwlVideoWidget *video_widget,
+                                     GError         *error);
+        
+        /* Future padding */
+        void (* _owl_reserved1) (void);
+        void (* _owl_reserved2) (void);
+        void (* _owl_reserved3) (void);
+        void (* _owl_reserved4) (void);
+} OwlVideoWidgetClass;
+
+GType
+owl_video_widget_get_type               (void) G_GNUC_CONST;
+
+GtkWidget *
+owl_video_widget_new                    (void);
+
+void
+owl_video_widget_set_uri                (OwlVideoWidget *video_widget,
+                                         const char     *uri);
+
+const char *
+owl_video_widget_get_uri                (OwlVideoWidget *video_widget);
+
+void
+owl_video_widget_set_playing            (OwlVideoWidget *video_widget,
+                                         gboolean        playing);
+
+gboolean
+owl_video_widget_get_playing            (OwlVideoWidget *video_widget);
+
+void
+owl_video_widget_set_position           (OwlVideoWidget *video_widget,
+                                         int             position);
+
+int
+owl_video_widget_get_position           (OwlVideoWidget *video_widget);
+
+void
+owl_video_widget_set_volume             (OwlVideoWidget *video_widget,
+                                         double          volume);
+
+double
+owl_video_widget_get_volume             (OwlVideoWidget *video_widget);
+
+gboolean
+owl_video_widget_get_can_seek           (OwlVideoWidget *video_widget);
+
+int
+owl_video_widget_get_buffer_percent     (OwlVideoWidget *video_widget);
+
+int
+owl_video_widget_get_duration           (OwlVideoWidget *video_widget);
+
+void
+owl_video_widget_set_force_aspect_ratio (OwlVideoWidget *video_widget,
+                                         gboolean        force_aspect_ratio);
+
+gboolean
+owl_video_widget_get_force_aspect_ratio (OwlVideoWidget *video_widget);
+
+G_END_DECLS
+
+#endif /* __OWL_VIDEO_WIDGET_H__ */
diff --git a/src/plugins/gst-renderer/owl-video-widget.vapi b/src/plugins/gst-renderer/owl-video-widget.vapi
new file mode 100644
index 0000000..7de87c8
--- /dev/null
+++ b/src/plugins/gst-renderer/owl-video-widget.vapi
@@ -0,0 +1,31 @@
+[CCode (cprefix = "Owl", lower_case_cprefix = "owl_")]
+namespace Owl {
+	[CCode (cheader_filename = "owl-video-widget.h")]
+	public class VideoWidget : Gtk.Bin, Atk.Implementor, Gtk.Buildable {
+		public int get_buffer_percent ();
+		public bool get_can_seek ();
+		public int get_duration ();
+		public bool get_force_aspect_ratio ();
+		public bool get_playing ();
+		public int get_position ();
+		public weak string get_uri ();
+		public double get_volume ();
+		public VideoWidget ();
+		public void set_force_aspect_ratio (bool force_aspect_ratio);
+		public void set_playing (bool playing);
+		public void set_position (int position);
+		public void set_uri (string uri);
+		public void set_volume (double volume);
+		public int buffer_percent { get; }
+		public bool can_seek { get; }
+		public int duration { get; }
+		public bool force_aspect_ratio { get; set; }
+		public bool playing { get; set; }
+		public int position { get; set; }
+		public string uri { get; set; }
+		public double volume { get; set; }
+		public virtual signal void eos ();
+		public virtual signal void error (GLib.Error error);
+		public virtual signal void tag_list_available (Gst.TagList tag_list);
+	}
+}
diff --git a/src/plugins/gst-renderer/rygel-gst-av-transport.vala b/src/plugins/gst-renderer/rygel-gst-av-transport.vala
new file mode 100644
index 0000000..da2ad42
--- /dev/null
+++ b/src/plugins/gst-renderer/rygel-gst-av-transport.vala
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2008 OpenedHand Ltd.
+ *
+ * Author: Jorn Baayen <jorn openedhand com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ */
+
+using GUPnP;
+
+public class Rygel.GstAVTransport : Service {
+    public const string UPNP_ID = "urn:upnp-org:serviceId:AVTransport";
+    public const string UPNP_TYPE =
+                    "urn:schemas-upnp-org:service:AVTransport:2";
+    public const string DESCRIPTION_PATH = "xml/AVTransport2.xml";
+
+    // The setters below update the LastChange message
+    private uint _n_tracks = 0;
+    public uint n_tracks {
+        get {
+            return _n_tracks;
+        }
+
+        set {
+            _n_tracks = value;
+
+            this.changelog.log ("NumberOfTracks", _n_tracks.to_string ());
+        }
+    }
+
+    private uint _track = 0;
+    public uint track {
+        get {
+            return _track;
+        }
+
+        set {
+            _track = value;
+
+            this.changelog.log ("CurrentTrack", _track.to_string ());
+        }
+    }
+
+    private string _metadata = "";
+    public string metadata {
+        get {
+            return _metadata;
+        }
+
+        set {
+            _metadata = value;
+
+            string escaped = Markup.escape_text (_metadata, -1);
+
+            this.changelog.log ("CurrentTrackMetadata", escaped);
+        }
+    }
+
+    private string _status = "OK";
+    public string status {
+        get {
+            return _status;
+        }
+
+        set {
+            _status = value;
+
+            this.changelog.log ("TransportStatus", _status);
+        }
+    }
+
+    private string _speed = "1";
+    public string speed {
+        get {
+            return _speed;
+        }
+
+        set {
+            _speed = value;
+
+            this.changelog.log ("TransportPlaySpeed", _speed);
+        }
+    }
+
+    private string _mode = "NORMAL";
+    public string mode {
+        get {
+            return _mode;
+        }
+
+        set {
+            _mode = value;
+
+            this.changelog.log ("CurrentPlayMode", _mode);
+        }
+    }
+
+    private GstChangeLog changelog;
+    private GstVideoWindow video_window;
+
+    public override void constructed () {
+        this.changelog = new GstChangeLog (this);
+        this.video_window = GstVideoWindow.get_default ();
+
+        query_variable["LastChange"] += query_last_change_cb;
+
+        action_invoked["SetAVTransportURI"]     += set_av_transport_uri_cb;
+        action_invoked["GetMediaInfo"]          += get_media_info_cb;
+        action_invoked["GetTransportInfo"]      += get_transport_info_cb;
+        action_invoked["GetPositionInfo"]       += get_position_info_cb;
+        action_invoked["GetDeviceCapabilities"] += get_device_capabilities_cb;
+        action_invoked["GetTransportSettings"]  += get_transport_settings_cb;
+        action_invoked["Stop"]                  += stop_cb;
+        action_invoked["Play"]                  += play_cb;
+        action_invoked["Pause"]                 += pause_cb;
+        action_invoked["Seek"]                  += seek_cb;
+        action_invoked["Next"]                  += next_cb;
+        action_invoked["Previous"]              += previous_cb;
+
+        this.video_window.notify["uri"] += this.notify_uri_cb;
+        this.video_window.notify["playback-state"] += this.notify_state_cb;
+        this.video_window.notify["duration"] += this.notify_duration_cb;
+    }
+
+    private void query_last_change_cb (GstAVTransport s,
+                                       string         variable,
+                                       ref Value      val) {
+        // Send current state
+        GstChangeLog log = new GstChangeLog (null);
+
+        string escaped;
+
+        log.log ("TransportState",
+                 this.video_window.playback_state);
+        log.log ("TransportStatus",              this.status);
+        log.log ("PlaybackStorageMedium",        "NOT_IMPLEMENTED");
+        log.log ("RecordStorageMedium",          "NOT_IMPLEMENTED");
+        log.log ("PossiblePlaybackStorageMedia", "NOT_IMPLEMENTED");
+        log.log ("PossibleRecordStorageMedia",   "NOT_IMPLEMENTED");
+        log.log ("CurrentPlayMode",              this.mode);
+        log.log ("TransportPlaySpeed",           this.speed);
+        log.log ("RecordMediumWriteStatus",      "NOT_IMPLEMENTED");
+        log.log ("CurrentRecordQualityMode",     "NOT_IMPLEMENTED");
+        log.log ("PossibleRecordQualityMode",    "NOT_IMPLEMENTED");
+        log.log ("NumberOfTracks",               this.n_tracks.to_string ());
+        log.log ("CurrentTrack",                 this.track.to_string ());
+        log.log ("CurrentTrackDuration",         this.video_window.duration);
+        log.log ("CurrentMediaDuration",         this.video_window.duration);
+        escaped = Markup.escape_text (this.metadata, -1);
+        log.log ("CurrentTrackMetadata",         escaped);
+        escaped = Markup.escape_text (this.video_window.uri, -1);
+        log.log ("CurrentTrackURI",              escaped);
+        log.log ("AVTransportURI",               escaped);
+        log.log ("NextAVTransportURI",           "NOT_IMPLEMENTED");
+
+        val.init (typeof (string));
+        val.set_string (log.finish ());
+    }
+
+    // Error out if InstanceID is not 0
+    private bool check_instance_id (ServiceAction action) {
+        uint instance_id;
+
+        action.get ("InstanceID", typeof (uint), out instance_id);
+        if (instance_id != 0) {
+            action.return_error (718, "Invalid InstanceID");
+
+            return false;
+        }
+
+        return true;
+    }
+
+    private void set_av_transport_uri_cb (GstAVTransport      s,
+                                          owned ServiceAction action) {
+        if (!check_instance_id (action)) {
+            return;
+        }
+
+        string _uri, _metadata;
+
+        action.get ("CurrentURI",         typeof (string), out _uri,
+                    "CurrentURIMetaData", typeof (string), out _metadata);
+
+        this.video_window.uri = _uri;
+        this.metadata = _metadata;
+
+        action.return ();
+    }
+
+    private void get_media_info_cb (GstAVTransport      s,
+                                    owned ServiceAction action) {
+        if (!check_instance_id (action)) {
+            return;
+        }
+
+        action.set ("NrTracks",
+                        typeof (uint),
+                        this.n_tracks,
+                    "MediaDuration",
+                        typeof (string),
+                        this.video_window.duration,
+                    "CurrentURI",
+                        typeof (string),
+                        Markup.escape_text (this.video_window.uri, -1),
+                    "CurrentURIMetaData",
+                        typeof (string),
+                        this.metadata,
+                    "NextURI",
+                        typeof (string),
+                        "NOT_IMPLEMENTED",
+                    "NextURIMetaData",
+                        typeof (string),
+                        "NOT_IMPLEMENTED",
+                    "PlayMedium",
+                        typeof (string),
+                        "NOT_IMPLEMENTED",
+                    "RecordMedium",
+                        typeof (string),
+                        "NOT_IMPLEMENTED",
+                    "WriteStatus",
+                        typeof (string),
+                        "NOT_IMPLEMENTED");
+
+        action.return ();
+    }
+
+    private void get_transport_info_cb (GstAVTransport      s,
+                                        owned ServiceAction action) {
+        if (!check_instance_id (action)) {
+            return;
+        }
+
+        action.set ("CurrentTransportState",
+                        typeof (string),
+                        this.video_window.playback_state,
+                    "CurrentTransportStatus",
+                        typeof (string),
+                        this.status,
+                    "CurrentSpeed",
+                        typeof (string),
+                        this.speed);
+
+        action.return ();
+    }
+
+    private void get_position_info_cb (GstAVTransport      s,
+                                       owned ServiceAction action) {
+        if (!check_instance_id (action)) {
+            return;
+        }
+
+        action.set ("Track",
+                        typeof (uint),
+                        this.track,
+                    "TrackDuration",
+                        typeof (string),
+                        this.video_window.duration,
+                    "TrackMetaData",
+                        typeof (string),
+                        this.metadata,
+                    "TrackURI",
+                        typeof (string),
+                        Markup.escape_text (this.video_window.uri, -1),
+                    "RelTime",
+                        typeof (string),
+                        this.video_window.position,
+                    "AbsTime",
+                        typeof (string),
+                        this.video_window.position,
+                    "RelCount",
+                        typeof (int),
+                        int.MAX,
+                    "AbsCount",
+                        typeof (int),
+                        int.MAX);
+
+        action.return ();
+    }
+
+    private void get_device_capabilities_cb (GstAVTransport      s,
+                                             owned ServiceAction action) {
+        if (!check_instance_id (action)) {
+            return;
+        }
+
+        action.set ("PlayMedia",       typeof (string), "NOT_IMPLEMENTED",
+                    "RecMedia",        typeof (string), "NOT_IMPLEMENTED",
+                    "RecQualityModes", typeof (string), "NOT_IMPLEMENTED");
+
+        action.return ();
+    }
+
+    private void get_transport_settings_cb (GstAVTransport      s,
+                                            owned ServiceAction action) {
+        if (!check_instance_id (action)) {
+            return;
+        }
+
+        action.set ("PlayMode",       typeof (string), this.mode,
+                    "RecQualityMode", typeof (string), "NOT_IMPLEMENTED");
+
+        action.return ();
+    }
+
+    private void stop_cb (GstAVTransport s, owned ServiceAction action) {
+        if (!check_instance_id (action)) {
+            return;
+        }
+
+        this.video_window.playback_state = "STOPPED";
+
+        action.return ();
+    }
+
+    private void play_cb (GstAVTransport s, owned ServiceAction action) {
+        if (!check_instance_id (action)) {
+            return;
+        }
+
+        string speed;
+
+        action.get ("Speed", typeof (string), out speed);
+        if (speed != "1") {
+            action.return_error (717, "Play speed not supported");
+
+            return;
+        }
+
+        this.video_window.playback_state = "PLAYING";
+
+        action.return ();
+    }
+
+    private void pause_cb (GstAVTransport s, owned ServiceAction action) {
+        if (!check_instance_id (action)) {
+            return;
+        }
+
+        this.video_window.playback_state = "PAUSED_PLAYBACK";
+
+        action.return ();
+    }
+
+    private void seek_cb (GstAVTransport s, owned ServiceAction action) {
+        if (!check_instance_id (action)) {
+            return;
+        }
+
+        string unit, target;
+
+        action.get ("Unit",   typeof (string), out unit,
+                    "Target", typeof (string), out target);
+        switch (unit) {
+        case "ABS_TIME":
+        case "REL_TIME":
+            if (!this.video_window.seek (target)) {
+                action.return_error (710, "Seek mode not supported");
+
+                return;
+            }
+
+            action.return ();
+
+            return;
+        default:
+            action.return_error (710, "Seek mode not supported");
+
+            return;
+        }
+    }
+
+    private void next_cb (GstAVTransport s, owned ServiceAction action) {
+        action.return_error (701, "Transition not available");
+    }
+
+    private void previous_cb (GstAVTransport s, owned ServiceAction action) {
+        action.return_error (701, "Transition not available");
+    }
+
+    private void notify_state_cb (GstVideoWindow video_window,
+                                  ParamSpec      p) {
+        this.changelog.log ("TransportState", this.video_window.playback_state);
+    }
+
+    private void notify_uri_cb (GstVideoWindow video_window,
+                                ParamSpec      p) {
+        var escaped = Markup.escape_text (video_window.uri, -1);
+
+        this.changelog.log ("CurrentTrackURI", escaped);
+        this.changelog.log ("AVTransportURI", escaped);
+    }
+
+    private void notify_duration_cb (GstVideoWindow window,
+                                     ParamSpec      p) {
+        this.changelog.log ("CurrentTrackDuration", window.duration);
+        this.changelog.log ("CurrentMediaDuration", window.duration);
+    }
+}
diff --git a/src/plugins/gst-renderer/rygel-gst-changelog.vala b/src/plugins/gst-renderer/rygel-gst-changelog.vala
new file mode 100644
index 0000000..745457a
--- /dev/null
+++ b/src/plugins/gst-renderer/rygel-gst-changelog.vala
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2008 OpenedHand Ltd.
+ *
+ * Author: Jorn Baayen <jorn openedhand com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ */
+
+using GUPnP;
+
+// Helper class for building LastChange messages
+public class Rygel.GstChangeLog : Object {
+    public Service service { get; set; }
+
+    private StringBuilder str;
+
+    private Gee.HashMap<string, string> hash;
+
+    private uint timeout_id = 0;
+
+    public GstChangeLog (Service? _service) {
+        service = _service;
+        str = new StringBuilder ();
+        hash = new Gee.HashMap<string, string> (str_hash, str_equal, str_equal);
+    }
+
+    ~GstChangeLog () {
+        if (timeout_id != 0) {
+            Source.remove (timeout_id);
+        }
+    }
+
+    private bool timeout () {
+        // Emit notification
+        service.notify ("LastChange", typeof (string), finish ());
+
+        // Reset
+        hash.clear ();
+        str.erase (0, -1);
+        timeout_id = 0;
+
+        return false;
+    }
+
+    private void ensure_timeout () {
+        // Make sure we have a notification timeout
+        if (service != null && timeout_id == 0) {
+            timeout_id = Timeout.add (200, timeout);
+        }
+    }
+
+    public void log (string var, string val) {
+        hash.set (var, "<%s val=\"%s\"/>".printf (var, val));
+
+        ensure_timeout ();
+    }
+
+    public void log_with_channel (string var, string val, string channel) {
+        hash.set (var, "<%s val=\"%s\" channel=\"%s\"/>".printf (var, val,
+                                                                 channel));
+
+        ensure_timeout ();
+    }
+
+    public string finish () {
+        str.append ("<Event xmlns=\"" +
+                    "urn:schemas-upnp-org:metadata-1-0/AVT_RCS\">" +
+                    "<InstanceID val=\"0\">");
+        foreach (string line in hash.get_values ()) {
+            str.append (line);
+        }
+        str.append ("</InstanceID></Event>");
+
+        return str.str;
+    }
+}
diff --git a/src/plugins/gst-renderer/rygel-gst-connection-manager.vala b/src/plugins/gst-renderer/rygel-gst-connection-manager.vala
new file mode 100644
index 0000000..64a478d
--- /dev/null
+++ b/src/plugins/gst-renderer/rygel-gst-connection-manager.vala
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2008 OpenedHand Ltd.
+ *
+ * Author: Jorn Baayen <jorn openedhand com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ */
+
+using GUPnP;
+
+public class Rygel.GstConnectionManager : Rygel.ConnectionManager {
+    // Creates a list of supported sink protocols based on GStreamer's
+    // registry. We don't use this because of the spam it generates ..
+    /*
+    private void setup_sink_protocol_info () {
+        Gst.Registry reg = Gst.Registry.get_default ();
+
+        Gee.HashSet<string> mime_types =
+            new Gee.HashSet<string> (GLib.str_hash, GLib.str_equal);
+
+        weak List<Gst.ElementFactory> factories =
+                reg.get_feature_list (typeof (Gst.ElementFactory));
+        foreach (Gst.ElementFactory factory in factories) {
+            weak List<Gst.StaticPadTemplate> templates =
+                factory.staticpadtemplates;
+            foreach (weak Gst.StaticPadTemplate template in templates) {
+                if (template.direction != Gst.PadDirection.SINK) {
+                    continue;
+                }
+
+                Gst.Caps caps = template.static_caps.get ();
+                for (int i = 0; i < caps.get_size (); i++) {
+                    weak Gst.Structure str =
+                        template.static_caps.get_structure (i);
+
+                    mime_types.add (str.get_name ());
+                }
+            }
+        }
+
+        foreach (string type in mime_types) {
+            stdout.printf ("%s\n", type);
+        }
+    }
+    */
+
+    public override void constructed () {
+        base.constructed ();
+
+        this.connection_ids       = "0";
+        this.source_protocol_info = "";
+        this.sink_protocol_info   = "http-get:*:audio/mpeg:*," +
+                                    "http-get:*:application/ogg:*," +
+                                    "http-get:*:audio/x-vorbis:*," +
+                                    "http-get:*:audio/x-vorbis+ogg:*," +
+                                    "http-get:*:audio/x-ms-wma:*," +
+                                    "http-get:*:audio/x-ms-asf:*," +
+                                    "http-get:*:audio/x-flac:*," +
+                                    "http-get:*:audio/x-mod:*," +
+                                    "http-get:*:audio/x-wav:*," +
+                                    "http-get:*:audio/x-ac3:*," +
+                                    "http-get:*:audio/x-m4a:*," +
+                                    "http-get:*:video/x-theora:*," +
+                                    "http-get:*:video/x-dirac:*," +
+                                    "http-get:*:video/x-wmv:*," +
+                                    "http-get:*:video/x-wma:*," +
+                                    "http-get:*:video/x-msvideo:*," +
+                                    "http-get:*:video/x-3ivx:*," +
+                                    "http-get:*:video/x-3ivx:*," +
+                                    "http-get:*:video/x-matroska:*," +
+                                    "http-get:*:video/mpeg:*," +
+                                    "http-get:*:video/x-ms-asf:*," +
+                                    "http-get:*:video/x-xvid:*," +
+                                    "http-get:*:video/x-ms-wmv:*," +
+                                    "http-get:*:audio/L16;" +
+                                               "rate=44100;" +
+                                               "channels=2:*," +
+                                    "http-get:*:audio/L16;" +
+                                               "rate=44100;" +
+                                               "channels=1:*";
+    }
+}
diff --git a/src/plugins/gst-renderer/rygel-gst-plugin.vala b/src/plugins/gst-renderer/rygel-gst-plugin.vala
new file mode 100644
index 0000000..c528ff7
--- /dev/null
+++ b/src/plugins/gst-renderer/rygel-gst-plugin.vala
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2008 Zeeshan Ali (Khattak) <zeeshanak gnome org>.
+ * Copyright (C) 2008 Nokia Corporation, all rights reserved.
+ *
+ * Author: Zeeshan Ali (Khattak) <zeeshanak gnome org>
+ *                               <zeeshan ali nokia com>
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Rygel is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using Rygel;
+using Gee;
+using CStuff;
+
+[ModuleInit]
+public void module_init (PluginLoader loader) {
+    string MEDIA_RENDERER_DESC_PATH = BuildConfig.DATA_DIR +
+                                      "/xml/MediaRenderer2.xml";
+
+    var plugin = new Plugin (MEDIA_RENDERER_DESC_PATH,
+                             "GstRenderer",
+                             "GStreamer Renderer");
+
+    plugin.add_resource (new ResourceInfo (ConnectionManager.UPNP_ID,
+                                           ConnectionManager.UPNP_TYPE,
+                                           ConnectionManager.DESCRIPTION_PATH,
+                                           typeof (GstConnectionManager)));
+    plugin.add_resource (new ResourceInfo (GstAVTransport.UPNP_ID,
+                                           GstAVTransport.UPNP_TYPE,
+                                           GstAVTransport.DESCRIPTION_PATH,
+                                           typeof (GstAVTransport)));
+    plugin.add_resource (new ResourceInfo (GstRenderingControl.UPNP_ID,
+                                           GstRenderingControl.UPNP_TYPE,
+                                           GstRenderingControl.DESCRIPTION_PATH,
+                                           typeof (GstRenderingControl)));
+
+    loader.add_plugin (plugin);
+}
+
diff --git a/src/plugins/gst-renderer/rygel-gst-rendering-control.vala b/src/plugins/gst-renderer/rygel-gst-rendering-control.vala
new file mode 100644
index 0000000..182af94
--- /dev/null
+++ b/src/plugins/gst-renderer/rygel-gst-rendering-control.vala
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2008 OpenedHand Ltd.
+ *
+ * Author: Jorn Baayen <jorn openedhand com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ */
+
+using GUPnP;
+
+public class Rygel.GstRenderingControl : Service {
+    public const string UPNP_ID = "urn:upnp-org:serviceId:RenderingControl";
+    public const string UPNP_TYPE =
+                    "urn:schemas-upnp-org:service:RenderingControl:2";
+    public const string DESCRIPTION_PATH = "xml/RenderingControl2.xml";
+
+    private bool _mute = false;
+    public bool mute {
+        get {
+            return _mute;
+        }
+
+        set {
+            _mute = value;
+
+            if (this._mute) {
+                this.video_window.volume = 0;
+            } else {
+                this.video_window.volume = Volume.from_percentage (this.volume);
+            }
+
+            this.changelog.log_with_channel ("Mute",
+                                             this.mute ? "1" : "0",
+                                             "Master");
+        }
+    }
+
+    private uint _volume = 0;
+    public uint volume {
+        get {
+            return _volume;
+        }
+
+        set {
+            _volume = value;
+
+            if (!this.mute) {
+                this.video_window.volume = Volume.from_percentage (this.volume);
+            }
+
+            this.changelog.log_with_channel ("Volume",
+                                             this.volume.to_string (),
+                                             "Master");
+        }
+    }
+
+    private string preset_name_list = "";
+
+    private GstChangeLog changelog;
+    private GstVideoWindow video_window;
+
+    public override void constructed () {
+        this.changelog = new GstChangeLog (this);
+        this.video_window = GstVideoWindow.get_default ();
+
+        query_variable["LastChange"] += query_last_change_cb;
+
+        action_invoked["ListPresets"]  += list_presets_cb;
+        action_invoked["SelectPreset"] += select_preset_cb;
+        action_invoked["GetMute"]      += get_mute_cb;
+        action_invoked["SetMute"]      += set_mute_cb;
+        action_invoked["GetVolume"]    += get_volume_cb;
+        action_invoked["SetVolume"]    += set_volume_cb;
+
+        this._volume = Volume.to_percentage (this.video_window.volume);
+    }
+
+    private void query_last_change_cb (GstRenderingControl s,
+                                       string              var,
+                                       ref GLib.Value      val) {
+        // Send current state
+        var log = new GstChangeLog (null);
+
+        log.log_with_channel ("Mute", mute ? "1" : "0", "Master");
+        log.log_with_channel ("Volume", this.volume.to_string (), "Master");
+
+        val.init (typeof (string));
+        val.set_string (log.finish ());
+    }
+
+    // Error out if InstanceID is not 0
+    private bool check_instance_id (ServiceAction action) {
+        uint instance_id;
+
+        action.get ("InstanceID", typeof (uint), out instance_id);
+        if (instance_id != 0) {
+            action.return_error (702, "Invalid InstanceID");
+
+            return false;
+        }
+
+        return true;
+    }
+
+    private void list_presets_cb (GstRenderingControl s,
+                                  owned ServiceAction action) {
+        if (!check_instance_id (action)) {
+            return;
+        }
+
+        action.set ("CurrentPresetNameList",
+                        typeof (string),
+                        this.preset_name_list);
+
+        action.return ();
+    }
+
+    private void select_preset_cb (GstRenderingControl s,
+                                   owned ServiceAction action) {
+        if (!check_instance_id (action)) {
+            return;
+        }
+
+        string preset_name;
+
+        action.get ("PresetName", typeof (string), out preset_name);
+        if (preset_name != "") {
+            action.return_error (701, "Invalid Name");
+
+            return;
+        }
+
+        action.return ();
+    }
+
+    // Error out if 'Channel' is not 'Master'
+    private bool check_channel (ServiceAction action) {
+        string channel;
+
+        action.get ("Channel", typeof (string), out channel);
+        if (channel != "Master") {
+            action.return_error (501, "Action Failed");
+
+            return false;
+        }
+
+        return true;
+    }
+
+    private void get_mute_cb (GstRenderingControl s,
+                              owned ServiceAction action) {
+        if (!check_instance_id (action)) {
+            return;
+        }
+
+        if (!check_channel (action)) {
+            return;
+        }
+
+        action.set ("CurrentMute", typeof (bool), this.mute);
+
+        action.return ();
+    }
+
+    private void set_mute_cb (GstRenderingControl s,
+                              owned ServiceAction action) {
+        if (!check_instance_id (action)) {
+            return;
+        }
+
+        if (!check_channel (action)) {
+            return;
+        }
+
+        bool mute;
+
+        action.get ("DesiredMute", typeof (bool), out mute);
+
+        this.mute = mute;
+
+        action.return ();
+    }
+
+    private void get_volume_cb (GstRenderingControl s,
+                                owned ServiceAction action) {
+        if (!check_instance_id (action)) {
+            return;
+        }
+
+        if (!check_channel (action)) {
+            return;
+        }
+
+        action.set ("CurrentVolume", typeof (uint), this.volume);
+
+        action.return ();
+    }
+
+    private void set_volume_cb (GstRenderingControl s,
+                                owned ServiceAction action) {
+        if (!check_instance_id (action)) {
+            return;
+        }
+
+        if (!check_channel (action)) {
+            return;
+        }
+
+        uint volume;
+
+        action.get ("DesiredVolume", typeof (uint), out volume);
+        if (volume > 100) {
+            action.return_error (501, "Action Failed");
+
+            return;
+        }
+
+        this.volume = volume;
+
+        action.return ();
+    }
+}
+
+// Helper class for converting between double and percentage representations
+// of volume.
+private class Volume {
+    public static double from_percentage (uint percentage) {
+        return ((double) percentage / 100.0) * 4.0;
+    }
+
+    public static uint to_percentage (double volume) {
+        return (uint) ((volume / 4.0) * 100.0);
+    }
+}
+
diff --git a/src/plugins/gst-renderer/rygel-gst-video-window.vala b/src/plugins/gst-renderer/rygel-gst-video-window.vala
new file mode 100644
index 0000000..70ec6be
--- /dev/null
+++ b/src/plugins/gst-renderer/rygel-gst-video-window.vala
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2008 OpenedHand Ltd.
+ *
+ * Author: Jorn Baayen <jorn openedhand com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ */
+
+using Gtk;
+using Gst;
+using Owl;
+
+public class Rygel.GstVideoWindow : Window {
+    private static GstVideoWindow video_window;
+
+    private VideoWidget video_widget;
+
+    private string _playback_state = "STOPPED";
+    public string playback_state {
+        get {
+            return this._playback_state;
+        }
+
+        set {
+            this._playback_state = value;
+
+            switch (_playback_state) {
+                case "STOPPED":
+                    this.video_widget.playing = false;
+
+                if (this.video_widget.can_seek) {
+                    this.video_widget.position = 0;
+                }
+
+                break;
+                case "PAUSED_PLAYBACK":
+                    this.video_widget.playing = false;
+                break;
+                case "PLAYING":
+                    this.video_widget.playing = true;
+                break;
+                default:
+                break;
+            }
+        }
+    }
+
+    public string uri {
+        get {
+            return this.video_widget.uri;
+        }
+
+        set {
+            this.video_widget.uri = value;
+        }
+    }
+
+    public double volume {
+        get {
+            return this.video_widget.volume;
+        }
+
+        set {
+            this.video_widget.volume = value;
+        }
+    }
+
+    public string duration { get; private set; }
+    public string playback_position { get; private set; }
+
+    construct {
+        this.type = WindowType.TOPLEVEL;
+    }
+
+    private GstVideoWindow () {
+        this.fullscreen_state = true;
+
+        this.video_widget.eos += this.eos_cb;
+        this.video_widget.notify["duration"] += this.notify_duration_cb;
+        this.video_widget.notify["position"] += this.notify_position_cb;
+
+        // Show a video widget
+        this.video_widget = new VideoWidget ();
+        this.video_widget.show ();
+
+        this.add (video_widget);
+        this.show_all ();
+
+        this.key_press_event += this.key_press_callback;
+    }
+
+    public static GstVideoWindow get_default () {
+        if (video_window == null) {
+            video_window = new GstVideoWindow ();
+        }
+
+        return video_window;
+    }
+
+    public bool fullscreen_state {
+        get {
+            if (this.window != null) {
+                return (this.window.get_state () &
+                        Gdk.WindowState.FULLSCREEN) != 0;
+            }
+
+            return false;
+        }
+
+        set {
+            if (value)
+                this.fullscreen ();
+            else {
+                this.unfullscreen ();
+            }
+        }
+    }
+
+    private bool key_press_callback (GstVideoWindow window,
+                                     Gdk.EventKey   event) {
+        switch (event.keyval) {
+            case 0xffc8: /* Gdk.KeySyms.F11 */
+                this.fullscreen_state = ! fullscreen_state;
+                break;
+            case 0xff1b: /* Gdk.KeySyms.Escape */
+                this.fullscreen_state = false;
+                break;
+            default:
+                break;
+        }
+        return false;
+    }
+
+    private void eos_cb (VideoWidget video_widget) {
+        this.playback_state = "STOPPED";
+    }
+
+    private void notify_duration_cb (VideoWidget video_widget,
+                                     ParamSpec   p) {
+        this.duration = Time.to_string (video_widget.duration);
+    }
+
+    private void notify_position_cb (VideoWidget video_widget,
+                                     ParamSpec   p) {
+        this.playback_position = Time.to_string (video_widget.position);
+    }
+
+    public bool seek (string time) {
+        if (this.video_widget.can_seek) {
+            this.video_widget.position = Time.from_string (time);
+
+            return true;
+        } else {
+            return false;
+        }
+    }
+}
+
+// Helper class for converting between second and string representations
+// of time.
+private class Time {
+    public static int from_string (string str) {
+        int hours, minutes, seconds;
+
+        str.scanf ("%d:%2d:%2d%*s", out hours, out minutes, out seconds);
+
+        return hours * 3600 + minutes * 60 + seconds;
+    }
+
+    public static string to_string (int time) {
+        int hours, minutes, seconds;
+
+        hours   = time / 3600;
+        seconds = time % 3600;
+        minutes = seconds / 60;
+        seconds = seconds % 60;
+
+        return "%d:%.2d:%.2d".printf (hours, minutes, seconds);
+    }
+}
+



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]