From a1bbaa24fa4925f885dbfd44e921ce4c9e00fbbd Mon Sep 17 00:00:00 2001 From: "Zeeshan Ali (Khattak)" Date: Tue, 16 Jun 2009 22:33:32 +0300 Subject: [PATCH] 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(-) create mode 100644 data/xml/AVTransport2.xml create mode 100644 data/xml/MediaRenderer2.xml create mode 100644 data/xml/RenderingControl2.xml create mode 100644 src/plugins/gst-renderer/Makefile.am create mode 100644 src/plugins/gst-renderer/owl-video-widget.c create mode 100644 src/plugins/gst-renderer/owl-video-widget.h create mode 100644 src/plugins/gst-renderer/owl-video-widget.vapi create mode 100644 src/plugins/gst-renderer/rygel-gst-av-transport.vala create mode 100644 src/plugins/gst-renderer/rygel-gst-changelog.vala create mode 100644 src/plugins/gst-renderer/rygel-gst-connection-manager.vala create mode 100644 src/plugins/gst-renderer/rygel-gst-plugin.vala create mode 100644 src/plugins/gst-renderer/rygel-gst-rendering-control.vala create mode 100644 src/plugins/gst-renderer/rygel-gst-video-window.vala 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 @@ + + + + + TransportState + no + string + + STOPPED + PLAYING + + + + + TransportStatus + no + string + + OK + ERROR_OCCURRED + + + + + CurrentMediaCategory + no + string + + NO_MEDIA + TRACK_AWARE + TRACK_UNAWARE + + + + + PlaybackStorageMedium + no + string + + + + RecordStorageMedium + no + string + + + + PossiblePlaybackStorageMedia + no + string + + + + PossibleRecordStorageMedia + no + string + + + + CurrentPlayMode + no + string + + NORMAL + + NORMAL + + + + TransportPlaySpeed + no + string + + 1 + + 1 + + + + RecordMediumWriteStatus + no + string + + + + CurrentRecordQualityMode + no + string + + + + PossibleRecordQualityModes + no + string + + + + NumberOfTracks + no + ui4 + + 0 + + + + + CurrentTrack + no + ui4 + + 0 + 1 + + + + + CurrentTrackDuration + no + string + + + + CurrentMediaDuration + no + string + + + + CurrentTrackMetaData + no + string + + + + CurrentTrackURI + no + string + + + + AVTransportURI + no + string + + + + AVTransportURIMetaData + no + string + + + + NextAVTransportURI + no + string + + + + NextAVTransportURIMetaData + no + string + + + + RelativeTimePosition + no + string + + + + AbsoluteTimePosition + no + string + + + + RelativeCounterPosition + no + i4 + + + + AbsoluteCounterPosition + no + i4 + + + + + CurrentTransportActions + no + string + + + + LastChange + yes + string + + + + + DRMState + yes + string + + OK + + UNKNOWN + + + + A_ARG_TYPE_SeekMode + no + string + + TRACK_NR + + + + + A_ARG_TYPE_SeekTarget + no + string + + + + A_ARG_TYPE_InstanceID + no + ui4 + + + + + A_ARG_TYPE_DeviceUDN + no + string + + + + + A_ARG_TYPE_ServiceType + no + string + + + + + A_ARG_TYPE_ServiceID + no + string + + + + + A_ARG_TYPE_StateVariableValuePairs + no + string + + + + + A_ARG_TYPE_StateVariableList + no + string + + + + + + SetAVTransportURI + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + CurrentURI + in + AVTransportURI + + + CurrentURIMetaData + in + AVTransportURIMetaData + + + + + + + SetNextAVTransportURI + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + NextURI + in + NextAVTransportURI + + + NextURIMetaData + in + NextAVTransportURIMetaData + + + + + + GetMediaInfo + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + NrTracks + out + NumberOfTracks + + + MediaDuration + out + CurrentMediaDuration + + + CurrentURI + out + AVTransportURI + + + CurrentURIMetaData + out + AVTransportURIMetaData + + + NextURI + out + NextAVTransportURI + + + NextURIMetaData + out + NextAVTransportURIMetaData + + + PlayMedium + out + PlaybackStorageMedium + + + RecordMedium + out + RecordStorageMedium + + + WriteStatus + out + RecordMediumWriteStatus + + + + + + GetMediaInfo_Ext + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + CurrentType + out + CurrentMediaCategory + + + NrTracks + out + NumberOfTracks + + + MediaDuration + out + CurrentMediaDuration + + + CurrentURI + out + AVTransportURI + + + CurrentURIMetaData + out + AVTransportURIMetaData + + + NextURI + out + NextAVTransportURI + + + NextURIMetaData + out + NextAVTransportURIMetaData + + + PlayMedium + out + PlaybackStorageMedium + + + RecordMedium + out + RecordStorageMedium + + + WriteStatus + out + RecordMediumWriteStatus + + + + + + GetTransportInfo + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + CurrentTransportState + out + TransportState + + + CurrentTransportStatus + out + TransportStatus + + + CurrentSpeed + out + TransportPlaySpeed + + + + + + GetPositionInfo + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + Track + out + CurrentTrack + + + TrackDuration + out + CurrentTrackDuration + + + TrackMetaData + out + CurrentTrackMetaData + + + TrackURI + out + CurrentTrackURI + + + RelTime + out + RelativeTimePosition + + + AbsTime + out + AbsoluteTimePosition + + + RelCount + out + RelativeCounterPosition + + + AbsCount + out + AbsoluteCounterPosition + + + + + + GetDeviceCapabilities + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + PlayMedia + out + PossiblePlaybackStorageMedia + + + RecMedia + out + PossibleRecordStorageMedia + + + RecQualityModes + out + PossibleRecordQualityModes + + + + + + GetTransportSettings + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + PlayMode + out + CurrentPlayMode + + + RecQualityMode + out + CurrentRecordQualityMode + + + + + + Stop + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + + + + Play + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + Speed + in + TransportPlaySpeed + + + + + + + Pause + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + + + + + Record + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + + + + Seek + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + Unit + in + A_ARG_TYPE_SeekMode + + + Target + in + A_ARG_TYPE_SeekTarget + + + + + + Next + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + + + + Previous + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + + + + + SetPlayMode + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + NewPlayMode + in + CurrentPlayMode + + + + + + + SetRecordQualityMode + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + NewRecordQualityMode + in + CurrentRecordQualityMode + + + + + + + GetCurrentTransportActions + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + Actions + out + CurrentTransportActions + + + + + + + GetDRMState + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + CurrentDRMState + out + DRMState + + + + + + + GetStateVariables + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + StateVariableList + in + A_ARG_TYPE_StateVariableList + + + StateVariableValuePairs + out + A_ARG_TYPE_StateVariableValuePairs + + + + + + + SetStateVariables + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + AVTransportUDN + in + A_ARG_TYPE_DeviceUDN + + + ServiceType + in + A_ARG_TYPE_ServiceType + + + ServiceId + in + A_ARG_TYPE_ServiceID + + + StateVariableValuePairs + in + A_ARG_TYPE_StateVariableValuePairs + + + StateVariableList + out + A_ARG_TYPE_StateVariableList + + + + + + 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 @@ + + + + + urn:schemas-upnp-org:service:RenderingControl:2 + RenderingControl + + + urn:schemas-upnp-org:service:ConnectionManager:2 + ConnectionManager + + + + urn:schemas-upnp-org:service:AVTransport:2 + AVTransport + + + \ 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 @@ + + + + + LastChange + yes + string + + + + PresetNameList + no + string + + + + + Brightness + no + ui2 + + 0 + 1 + + + + + + Contrast + no + ui2 + + 0 + 1 + + + + + + Sharpness + no + ui2 + + 0 + 1 + + + + + + RedVideoGain + no + ui2 + + 0 + 1 + + + + + + GreenVideoGain + no + ui2 + + 0 + 1 + + + + + + BlueVideoGain + no + ui2 + + 0 + 1 + + + + + + RedVideoBlackLevel + no + ui2 + + 0 + 1 + + + + + + GreenVideoBlackLevel + no + ui2 + + 0 + 1 + + + + + + BlueVideoBlackLevel + no + ui2 + + 0 + 1 + + + + + + ColorTemperature + no + ui2 + + 0 + 1 + + + + + + HorizontalKeystone + no + i2 + + 1 + + + + + + VerticalKeystone + no + i2 + + 1 + + + + + + Mute + no + boolean + + + + + Volume + no + ui2 + + 0 + 1 + + + + + + VolumeDB + no + i2 + + + + + Loudness + no + boolean + + + + + A_ARG_TYPE_Channel + no + string + + Master + + + + + A_ARG_TYPE_InstanceID + no + ui4 + + + + A_ARG_TYPE_PresetName + no + string + + FactoryDefaults + + + + + + A_ARG_TYPE_DeviceUDN + no + string + + + + + A_ARG_TYPE_ServiceType + no + string + + + + + A_ARG_TYPE_ServiceID + no + string + + + + + A_ARG_TYPE_StateVariableValuePairs + no + string + + + + + A_ARG_TYPE_StateVariableList + no + string + + + + + + ListPresets + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + CurrentPresetNameList + out + PresetNameList + + + + + + SelectPreset + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + PresetName + in + A_ARG_TYPE_PresetName + + + + + + + GetBrightness + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + CurrentBrightness + out + Brightness + + + + + + + SetBrightness + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + DesiredBrightness + in + Brightness + + + + + + + GetContrast + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + CurrentContrast + out + Contrast + + + + + + SetContrast + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + DesiredContrast + in + Contrast + + + + + + + GetSharpness + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + CurrentSharpness + out + Sharpness + + + + + + + SetSharpness + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + DesiredSharpness + in + Sharpness + + + + + + + GetRedVideoGain + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + CurrentRedVideoGain + out + RedVideoGain + + + + + + + SetRedVideoGain + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + DesiredRedVideoGain + in + RedVideoGain + + + + + + + GetGreenVideoGain + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + CurrentGreenVideoGain + out + GreenVideoGain + + + + + + + SetGreenVideoGain + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + DesiredGreenVideoGain + in + GreenVideoGain + + + + + + + GetBlueVideoGain + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + CurrentBlueVideoGain + out + BlueVideoGain + + + + + + + SetBlueVideoGain + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + DesiredBlueVideoGain + in + BlueVideoGain + + + + + + + GetRedVideoBlackLevel + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + CurrentRedVideoBlackLevel + out + RedVideoBlackLevel + + + + + + + SetRedVideoBlackLevel + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + DesiredRedVideoBlackLevel + in + RedVideoBlackLevel + + + + + + + GetGreenVideoBlackLevel + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + CurrentGreenVideoBlackLevel + out + GreenVideoBlackLevel + + + + + + + SetGreenVideoBlackLevel + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + DesiredGreenVideoBlackLevel + in + GreenVideoBlackLevel + + + + + + + GetBlueVideoBlackLevel + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + CurrentBlueVideoBlackLevel + out + BlueVideoBlackLevel + + + + + + + SetBlueVideoBlackLevel + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + DesiredBlueVideoBlackLevel + in + BlueVideoBlackLevel + + + + + + + GetColorTemperature + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + CurrentColorTemperature + out + ColorTemperature + + + + + + + SetColorTemperature + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + DesiredColorTemperature + in + ColorTemperature + + + + + + + GetHorizontalKeystone + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + CurrentHorizontalKeystone + out + HorizontalKeystone + + + + + + + SetHorizontalKeystone + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + DesiredHorizontalKeystone + in + HorizontalKeystone + + + + + + + GetVerticalKeystone + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + CurrentVerticalKeystone + out + VerticalKeystone + + + + + + + SetVerticalKeystone + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + DesiredVerticalKeystone + in + VerticalKeystone + + + + + + + GetMute + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + Channel + in + A_ARG_TYPE_Channel + + + CurrentMute + out + Mute + + + + + + + SetMute + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + Channel + in + A_ARG_TYPE_Channel + + + DesiredMute + in + Mute + + + + + + + GetVolume + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + Channel + in + A_ARG_TYPE_Channel + + + CurrentVolume + out + Volume + + + + + + + SetVolume + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + Channel + in + A_ARG_TYPE_Channel + + + DesiredVolume + in + Volume + + + + + + + GetVolumeDB + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + Channel + in + A_ARG_TYPE_Channel + + + CurrentVolume + out + VolumeDB + + + + + + + SetVolumeDB + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + Channel + in + A_ARG_TYPE_Channel + + + DesiredVolume + in + VolumeDB + + + + + + + GetVolumeDBRange + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + Channel + in + A_ARG_TYPE_Channel + + + MinValue + out + VolumeDB + + + MaxValue + out + VolumeDB + + + + + + + GetLoudness + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + Channel + in + A_ARG_TYPE_Channel + + + CurrentLoudness + out + Loudness + + + + + + + SetLoudness + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + Channel + in + A_ARG_TYPE_Channel + + + DesiredLoudness + in + Loudness + + + + + + + GetStateVariables + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + StateVariableList + in + A_ARG_TYPE_StateVariableList + + + StateVariableValuePairs + out + A_ARG_TYPE_StateVariableValuePairs + + + + + + + SetStateVariables + + + InstanceID + in + A_ARG_TYPE_InstanceID + + + RenderingControlUDN + in + A_ARG_TYPE_DeviceUDN + + + ServiceType + in + A_ARG_TYPE_ServiceType + + + ServiceId + in + A_ARG_TYPE_ServiceID + + + StateVariableValuePairs + in + A_ARG_TYPE_StateVariableValuePairs + + + StateVariableList + out + A_ARG_TYPE_StateVariableList + + + + + \ 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 + */ + +#include +#include +#include + +#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 + */ + +#ifndef __OWL_VIDEO_WIDGET_H__ +#define __OWL_VIDEO_WIDGET_H__ + +#include +#include + +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 + * + * 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 + * + * 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 hash; + + private uint timeout_id = 0; + + public GstChangeLog (Service? _service) { + service = _service; + str = new StringBuilder (); + hash = new Gee.HashMap (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 ("" + + ""); + foreach (string line in hash.get_values ()) { + str.append (line); + } + str.append (""); + + 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 + * + * 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 mime_types = + new Gee.HashSet (GLib.str_hash, GLib.str_equal); + + weak List factories = + reg.get_feature_list (typeof (Gst.ElementFactory)); + foreach (Gst.ElementFactory factory in factories) { + weak List 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) . + * Copyright (C) 2008 Nokia Corporation, all rights reserved. + * + * Author: Zeeshan Ali (Khattak) + * + * + * 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 + * + * 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 + * + * 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); + } +} + -- 2.7.4