banshee r4948 - in trunk/banshee: . build/m4/banshee libbanshee src/Backends/Banshee.GStreamer/Banshee.GStreamer src/Core/Banshee.Services src/Core/Banshee.Services/Banshee.MediaEngine src/Core/Banshee.Services/Banshee.ServiceStack src/Core/Banshee.ThickClient src/Core/Banshee.ThickClient/Banshee.Gui src/Extensions/Banshee.NowPlaying src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying
- From: abock svn gnome org
- To: svn-commits-list gnome org
- Subject: banshee r4948 - in trunk/banshee: . build/m4/banshee libbanshee src/Backends/Banshee.GStreamer/Banshee.GStreamer src/Core/Banshee.Services src/Core/Banshee.Services/Banshee.MediaEngine src/Core/Banshee.Services/Banshee.ServiceStack src/Core/Banshee.ThickClient src/Core/Banshee.ThickClient/Banshee.Gui src/Extensions/Banshee.NowPlaying src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying
- Date: Sat, 24 Jan 2009 23:05:00 +0000 (UTC)
Author: abock
Date: Sat Jan 24 23:05:00 2009
New Revision: 4948
URL: http://svn.gnome.org/viewvc/banshee?rev=4948&view=rev
Log:
2009-01-24 Aaron Bockover <abock gnome org>
First hacky pass at Clutter integration; nothing to see, and I probably
broke some things. trunk is going to be sketchy for a bit...
* src/Core/Banshee.Services/Banshee.MediaEngine/VideoDisplayContextType.cs:
Enum of context types pipelines might support for video (e.g. xoverlay,
clutter, cairo, etc.)
* src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/VideoDisplay.cs:
* src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/NowPlayingContents.cs:
* src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/IVideoDisplay.cs:
* src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/XOverlayVideoDisplay.cs:
Updated to support the video context API changes in the player engine
* src/Core/Banshee.ThickClient/Banshee.Gui/GtkBaseClient.cs: Temporary
clutter initialization, to be moved into the clutter extension
* src/Core/Banshee.Services/Banshee.ServiceStack/ServiceManager.cs:
Fix a small string.format bug
* src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs:
* src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs:
Instead of a simple SupportsVideo bool, replace with
VideoDisplayContextType, and change the VideoWindow property to
VideoDisplayContext with both a getter and setter, the meaning of which
depends on VideoDisplayContextType
* src/Core/Banshee.Services/Banshee.MediaEngine/NullPlayerEngine.cs:
* src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs:
Implement changes to API above
* libbanshee/banshee-player-video.c: Implement inital clutter support,
and the backend implementation for the new video context API above,
providing both xoverlay (GDK) and clutter contexts
* libbanshee/clutter-gst-shaders.h:
* libbanshee/clutter-gst-video-sink.h:
* libbanshee/clutter-gst-video-sink.c: Clutter GStreamer integration
from the clutter-gst package; currently I don't want to take the dep
as changes might need to be made
* libbanshee/banshee-player-private.h: Add the clutter texture if we
are building with clutter support
* build/m4/banshee/libbanshee.m4: Check for clutter (optional)
* configure.ac: Clean up the configure summary, make it nicer and more
organized
* build/m4/banshee/dap-karma.m4: Small fix
Added:
trunk/banshee/libbanshee/clutter-gst-shaders.h
trunk/banshee/libbanshee/clutter-gst-video-sink.c
trunk/banshee/libbanshee/clutter-gst-video-sink.h
trunk/banshee/src/Core/Banshee.Services/Banshee.MediaEngine/VideoDisplayContextType.cs
trunk/banshee/src/Core/Banshee.ThickClient/Banshee.ThickClient.dll.config
trunk/banshee/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying.dll.config
trunk/banshee/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/IVideoDisplay.cs
Modified:
trunk/banshee/ChangeLog
trunk/banshee/build/m4/banshee/dap-karma.m4
trunk/banshee/build/m4/banshee/libbanshee.m4
trunk/banshee/configure.ac
trunk/banshee/libbanshee/Makefile.am
trunk/banshee/libbanshee/banshee-player-private.h
trunk/banshee/libbanshee/banshee-player-video.c
trunk/banshee/libbanshee/libbanshee.cproj
trunk/banshee/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs
trunk/banshee/src/Core/Banshee.Services/Banshee.MediaEngine/NullPlayerEngine.cs
trunk/banshee/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs
trunk/banshee/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs
trunk/banshee/src/Core/Banshee.Services/Banshee.ServiceStack/ServiceManager.cs
trunk/banshee/src/Core/Banshee.Services/Makefile.am
trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/GtkBaseClient.cs
trunk/banshee/src/Core/Banshee.ThickClient/Makefile.am
trunk/banshee/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/NowPlayingContents.cs
trunk/banshee/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/VideoDisplay.cs
trunk/banshee/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/XOverlayVideoDisplay.cs
trunk/banshee/src/Extensions/Banshee.NowPlaying/Makefile.am
Modified: trunk/banshee/build/m4/banshee/dap-karma.m4
==============================================================================
--- trunk/banshee/build/m4/banshee/dap-karma.m4 (original)
+++ trunk/banshee/build/m4/banshee/dap-karma.m4 Sat Jan 24 23:05:00 2009
@@ -11,8 +11,6 @@
karma-sharp >= $KARMASHARP_REQUIRED,
enable_karmasharp="$enable_karmasharp", enable_karmasharp=no)
- AC_MSG_RESULT([$enable_karmasharp])
-
if test "x$enable_karmasharp" = "xyes"; then
KARMASHARP_ASSEMBLIES="`$PKG_CONFIG --variable=Libraries karma-sharp`"
AC_SUBST(KARMASHARP_ASSEMBLIES)
Modified: trunk/banshee/build/m4/banshee/libbanshee.m4
==============================================================================
--- trunk/banshee/build/m4/banshee/libbanshee.m4 (original)
+++ trunk/banshee/build/m4/banshee/libbanshee.m4 Sat Jan 24 23:05:00 2009
@@ -25,8 +25,19 @@
PKG_CHECK_MODULES(GTK, gtk+-2.0 >= 2.8)
fi
+ PKG_CHECK_MODULES(CLUTTER,
+ clutter-0.8 >= 0.8.6,
+ enable_clutter=yes, enable_clutter=no)
+
+ if test "x$enable_clutter" = "xyes"; then
+ SHAMROCK_CONCAT_MODULE(LIBBANSHEE, CLUTTER)
+ AC_DEFINE(HAVE_CLUTTER, 1,
+ [Define if the video sink should be Clutter])
+ fi
+
AM_CONDITIONAL(HAVE_X11, test "x$GRAPHICS_SUBSYSTEM" = "xX11")
AM_CONDITIONAL(HAVE_QUARTZ, test "x$GRAPHICS_SUBSYSTEM" = "xQuartz")
+ AM_CONDITIONAL(HAVE_CLUTTER, test "x$enable_clutter" = "xyes")
AC_SUBST(GRAPHICS_SUBSYSTEM)
AC_SUBST(LIBBANSHEE_CFLAGS)
Modified: trunk/banshee/configure.ac
==============================================================================
--- trunk/banshee/configure.ac (original)
+++ trunk/banshee/configure.ac Sat Jan 24 23:05:00 2009
@@ -250,9 +250,11 @@
src/Extensions/Banshee.RemoteAudio/Makefile
])
-echo "
+cat <<EOF
+
${PACKAGE}-${VERSION}
+ Build Environment
Install Prefix: ${prefix}
Datadir: ${expanded_datadir}
Libdir: ${expanded_libdir}
@@ -261,36 +263,44 @@
Mono C# Compiler: ${MCS} ${GMCS_FLAGS}
Mono Runtime: ${MONO}
- Digital Audio Player (DAP) Support:
- Mass Storage: yes
- MTP: ${enable_libmtp}
- iPod: ${enable_ipodsharp}
- Karma: ${enable_karmasharp}
+ Video/Graphics:
+ Graphics System: ${GRAPHICS_SUBSYSTEM}
+ X11 Video: ${have_xvidmode}
+ Clutter: ${enable_clutter} (experimental)
+
+ Operating System/Desktop Environment:
+ GNOME Support: ${enable_gnome}
+ OSX Support: ${enable_osx}
+
+ Digital Audio Player (DAP) Support:
+ Mass Storage: yes
+ MTP: ${enable_libmtp}
+ iPod: ${enable_ipodsharp}
+ Karma: ${enable_karmasharp}
+ Extra Features:
DAAP Support: ${enable_daap}
Podcast Support: ${enable_podcast}
Boo Scripting: ${enable_boo}
- X11 Video Support: ${have_xvidmode}
- GNOME Support: ${enable_gnome}
- OSX Support: ${enable_osx}
- Graphics System: ${GRAPHICS_SUBSYSTEM}
- Unit Tests: ${do_tests} (requires nunit >= ${NUNIT_REQUIRED})
+ Build/Development:
+ Unit Tests: ${do_tests} (requires nunit >= ${NUNIT_REQUIRED})
Release Build: ${enable_release}
Vendor Build ID: ${vendor_build_id}
-"
+
+EOF
# Unstable/in-development features; only show them if they were manually enabled
-if test "x$enable_moonlight" = "xyes"; then echo " Moonlight Effects: ${enable_moonlight}"; fi
-if test "x$enable_remote_audio" = "xyes"; then echo " Remote Audio: ${enable_remote_audio}"; fi
-if test "x$enable_mediaweb" = "xyes"; then echo " MediaWeb: ${enable_mediaweb}"; fi
-if test "x$enable_torrent" = "xyes"; then echo " Torrent Podcasts: ${enable_torrent}"; fi
+if test "x$enable_moonlight" = "xyes"; then br=yes; echo " Moonlight Effects: ${enable_moonlight}"; fi
+if test "x$enable_remote_audio" = "xyes"; then br=yes; echo " Remote Audio: ${enable_remote_audio}"; fi
+if test "x$enable_mediaweb" = "xyes"; then br=yes; echo " MediaWeb: ${enable_mediaweb}"; fi
+if test "x$enable_torrent" = "xyes"; then br=yes; echo " Torrent Podcasts: ${enable_torrent}"; fi
if test -d ${expanded_libdir}/${PACKAGE}; then
- echo
- echo "WARNING: An existing Banshee install is in ${expanded_libdir}/${PACKAGE}"
- echo " Remove the existing install before installing this build."
- echo " Installing over an existing install will cause conflicts!"
+ if test x$br = xyes; then echo; fi
+ echo " WARNING: An existing Banshee install is in ${expanded_libdir}/${PACKAGE}"
+ echo " Remove the existing install before installing this build."
+ echo " Installing over an existing install will cause conflicts!"
echo
fi
Modified: trunk/banshee/libbanshee/Makefile.am
==============================================================================
--- trunk/banshee/libbanshee/Makefile.am (original)
+++ trunk/banshee/libbanshee/Makefile.am Sat Jan 24 23:05:00 2009
@@ -24,22 +24,13 @@
banshee-tagger.c \
banshee-transcoder.c
-noinst_HEADERS = \
- banshee-gst.h \
- banshee-player-cdda.h \
- banshee-player-equalizer.h \
- banshee-player-missing-elements.h \
- banshee-player-pipeline.h \
- banshee-player-private.h \
- banshee-player-replaygain.h \
- banshee-player-video.h \
- banshee-player-vis.h \
- banshee-tagger.h
+if HAVE_CLUTTER
+libbanshee_la_SOURCES += clutter-gst-video-sink.c
+endif
libbanshee_la_LIBADD = \
$(LIBBANSHEE_LIBS) \
- $(GST_LIBS) \
- $(LIBNAUTILUS_BURN_LIBS)
+ $(GST_LIBS)
all: $(top_builddir)/bin/libbanshee.so
@@ -49,5 +40,7 @@
CLEANFILES = $(top_builddir)/bin/libbanshee.so
MAINTAINERCLEANFILES = Makefile.in
-EXTRA_DIST = $(libbanshee_la_SOURCES)
-
+EXTRA_DIST = \
+ $(libbanshee_la_SOURCES) \
+ $(wildcard *.h) \
+ $(wildcard clutter-*.c)
Modified: trunk/banshee/libbanshee/banshee-player-private.h
==============================================================================
--- trunk/banshee/libbanshee/banshee-player-private.h (original)
+++ trunk/banshee/libbanshee/banshee-player-private.h Sat Jan 24 23:05:00 2009
@@ -47,6 +47,10 @@
# include <gst/interfaces/xoverlay.h>
#endif
+#ifdef HAVE_CLUTTER
+# include <clutter/clutter.h>
+#endif
+
#include "banshee-gst.h"
#define P_INVOKE
@@ -99,6 +103,12 @@
GdkWindow *video_window;
#endif
+ // Clutter State
+ #ifdef HAVE_CLUTTER
+ GstElement *clutter_sink;
+ ClutterTexture *clutter_texture;
+ #endif
+
// Visualization State
GstAdapter *vis_buffer;
gfloat *spectrum_buffer;
Modified: trunk/banshee/libbanshee/banshee-player-video.c
==============================================================================
--- trunk/banshee/libbanshee/banshee-player-video.c (original)
+++ trunk/banshee/libbanshee/banshee-player-video.c Sat Jan 24 23:05:00 2009
@@ -32,6 +32,37 @@
// Private Functions
// ---------------------------------------------------------------------------
+#ifdef HAVE_CLUTTER
+
+#include <clutter/clutter.h>
+#include "clutter-gst-video-sink.h"
+
+typedef enum {
+ BP_VIDEO_DISPLAY_CONTEXT_UNSUPPORTED = 0,
+ BP_VIDEO_DISPLAY_CONTEXT_GDK_WINDOW = 1,
+ BP_VIDEO_DISPLAY_CONTEXT_CLUTTER_TEXTURE = 2
+} BpVideoDisplayContextType;
+
+static gboolean
+bp_video_pipeline_setup_clutter (BansheePlayer *player, GstBus *bus)
+{
+ if (player->clutter_texture == NULL) {
+ player->clutter_texture = g_object_new (CLUTTER_TYPE_TEXTURE,
+ "sync-size", FALSE,
+ "disable-slicing", TRUE,
+ NULL);
+ }
+
+ bp_debug ("Created ClutterTexture: %p", player->clutter_texture);
+
+ player->clutter_sink = clutter_gst_video_sink_new (CLUTTER_TEXTURE (player->clutter_texture));
+ g_object_set (G_OBJECT (player->playbin), "video-sink", player->clutter_sink, NULL);
+
+ return TRUE;
+}
+
+#endif /* HAVE_CLUTTER */
+
#ifdef GDK_WINDOWING_X11
static gboolean
@@ -126,10 +157,16 @@
{
GstElement *videosink;
- #ifdef GDK_WINDOWING_X11
-
g_return_if_fail (IS_BANSHEE_PLAYER (player));
+ #if HAVE_CLUTTER
+ if (bp_video_pipeline_setup_clutter (player, bus)) {
+ return;
+ }
+ #endif
+
+ #ifdef GDK_WINDOWING_X11
+
videosink = gst_element_factory_make ("gconfvideosink", "videosink");
if (videosink == NULL) {
videosink = gst_element_factory_make ("ximagesink", "videosink");
@@ -150,6 +187,15 @@
g_signal_connect (videosink, "element-added", G_CALLBACK (bp_video_sink_element_added), player);
}
+ #else
+
+ videosink = gst_element_factory_make ("fakesink", "videosink");
+ if (videosink != NULL) {
+ g_object_set (G_OBJECT (videosink), "sync", TRUE, NULL);
+ }
+
+ g_object_set (G_OBJECT (player->playbin), "video-sink", videosink, NULL);
+
#endif
}
@@ -159,18 +205,44 @@
#ifdef GDK_WINDOWING_X11
-P_INVOKE gboolean
-bp_video_is_supported (BansheePlayer *player)
+P_INVOKE BpVideoDisplayContextType
+bp_video_get_display_context_type (BansheePlayer *player)
{
- return TRUE; // bp_video_find_xoverlay (player);
+ #ifdef HAVE_CLUTTER
+ return player->clutter_sink == NULL
+ ? BP_VIDEO_DISPLAY_CONTEXT_GDK_WINDOW
+ : BP_VIDEO_DISPLAY_CONTEXT_CLUTTER_TEXTURE;
+ #else
+ return BP_VIDEO_DISPLAY_CONTEXT_GDK_WINDOW; // bp_video_find_xoverlay (player);
+ #endif
}
P_INVOKE void
-bp_video_set_window (BansheePlayer *player, GdkWindow *window)
+bp_video_set_display_context (BansheePlayer *player, gpointer context)
{
g_return_if_fail (IS_BANSHEE_PLAYER (player));
- player->video_window = window;
+ if (bp_video_get_display_context_type (player) == BP_VIDEO_DISPLAY_CONTEXT_GDK_WINDOW) {
+ player->video_window = (GdkWindow *)context;
+ }
+}
+
+P_INVOKE gpointer
+bp_video_get_display_context (BansheePlayer *player)
+{
+ g_return_val_if_fail (IS_BANSHEE_PLAYER (player), NULL);
+
+ #ifdef HAVE_CLUTTER
+ if (bp_video_get_display_context_type (player) == BP_VIDEO_DISPLAY_CONTEXT_CLUTTER_TEXTURE) {
+ return player->clutter_texture;
+ }
+ #endif
+
+ if (bp_video_get_display_context_type (player) == BP_VIDEO_DISPLAY_CONTEXT_GDK_WINDOW) {
+ return player->video_window;
+ }
+
+ return NULL;
}
P_INVOKE void
@@ -205,15 +277,33 @@
#else /* GDK_WINDOWING_X11 */
-P_INVOKE gboolean
-bp_video_is_supported (BansheePlayer *player)
+P_INVOKE BpVideoDisplayContextType
+bp_video_get_display_context_type (BansheePlayer *player)
{
- return FALSE;
+ #ifdef HAVE_CLUTTER
+ return player->clutter_sink == NULL
+ ? BP_VIDEO_DISPLAY_CONTEXT_UNSUPPORTED
+ : BP_VIDEO_DISPLAY_CONTEXT_CLUTTER_TEXTURE;
+ #else
+ return BP_VIDEO_DISPLAY_CONTEXT_UNSUPPORTED;
+ #endif
}
P_INVOKE void
-bp_video_set_window (BansheePlayer *player, GdkWindow *window)
+bp_video_set_display_context (BansheePlayer *player, gpointer context)
+{
+}
+
+P_INVOKE gpointer
+bp_video_get_display_context (BansheePlayer *player)
{
+ #ifdef HAVE_CLUTTER
+ if (bp_video_get_display_context_type (player) == BP_VIDEO_DISPLAY_CONTEXT_CLUTTER_TEXTURE) {
+ return player->clutter_texture;
+ }
+ #endif
+
+ return NULL;
}
P_INVOKE void
Added: trunk/banshee/libbanshee/clutter-gst-shaders.h
==============================================================================
--- (empty file)
+++ trunk/banshee/libbanshee/clutter-gst-shaders.h Sat Jan 24 23:05:00 2009
@@ -0,0 +1,43 @@
+
+#ifndef CLUTTER_GST_SHADERS_H
+#define CLUTTER_GST_SHADERS_H
+
+#include <clutter/clutter.h>
+
+/* Copied from test-shaders */
+
+/* These variables are used instead of the standard GLSL variables on
+ GLES 2 */
+#ifdef HAVE_COGL_GLES2
+
+#define GLES2_VARS \
+ "precision mediump float;\n" \
+ "varying vec2 tex_coord;\n" \
+ "varying vec4 frag_color;\n"
+#define TEX_COORD "tex_coord"
+#define COLOR_VAR "frag_color"
+
+#else /* HAVE_COGL_GLES2 */
+
+#define GLES2_VARS ""
+#define TEX_COORD "gl_TexCoord[0]"
+#define COLOR_VAR "gl_Color"
+
+#endif /* HAVE_COGL_GLES2 */
+
+/* a couple of boilerplate defines that are common amongst all the
+ * sample shaders
+ */
+
+#define FRAGMENT_SHADER_VARS \
+ GLES2_VARS
+
+/* FRAGMENT_SHADER_END: apply the changed color to the output buffer correctly
+ * blended with the gl specified color (makes the opacity of actors work
+ * correctly).
+ */
+#define FRAGMENT_SHADER_END \
+ " gl_FragColor = gl_FragColor * " COLOR_VAR ";"
+
+#endif
+
Added: trunk/banshee/libbanshee/clutter-gst-video-sink.c
==============================================================================
--- (empty file)
+++ trunk/banshee/libbanshee/clutter-gst-video-sink.c Sat Jan 24 23:05:00 2009
@@ -0,0 +1,781 @@
+/*
+ * Clutter-GStreamer.
+ *
+ * GStreamer integration library for Clutter.
+ *
+ * clutter-gst-video-sink.h - Gstreamer Video Sink that renders to a
+ * Clutter Texture.
+ *
+ * Authored by Jonathan Matthew <jonathan kaolin wh9 net>,
+ * Chris Lord <chris openedhand com>
+ *
+ * Copyright (C) 2007,2008 OpenedHand
+ *
+ * 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 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/**
+ * SECTION:clutter-gst-video-sink
+ * @short_description: GStreamer video sink
+ *
+ * #ClutterGstVideoSink is a GStreamer sink element that sends
+ * data to a #ClutterTexture.
+ */
+
+#include "config.h"
+
+#include "clutter-gst-video-sink.h"
+#include "clutter-gst-shaders.h"
+
+#include <gst/gst.h>
+#include <gst/gstvalue.h>
+#include <gst/video/video.h>
+#include <gst/riff/riff-ids.h>
+
+#include <glib.h>
+#include <clutter/clutter.h>
+#include <string.h>
+
+static gchar *ayuv_to_rgba_shader = \
+ FRAGMENT_SHADER_VARS
+ "uniform sampler2D tex;"
+ "void main () {"
+ " vec4 color = texture2D (tex, vec2(" TEX_COORD "));"
+ " float y = 1.1640625 * (color.g - 0.0625);"
+ " float u = color.b - 0.5;"
+ " float v = color.a - 0.5;"
+ " color.a = color.r;"
+ " color.r = y + 1.59765625 * v;"
+ " color.g = y - 0.390625 * u - 0.8125 * v;"
+ " color.b = y + 2.015625 * u;"
+ " gl_FragColor = color;"
+ FRAGMENT_SHADER_END
+ "}";
+
+static gchar *dummy_shader = \
+ FRAGMENT_SHADER_VARS
+ "void main () {"
+ "}";
+
+static gchar *yv12_to_rgba_shader = \
+ FRAGMENT_SHADER_VARS
+ "uniform sampler2D ytex;"
+ "uniform sampler2D utex;"
+ "uniform sampler2D vtex;"
+ "void main () {"
+ " vec2 coord = vec2(" TEX_COORD ");"
+ " float y = 1.1640625 * (texture2D (ytex, coord).g - 0.0625);"
+ " float u = texture2D (utex, coord).g - 0.5;"
+ " float v = texture2D (vtex, coord).g - 0.5;"
+ " vec4 color;"
+ " color.r = y + 1.59765625 * v;"
+ " color.g = y - 0.390625 * u - 0.8125 * v;"
+ " color.b = y + 2.015625 * u;"
+ " color.a = 1.0;"
+ " gl_FragColor = color;"
+ FRAGMENT_SHADER_END
+ "}";
+
+static GstStaticPadTemplate sinktemplate
+ = GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS (GST_VIDEO_CAPS_RGBx ";" \
+ GST_VIDEO_CAPS_BGRx "; " \
+ GST_VIDEO_CAPS_RGB ";" \
+ GST_VIDEO_CAPS_BGR \
+ ));
+
+/* Multi-texturing will only be available on GL, so decide on capabilties
+ * accordingly.
+ */
+
+#ifdef CLUTTER_COGL_HAS_GL
+#define YUV_CAPS GST_VIDEO_CAPS_YUV("AYUV") ";" GST_VIDEO_CAPS_YUV("YV12") ";"
+#else
+#define YUV_CAPS GST_VIDEO_CAPS_YUV("AYUV") ";"
+#endif
+
+/* Don't advertise RGB/BGR as it seems to override yv12, even when it's the
+ * better choice. Unfortunately, RGBx/BGRx also override AYUV when it's the
+ * better choice too, but that's not quite as bad.
+ */
+
+static GstStaticPadTemplate sinktemplate_shaders
+ = GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS (YUV_CAPS \
+ GST_VIDEO_CAPS_RGBx ";" \
+ GST_VIDEO_CAPS_BGRx));
+
+static GstStaticPadTemplate sinktemplate_all
+ = GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS (YUV_CAPS \
+ GST_VIDEO_CAPS_RGBx ";" \
+ GST_VIDEO_CAPS_BGRx ";" \
+ GST_VIDEO_CAPS_RGB ";" \
+ GST_VIDEO_CAPS_BGR));
+
+GST_DEBUG_CATEGORY_STATIC (clutter_gst_video_sink_debug);
+#define GST_CAT_DEFAULT clutter_gst_video_sink_debug
+
+static GstElementDetails clutter_gst_video_sink_details =
+ GST_ELEMENT_DETAILS ("Clutter video sink",
+ "Sink/Video",
+ "Sends video data from a GStreamer pipeline to a Clutter texture",
+ "Jonathan Matthew <jonathan kaolin wh9 net>, "
+ "Matthew Allum <mallum o-hand com, "
+ "Chris Lord <chris o-hand com>");
+
+enum
+{
+ PROP_0,
+ PROP_TEXTURE,
+ PROP_USE_SHADERS,
+};
+
+typedef enum
+{
+ CLUTTER_GST_NOFORMAT,
+ CLUTTER_GST_RGB32,
+ CLUTTER_GST_RGB24,
+ CLUTTER_GST_AYUV,
+ CLUTTER_GST_YV12,
+} ClutterGstVideoFormat;
+
+typedef void (*GLUNIFORM1IPROC)(COGLint location, COGLint value);
+
+struct _ClutterGstVideoSinkPrivate
+{
+ ClutterTexture *texture;
+ CoglHandle u_tex;
+ CoglHandle v_tex;
+ CoglHandle program;
+ CoglHandle shader;
+ GAsyncQueue *async_queue;
+ ClutterGstVideoFormat format;
+ gboolean bgr;
+ int width;
+ int height;
+ int fps_n, fps_d;
+ int par_n, par_d;
+ gboolean use_shaders;
+ gboolean shaders_init;
+
+ GLUNIFORM1IPROC glUniform1iARB;
+};
+
+
+#define _do_init(bla) \
+ GST_DEBUG_CATEGORY_INIT (clutter_gst_video_sink_debug, \
+ "cluttersink", \
+ 0, \
+ "clutter video sink")
+
+GST_BOILERPLATE_FULL (ClutterGstVideoSink,
+ clutter_gst_video_sink,
+ GstBaseSink,
+ GST_TYPE_BASE_SINK,
+ _do_init);
+
+static void
+clutter_gst_video_sink_base_init (gpointer g_class)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
+
+ gst_element_class_add_pad_template
+ (element_class,
+ gst_static_pad_template_get (&sinktemplate_all));
+
+ gst_element_class_set_details (element_class,
+ &clutter_gst_video_sink_details);
+}
+
+static void
+clutter_gst_video_sink_init (ClutterGstVideoSink *sink,
+ ClutterGstVideoSinkClass *klass)
+{
+ ClutterGstVideoSinkPrivate *priv;
+
+ sink->priv = priv =
+ G_TYPE_INSTANCE_GET_PRIVATE (sink, CLUTTER_GST_TYPE_VIDEO_SINK,
+ ClutterGstVideoSinkPrivate);
+
+ priv->async_queue = g_async_queue_new ();
+
+#ifdef CLUTTER_COGL_HAS_GL
+ priv->glUniform1iARB = (GLUNIFORM1IPROC)
+ cogl_get_proc_address ("glUniform1iARB");
+#endif
+}
+
+static void
+clutter_gst_video_sink_paint (ClutterActor *actor,
+ ClutterGstVideoSink *sink)
+{
+ ClutterGstVideoSinkPrivate *priv = sink->priv;
+ if (priv->program)
+ cogl_program_use (priv->program);
+}
+
+static void
+clutter_gst_yv12_paint (ClutterActor *actor,
+ ClutterGstVideoSink *sink)
+{
+#ifdef CLUTTER_COGL_HAS_GL
+ ClutterGstVideoSinkPrivate *priv = sink->priv;
+ GLuint texture;
+
+ /* Bind the U and V textures in texture units 1 and 2 */
+ if (priv->u_tex)
+ {
+ cogl_texture_get_gl_texture (priv->u_tex, &texture, NULL);
+ glActiveTexture (GL_TEXTURE1);
+ glEnable (GL_TEXTURE_2D);
+ glBindTexture (GL_TEXTURE_2D, texture);
+ }
+
+ if (priv->v_tex)
+ {
+ cogl_texture_get_gl_texture (priv->v_tex, &texture, NULL);
+ glActiveTexture (GL_TEXTURE2);
+ glEnable (GL_TEXTURE_2D);
+ glBindTexture (GL_TEXTURE_2D, texture);
+ }
+
+ glActiveTexture (GL_TEXTURE0_ARB);
+#endif
+}
+
+static void
+clutter_gst_yv12_post_paint (ClutterActor *actor,
+ ClutterGstVideoSink *sink)
+{
+#ifdef CLUTTER_COGL_HAS_GL
+ /* Disable the extra texture units */
+ glActiveTexture (GL_TEXTURE1);
+ glDisable (GL_TEXTURE_2D);
+ glActiveTexture (GL_TEXTURE2);
+ glDisable (GL_TEXTURE_2D);
+ glActiveTexture (GL_TEXTURE0);
+#endif
+}
+
+static void
+clutter_gst_video_sink_set_shader (ClutterGstVideoSink *sink,
+ const gchar *shader_src)
+{
+ ClutterGstVideoSinkPrivate *priv = sink->priv;
+
+ priv->shaders_init = FALSE;
+ if (priv->texture)
+ clutter_actor_set_shader (CLUTTER_ACTOR (priv->texture), NULL);
+
+ if (priv->program)
+ {
+ cogl_program_unref (priv->program);
+ priv->program = NULL;
+ }
+
+ if (priv->shader)
+ {
+ cogl_program_unref (priv->shader);
+ priv->shader = NULL;
+ }
+
+ if (shader_src)
+ {
+ ClutterShader *shader;
+
+ /* Set a dummy shader so we don't interfere with the shader stack */
+ shader = clutter_shader_new ();
+ clutter_shader_set_fragment_source (shader, dummy_shader, -1);
+ clutter_actor_set_shader (CLUTTER_ACTOR (priv->texture), shader);
+ g_object_unref (shader);
+
+ /* Create shader through COGL - necessary as we need to be able to set
+ * integer uniform variables for multi-texturing.
+ */
+ priv->shader = cogl_create_shader (CGL_FRAGMENT_SHADER);
+ cogl_shader_source (priv->shader, shader_src);
+ cogl_shader_compile (priv->shader);
+
+ priv->program = cogl_create_program ();
+ cogl_program_attach_shader (priv->program, priv->shader);
+ cogl_program_link (priv->program);
+
+ /* Hook onto the pre-paint signal to replace the dummy shader with
+ * the real shader.
+ */
+ g_signal_connect (priv->texture,
+ "paint",
+ G_CALLBACK (clutter_gst_video_sink_paint),
+ sink);
+
+ priv->shaders_init = TRUE;
+ }
+}
+
+static gboolean
+clutter_gst_video_sink_idle_func (gpointer data)
+{
+ ClutterGstVideoSink *sink;
+ ClutterGstVideoSinkPrivate *priv;
+ GstBuffer *buffer;
+
+ sink = data;
+ priv = sink->priv;
+
+ buffer = g_async_queue_try_pop (priv->async_queue);
+ if (buffer == NULL || G_UNLIKELY (!GST_IS_BUFFER (buffer)))
+ {
+ return FALSE;
+ }
+
+ if ((priv->format == CLUTTER_GST_RGB32) || (priv->format == CLUTTER_GST_AYUV))
+ {
+ clutter_texture_set_from_rgb_data (priv->texture,
+ GST_BUFFER_DATA (buffer),
+ TRUE,
+ priv->width,
+ priv->height,
+ GST_ROUND_UP_4 (4 * priv->width),
+ 4,
+ priv->bgr ?
+ CLUTTER_TEXTURE_RGB_FLAG_BGR : 0,
+ NULL);
+
+ /* Initialise AYUV shader */
+ if ((priv->format == CLUTTER_GST_AYUV) && !priv->shaders_init)
+ clutter_gst_video_sink_set_shader (sink,
+ ayuv_to_rgba_shader);
+ }
+ else if (priv->format == CLUTTER_GST_RGB24)
+ {
+ clutter_texture_set_from_rgb_data (priv->texture,
+ GST_BUFFER_DATA (buffer),
+ FALSE,
+ priv->width,
+ priv->height,
+ GST_ROUND_UP_4 (3 * priv->width),
+ 3,
+ priv->bgr ?
+ CLUTTER_TEXTURE_RGB_FLAG_BGR : 0,
+ NULL);
+ }
+ else if (priv->format == CLUTTER_GST_YV12)
+ {
+ CoglHandle y_tex =
+ cogl_texture_new_from_data (priv->width,
+ priv->height,
+ -1,
+ FALSE,
+ COGL_PIXEL_FORMAT_G_8,
+ COGL_PIXEL_FORMAT_G_8,
+ priv->width,
+ GST_BUFFER_DATA (buffer));
+ cogl_texture_set_filters (y_tex, CGL_LINEAR, CGL_LINEAR);
+ clutter_texture_set_cogl_texture (priv->texture, y_tex);
+ cogl_texture_unref (y_tex);
+
+ if (priv->u_tex)
+ cogl_texture_unref (priv->u_tex);
+
+ if (priv->v_tex)
+ cogl_texture_unref (priv->v_tex);
+
+ priv->v_tex =
+ cogl_texture_new_from_data (priv->width/2,
+ priv->height/2,
+ -1,
+ FALSE,
+ COGL_PIXEL_FORMAT_G_8,
+ COGL_PIXEL_FORMAT_G_8,
+ priv->width/2,
+ GST_BUFFER_DATA (buffer) +
+ (priv->width * priv->height));
+ cogl_texture_set_filters (priv->v_tex, CGL_LINEAR, CGL_LINEAR);
+
+ priv->u_tex =
+ cogl_texture_new_from_data (priv->width/2,
+ priv->height/2,
+ -1,
+ FALSE,
+ COGL_PIXEL_FORMAT_G_8,
+ COGL_PIXEL_FORMAT_G_8,
+ priv->width/2,
+ GST_BUFFER_DATA (buffer) +
+ (priv->width * priv->height) +
+ (priv->width/2 * priv->height/2));
+ cogl_texture_set_filters (priv->u_tex, CGL_LINEAR, CGL_LINEAR);
+
+ /* Initialise YV12 shader */
+ if (!priv->shaders_init)
+ {
+#ifdef CLUTTER_COGL_HAS_GL
+ COGLint location;
+ clutter_gst_video_sink_set_shader (sink,
+ yv12_to_rgba_shader);
+
+ cogl_program_use (priv->program);
+ location = cogl_program_get_uniform_location (priv->program, "ytex");
+ priv->glUniform1iARB (location, 0);
+ location = cogl_program_get_uniform_location (priv->program, "utex");
+ priv->glUniform1iARB (location, 1);
+ location = cogl_program_get_uniform_location (priv->program, "vtex");
+ priv->glUniform1iARB (location, 2);
+ cogl_program_use (COGL_INVALID_HANDLE);
+
+ g_signal_connect (priv->texture,
+ "paint",
+ G_CALLBACK (clutter_gst_yv12_paint),
+ sink);
+ g_signal_connect_after (priv->texture,
+ "paint",
+ G_CALLBACK (clutter_gst_yv12_post_paint),
+ sink);
+#endif
+ }
+ }
+
+ gst_buffer_unref (buffer);
+
+ return FALSE;
+}
+
+static GstFlowReturn
+clutter_gst_video_sink_render (GstBaseSink *bsink,
+ GstBuffer *buffer)
+{
+ ClutterGstVideoSink *sink;
+ ClutterGstVideoSinkPrivate *priv;
+
+ sink = CLUTTER_GST_VIDEO_SINK (bsink);
+ priv = sink->priv;
+
+ g_async_queue_push (priv->async_queue, gst_buffer_ref (buffer));
+
+ clutter_threads_add_idle_full (G_PRIORITY_HIGH_IDLE,
+ clutter_gst_video_sink_idle_func,
+ sink,
+ NULL);
+
+ return GST_FLOW_OK;
+}
+
+static GstCaps *
+clutter_gst_video_sink_get_caps (GstBaseSink *sink)
+{
+ ClutterGstVideoSinkPrivate *priv = CLUTTER_GST_VIDEO_SINK (sink)->priv;
+
+ if (priv->use_shaders && cogl_features_available (COGL_FEATURE_SHADERS_GLSL))
+ return gst_static_pad_template_get_caps (&sinktemplate_shaders);
+ else
+ return gst_static_pad_template_get_caps (&sinktemplate);
+}
+
+static gboolean
+clutter_gst_video_sink_set_caps (GstBaseSink *bsink,
+ GstCaps *caps)
+{
+ ClutterGstVideoSink *sink;
+ ClutterGstVideoSinkPrivate *priv;
+ GstCaps *intersection;
+ GstStructure *structure;
+ gboolean ret;
+ const GValue *fps;
+ const GValue *par;
+ gint width, height;
+ guint32 fourcc;
+ int red_mask, blue_mask;
+
+ sink = CLUTTER_GST_VIDEO_SINK(bsink);
+ priv = sink->priv;
+
+ clutter_gst_video_sink_set_shader (sink, NULL);
+
+ if (priv->use_shaders && cogl_features_available (COGL_FEATURE_SHADERS_GLSL))
+ intersection
+ = gst_caps_intersect
+ (gst_static_pad_template_get_caps (&sinktemplate_shaders),
+ caps);
+ else
+ intersection
+ = gst_caps_intersect
+ (gst_static_pad_template_get_caps (&sinktemplate),
+ caps);
+
+ if (gst_caps_is_empty (intersection))
+ return FALSE;
+
+ gst_caps_unref (intersection);
+
+ structure = gst_caps_get_structure (caps, 0);
+
+ ret = gst_structure_get_int (structure, "width", &width);
+ ret &= gst_structure_get_int (structure, "height", &height);
+ fps = gst_structure_get_value (structure, "framerate");
+ ret &= (fps != NULL);
+
+ par = gst_structure_get_value (structure, "pixel-aspect-ratio");
+
+ if (!ret)
+ return FALSE;
+
+ priv->width = width;
+ priv->height = height;
+
+ /* We dont yet use fps or pixel aspect into but handy to have */
+ priv->fps_n = gst_value_get_fraction_numerator (fps);
+ priv->fps_d = gst_value_get_fraction_denominator (fps);
+
+ if (par)
+ {
+ priv->par_n = gst_value_get_fraction_numerator (par);
+ priv->par_d = gst_value_get_fraction_denominator (par);
+ }
+ else
+ priv->par_n = priv->par_d = 1;
+
+ ret = gst_structure_get_fourcc (structure, "format", &fourcc);
+ if (ret && (fourcc == GST_RIFF_YV12))
+ {
+ priv->format = CLUTTER_GST_YV12;
+ }
+ else if (ret && (fourcc == GST_MAKE_FOURCC ('A', 'Y', 'U', 'V')))
+ {
+ priv->format = CLUTTER_GST_AYUV;
+ priv->bgr = FALSE;
+ }
+ else
+ {
+ guint32 width;
+ gst_structure_get_int (structure, "red_mask", &red_mask);
+ gst_structure_get_int (structure, "blue_mask", &blue_mask);
+
+ width = red_mask | blue_mask;
+ if (width < 0x1000000)
+ {
+ priv->format = CLUTTER_GST_RGB24;
+ priv->bgr = (red_mask == 0xff0000) ? FALSE : TRUE;
+ }
+ else
+ {
+ priv->format = CLUTTER_GST_RGB32;
+ priv->bgr = (red_mask == 0xff000000) ? FALSE : TRUE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+clutter_gst_video_sink_dispose (GObject *object)
+{
+ ClutterGstVideoSink *self;
+ ClutterGstVideoSinkPrivate *priv;
+
+ self = CLUTTER_GST_VIDEO_SINK (object);
+ priv = self->priv;
+
+ clutter_gst_video_sink_set_shader (self, NULL);
+
+ if (priv->texture)
+ {
+ g_object_unref (priv->texture);
+ priv->texture = NULL;
+ }
+
+ if (priv->async_queue)
+ {
+ g_async_queue_unref (priv->async_queue);
+ priv->async_queue = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+clutter_gst_video_sink_finalize (GObject *object)
+{
+ ClutterGstVideoSink *self;
+ ClutterGstVideoSinkPrivate *priv;
+
+ self = CLUTTER_GST_VIDEO_SINK (object);
+ priv = self->priv;
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+clutter_gst_video_sink_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ClutterGstVideoSink *sink;
+ ClutterGstVideoSinkPrivate *priv;
+ gboolean use_shaders;
+
+ sink = CLUTTER_GST_VIDEO_SINK (object);
+ priv = sink->priv;
+
+ switch (prop_id)
+ {
+ case PROP_TEXTURE:
+ if (priv->texture)
+ g_object_unref (priv->texture);
+
+ priv->texture = CLUTTER_TEXTURE (g_value_dup_object (value));
+ break;
+ case PROP_USE_SHADERS:
+ use_shaders = g_value_get_boolean (value);
+ if (priv->use_shaders != use_shaders)
+ {
+ priv->use_shaders = use_shaders;
+ g_object_notify (object, "use_shaders");
+ }
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+clutter_gst_video_sink_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ClutterGstVideoSink *sink;
+
+ sink = CLUTTER_GST_VIDEO_SINK (object);
+
+ switch (prop_id)
+ {
+ case PROP_TEXTURE:
+ g_value_set_object (value, sink->priv->texture);
+ break;
+ case PROP_USE_SHADERS:
+ g_value_set_boolean (value, sink->priv->use_shaders);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+clutter_gst_video_sink_stop (GstBaseSink *base_sink)
+{
+ ClutterGstVideoSinkPrivate *priv;
+ GstBuffer *buffer;
+
+ priv = CLUTTER_GST_VIDEO_SINK (base_sink)->priv;
+
+ g_async_queue_lock (priv->async_queue);
+
+ /* Remove all remaining objects from the queue */
+ do
+ {
+ buffer = g_async_queue_try_pop_unlocked (priv->async_queue);
+ if (buffer)
+ gst_buffer_unref (buffer);
+ } while (buffer != NULL);
+
+ g_async_queue_unlock (priv->async_queue);
+
+ return TRUE;
+}
+
+static void
+clutter_gst_video_sink_class_init (ClutterGstVideoSinkClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GstBaseSinkClass *gstbase_sink_class = GST_BASE_SINK_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (ClutterGstVideoSinkPrivate));
+
+ gobject_class->set_property = clutter_gst_video_sink_set_property;
+ gobject_class->get_property = clutter_gst_video_sink_get_property;
+
+ gobject_class->dispose = clutter_gst_video_sink_dispose;
+ gobject_class->finalize = clutter_gst_video_sink_finalize;
+
+ gstbase_sink_class->render = clutter_gst_video_sink_render;
+ gstbase_sink_class->preroll = clutter_gst_video_sink_render;
+ gstbase_sink_class->stop = clutter_gst_video_sink_stop;
+ gstbase_sink_class->set_caps = clutter_gst_video_sink_set_caps;
+ gstbase_sink_class->get_caps = clutter_gst_video_sink_get_caps;
+
+ g_object_class_install_property
+ (gobject_class, PROP_TEXTURE,
+ g_param_spec_object ("texture",
+ "texture",
+ "Target ClutterTexture object",
+ CLUTTER_TYPE_TEXTURE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property
+ (gobject_class, PROP_USE_SHADERS,
+ g_param_spec_boolean ("use_shaders",
+ "Use shaders",
+ "Use a fragment shader to accelerate "
+ "colour-space conversion.",
+ FALSE,
+ G_PARAM_READWRITE));
+}
+
+/**
+ * clutter_gst_video_sink_new:
+ * @texture: a #ClutterTexture
+ *
+ * Creates a new GStreamer video sink which uses @texture as the target
+ * for sinking a video stream from GStreamer.
+ *
+ * Return value: a #GstElement for the newly created video sink
+ */
+GstElement *
+clutter_gst_video_sink_new (ClutterTexture *texture)
+{
+ return g_object_new (CLUTTER_GST_TYPE_VIDEO_SINK,
+ "texture", texture,
+ NULL);
+}
+
+static gboolean
+plugin_init (GstPlugin *plugin)
+{
+ gboolean ret = gst_element_register (plugin,
+ "cluttersink",
+ GST_RANK_PRIMARY,
+ CLUTTER_GST_TYPE_VIDEO_SINK);
+ return ret;
+}
+
+GST_PLUGIN_DEFINE_STATIC (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ "cluttersink",
+ "Element to render to Clutter textures",
+ plugin_init,
+ VERSION,
+ "LGPL", /* license */
+ PACKAGE,
+ "");
Added: trunk/banshee/libbanshee/clutter-gst-video-sink.h
==============================================================================
--- (empty file)
+++ trunk/banshee/libbanshee/clutter-gst-video-sink.h Sat Jan 24 23:05:00 2009
@@ -0,0 +1,103 @@
+/*
+ * Clutter-GStreamer.
+ *
+ * GStreamer integration library for Clutter.
+ *
+ * clutter-gst-video-sink.h - Gstreamer Video Sink that renders to a
+ * Clutter Texture.
+ *
+ * Authored by Jonathan Matthew <jonathan kaolin wh9 net>
+ *
+ * Copyright (C) 2007 OpenedHand
+ *
+ * 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 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _HAVE_CLUTTER_GST_VIDEO_SINK_H
+#define _HAVE_CLUTTER_GST_VIDEO_SINK_H
+
+#include <glib-object.h>
+#include <gst/base/gstbasesink.h>
+#include <clutter/clutter.h>
+
+G_BEGIN_DECLS
+
+#define CLUTTER_GST_TYPE_VIDEO_SINK clutter_gst_video_sink_get_type()
+
+#define CLUTTER_GST_VIDEO_SINK(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ CLUTTER_GST_TYPE_VIDEO_SINK, ClutterGstVideoSink))
+
+#define CLUTTER_GST_VIDEO_SINK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ CLUTTER_GST_TYPE_VIDEO_SINK, ClutterGstVideoSinkClass))
+
+#define CLUTTER_GST_IS_VIDEO_SINK(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ CLUTTER_GST_TYPE_VIDEO_SINK))
+
+#define CLUTTER_GST_IS_VIDEO_SINK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ CLUTTER_GST_TYPE_VIDEO_SINK))
+
+#define CLUTTER_GST_VIDEO_SINK_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ CLUTTER_GST_TYPE_VIDEO_SINK, ClutterGstVideoSinkClass))
+
+typedef struct _ClutterGstVideoSink ClutterGstVideoSink;
+typedef struct _ClutterGstVideoSinkClass ClutterGstVideoSinkClass;
+typedef struct _ClutterGstVideoSinkPrivate ClutterGstVideoSinkPrivate;
+
+/**
+ * ClutterGstVideoSink:
+ *
+ * Class implementing a GStreamer sink element for #ClutterTexture<!-- -->s.
+ *
+ * The #ClutterGstVideoSink structure contains only private data and should
+ * not be accessed directly.
+ */
+struct _ClutterGstVideoSink
+{
+ /*< private >*/
+ GstBaseSink parent;
+ ClutterGstVideoSinkPrivate *priv;
+};
+
+/**
+ * ClutterGstVideoSinkClass:
+ *
+ * Base class for #ClutterGstVideoSink.
+ */
+struct _ClutterGstVideoSinkClass
+{
+ /*< private >*/
+ GstBaseSinkClass parent_class;
+
+ /* Future padding */
+ void (* _clutter_reserved1) (void);
+ void (* _clutter_reserved2) (void);
+ void (* _clutter_reserved3) (void);
+ void (* _clutter_reserved4) (void);
+ void (* _clutter_reserved5) (void);
+ void (* _clutter_reserved6) (void);
+};
+
+GType clutter_gst_video_sink_get_type (void) G_GNUC_CONST;
+GstElement *clutter_gst_video_sink_new (ClutterTexture *texture);
+
+G_END_DECLS
+
+#endif
Modified: trunk/banshee/libbanshee/libbanshee.cproj
==============================================================================
--- trunk/banshee/libbanshee/libbanshee.cproj (original)
+++ trunk/banshee/libbanshee/libbanshee.cproj Sat Jan 24 23:05:00 2009
@@ -5,9 +5,6 @@
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.50727</ProductVersion>
<ProjectGuid>{6B781836-AB65-49EF-BECD-CCC193C5D589}</ProjectGuid>
- <Packages>
- <Packages />
- </Packages>
<Compiler>
<Compiler ctype="GccCompiler" />
</Compiler>
Modified: trunk/banshee/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs
==============================================================================
--- trunk/banshee/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs (original)
+++ trunk/banshee/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs Sat Jan 24 23:05:00 2009
@@ -423,19 +423,13 @@
}
}
- private bool? supports_video = null;
- public override bool SupportsVideo {
- get {
- if (supports_video == null) {
- supports_video = bp_video_is_supported (handle);
- }
-
- return supports_video.Value;
- }
+ public override VideoDisplayContextType VideoDisplayContextType {
+ get { return bp_video_get_display_context_type (handle); }
}
- public override IntPtr VideoWindow {
- set { bp_video_set_window (handle, value); }
+ public override IntPtr VideoDisplayContext {
+ set { bp_video_set_display_context (handle, value); }
+ get { return bp_video_get_display_context (handle); }
}
public double AmplifierLevel {
@@ -599,13 +593,16 @@
private static extern void bp_set_application_gdk_window (HandleRef player, IntPtr window);
[DllImport ("libbanshee")]
- private static extern bool bp_video_is_supported (HandleRef player);
+ private static extern VideoDisplayContextType bp_video_get_display_context_type (HandleRef player);
+
+ [DllImport ("libbanshee")]
+ private static extern void bp_video_set_display_context (HandleRef player, IntPtr displayContext);
[DllImport ("libbanshee")]
- private static extern void bp_video_set_window (HandleRef player, IntPtr window);
+ private static extern IntPtr bp_video_get_display_context (HandleRef player);
[DllImport ("libbanshee")]
- private static extern void bp_video_window_expose (HandleRef player, IntPtr window, bool direct);
+ private static extern void bp_video_window_expose (HandleRef player, IntPtr displayContext, bool direct);
[DllImport ("libbanshee")]
private static extern void bp_get_error_quarks (out uint core, out uint library,
Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.MediaEngine/NullPlayerEngine.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.MediaEngine/NullPlayerEngine.cs (original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.MediaEngine/NullPlayerEngine.cs Sat Jan 24 23:05:00 2009
@@ -72,8 +72,8 @@
get { return false; }
}
- public override bool SupportsVideo {
- get { return false; }
+ public override VideoDisplayContextType VideoDisplayContextType {
+ get { return VideoDisplayContextType.Unsupported; }
}
private static string [] source_capabilities = { "file", "http", "cdda" };
Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs (original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs Sat Jan 24 23:05:00 2009
@@ -107,7 +107,7 @@
public abstract void Pause ();
- public virtual void VideoExpose (IntPtr window, bool direct)
+ public virtual void VideoExpose (IntPtr displayContext, bool direct)
{
throw new NotImplementedException ("Engine must implement VideoExpose since this method only gets called when SupportsVideo is true");
}
@@ -251,12 +251,13 @@
get;
}
- public abstract bool SupportsVideo {
+ public abstract VideoDisplayContextType VideoDisplayContextType {
get;
}
- public virtual IntPtr VideoWindow {
+ public virtual IntPtr VideoDisplayContext {
set { }
+ get { return IntPtr.Zero; }
}
}
}
Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs (original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs Sat Jan 24 23:05:00 2009
@@ -416,13 +416,14 @@
}
}
- public void VideoExpose (IntPtr window, bool direct)
+ public void VideoExpose (IntPtr displayContext, bool direct)
{
- active_engine.VideoExpose (window, direct);
+ active_engine.VideoExpose (displayContext, direct);
}
- public IntPtr VideoWindow {
- set { active_engine.VideoWindow = value; }
+ public IntPtr VideoDisplayContext {
+ set { active_engine.VideoDisplayContext = value; }
+ get { return active_engine.VideoDisplayContext; }
}
public void TrackInfoUpdated ()
@@ -522,8 +523,8 @@
get { return ((active_engine is IEqualizer) && active_engine.SupportsEqualizer); }
}
- public bool SupportsVideo {
- get { return active_engine.SupportsVideo; }
+ public VideoDisplayContextType VideoDisplayContextType {
+ get { return active_engine.VideoDisplayContextType; }
}
public uint Length {
Added: trunk/banshee/src/Core/Banshee.Services/Banshee.MediaEngine/VideoDisplayContextType.cs
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.MediaEngine/VideoDisplayContextType.cs Sat Jan 24 23:05:00 2009
@@ -0,0 +1,39 @@
+//
+// VideoDisplayContextType.cs
+//
+// Author:
+// Aaron Bockover <abockover novell com>
+//
+// Copyright (C) 2009 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Banshee.MediaEngine
+{
+ public enum VideoDisplayContextType
+ {
+ Unsupported = 0,
+ GdkWindow = 1,
+ ClutterTexture = 2
+ }
+}
Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.ServiceStack/ServiceManager.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.ServiceStack/ServiceManager.cs (original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.ServiceStack/ServiceManager.cs Sat Jan 24 23:05:00 2009
@@ -274,7 +274,7 @@
((IDisposable)service).Dispose ();
Log.DebugFormat ("Service disposed ({0})", service.ServiceName);
} catch (Exception e) {
- Log.Exception ("Service disposal ({0}) threw an exception", e);
+ Log.Exception (String.Format ("Service disposal ({0}) threw an exception", service.ServiceName), e);
}
}
Modified: trunk/banshee/src/Core/Banshee.Services/Makefile.am
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Makefile.am (original)
+++ trunk/banshee/src/Core/Banshee.Services/Makefile.am Sat Jan 24 23:05:00 2009
@@ -85,6 +85,7 @@
Banshee.MediaEngine/PlayerEngineService.cs \
Banshee.MediaEngine/PlayerEvent.cs \
Banshee.MediaEngine/TranscoderService.cs \
+ Banshee.MediaEngine/VideoDisplayContextType.cs \
Banshee.MediaProfiles/MediaProfileManager.cs \
Banshee.MediaProfiles/Pipeline.cs \
Banshee.MediaProfiles/PipelineVariable.cs \
Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/GtkBaseClient.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/GtkBaseClient.cs (original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/GtkBaseClient.cs Sat Jan 24 23:05:00 2009
@@ -92,6 +92,9 @@
}
}
+ [System.Runtime.InteropServices.DllImport ("clutter-gtk")]
+ private static extern int gtk_clutter_init (IntPtr argc, IntPtr argv);
+
protected void Initialize (bool registerCommonServices)
{
// Set the process name so system process listings and commands are pretty
@@ -99,8 +102,15 @@
Application.Initialize ();
- // Initialize GTK
- Gtk.Application.Init ();
+ // Initialize Clutter/GTK
+ try {
+ if (gtk_clutter_init (IntPtr.Zero, IntPtr.Zero) != 1) {
+ Gtk.Application.Init ();
+ }
+ } catch {
+ Gtk.Application.Init ();
+ }
+
Gtk.Window.DefaultIconName = default_icon_name;
ThreadAssist.InitializeMainThread ();
Added: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.ThickClient.dll.config
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.ThickClient.dll.config Sat Jan 24 23:05:00 2009
@@ -0,0 +1,3 @@
+<configuration>
+ <dllmap dll="clutter-gtk" target="libclutter-gtk-0.8.so.0"/>
+</configuration>
Modified: trunk/banshee/src/Core/Banshee.ThickClient/Makefile.am
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Makefile.am (original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Makefile.am Sat Jan 24 23:05:00 2009
@@ -152,3 +152,6 @@
include $(top_srcdir)/build/build.mk
+module_SCRIPTS += Banshee.ThickClient.dll.config
+EXTRA_DIST += Banshee.ThickClient.dll.config
+
Added: trunk/banshee/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying.dll.config
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying.dll.config Sat Jan 24 23:05:00 2009
@@ -0,0 +1,3 @@
+<configuration>
+ <dllmap dll="clutter-gtk" target="libclutter-gtk-0.8.so.0"/>
+</configuration>
Added: trunk/banshee/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/IVideoDisplay.cs
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/IVideoDisplay.cs Sat Jan 24 23:05:00 2009
@@ -0,0 +1,38 @@
+//
+// IVideoDisplay.cs
+//
+// Author:
+// Aaron Bockover <abockover novell com>
+//
+// Copyright (C) 2009 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Banshee.NowPlaying
+{
+ public interface IVideoDisplay
+ {
+ event EventHandler IdleStateChanged;
+ bool IsIdle { get; }
+ }
+}
Modified: trunk/banshee/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/NowPlayingContents.cs
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/NowPlayingContents.cs (original)
+++ trunk/banshee/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/NowPlayingContents.cs Sat Jan 24 23:05:00 2009
@@ -35,7 +35,7 @@
{
public class NowPlayingContents : Table, IDisposable
{
- private VideoDisplay video_display;
+ private Widget video_display;
private bool video_display_initial_shown = false;
private TrackInfoDisplay track_info_display;
@@ -44,8 +44,21 @@
{
NoShowAll = true;
- video_display = new XOverlayVideoDisplay ();
- video_display.IdleStateChanged += OnVideoDisplayIdleStateChanged;
+ switch (Banshee.ServiceStack.ServiceManager.PlayerEngine.VideoDisplayContextType) {
+ case Banshee.MediaEngine.VideoDisplayContextType.GdkWindow:
+ video_display = new XOverlayVideoDisplay ();
+ break;
+ case Banshee.MediaEngine.VideoDisplayContextType.Unsupported:
+ default:
+ video_display = null;
+ break;
+ }
+
+ IVideoDisplay ivideo_display = video_display as IVideoDisplay;
+ if (ivideo_display != null) {
+ ivideo_display.IdleStateChanged += OnVideoDisplayIdleStateChanged;
+ }
+
Attach (video_display, 0, 1, 0, 1,
AttachOptions.Expand | AttachOptions.Fill,
AttachOptions.Expand | AttachOptions.Fill, 0, 0);
@@ -58,8 +71,12 @@
public override void Dispose ()
{
+ IVideoDisplay ivideo_display = video_display as IVideoDisplay;
+ if (ivideo_display != null) {
+ ivideo_display.IdleStateChanged -= OnVideoDisplayIdleStateChanged;
+ }
+
if (video_display != null) {
- video_display.IdleStateChanged -= OnVideoDisplayIdleStateChanged;
video_display = null;
}
@@ -73,7 +90,11 @@
// Ugly hack to ensure the video window is mapped/realized
if (!video_display_initial_shown) {
video_display_initial_shown = true;
- video_display.Show ();
+
+ if (video_display != null) {
+ video_display.Show ();
+ }
+
GLib.Idle.Add (delegate {
CheckIdle ();
return false;
@@ -92,8 +113,11 @@
private void CheckIdle ()
{
- video_display.Visible = !video_display.IsIdle;
- track_info_display.Visible = video_display.IsIdle;
+ IVideoDisplay ivideo_display = video_display as IVideoDisplay;
+ if (ivideo_display != null) {
+ video_display.Visible = !ivideo_display.IsIdle;
+ track_info_display.Visible = ivideo_display.IsIdle;
+ }
}
private void OnVideoDisplayIdleStateChanged (object o, EventArgs args)
Modified: trunk/banshee/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/VideoDisplay.cs
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/VideoDisplay.cs (original)
+++ trunk/banshee/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/VideoDisplay.cs Sat Jan 24 23:05:00 2009
@@ -35,7 +35,7 @@
namespace Banshee.NowPlaying
{
- public abstract class VideoDisplay : Gtk.Widget
+ public abstract class VideoDisplay : Gtk.Widget, IVideoDisplay
{
private bool is_idle = true;
@@ -73,7 +73,7 @@
return true;
}
- if (!is_idle && ServiceManager.PlayerEngine.SupportsVideo) {
+ if (!is_idle && ServiceManager.PlayerEngine.VideoDisplayContextType != VideoDisplayContextType.Unsupported) {
ExposeVideo (evnt);
}
Modified: trunk/banshee/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/XOverlayVideoDisplay.cs
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/XOverlayVideoDisplay.cs (original)
+++ trunk/banshee/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/XOverlayVideoDisplay.cs Sat Jan 24 23:05:00 2009
@@ -30,6 +30,7 @@
using Gtk;
using Banshee.ServiceStack;
+using Banshee.MediaEngine;
namespace Banshee.NowPlaying
{
@@ -80,7 +81,11 @@
video_window.SetBackPixmap (null, false);
- ServiceManager.PlayerEngine.VideoWindow = video_window.Handle;
+ if (ServiceManager.PlayerEngine.VideoDisplayContextType == VideoDisplayContextType.GdkWindow) {
+ ServiceManager.PlayerEngine.VideoDisplayContext = video_window.Handle;
+ } else {
+ ServiceManager.PlayerEngine.VideoDisplayContext = IntPtr.Zero;
+ }
}
protected override void OnUnrealized ()
@@ -119,7 +124,7 @@
protected override bool OnConfigureEvent (Gdk.EventConfigure evnt)
{
- if (video_window != null && ServiceManager.PlayerEngine.SupportsVideo) {
+ if (video_window != null && ServiceManager.PlayerEngine.VideoDisplayContextType == VideoDisplayContextType.GdkWindow) {
ServiceManager.PlayerEngine.VideoExpose (video_window.Handle, true);
}
@@ -128,7 +133,9 @@
protected override void ExposeVideo (Gdk.EventExpose evnt)
{
- ServiceManager.PlayerEngine.VideoExpose (video_window.Handle, true);
+ if (ServiceManager.PlayerEngine.VideoDisplayContextType == VideoDisplayContextType.GdkWindow) {
+ ServiceManager.PlayerEngine.VideoExpose (video_window.Handle, true);
+ }
}
}
}
Modified: trunk/banshee/src/Extensions/Banshee.NowPlaying/Makefile.am
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.NowPlaying/Makefile.am (original)
+++ trunk/banshee/src/Extensions/Banshee.NowPlaying/Makefile.am Sat Jan 24 23:05:00 2009
@@ -9,6 +9,7 @@
Banshee.NowPlaying/FullscreenWindow.cs \
Banshee.NowPlaying/IFullscreenAdapter.cs \
Banshee.NowPlaying/IScreensaverManager.cs \
+ Banshee.NowPlaying/IVideoDisplay.cs \
Banshee.NowPlaying/NowPlayingContents.cs \
Banshee.NowPlaying/NowPlayingInterface.cs \
Banshee.NowPlaying/NowPlayingSource.cs \
@@ -25,3 +26,6 @@
include $(top_srcdir)/build/build.mk
+module_SCRIPTS += Banshee.NowPlaying.dll.config
+EXTRA_DIST += Banshee.NowPlaying.dll.config
+
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]