[chronojump/video-capture: 4/7] Add libcesarplayer to chronojump
- From: Andoni Morales Alastruey <amorales src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [chronojump/video-capture: 4/7] Add libcesarplayer to chronojump
- Date: Thu, 16 Dec 2010 00:30:28 +0000 (UTC)
commit e217946cf82ca56196583fea122730cbb44377d4
Author: Andoni Morales Alastruey <ylatuya gmail com>
Date: Wed Dec 15 23:50:06 2010 +0100
Add libcesarplayer to chronojump
libcesarplayer/Makefile.am | 14 +
libcesarplayer/src/Makefile.am | 50 +
libcesarplayer/src/bacon-resize.c | 338 ++
libcesarplayer/src/bacon-resize.h | 55 +
libcesarplayer/src/bacon-video-widget-gst-0.10.c | 5640 ++++++++++++++++++++++
libcesarplayer/src/bacon-video-widget.h | 416 ++
libcesarplayer/src/baconvideowidget-marshal.c | 247 +
libcesarplayer/src/baconvideowidget-marshal.h | 44 +
libcesarplayer/src/common.h | 106 +
libcesarplayer/src/gst-camera-capturer.c | 1783 +++++++
libcesarplayer/src/gst-camera-capturer.h | 103 +
libcesarplayer/src/gst-smart-video-scaler.c | 313 ++
libcesarplayer/src/gst-smart-video-scaler.h | 63 +
libcesarplayer/src/gst-video-editor.c | 1348 ++++++
libcesarplayer/src/gst-video-editor.h | 85 +
libcesarplayer/src/gstscreenshot.c | 203 +
libcesarplayer/src/gstscreenshot.h | 29 +
libcesarplayer/src/gstvideowidget.c | 1195 +++++
libcesarplayer/src/gstvideowidget.h | 125 +
libcesarplayer/src/macros.h | 39 +
libcesarplayer/src/main.c | 95 +
libcesarplayer/src/video-utils.c | 236 +
libcesarplayer/src/video-utils.h | 19 +
23 files changed, 12546 insertions(+), 0 deletions(-)
---
diff --git a/libcesarplayer/Makefile.am b/libcesarplayer/Makefile.am
new file mode 100644
index 0000000..675e822
--- /dev/null
+++ b/libcesarplayer/Makefile.am
@@ -0,0 +1,14 @@
+## Process this file with automake to produce Makefile.in
+## Created by Anjuta
+
+SUBDIRS = src
+
+
+# Copy all the spec files. Of cource, only one is actually used.
+dist-hook:
+ for specfile in *.spec; do \
+ if test -f $$specfile; then \
+ cp -p $$specfile $(distdir); \
+ fi \
+ done
+
diff --git a/libcesarplayer/src/Makefile.am b/libcesarplayer/src/Makefile.am
new file mode 100644
index 0000000..0bc96b0
--- /dev/null
+++ b/libcesarplayer/src/Makefile.am
@@ -0,0 +1,50 @@
+## Process this file with automake to produce Makefile.in
+
+
+AM_CPPFLAGS = \
+ -DPACKAGE_SRC_DIR=\""$(srcdir)"\" \
+ -DPACKAGE_DATA_DIR=\""$(datadir)"\" \
+ $(CESARPLAYER_CFLAGS)
+
+AM_CFLAGS =\
+ -Wall\
+ -g
+
+BVWMARSHALFILES = baconvideowidget-marshal.c baconvideowidget-marshal.h
+GLIB_GENMARSHAL=`pkg-config --variable=glib_genmarshal glib-2.0`
+BUILT_SOURCES = $(BVWMARSHALFILES)
+
+baconvideowidget-marshal.h: baconvideowidget-marshal.list
+ ( $(GLIB_GENMARSHAL) --prefix=baconvideowidget_marshal $(srcdir)/baconvideowidget-marshal.list --header > baconvideowidget-marshal.h )
+baconvideowidget-marshal.c: baconvideowidget-marshal.h
+ ( $(GLIB_GENMARSHAL) --prefix=baconvideowidget_marshal $(srcdir)/baconvideowidget-marshal.list --body --header > baconvideowidget-marshal.c )
+
+
+pkglib_LTLIBRARIES = \
+ libcesarplayer.la
+
+libcesarplayer_la_SOURCES = \
+ $(BVWMARSHALFILES) \
+ common.h\
+ bacon-video-widget.h\
+ bacon-video-widget-gst-0.10.c\
+ gstscreenshot.c \
+ gstscreenshot.h \
+ gst-camera-capturer.c\
+ gst-camera-capturer.h\
+ gst-video-editor.c\
+ gst-video-editor.h\
+ bacon-resize.c\
+ bacon-resize.h\
+ video-utils.c\
+ video-utils.h\
+ macros.h
+
+libcesarplayer_la_LDFLAGS = \
+ $(CESARPLAYER_LIBS)
+
+CLEANFILES = $(BUILT_SOURCES)
+
+EXTRA_DIST = \
+ baconvideowidget-marshal.list
+
diff --git a/libcesarplayer/src/bacon-resize.c b/libcesarplayer/src/bacon-resize.c
new file mode 100644
index 0000000..98feea2
--- /dev/null
+++ b/libcesarplayer/src/bacon-resize.c
@@ -0,0 +1,338 @@
+/* bacon-resize.c
+ * Copyright (C) 2003-2004, Bastien Nocera <hadess hadess net>
+ * All rights reserved.
+ *
+ * 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
+ * Library 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
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ */
+
+#include "bacon-resize.h"
+#include <glib.h>
+
+#ifdef HAVE_XVIDMODE
+#include <gdk/gdkx.h>
+#include <gdk/gdk.h>
+#include <X11/X.h>
+#include <X11/Xlib.h>
+#include <X11/Xproto.h>
+
+#include <X11/extensions/xf86vmode.h>
+#include <X11/extensions/Xrandr.h>
+#include <X11/extensions/Xrender.h>
+#endif
+
+static void bacon_resize_set_property (GObject * object,
+ guint property_id, const GValue * value, GParamSpec * pspec);
+static void bacon_resize_get_property (GObject * object,
+ guint property_id, GValue * value, GParamSpec * pspec);
+#ifdef HAVE_XVIDMODE
+static void bacon_resize_finalize (GObject * object);
+#endif /* HAVE_XVIDMODE */
+
+static void set_video_widget (BaconResize * resize, GtkWidget * video_widget);
+
+
+struct BaconResizePrivate
+{
+ gboolean have_xvidmode;
+ gboolean resized;
+ GtkWidget *video_widget;
+#ifdef HAVE_XVIDMODE
+ /* XRandR */
+ XRRScreenConfiguration *xr_screen_conf;
+ XRRScreenSize *xr_sizes;
+ Rotation xr_current_rotation;
+ SizeID xr_original_size;
+#endif
+};
+
+enum
+{
+ PROP_HAVE_XVIDMODE = 1,
+ PROP_VIDEO_WIDGET
+};
+
+G_DEFINE_TYPE (BaconResize, bacon_resize, G_TYPE_OBJECT)
+ static void bacon_resize_class_init (BaconResizeClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (BaconResizePrivate));
+
+ object_class->set_property = bacon_resize_set_property;
+ object_class->get_property = bacon_resize_get_property;
+#ifdef HAVE_XVIDMODE
+ object_class->finalize = bacon_resize_finalize;
+#endif /* HAVE_XVIDMODE */
+
+ g_object_class_install_property (object_class, PROP_HAVE_XVIDMODE,
+ g_param_spec_boolean ("have-xvidmode",
+ NULL, NULL, FALSE, G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_VIDEO_WIDGET,
+ g_param_spec_object ("video-widget",
+ "video-widget",
+ "The related video widget",
+ GTK_TYPE_WIDGET, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+bacon_resize_init (BaconResize * resize)
+{
+ resize->priv =
+ G_TYPE_INSTANCE_GET_PRIVATE (resize, BACON_TYPE_RESIZE,
+ BaconResizePrivate);
+
+ resize->priv->have_xvidmode = FALSE;
+ resize->priv->resized = FALSE;
+}
+
+BaconResize *
+bacon_resize_new (GtkWidget * video_widget)
+{
+ return
+ BACON_RESIZE (g_object_new
+ (BACON_TYPE_RESIZE, "video-widget", video_widget, NULL));
+}
+
+#ifdef HAVE_XVIDMODE
+static void
+bacon_resize_finalize (GObject * object)
+{
+ BaconResize *self = BACON_RESIZE (object);
+
+ g_signal_handlers_disconnect_by_func (self->priv->video_widget,
+ screen_changed_cb, self);
+
+ G_OBJECT_CLASS (bacon_resize_parent_class)->finalize (object);
+}
+#endif /* HAVE_XVIDMODE */
+
+static void
+bacon_resize_set_property (GObject * object,
+ guint property_id, const GValue * value, GParamSpec * pspec)
+{
+ switch (property_id) {
+ case PROP_VIDEO_WIDGET:
+ set_video_widget (BACON_RESIZE (object),
+ GTK_WIDGET (g_value_get_object (value)));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+bacon_resize_get_property (GObject * object,
+ guint property_id, GValue * value, GParamSpec * pspec)
+{
+ switch (property_id) {
+ case PROP_HAVE_XVIDMODE:
+ g_value_set_boolean (value, BACON_RESIZE (object)->priv->have_xvidmode);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+set_video_widget (BaconResize * resize, GtkWidget * video_widget)
+{
+#ifdef HAVE_XVIDMODE
+ GdkDisplay *display;
+ GdkScreen *screen;
+ int event_basep, error_basep;
+ XRRScreenConfiguration *xr_screen_conf;
+#endif
+ g_return_if_fail (GTK_WIDGET_REALIZED (video_widget));
+
+ resize->priv->video_widget = video_widget;
+
+#ifdef HAVE_XVIDMODE
+ display = gtk_widget_get_display (video_widget);
+ screen = gtk_widget_get_screen (video_widget);
+
+ g_signal_connect (G_OBJECT (video_widget),
+ "screen-changed", G_CALLBACK (screen_changed_cb), resize);
+
+ XLockDisplay (GDK_DISPLAY_XDISPLAY (display));
+
+ if (!XF86VidModeQueryExtension
+ (GDK_DISPLAY_XDISPLAY (display), &event_basep, &error_basep))
+ goto bail;
+
+ if (!XRRQueryExtension
+ (GDK_DISPLAY_XDISPLAY (display), &event_basep, &error_basep))
+ goto bail;
+
+ /* We don't use the output here, checking whether XRRGetScreenInfo works */
+ xr_screen_conf =
+ XRRGetScreenInfo (GDK_DISPLAY_XDISPLAY (display),
+ GDK_WINDOW_XWINDOW (gdk_screen_get_root_window (screen)));
+ if (xr_screen_conf == NULL)
+ goto bail;
+
+ XRRFreeScreenConfigInfo (xr_screen_conf);
+ XUnlockDisplay (GDK_DISPLAY_XDISPLAY (display));
+ resize->priv->have_xvidmode = TRUE;
+ return;
+
+bail:
+ XUnlockDisplay (GDK_DISPLAY_XDISPLAY (display));
+ resize->priv->have_xvidmode = FALSE;
+#endif /* HAVE_XVIDMODE */
+}
+
+
+void
+bacon_resize_resize (BaconResize * resize)
+{
+#ifdef HAVE_XVIDMODE
+ int width, height, i, xr_nsize, res, dotclock;
+ XF86VidModeModeLine modeline;
+ XRRScreenSize *xr_sizes;
+ gboolean found = FALSE;
+ GdkWindow *root;
+ GdkScreen *screen;
+ Display *Display;
+
+ g_return_if_fail (GTK_IS_WIDGET (resize->priv->video_widget));
+ g_return_if_fail (GTK_WIDGET_REALIZED (resize->priv->video_widget));
+
+ Display = GDK_DRAWABLE_XDISPLAY (resize->priv->video_widget->window);
+ if (Display == NULL)
+ return;
+
+ XLockDisplay (Display);
+
+ screen = gtk_widget_get_screen (resize->priv->video_widget);
+ root = gdk_screen_get_root_window (screen);
+
+ /* XF86VidModeGetModeLine just doesn't work nicely with multiple monitors */
+ if (gdk_screen_get_n_monitors (screen) > 1)
+ goto bail;
+
+ res =
+ XF86VidModeGetModeLine (Display, GDK_SCREEN_XNUMBER (screen), &dotclock,
+ &modeline);
+ if (!res)
+ goto bail;
+
+ /* Check if there's a viewport */
+ width = gdk_screen_get_width (screen);
+ height = gdk_screen_get_height (screen);
+
+ if (width <= modeline.hdisplay && height <= modeline.vdisplay)
+ goto bail;
+
+ gdk_error_trap_push ();
+
+ /* Find the XRandR mode that corresponds to the real size */
+ resize->priv->xr_screen_conf =
+ XRRGetScreenInfo (Display, GDK_WINDOW_XWINDOW (root));
+ xr_sizes = XRRConfigSizes (resize->priv->xr_screen_conf, &xr_nsize);
+ resize->priv->xr_original_size =
+ XRRConfigCurrentConfiguration (resize->priv->xr_screen_conf,
+ &(resize->priv->xr_current_rotation));
+ if (gdk_error_trap_pop ()) {
+ g_warning ("XRRConfigSizes or XRRConfigCurrentConfiguration failed");
+ goto bail;
+ }
+
+ for (i = 0; i < xr_nsize; i++) {
+ if (modeline.hdisplay == xr_sizes[i].width
+ && modeline.vdisplay == xr_sizes[i].height) {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found)
+ goto bail;
+
+ gdk_error_trap_push ();
+ XRRSetScreenConfig (Display,
+ resize->priv->xr_screen_conf,
+ GDK_WINDOW_XWINDOW (root),
+ (SizeID) i, resize->priv->xr_current_rotation, CurrentTime);
+ gdk_flush ();
+ if (gdk_error_trap_pop ())
+ g_warning ("XRRSetScreenConfig failed");
+ else
+ resize->priv->resized = TRUE;
+
+bail:
+ XUnlockDisplay (Display);
+#endif /* HAVE_XVIDMODE */
+}
+
+void
+bacon_resize_restore (BaconResize * resize)
+{
+#ifdef HAVE_XVIDMODE
+ int width, height, res, dotclock;
+ XF86VidModeModeLine modeline;
+ GdkWindow *root;
+ GdkScreen *screen;
+ Display *Display;
+
+ g_return_if_fail (GTK_IS_WIDGET (resize->priv->video_widget));
+ g_return_if_fail (GTK_WIDGET_REALIZED (resize->priv->video_widget));
+
+ /* We haven't called bacon_resize_resize before, or it exited
+ * as we didn't need a resize. */
+ if (resize->priv->xr_screen_conf == NULL)
+ return;
+
+ Display = GDK_DRAWABLE_XDISPLAY (resize->priv->video_widget->window);
+ if (Display == NULL)
+ return;
+
+ XLockDisplay (Display);
+
+ screen = gtk_widget_get_screen (resize->priv->video_widget);
+ root = gdk_screen_get_root_window (screen);
+ res =
+ XF86VidModeGetModeLine (Display, GDK_SCREEN_XNUMBER (screen), &dotclock,
+ &modeline);
+ if (!res)
+ goto bail;
+
+ /* Check if there's a viewport */
+ width = gdk_screen_get_width (screen);
+ height = gdk_screen_get_height (screen);
+
+ if (width > modeline.hdisplay && height > modeline.vdisplay)
+ goto bail;
+
+ gdk_error_trap_push ();
+ XRRSetScreenConfig (Display,
+ resize->priv->xr_screen_conf,
+ GDK_WINDOW_XWINDOW (root),
+ resize->priv->xr_original_size,
+ resize->priv->xr_current_rotation, CurrentTime);
+ gdk_flush ();
+ if (gdk_error_trap_pop ())
+ g_warning ("XRRSetScreenConfig failed");
+ else
+ resize->priv->resized = FALSE;
+
+ XRRFreeScreenConfigInfo (resize->priv->xr_screen_conf);
+ resize->priv->xr_screen_conf = NULL;
+
+bail:
+ XUnlockDisplay (Display);
+#endif
+}
diff --git a/libcesarplayer/src/bacon-resize.h b/libcesarplayer/src/bacon-resize.h
new file mode 100644
index 0000000..3e713f2
--- /dev/null
+++ b/libcesarplayer/src/bacon-resize.h
@@ -0,0 +1,55 @@
+/* bacon-resize.h
+ * Copyright (C) 2003-2004, Bastien Nocera <hadess hadess net>
+ * All rights reserved.
+ *
+ * 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
+ * Library 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
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ */
+
+#ifndef BACON_RESIZE_H
+#define BACON_RESIZE_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+#define BACON_TYPE_RESIZE (bacon_resize_get_type ())
+#define BACON_RESIZE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), BACON_TYPE_RESIZE, BaconResize))
+#define BACON_RESIZE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), BACON_TYPE_RESIZE, BaconResizeClass))
+#define BACON_IS_RESIZE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), BACON_TYPE_RESIZE))
+#define BACON_IS_RESIZE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), BACON_TYPE_RESIZE))
+typedef struct BaconResize BaconResize;
+typedef struct BaconResizeClass BaconResizeClass;
+typedef struct BaconResizePrivate BaconResizePrivate;
+
+struct BaconResize
+{
+ GObject parent;
+ BaconResizePrivate *priv;
+};
+
+struct BaconResizeClass
+{
+ GObjectClass parent_class;
+};
+
+GType bacon_resize_get_type (void);
+BaconResize *bacon_resize_new (GtkWidget * video_widget);
+void bacon_resize_resize (BaconResize * resize);
+void bacon_resize_restore (BaconResize * resize);
+
+G_END_DECLS
+#endif /* BACON_RESIZE_H */
diff --git a/libcesarplayer/src/bacon-video-widget-gst-0.10.c b/libcesarplayer/src/bacon-video-widget-gst-0.10.c
new file mode 100644
index 0000000..199ec0c
--- /dev/null
+++ b/libcesarplayer/src/bacon-video-widget-gst-0.10.c
@@ -0,0 +1,5640 @@
+/*
+ * Copyright (C) 2003-2007 the GStreamer project
+ * Julien Moutte <julien moutte net>
+ * Ronald Bultje <rbultje ronald bitfreak net>
+ * Copyright (C) 2005-2008 Tim-Philipp Müller <tim centricular net>
+ * Copyright (C) 2009 Sebastian Dröge <sebastian droege collabora co uk>
+ * Copyright (C) 2009 Andoni Morales Alastruey <ylatuya gmail com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Totem project hereby grant permission for non-gpl compatible GStreamer
+ * plugins to be used and distributed together with GStreamer and Totem. This
+ * permission is above and beyond the permissions granted by the GPL license
+ * Totem is covered by.
+ *
+ */
+
+
+
+
+
+#include <gst/gst.h>
+
+/* GStreamer Interfaces */
+#include <gst/interfaces/xoverlay.h>
+#include <gst/interfaces/navigation.h>
+#include <gst/interfaces/colorbalance.h>
+/* for detecting sources of errors */
+#include <gst/video/gstvideosink.h>
+#include <gst/video/video.h>
+#include <gst/audio/gstbaseaudiosink.h>
+/* for pretty multichannel strings */
+#include <gst/audio/multichannel.h>
+
+
+/* for missing decoder/demuxer detection */
+#include <gst/pbutils/pbutils.h>
+
+/* for the cover metadata info */
+#include <gst/tag/tag.h>
+
+
+/* system */
+#include <time.h>
+#include <string.h>
+#include <stdio.h>
+#include <math.h>
+
+/* gtk+/gnome */
+#ifdef WIN32
+#include <gdk/gdkwin32.h>
+#define DEFAULT_VIDEO_SINK "autovideosink"
+#else
+#include <gdk/gdkx.h>
+#define DEFAULT_VIDEO_SINK "gconfvideosink"
+#endif
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+
+
+//#include <gconf/gconf-client.h>
+
+#include "bacon-video-widget.h"
+#include "baconvideowidget-marshal.h"
+#include "common.h"
+#include "gstscreenshot.h"
+#include "bacon-resize.h"
+#include "video-utils.h"
+
+#define DEFAULT_HEIGHT 420
+#define DEFAULT_WIDTH 315
+
+#define is_error(e, d, c) \
+ (e->domain == GST_##d##_ERROR && \
+ e->code == GST_##d##_ERROR_##c)
+
+/* Signals */
+enum
+{
+ SIGNAL_ERROR,
+ SIGNAL_EOS,
+ SIGNAL_SEGMENT_DONE,
+ SIGNAL_REDIRECT,
+ SIGNAL_TITLE_CHANGE,
+ SIGNAL_CHANNELS_CHANGE,
+ SIGNAL_TICK,
+ SIGNAL_GOT_METADATA,
+ SIGNAL_BUFFERING,
+ SIGNAL_MISSING_PLUGINS,
+ SIGNAL_STATE_CHANGE,
+ SIGNAL_GOT_DURATION,
+ SIGNAL_READY_TO_SEEK,
+ LAST_SIGNAL
+};
+
+/* Properties */
+enum
+{
+ PROP_0,
+ PROP_LOGO_MODE,
+ PROP_EXPAND_LOGO,
+ PROP_POSITION,
+ PROP_CURRENT_TIME,
+ PROP_STREAM_LENGTH,
+ PROP_PLAYING,
+ PROP_SEEKABLE,
+ PROP_SHOW_CURSOR,
+ PROP_MEDIADEV,
+ PROP_VOLUME
+};
+
+/* GstPlayFlags flags from playbin2 */
+typedef enum
+{
+ GST_PLAY_FLAGS_VIDEO = 0x01,
+ GST_PLAY_FLAGS_AUDIO = 0x02,
+ GST_PLAY_FLAGS_TEXT = 0x04,
+ GST_PLAY_FLAGS_VIS = 0x08,
+ GST_PLAY_FLAGS_SOFT_VOLUME = 0x10,
+ GST_PLAY_FLAGS_NATIVE_AUDIO = 0x20,
+ GST_PLAY_FLAGS_NATIVE_VIDEO = 0x40
+} GstPlayFlags;
+
+
+struct BaconVideoWidgetPrivate
+{
+ BvwAspectRatio ratio_type;
+
+ char *mrl;
+
+ GstElement *play;
+ GstXOverlay *xoverlay; /* protect with lock */
+ GstColorBalance *balance; /* protect with lock */
+ GstNavigation *navigation; /* protect with lock */
+ guint interface_update_id; /* protect with lock */
+ GMutex *lock;
+
+ guint update_id;
+
+ GdkPixbuf *logo_pixbuf;
+ GdkPixbuf *drawing_pixbuf;
+
+ gboolean media_has_video;
+ gboolean media_has_audio;
+ gint seekable; /* -1 = don't know, FALSE = no */
+ gint64 stream_length;
+ gint64 current_time_nanos;
+ gint64 current_time;
+ gfloat current_position;
+ gboolean is_live;
+
+ GstTagList *tagcache;
+ GstTagList *audiotags;
+ GstTagList *videotags;
+
+ gboolean got_redirect;
+
+ GdkWindow *video_window;
+ GdkCursor *cursor;
+
+
+ /* Other stuff */
+ gboolean logo_mode;
+ gboolean drawing_mode;
+ gboolean expand_logo;
+ gboolean cursor_shown;
+ gboolean fullscreen_mode;
+ gboolean auto_resize;
+ gboolean uses_fakesink;
+
+ gint video_width; /* Movie width */
+ gint video_height; /* Movie height */
+ gboolean window_resized; /* Whether the window has already been resized
+ for this media */
+ const GValue *movie_par; /* Movie pixel aspect ratio */
+ gint video_width_pixels; /* Scaled movie width */
+ gint video_height_pixels; /* Scaled movie height */
+ gint video_fps_n;
+ gint video_fps_d;
+
+ gdouble zoom;
+
+ GstElement *audio_capsfilter;
+
+ BvwAudioOutType speakersetup;
+ gint connection_speed;
+
+ gchar *media_device;
+
+
+ GstMessageType ignore_messages_mask;
+
+ GstBus *bus;
+ gulong sig_bus_sync;
+ gulong sig_bus_async;
+
+ BvwUseType use_type;
+
+ gint eos_id;
+
+ /* state we want to be in, as opposed to actual pipeline state
+ * which may change asynchronously or during buffering */
+ GstState target_state;
+ gboolean buffering;
+
+ /* for easy codec installation */
+ GList *missing_plugins; /* GList of GstMessages */
+ gboolean plugin_install_in_progress;
+
+ /* Bacon resize */
+ BaconResize *bacon_resize;
+};
+
+static void bacon_video_widget_set_property (GObject * object,
+ guint property_id, const GValue * value, GParamSpec * pspec);
+static void bacon_video_widget_get_property (GObject * object,
+ guint property_id, GValue * value, GParamSpec * pspec);
+static void bvw_update_interface_implementations (BaconVideoWidget * bvw);
+
+static void bacon_video_widget_finalize (GObject * object);
+static void bvw_update_interface_implementations (BaconVideoWidget * bvw);
+static gboolean bacon_video_widget_configure_event (GtkWidget * widget,
+ GdkEventConfigure * event, BaconVideoWidget * bvw);
+static void size_changed_cb (GdkScreen * screen, BaconVideoWidget * bvw);
+static void bvw_process_pending_tag_messages (BaconVideoWidget * bvw);
+static void bvw_stop_play_pipeline (BaconVideoWidget * bvw);
+static GError *bvw_error_from_gst_error (BaconVideoWidget * bvw,
+ GstMessage * m);
+
+
+
+static GtkWidgetClass *parent_class = NULL;
+
+static GThread *gui_thread;
+
+static int bvw_signals[LAST_SIGNAL] = { 0 };
+
+GST_DEBUG_CATEGORY (_totem_gst_debug_cat);
+#define GST_CAT_DEFAULT _totem_gst_debug_cat
+
+
+typedef gchar *(*MsgToStrFunc) (GstMessage * msg);
+
+static gchar **
+bvw_get_missing_plugins_foo (const GList * missing_plugins, MsgToStrFunc func)
+{
+ GPtrArray *arr = g_ptr_array_new ();
+
+ while (missing_plugins != NULL) {
+ g_ptr_array_add (arr, func (GST_MESSAGE (missing_plugins->data)));
+ missing_plugins = missing_plugins->next;
+ }
+ g_ptr_array_add (arr, NULL);
+ return (gchar **) g_ptr_array_free (arr, FALSE);
+}
+
+static gchar **
+bvw_get_missing_plugins_details (const GList * missing_plugins)
+{
+ return bvw_get_missing_plugins_foo (missing_plugins,
+ gst_missing_plugin_message_get_installer_detail);
+}
+
+static gchar **
+bvw_get_missing_plugins_descriptions (const GList * missing_plugins)
+{
+ return bvw_get_missing_plugins_foo (missing_plugins,
+ gst_missing_plugin_message_get_description);
+}
+
+static void
+bvw_clear_missing_plugins_messages (BaconVideoWidget * bvw)
+{
+ g_list_foreach (bvw->priv->missing_plugins,
+ (GFunc) gst_mini_object_unref, NULL);
+ g_list_free (bvw->priv->missing_plugins);
+ bvw->priv->missing_plugins = NULL;
+}
+
+static void
+bvw_check_if_video_decoder_is_missing (BaconVideoWidget * bvw)
+{
+ GList *l;
+
+ if (bvw->priv->media_has_video || bvw->priv->missing_plugins == NULL)
+ return;
+
+ for (l = bvw->priv->missing_plugins; l != NULL; l = l->next) {
+ GstMessage *msg = GST_MESSAGE (l->data);
+ gchar *d, *f;
+
+ if ((d = gst_missing_plugin_message_get_installer_detail (msg))) {
+ if ((f = strstr (d, "|decoder-")) && strstr (f, "video")) {
+ GError *err;
+
+ /* create a fake GStreamer error so we get a nice warning message */
+ err = g_error_new (GST_CORE_ERROR, GST_CORE_ERROR_MISSING_PLUGIN, "x");
+ msg = gst_message_new_error (GST_OBJECT (bvw->priv->play), err, NULL);
+ g_error_free (err);
+ err = bvw_error_from_gst_error (bvw, msg);
+ gst_message_unref (msg);
+ g_signal_emit (bvw, bvw_signals[SIGNAL_ERROR], 0, err->message);
+ g_error_free (err);
+ g_free (d);
+ break;
+ }
+ g_free (d);
+ }
+ }
+}
+
+static void
+bvw_error_msg (BaconVideoWidget * bvw, GstMessage * msg)
+{
+ GError *err = NULL;
+ gchar *dbg = NULL;
+
+ GST_DEBUG_BIN_TO_DOT_FILE (GST_BIN_CAST (bvw->priv->play),
+ GST_DEBUG_GRAPH_SHOW_ALL ^
+ GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS, "totem-error");
+
+ gst_message_parse_error (msg, &err, &dbg);
+ if (err) {
+ GST_ERROR ("message = %s", GST_STR_NULL (err->message));
+ GST_ERROR ("domain = %d (%s)", err->domain,
+ GST_STR_NULL (g_quark_to_string (err->domain)));
+ GST_ERROR ("code = %d", err->code);
+ GST_ERROR ("debug = %s", GST_STR_NULL (dbg));
+ GST_ERROR ("source = %" GST_PTR_FORMAT, msg->src);
+ GST_ERROR ("uri = %s", GST_STR_NULL (bvw->priv->mrl));
+
+ g_message ("Error: %s\n%s\n", GST_STR_NULL (err->message),
+ GST_STR_NULL (dbg));
+
+ g_error_free (err);
+ }
+ g_free (dbg);
+}
+
+static void
+get_media_size (BaconVideoWidget * bvw, gint * width, gint * height)
+{
+ if (bvw->priv->logo_mode) {
+ if (bvw->priv->logo_pixbuf) {
+ *width = gdk_pixbuf_get_width (bvw->priv->logo_pixbuf);
+ *height = gdk_pixbuf_get_height (bvw->priv->logo_pixbuf);
+ } else {
+ *width = 0;
+ *height = 0;
+ }
+ } else {
+ if (bvw->priv->media_has_video) {
+ GValue *disp_par = NULL;
+ guint movie_par_n, movie_par_d, disp_par_n, disp_par_d, num, den;
+
+ /* Create and init the fraction value */
+ disp_par = g_new0 (GValue, 1);
+ g_value_init (disp_par, GST_TYPE_FRACTION);
+
+ /* Square pixel is our default */
+ gst_value_set_fraction (disp_par, 1, 1);
+
+ /* Now try getting display's pixel aspect ratio */
+ if (bvw->priv->xoverlay) {
+ GObjectClass *klass;
+ GParamSpec *pspec;
+
+ klass = G_OBJECT_GET_CLASS (bvw->priv->xoverlay);
+ pspec = g_object_class_find_property (klass, "pixel-aspect-ratio");
+
+ if (pspec != NULL) {
+ GValue disp_par_prop = { 0, };
+
+ g_value_init (&disp_par_prop, pspec->value_type);
+ g_object_get_property (G_OBJECT (bvw->priv->xoverlay),
+ "pixel-aspect-ratio", &disp_par_prop);
+
+ if (!g_value_transform (&disp_par_prop, disp_par)) {
+ GST_WARNING ("Transform failed, assuming pixel-aspect-ratio = 1/1");
+ gst_value_set_fraction (disp_par, 1, 1);
+ }
+
+ g_value_unset (&disp_par_prop);
+ }
+ }
+
+ disp_par_n = gst_value_get_fraction_numerator (disp_par);
+ disp_par_d = gst_value_get_fraction_denominator (disp_par);
+
+ GST_DEBUG ("display PAR is %d/%d", disp_par_n, disp_par_d);
+
+ /* If movie pixel aspect ratio is enforced, use that */
+ if (bvw->priv->ratio_type != BVW_RATIO_AUTO) {
+ switch (bvw->priv->ratio_type) {
+ case BVW_RATIO_SQUARE:
+ movie_par_n = 1;
+ movie_par_d = 1;
+ break;
+ case BVW_RATIO_FOURBYTHREE:
+ movie_par_n = 4 * bvw->priv->video_height;
+ movie_par_d = 3 * bvw->priv->video_width;
+ break;
+ case BVW_RATIO_ANAMORPHIC:
+ movie_par_n = 16 * bvw->priv->video_height;
+ movie_par_d = 9 * bvw->priv->video_width;
+ break;
+ case BVW_RATIO_DVB:
+ movie_par_n = 20 * bvw->priv->video_height;
+ movie_par_d = 9 * bvw->priv->video_width;
+ break;
+ /* handle these to avoid compiler warnings */
+ case BVW_RATIO_AUTO:
+ default:
+ movie_par_n = 0;
+ movie_par_d = 0;
+ g_assert_not_reached ();
+ }
+ } else {
+ /* Use the movie pixel aspect ratio if any */
+ if (bvw->priv->movie_par) {
+ movie_par_n = gst_value_get_fraction_numerator (bvw->priv->movie_par);
+ movie_par_d =
+ gst_value_get_fraction_denominator (bvw->priv->movie_par);
+ } else {
+ /* Square pixels */
+ movie_par_n = 1;
+ movie_par_d = 1;
+ }
+ }
+
+ GST_DEBUG ("movie PAR is %d/%d", movie_par_n, movie_par_d);
+
+ if (bvw->priv->video_width == 0 || bvw->priv->video_height == 0) {
+ GST_DEBUG ("width and/or height 0, assuming 1/1 ratio");
+ num = 1;
+ den = 1;
+ } else if (!gst_video_calculate_display_ratio (&num, &den,
+ bvw->priv->video_width,
+ bvw->priv->video_height,
+ movie_par_n, movie_par_d, disp_par_n, disp_par_d)) {
+ GST_WARNING ("overflow calculating display aspect ratio!");
+ num = 1; /* FIXME: what values to use here? */
+ den = 1;
+ }
+
+ GST_DEBUG ("calculated scaling ratio %d/%d for video %dx%d", num,
+ den, bvw->priv->video_width, bvw->priv->video_height);
+
+ /* now find a width x height that respects this display ratio.
+ * prefer those that have one of w/h the same as the incoming video
+ * using wd / hd = num / den */
+
+ /* start with same height, because of interlaced video */
+ /* check hd / den is an integer scale factor, and scale wd with the PAR */
+ if (bvw->priv->video_height % den == 0) {
+ GST_DEBUG ("keeping video height");
+ bvw->priv->video_width_pixels =
+ (guint) gst_util_uint64_scale (bvw->priv->video_height, num, den);
+ bvw->priv->video_height_pixels = bvw->priv->video_height;
+ } else if (bvw->priv->video_width % num == 0) {
+ GST_DEBUG ("keeping video width");
+ bvw->priv->video_width_pixels = bvw->priv->video_width;
+ bvw->priv->video_height_pixels =
+ (guint) gst_util_uint64_scale (bvw->priv->video_width, den, num);
+ } else {
+ GST_DEBUG ("approximating while keeping video height");
+ bvw->priv->video_width_pixels =
+ (guint) gst_util_uint64_scale (bvw->priv->video_height, num, den);
+ bvw->priv->video_height_pixels = bvw->priv->video_height;
+ }
+ GST_DEBUG ("scaling to %dx%d", bvw->priv->video_width_pixels,
+ bvw->priv->video_height_pixels);
+
+ *width = bvw->priv->video_width_pixels;
+ *height = bvw->priv->video_height_pixels;
+
+ /* Free the PAR fraction */
+ g_value_unset (disp_par);
+ g_free (disp_par);
+ } else {
+ *width = 0;
+ *height = 0;
+ }
+ }
+}
+
+static void
+bacon_video_widget_realize (GtkWidget * widget)
+{
+ BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (widget);
+ GdkWindowAttr attributes;
+ gint attributes_mask, w, h;
+ GdkColor colour;
+ GdkWindow *window;
+ GdkEventMask event_mask;
+
+ event_mask = gtk_widget_get_events (widget)
+ | GDK_POINTER_MOTION_MASK | GDK_KEY_PRESS_MASK;
+ gtk_widget_set_events (widget, event_mask);
+
+ GTK_WIDGET_CLASS (parent_class)->realize (widget);
+
+ window = gtk_widget_get_window (widget);
+
+ /* Creating our video window */
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.x = 0;
+ attributes.y = 0;
+ attributes.width = widget->allocation.width;
+ attributes.height = widget->allocation.height;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.event_mask = gtk_widget_get_events (widget);
+ attributes.event_mask |= GDK_EXPOSURE_MASK |
+ GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_KEY_PRESS_MASK;
+ attributes_mask = GDK_WA_X | GDK_WA_Y;
+
+ bvw->priv->video_window = gdk_window_new (window,
+ &attributes, attributes_mask);
+ gdk_window_set_user_data (bvw->priv->video_window, widget);
+
+ gdk_color_parse ("black", &colour);
+ gdk_colormap_alloc_color (gtk_widget_get_colormap (widget),
+ &colour, TRUE, TRUE);
+ gdk_window_set_background (window, &colour);
+ gtk_widget_set_style (widget,
+ gtk_style_attach (gtk_widget_get_style (widget), window));
+
+ GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+
+ /* Connect to configure event on the top level window */
+ g_signal_connect (G_OBJECT (gtk_widget_get_toplevel (widget)),
+ "configure-event", G_CALLBACK (bacon_video_widget_configure_event), bvw);
+
+ /* get screen size changes */
+ g_signal_connect (G_OBJECT (gtk_widget_get_screen (widget)),
+ "size-changed", G_CALLBACK (size_changed_cb), bvw);
+
+ /* nice hack to show the logo fullsize, while still being resizable */
+ get_media_size (BACON_VIDEO_WIDGET (widget), &w, &h);
+
+ /*ANDONI
+ totem_widget_set_preferred_size (widget, w, h); */
+
+ bvw->priv->bacon_resize = bacon_resize_new (widget);
+}
+
+static void
+bacon_video_widget_unrealize (GtkWidget * widget)
+{
+ BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (widget);
+
+ g_object_unref (bvw->priv->bacon_resize);
+ gdk_window_set_user_data (bvw->priv->video_window, NULL);
+ gdk_window_destroy (bvw->priv->video_window);
+ bvw->priv->video_window = NULL;
+
+ GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
+}
+
+static void
+bacon_video_widget_show (GtkWidget * widget)
+{
+ BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (widget);
+ GdkWindow *window;
+
+ window = gtk_widget_get_window (widget);
+ if (window)
+ gdk_window_show (window);
+ if (bvw->priv->video_window)
+ gdk_window_show (bvw->priv->video_window);
+
+ if (GTK_WIDGET_CLASS (parent_class)->show)
+ GTK_WIDGET_CLASS (parent_class)->show (widget);
+}
+
+static void
+bacon_video_widget_hide (GtkWidget * widget)
+{
+ BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (widget);
+ GdkWindow *window;
+
+ window = gtk_widget_get_window (widget);
+ if (window)
+ gdk_window_hide (window);
+ if (bvw->priv->video_window)
+ gdk_window_hide (bvw->priv->video_window);
+
+ if (GTK_WIDGET_CLASS (parent_class)->hide)
+ GTK_WIDGET_CLASS (parent_class)->hide (widget);
+}
+
+static gboolean
+bacon_video_widget_configure_event (GtkWidget * widget,
+ GdkEventConfigure * event, BaconVideoWidget * bvw)
+{
+ GstXOverlay *xoverlay = NULL;
+
+ g_return_val_if_fail (bvw != NULL, FALSE);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
+
+ xoverlay = bvw->priv->xoverlay;
+
+ if (xoverlay != NULL && GST_IS_X_OVERLAY (xoverlay)) {
+ gst_x_overlay_expose (xoverlay);
+ }
+
+ return FALSE;
+}
+
+static void
+size_changed_cb (GdkScreen * screen, BaconVideoWidget * bvw)
+{
+ /* FIXME:Used for visualization */
+ //setup_vis (bvw);
+}
+
+static gboolean
+bacon_video_widget_expose_event (GtkWidget * widget, GdkEventExpose * event)
+{
+ BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (widget);
+ GstXOverlay *xoverlay;
+ gboolean draw_logo;
+ GdkWindow *win;
+
+ if (event && event->count > 0)
+ return TRUE;
+
+ g_mutex_lock (bvw->priv->lock);
+ xoverlay = bvw->priv->xoverlay;
+ if (xoverlay == NULL) {
+ bvw_update_interface_implementations (bvw);
+ xoverlay = bvw->priv->xoverlay;
+ }
+ if (xoverlay != NULL)
+ gst_object_ref (xoverlay);
+
+ g_mutex_unlock (bvw->priv->lock);
+
+
+ if (xoverlay != NULL && GST_IS_X_OVERLAY (xoverlay)) {
+#ifdef WIN32
+ gst_x_overlay_set_xwindow_id (bvw->priv->xoverlay,
+ GDK_WINDOW_HWND (bvw->priv->video_window));
+#else
+ gst_x_overlay_set_xwindow_id (bvw->priv->xoverlay,
+ GDK_WINDOW_XID (bvw->priv->video_window));
+#endif
+ }
+
+ /* Start with a nice black canvas */
+ win = gtk_widget_get_window (widget);
+ gdk_draw_rectangle (win, gtk_widget_get_style (widget)->black_gc, TRUE, 0,
+ 0, widget->allocation.width, widget->allocation.height);
+
+ /* if there's only audio and no visualisation, draw the logo as well */
+ draw_logo = bvw->priv->media_has_audio && !bvw->priv->media_has_video;
+
+ if (bvw->priv->logo_mode || draw_logo) {
+ if (bvw->priv->logo_pixbuf != NULL) {
+ GdkPixbuf *frame;
+ GdkPixbuf *drawing;
+ guchar *pixels;
+ int rowstride;
+ gint width, height, alloc_width, alloc_height, logo_x, logo_y;
+ gfloat ratio;
+
+ /* Checking if allocated space is smaller than our logo */
+
+
+ width = gdk_pixbuf_get_width (bvw->priv->logo_pixbuf);
+ height = gdk_pixbuf_get_height (bvw->priv->logo_pixbuf);
+ alloc_width = widget->allocation.width;
+ alloc_height = widget->allocation.height;
+
+ if ((gfloat) alloc_width / width > (gfloat) alloc_height / height) {
+ ratio = (gfloat) alloc_height / height;
+ } else {
+ ratio = (gfloat) alloc_width / width;
+ }
+
+ width *= ratio;
+ height *= ratio;
+
+ logo_x = (alloc_width / 2) - (width / 2);
+ logo_y = (alloc_height / 2) - (height / 2);
+
+
+ /* Drawing our frame */
+
+ if (bvw->priv->expand_logo && !bvw->priv->drawing_mode) {
+ /* Scaling to available space */
+
+ frame = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ FALSE, 8, widget->allocation.width, widget->allocation.height);
+
+ gdk_pixbuf_composite (bvw->priv->logo_pixbuf,
+ frame,
+ 0, 0,
+ alloc_width, alloc_height,
+ logo_x, logo_y, ratio, ratio, GDK_INTERP_BILINEAR, 255);
+
+ rowstride = gdk_pixbuf_get_rowstride (frame);
+
+ pixels = gdk_pixbuf_get_pixels (frame) +
+ rowstride * event->area.y + event->area.x * 3;
+
+ gdk_draw_rgb_image_dithalign (widget->window,
+ widget->style->black_gc,
+ event->area.x, event->area.y,
+ event->area.width,
+ event->area.height,
+ GDK_RGB_DITHER_NORMAL, pixels,
+ rowstride, event->area.x, event->area.y);
+
+ g_object_unref (frame);
+ } else {
+ gdk_window_clear_area (win,
+ 0, 0, widget->allocation.width, widget->allocation.height);
+
+ if (width <= 1 || height <= 1) {
+ if (xoverlay != NULL)
+ gst_object_unref (xoverlay);
+ gdk_window_end_paint (win);
+ return TRUE;
+ }
+
+ frame = gdk_pixbuf_scale_simple (bvw->priv->logo_pixbuf,
+ width, height, GDK_INTERP_BILINEAR);
+ gdk_draw_pixbuf (win, gtk_widget_get_style (widget)->fg_gc[0],
+ frame, 0, 0, logo_x, logo_y, width, height,
+ GDK_RGB_DITHER_NONE, 0, 0);
+
+ if (bvw->priv->drawing_mode && bvw->priv->drawing_pixbuf != NULL) {
+ drawing =
+ gdk_pixbuf_scale_simple (bvw->priv->drawing_pixbuf, width,
+ height, GDK_INTERP_BILINEAR);
+ gdk_draw_pixbuf (win,
+ gtk_widget_get_style (widget)->fg_gc[0],
+ drawing, 0, 0, logo_x, logo_y, width,
+ height, GDK_RGB_DITHER_NONE, 0, 0);
+ g_object_unref (drawing);
+ }
+
+ g_object_unref (frame);
+ }
+ } else if (win) {
+ /* No pixbuf, just draw a black background then */
+ gdk_window_clear_area (win,
+ 0, 0, widget->allocation.width, widget->allocation.height);
+ }
+ } else {
+ /* no logo, pass the expose to gst */
+ if (xoverlay != NULL && GST_IS_X_OVERLAY (xoverlay))
+ gst_x_overlay_expose (xoverlay);
+ else {
+ /* No xoverlay to expose yet */
+ gdk_window_clear_area (win,
+ 0, 0, widget->allocation.width, widget->allocation.height);
+ }
+ }
+ if (xoverlay != NULL)
+ gst_object_unref (xoverlay);
+
+ return TRUE;
+}
+
+static GstNavigation *
+bvw_get_navigation_iface (BaconVideoWidget * bvw)
+{
+ GstNavigation *nav = NULL;
+ g_mutex_lock (bvw->priv->lock);
+ if (bvw->priv->navigation == NULL)
+ bvw_update_interface_implementations (bvw);
+ if (bvw->priv->navigation)
+ nav = gst_object_ref (GST_OBJECT (bvw->priv->navigation));
+ g_mutex_unlock (bvw->priv->lock);
+
+ return nav;
+}
+
+/* need to use gstnavigation interface for these vmethods, to allow for the sink
+ to map screen coordinates to video coordinates in the presence of e.g.
+ hardware scaling */
+
+static gboolean
+bacon_video_widget_motion_notify (GtkWidget * widget, GdkEventMotion * event)
+{
+ gboolean res = FALSE;
+ BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (widget);
+
+ g_return_val_if_fail (bvw->priv->play != NULL, FALSE);
+
+ if (!bvw->priv->logo_mode) {
+ GstNavigation *nav = bvw_get_navigation_iface (bvw);
+ if (nav) {
+ gst_navigation_send_mouse_event (nav, "mouse-move", 0, event->x,
+ event->y);
+ gst_object_unref (GST_OBJECT (nav));
+ }
+ }
+
+ if (GTK_WIDGET_CLASS (parent_class)->motion_notify_event)
+ res |= GTK_WIDGET_CLASS (parent_class)->motion_notify_event (widget, event);
+
+ return res;
+}
+
+static gboolean
+bacon_video_widget_button_press (GtkWidget * widget, GdkEventButton * event)
+{
+ gboolean res = FALSE;
+ BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (widget);
+
+ g_return_val_if_fail (bvw->priv->play != NULL, FALSE);
+
+ if (!bvw->priv->logo_mode) {
+ GstNavigation *nav = bvw_get_navigation_iface (bvw);
+ if (nav) {
+ gst_navigation_send_mouse_event (nav,
+ "mouse-button-press", event->button, event->x, event->y);
+ gst_object_unref (GST_OBJECT (nav));
+
+ /* FIXME need to check whether the backend will have handled
+ * the button press
+ res = TRUE; */
+ }
+ }
+
+ if (GTK_WIDGET_CLASS (parent_class)->button_press_event)
+ res |= GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, event);
+
+ return res;
+}
+
+static gboolean
+bacon_video_widget_button_release (GtkWidget * widget, GdkEventButton * event)
+{
+ gboolean res = FALSE;
+ BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (widget);
+
+ g_return_val_if_fail (bvw->priv->play != NULL, FALSE);
+
+ if (!bvw->priv->logo_mode) {
+ GstNavigation *nav = bvw_get_navigation_iface (bvw);
+ if (nav) {
+ gst_navigation_send_mouse_event (nav,
+ "mouse-button-release", event->button, event->x, event->y);
+ gst_object_unref (GST_OBJECT (nav));
+
+ res = TRUE;
+ }
+ }
+
+ if (GTK_WIDGET_CLASS (parent_class)->button_release_event)
+ res |=
+ GTK_WIDGET_CLASS (parent_class)->button_release_event (widget, event);
+
+ return res;
+}
+
+static void
+bacon_video_widget_size_request (GtkWidget * widget,
+ GtkRequisition * requisition)
+{
+ requisition->width = 240;
+ requisition->height = 180;
+}
+
+static void
+resize_video_window (BaconVideoWidget * bvw)
+{
+ const GtkAllocation *allocation;
+ gfloat width, height, ratio, x, y;
+ int w, h;
+
+ g_return_if_fail (bvw != NULL);
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+
+ allocation = >K_WIDGET (bvw)->allocation;
+
+ get_media_size (bvw, &w, &h);
+ if (!w || !h) {
+ w = allocation->width;
+ h = allocation->height;
+ }
+ width = w;
+ height = h;
+
+ /* calculate ratio for fitting video into the available space */
+ if ((gfloat) allocation->width / width > (gfloat) allocation->height / height) {
+ ratio = (gfloat) allocation->height / height;
+ } else {
+ ratio = (gfloat) allocation->width / width;
+ }
+
+ /* apply zoom factor */
+ ratio = ratio * bvw->priv->zoom;
+
+ width *= ratio;
+ height *= ratio;
+ x = (allocation->width - width) / 2;
+ y = (allocation->height - height) / 2;
+
+ gdk_window_move_resize (bvw->priv->video_window, x, y, width, height);
+ gtk_widget_queue_draw (GTK_WIDGET (bvw));
+}
+
+static void
+bacon_video_widget_size_allocate (GtkWidget * widget,
+ GtkAllocation * allocation)
+{
+ BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (widget);
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (widget));
+
+ widget->allocation = *allocation;
+
+ if (GTK_WIDGET_REALIZED (widget)) {
+
+ gdk_window_move_resize (gtk_widget_get_window (widget),
+ allocation->x, allocation->y, allocation->width, allocation->height);
+
+ resize_video_window (bvw);
+ }
+}
+
+
+static gboolean
+bvw_boolean_handled_accumulator (GSignalInvocationHint * ihint,
+ GValue * return_accu, const GValue * handler_return, gpointer foobar)
+{
+ gboolean continue_emission;
+ gboolean signal_handled;
+
+ signal_handled = g_value_get_boolean (handler_return);
+ g_value_set_boolean (return_accu, signal_handled);
+ continue_emission = !signal_handled;
+
+ return continue_emission;
+}
+
+static void
+bacon_video_widget_class_init (BaconVideoWidgetClass * klass)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ object_class = (GObjectClass *) klass;
+ widget_class = (GtkWidgetClass *) klass;
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ g_type_class_add_private (object_class, sizeof (BaconVideoWidgetPrivate));
+
+ /* GtkWidget */
+ widget_class->size_request = bacon_video_widget_size_request;
+ widget_class->size_allocate = bacon_video_widget_size_allocate;
+ widget_class->realize = bacon_video_widget_realize;
+ widget_class->unrealize = bacon_video_widget_unrealize;
+ widget_class->show = bacon_video_widget_show;
+ widget_class->hide = bacon_video_widget_hide;
+ widget_class->expose_event = bacon_video_widget_expose_event;
+ widget_class->motion_notify_event = bacon_video_widget_motion_notify;
+ widget_class->button_press_event = bacon_video_widget_button_press;
+ widget_class->button_release_event = bacon_video_widget_button_release;
+
+
+ /* GObject */
+ object_class->set_property = bacon_video_widget_set_property;
+ object_class->get_property = bacon_video_widget_get_property;
+ object_class->finalize = bacon_video_widget_finalize;
+
+ /* Properties */
+ g_object_class_install_property (object_class, PROP_LOGO_MODE,
+ g_param_spec_boolean ("logo_mode", NULL, NULL, FALSE, G_PARAM_READWRITE));
+ g_object_class_install_property (object_class, PROP_EXPAND_LOGO,
+ g_param_spec_boolean ("expand_logo", NULL,
+ NULL, TRUE, G_PARAM_READWRITE));
+ g_object_class_install_property (object_class, PROP_POSITION,
+ g_param_spec_int ("position", NULL, NULL,
+ 0, G_MAXINT, 0, G_PARAM_READABLE));
+ g_object_class_install_property (object_class, PROP_STREAM_LENGTH,
+ g_param_spec_int64 ("stream_length", NULL,
+ NULL, 0, G_MAXINT64, 0, G_PARAM_READABLE));
+ g_object_class_install_property (object_class, PROP_PLAYING,
+ g_param_spec_boolean ("playing", NULL, NULL, FALSE, G_PARAM_READABLE));
+ g_object_class_install_property (object_class, PROP_SEEKABLE,
+ g_param_spec_boolean ("seekable", NULL, NULL, FALSE, G_PARAM_READABLE));
+ g_object_class_install_property (object_class, PROP_VOLUME,
+ g_param_spec_int ("volume", NULL, NULL, 0, 100, 0, G_PARAM_READABLE));
+ g_object_class_install_property (object_class, PROP_SHOW_CURSOR,
+ g_param_spec_boolean ("showcursor", NULL,
+ NULL, FALSE, G_PARAM_READWRITE));
+ g_object_class_install_property (object_class, PROP_MEDIADEV,
+ g_param_spec_string ("mediadev", NULL, NULL, FALSE, G_PARAM_READWRITE));
+
+
+ /* Signals */
+ bvw_signals[SIGNAL_ERROR] =
+ g_signal_new ("error",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (BaconVideoWidgetClass, error),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING);
+
+ bvw_signals[SIGNAL_EOS] =
+ g_signal_new ("eos",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (BaconVideoWidgetClass, eos),
+ NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+
+ bvw_signals[SIGNAL_SEGMENT_DONE] =
+ g_signal_new ("segment_done",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (BaconVideoWidgetClass, segment_done),
+ NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+
+ bvw_signals[SIGNAL_READY_TO_SEEK] =
+ g_signal_new ("ready_to_seek",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (BaconVideoWidgetClass, ready_to_seek),
+ NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+
+ bvw_signals[SIGNAL_GOT_DURATION] =
+ g_signal_new ("got_duration",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (BaconVideoWidgetClass, got_duration),
+ NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+
+ bvw_signals[SIGNAL_GOT_METADATA] =
+ g_signal_new ("got-metadata",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (BaconVideoWidgetClass, got_metadata),
+ NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+
+ bvw_signals[SIGNAL_REDIRECT] =
+ g_signal_new ("got-redirect",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (BaconVideoWidgetClass, got_redirect),
+ NULL, NULL, g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+
+ bvw_signals[SIGNAL_TITLE_CHANGE] =
+ g_signal_new ("title-change",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (BaconVideoWidgetClass, title_change),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING);
+
+ bvw_signals[SIGNAL_CHANNELS_CHANGE] =
+ g_signal_new ("channels-change",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (BaconVideoWidgetClass, channels_change),
+ NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+
+ bvw_signals[SIGNAL_TICK] =
+ g_signal_new ("tick",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (BaconVideoWidgetClass, tick),
+ NULL, NULL,
+ baconvideowidget_marshal_VOID__INT64_INT64_FLOAT_BOOLEAN,
+ G_TYPE_NONE, 4, G_TYPE_INT64, G_TYPE_INT64, G_TYPE_FLOAT, G_TYPE_BOOLEAN);
+
+ bvw_signals[SIGNAL_BUFFERING] =
+ g_signal_new ("buffering",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (BaconVideoWidgetClass, buffering),
+ NULL, NULL, g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT);
+
+ bvw_signals[SIGNAL_STATE_CHANGE] =
+ g_signal_new ("state_change",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (BaconVideoWidgetClass, state_change),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
+
+ /* missing plugins signal:
+ * - string array: details of missing plugins for libgimme-codec
+ * - string array: details of missing plugins (human-readable strings)
+ * - bool: if we managed to start playing something even without those plugins
+ * return value: callback must return TRUE to indicate that it took some
+ * action, FALSE will be interpreted as no action taken
+ */
+ bvw_signals[SIGNAL_MISSING_PLUGINS] = g_signal_new ("missing-plugins", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, /* signal is enough, we don't need a vfunc */
+ bvw_boolean_handled_accumulator,
+ NULL,
+ baconvideowidget_marshal_BOOLEAN__BOXED_BOXED_BOOLEAN,
+ G_TYPE_BOOLEAN, 3, G_TYPE_STRV, G_TYPE_STRV, G_TYPE_BOOLEAN);
+}
+
+static void
+bacon_video_widget_init (BaconVideoWidget * bvw)
+{
+ BaconVideoWidgetPrivate *priv;
+
+ GTK_WIDGET_SET_FLAGS (GTK_WIDGET (bvw), GTK_CAN_FOCUS);
+ GTK_WIDGET_UNSET_FLAGS (GTK_WIDGET (bvw), GTK_DOUBLE_BUFFERED);
+
+ bvw->priv = priv =
+ G_TYPE_INSTANCE_GET_PRIVATE (bvw, BACON_TYPE_VIDEO_WIDGET,
+ BaconVideoWidgetPrivate);
+
+ priv->update_id = 0;
+ priv->tagcache = NULL;
+ priv->audiotags = NULL;
+ priv->videotags = NULL;
+ priv->zoom = 1.0;
+ priv->lock = g_mutex_new ();
+
+ bvw->priv->missing_plugins = NULL;
+ bvw->priv->plugin_install_in_progress = FALSE;
+}
+
+static void
+shrink_toplevel (BaconVideoWidget * bvw)
+{
+ GtkWidget *toplevel, *widget;
+ widget = GTK_WIDGET (bvw);
+ toplevel = gtk_widget_get_toplevel (widget);
+ if (toplevel != widget && GTK_IS_WINDOW (toplevel) != FALSE)
+ gtk_window_resize (GTK_WINDOW (toplevel), 1, 1);
+}
+
+static gboolean bvw_query_timeout (BaconVideoWidget * bvw);
+static void parse_stream_info (BaconVideoWidget * bvw);
+
+static void
+bvw_update_stream_info (BaconVideoWidget * bvw)
+{
+ parse_stream_info (bvw);
+
+ /* if we're not interactive, we want to announce metadata
+ * only later when we can be sure we got it all */
+ if (bvw->priv->use_type == BVW_USE_TYPE_VIDEO ||
+ bvw->priv->use_type == BVW_USE_TYPE_AUDIO) {
+ g_signal_emit (bvw, bvw_signals[SIGNAL_GOT_METADATA], 0, NULL);
+ g_signal_emit (bvw, bvw_signals[SIGNAL_CHANNELS_CHANGE], 0);
+ }
+}
+
+static void
+bvw_handle_application_message (BaconVideoWidget * bvw, GstMessage * msg)
+{
+ const gchar *msg_name;
+ GdkWindow *window;
+
+ msg_name = gst_structure_get_name (msg->structure);
+ g_return_if_fail (msg_name != NULL);
+
+ GST_DEBUG ("Handling application message: %" GST_PTR_FORMAT, msg->structure);
+
+ if (strcmp (msg_name, "stream-changed") == 0) {
+ bvw_update_stream_info (bvw);
+ } else if (strcmp (msg_name, "video-size") == 0) {
+ /* if we're not interactive, we want to announce metadata
+ * only later when we can be sure we got it all */
+ if (bvw->priv->use_type == BVW_USE_TYPE_VIDEO ||
+ bvw->priv->use_type == BVW_USE_TYPE_AUDIO) {
+ g_signal_emit (bvw, bvw_signals[SIGNAL_GOT_METADATA], 0, NULL);
+ }
+
+ if (bvw->priv->auto_resize
+ && !bvw->priv->fullscreen_mode && !bvw->priv->window_resized) {
+ bacon_video_widget_set_scale_ratio (bvw, 1);
+ } else {
+ bacon_video_widget_size_allocate (GTK_WIDGET (bvw),
+ >K_WIDGET (bvw)->allocation);
+
+ /* Uhm, so this ugly hack here makes media loading work for
+ * weird laptops with NVIDIA graphics cards... Dunno what the
+ * bug is really, but hey, it works. :). */
+ window = gtk_widget_get_window (GTK_WIDGET (bvw));
+ if (window) {
+ gdk_window_hide (window);
+ gdk_window_show (window);
+
+ bacon_video_widget_expose_event (GTK_WIDGET (bvw), NULL);
+ }
+ }
+ bvw->priv->window_resized = TRUE;
+ } else {
+ g_message ("Unhandled application message %s", msg_name);
+ }
+}
+
+static void
+bvw_handle_element_message (BaconVideoWidget * bvw, GstMessage * msg)
+{
+ const gchar *type_name = NULL;
+ gchar *src_name;
+
+ src_name = gst_object_get_name (msg->src);
+ if (msg->structure)
+ type_name = gst_structure_get_name (msg->structure);
+
+ GST_DEBUG ("from %s: %" GST_PTR_FORMAT, src_name, msg->structure);
+
+ if (type_name == NULL)
+ goto unhandled;
+
+ if (strcmp (type_name, "redirect") == 0) {
+ const gchar *new_location;
+
+ new_location = gst_structure_get_string (msg->structure, "new-location");
+ GST_DEBUG ("Got redirect to '%s'", GST_STR_NULL (new_location));
+
+ if (new_location && *new_location) {
+ g_signal_emit (bvw, bvw_signals[SIGNAL_REDIRECT], 0, new_location);
+ goto done;
+ }
+ } else if (strcmp (type_name, "progress") == 0) {
+ /* this is similar to buffering messages, but shouldn't affect pipeline
+ * state; qtdemux emits those when headers are after movie data and
+ * it is in streaming mode and has to receive all the movie data first */
+ if (!bvw->priv->buffering) {
+ gint percent = 0;
+
+ if (gst_structure_get_int (msg->structure, "percent", &percent))
+ g_signal_emit (bvw, bvw_signals[SIGNAL_BUFFERING], 0, percent);
+ }
+ goto done;
+ } else if (strcmp (type_name, "prepare-xwindow-id") == 0 ||
+ strcmp (type_name, "have-xwindow-id") == 0) {
+ /* we handle these synchronously or want to ignore them */
+ goto done;
+ } else if (gst_is_missing_plugin_message (msg)) {
+ bvw->priv->missing_plugins =
+ g_list_prepend (bvw->priv->missing_plugins, gst_message_ref (msg));
+ goto done;
+ } else {
+#if 0
+ GstNavigationMessageType nav_msg_type =
+ gst_navigation_message_get_type (msg);
+
+ switch (nav_msg_type) {
+ case GST_NAVIGATION_MESSAGE_MOUSE_OVER:
+ {
+ gint active;
+ if (!gst_navigation_message_parse_mouse_over (msg, &active))
+ break;
+ if (active) {
+ if (bvw->priv->cursor == NULL) {
+ bvw->priv->cursor = gdk_cursor_new (GDK_HAND2);
+ }
+ } else {
+ if (bvw->priv->cursor != NULL) {
+ gdk_cursor_unref (bvw->priv->cursor);
+ bvw->priv->cursor = NULL;
+ }
+ }
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (bvw)),
+ bvw->priv->cursor);
+ break;
+ }
+ default:
+ break;
+ }
+#endif
+ }
+
+unhandled:
+ GST_WARNING ("Unhandled element message %s from %s: %" GST_PTR_FORMAT,
+ GST_STR_NULL (type_name), GST_STR_NULL (src_name), msg);
+
+done:
+ g_free (src_name);
+}
+
+/* This is a hack to avoid doing poll_for_state_change() indirectly
+ * from the bus message callback (via EOS => totem => close => wait for ready)
+ * and deadlocking there. We need something like a
+ * gst_bus_set_auto_flushing(bus, FALSE) ... */
+static gboolean
+bvw_signal_eos_delayed (gpointer user_data)
+{
+ BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (user_data);
+ g_signal_emit (bvw, bvw_signals[SIGNAL_EOS], 0, NULL);
+ bvw->priv->eos_id = 0;
+ return FALSE;
+}
+
+static void
+bvw_reconfigure_tick_timeout (BaconVideoWidget * bvw, guint msecs)
+{
+ if (bvw->priv->update_id != 0) {
+ GST_INFO ("removing tick timeout");
+ g_source_remove (bvw->priv->update_id);
+ bvw->priv->update_id = 0;
+ }
+ if (msecs > 0) {
+ GST_INFO ("adding tick timeout (at %ums)", msecs);
+ bvw->priv->update_id =
+ g_timeout_add (msecs, (GSourceFunc) bvw_query_timeout, bvw);
+ }
+}
+
+/* returns TRUE if the error/signal has been handled and should be ignored */
+static gboolean
+bvw_emit_missing_plugins_signal (BaconVideoWidget * bvw, gboolean prerolled)
+{
+ gboolean handled = FALSE;
+ gchar **descriptions, **details;
+
+ details = bvw_get_missing_plugins_details (bvw->priv->missing_plugins);
+ descriptions =
+ bvw_get_missing_plugins_descriptions (bvw->priv->missing_plugins);
+
+ GST_LOG ("emitting missing-plugins signal (prerolled=%d)", prerolled);
+
+ g_signal_emit (bvw, bvw_signals[SIGNAL_MISSING_PLUGINS], 0,
+ details, descriptions, prerolled, &handled);
+ GST_DEBUG ("missing-plugins signal was %shandled", (handled) ? "" : "not ");
+
+ g_strfreev (descriptions);
+ g_strfreev (details);
+
+ if (handled) {
+ bvw->priv->plugin_install_in_progress = TRUE;
+ bvw_clear_missing_plugins_messages (bvw);
+ }
+
+ /* if it wasn't handled, we might need the list of missing messages again
+ * later to create a proper error message with details of what's missing */
+
+ return handled;
+}
+
+
+/* returns TRUE if the error has been handled and should be ignored */
+static gboolean
+bvw_check_missing_plugins_error (BaconVideoWidget * bvw, GstMessage * err_msg)
+{
+ gboolean error_src_is_playbin;
+ gboolean ret = FALSE;
+ GError *err = NULL;
+
+ if (bvw->priv->missing_plugins == NULL) {
+ GST_DEBUG ("no missing-plugin messages");
+ return FALSE;
+ }
+
+ gst_message_parse_error (err_msg, &err, NULL);
+
+ error_src_is_playbin = (err_msg->src == GST_OBJECT_CAST (bvw->priv->play));
+
+ /* If we get a WRONG_TYPE error from playbin itself it's most likely because
+ * there is a subtitle stream we can decode, but no video stream to overlay
+ * it on. Since there were missing-plugins messages, we'll assume this is
+ * because we cannot decode the video stream (this should probably be fixed
+ * in playbin, but for now we'll work around it here) */
+ if (is_error (err, CORE, MISSING_PLUGIN) ||
+ is_error (err, STREAM, CODEC_NOT_FOUND) ||
+ (is_error (err, STREAM, WRONG_TYPE) && error_src_is_playbin)) {
+ ret = bvw_emit_missing_plugins_signal (bvw, FALSE);
+ if (ret) {
+ /* If it was handled, stop playback to make sure we're not processing any
+ * other error messages that might also be on the bus */
+ bacon_video_widget_stop (bvw);
+ }
+ } else {
+ GST_DEBUG ("not an error code we are looking for, doing nothing");
+ }
+
+ g_error_free (err);
+ return ret;
+}
+
+/* returns TRUE if the error/signal has been handled and should be ignored */
+static gboolean
+bvw_check_missing_plugins_on_preroll (BaconVideoWidget * bvw)
+{
+ if (bvw->priv->missing_plugins == NULL) {
+ GST_DEBUG ("no missing-plugin messages");
+ return FALSE;
+ }
+
+ return bvw_emit_missing_plugins_signal (bvw, TRUE);
+}
+
+static void
+bvw_bus_message_cb (GstBus * bus, GstMessage * message, gpointer data)
+{
+ BaconVideoWidget *bvw = (BaconVideoWidget *) data;
+ GstMessageType msg_type;
+
+ g_return_if_fail (bvw != NULL);
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+
+ msg_type = GST_MESSAGE_TYPE (message);
+
+ /* somebody else is handling the message, probably in poll_for_state_change */
+ if (bvw->priv->ignore_messages_mask & msg_type) {
+ GST_LOG ("Ignoring %s message from element %" GST_PTR_FORMAT
+ " as requested: %" GST_PTR_FORMAT,
+ GST_MESSAGE_TYPE_NAME (message), message->src, message);
+ return;
+ }
+
+ if (msg_type != GST_MESSAGE_STATE_CHANGED) {
+ gchar *src_name = gst_object_get_name (message->src);
+ GST_LOG ("Handling %s message from element %s",
+ gst_message_type_get_name (msg_type), src_name);
+ g_free (src_name);
+ }
+
+ switch (msg_type) {
+ case GST_MESSAGE_ERROR:
+ {
+ bvw_error_msg (bvw, message);
+
+ if (!bvw_check_missing_plugins_error (bvw, message)) {
+ GError *error;
+
+ error = bvw_error_from_gst_error (bvw, message);
+
+ bvw->priv->target_state = GST_STATE_NULL;
+ if (bvw->priv->play)
+ gst_element_set_state (bvw->priv->play, GST_STATE_NULL);
+
+ bvw->priv->buffering = FALSE;
+
+ g_signal_emit (bvw, bvw_signals[SIGNAL_ERROR], 0,
+ error->message, TRUE, FALSE);
+
+ g_error_free (error);
+ }
+ break;
+ }
+ case GST_MESSAGE_WARNING:
+ {
+ GST_WARNING ("Warning message: %" GST_PTR_FORMAT, message);
+ break;
+ }
+ case GST_MESSAGE_TAG:
+ {
+ GstTagList *tag_list, *result;
+ GstElementFactory *f;
+
+ gst_message_parse_tag (message, &tag_list);
+
+ GST_DEBUG ("Tags: %" GST_PTR_FORMAT, tag_list);
+
+ /* all tags (replace previous tags, title/artist/etc. might change
+ * in the middle of a stream, e.g. with radio streams) */
+ result = gst_tag_list_merge (bvw->priv->tagcache, tag_list,
+ GST_TAG_MERGE_REPLACE);
+ if (bvw->priv->tagcache)
+ gst_tag_list_free (bvw->priv->tagcache);
+ bvw->priv->tagcache = result;
+
+ /* media-type-specific tags */
+ if (GST_IS_ELEMENT (message->src) &&
+ (f = gst_element_get_factory (GST_ELEMENT (message->src)))) {
+ const gchar *klass = gst_element_factory_get_klass (f);
+ GstTagList **cache = NULL;
+
+ if (g_strrstr (klass, "Video")) {
+ cache = &bvw->priv->videotags;
+ } else if (g_strrstr (klass, "Audio")) {
+ cache = &bvw->priv->audiotags;
+ }
+
+ if (cache) {
+ result = gst_tag_list_merge (*cache, tag_list, GST_TAG_MERGE_REPLACE);
+ if (*cache)
+ gst_tag_list_free (*cache);
+ *cache = result;
+ }
+ }
+
+ /* clean up */
+ gst_tag_list_free (tag_list);
+
+ /* if we're not interactive, we want to announce metadata
+ * only later when we can be sure we got it all */
+ if (bvw->priv->use_type == BVW_USE_TYPE_VIDEO ||
+ bvw->priv->use_type == BVW_USE_TYPE_AUDIO) {
+ /* If we updated metadata and we have a new title, send it
+ * using TITLE_CHANGE, so that the UI knows it has a new
+ * streaming title */
+ GValue value = { 0, };
+
+ g_signal_emit (bvw, bvw_signals[SIGNAL_GOT_METADATA], 0);
+
+ bacon_video_widget_get_metadata (bvw, BVW_INFO_TITLE, &value);
+ if (g_value_get_string (&value))
+ g_signal_emit (bvw, bvw_signals[SIGNAL_TITLE_CHANGE], 0,
+ g_value_get_string (&value));
+ g_value_unset (&value);
+ }
+ break;
+ }
+ case GST_MESSAGE_EOS:
+ GST_DEBUG ("EOS message");
+ /* update slider one last time */
+ bvw_query_timeout (bvw);
+ if (bvw->priv->eos_id == 0)
+ bvw->priv->eos_id = g_idle_add (bvw_signal_eos_delayed, bvw);
+ break;
+ case GST_MESSAGE_BUFFERING:
+ {
+ gint percent = 0;
+
+ /* FIXME: use gst_message_parse_buffering() once core 0.10.11 is out */
+ gst_structure_get_int (message->structure, "buffer-percent", &percent);
+ g_signal_emit (bvw, bvw_signals[SIGNAL_BUFFERING], 0, percent);
+
+ if (percent >= 100) {
+ /* a 100% message means buffering is done */
+ bvw->priv->buffering = FALSE;
+ /* if the desired state is playing, go back */
+ if (bvw->priv->target_state == GST_STATE_PLAYING) {
+ GST_DEBUG ("Buffering done, setting pipeline back to PLAYING");
+ gst_element_set_state (bvw->priv->play, GST_STATE_PLAYING);
+ } else {
+ GST_DEBUG ("Buffering done, keeping pipeline PAUSED");
+ }
+ } else if (bvw->priv->buffering == FALSE &&
+ bvw->priv->target_state == GST_STATE_PLAYING) {
+ GstState cur_state;
+
+ gst_element_get_state (bvw->priv->play, &cur_state, NULL, 0);
+ if (cur_state == GST_STATE_PLAYING) {
+ GST_DEBUG ("Buffering ... temporarily pausing playback");
+ gst_element_set_state (bvw->priv->play, GST_STATE_PAUSED);
+ } else {
+ GST_DEBUG ("Buffering ... prerolling, not doing anything");
+ }
+ bvw->priv->buffering = TRUE;
+ } else {
+ GST_LOG ("Buffering ... %d", percent);
+ }
+ break;
+ }
+ case GST_MESSAGE_APPLICATION:
+ {
+ bvw_handle_application_message (bvw, message);
+ break;
+ }
+ case GST_MESSAGE_STATE_CHANGED:
+ {
+ GstState old_state, new_state;
+ gchar *src_name;
+
+ gst_message_parse_state_changed (message, &old_state, &new_state, NULL);
+
+ if (old_state == new_state)
+ break;
+
+ /* we only care about playbin (pipeline) state changes */
+ if (GST_MESSAGE_SRC (message) != GST_OBJECT (bvw->priv->play))
+ break;
+
+ src_name = gst_object_get_name (message->src);
+ GST_DEBUG ("%s changed state from %s to %s", src_name,
+ gst_element_state_get_name (old_state),
+ gst_element_state_get_name (new_state));
+ g_free (src_name);
+
+ /* now do stuff */
+ if (new_state <= GST_STATE_PAUSED) {
+ bvw_query_timeout (bvw);
+ bvw_reconfigure_tick_timeout (bvw, 0);
+ g_signal_emit (bvw, bvw_signals[SIGNAL_STATE_CHANGE], 0, FALSE);
+
+ } else if (new_state == GST_STATE_PAUSED) {
+ bvw_reconfigure_tick_timeout (bvw, 500);
+ g_signal_emit (bvw, bvw_signals[SIGNAL_STATE_CHANGE], 0, FALSE);
+
+ } else if (new_state > GST_STATE_PAUSED) {
+ bvw_reconfigure_tick_timeout (bvw, 200);
+ g_signal_emit (bvw, bvw_signals[SIGNAL_STATE_CHANGE], 0, TRUE);
+ }
+
+
+ if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED) {
+ GST_DEBUG_BIN_TO_DOT_FILE (GST_BIN_CAST (bvw->priv->play),
+ GST_DEBUG_GRAPH_SHOW_ALL ^
+ GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS, "totem-prerolled");
+ bvw->priv->stream_length = 0;
+ if (bacon_video_widget_get_stream_length (bvw) == 0) {
+ GST_DEBUG ("Failed to query duration in PAUSED state?!");
+ }
+ break;
+ bvw_update_stream_info (bvw);
+ if (!bvw_check_missing_plugins_on_preroll (bvw)) {
+ /* show a non-fatal warning message if we can't decode the video */
+ bvw_check_if_video_decoder_is_missing (bvw);
+ }
+ g_signal_emit (bvw, bvw_signals[SIGNAL_READY_TO_SEEK], 0, FALSE);
+
+ } else if (old_state == GST_STATE_PAUSED && new_state == GST_STATE_READY) {
+ bvw->priv->media_has_video = FALSE;
+ bvw->priv->media_has_audio = FALSE;
+
+ /* clean metadata cache */
+ if (bvw->priv->tagcache) {
+ gst_tag_list_free (bvw->priv->tagcache);
+ bvw->priv->tagcache = NULL;
+ }
+ if (bvw->priv->audiotags) {
+ gst_tag_list_free (bvw->priv->audiotags);
+ bvw->priv->audiotags = NULL;
+ }
+ if (bvw->priv->videotags) {
+ gst_tag_list_free (bvw->priv->videotags);
+ bvw->priv->videotags = NULL;
+ }
+
+ bvw->priv->video_width = 0;
+ bvw->priv->video_height = 0;
+ }
+ break;
+ }
+ case GST_MESSAGE_ELEMENT:
+ {
+ bvw_handle_element_message (bvw, message);
+ break;
+ }
+
+ case GST_MESSAGE_DURATION:
+ {
+ /* force _get_stream_length() to do new duration query */
+ /*bvw->priv->stream_length = 0;
+ if (bacon_video_widget_get_stream_length (bvw) == 0)
+ {
+ GST_DEBUG ("Failed to query duration after DURATION message?!");
+ }
+ break; */
+ }
+
+ case GST_MESSAGE_CLOCK_PROVIDE:
+ case GST_MESSAGE_CLOCK_LOST:
+ case GST_MESSAGE_NEW_CLOCK:
+ case GST_MESSAGE_STATE_DIRTY:
+ break;
+
+ default:
+ GST_LOG ("Unhandled message: %" GST_PTR_FORMAT, message);
+ break;
+ }
+}
+
+
+static void
+got_video_size (BaconVideoWidget * bvw)
+{
+ GstMessage *msg;
+
+ g_return_if_fail (bvw != NULL);
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+
+ msg = gst_message_new_application (GST_OBJECT (bvw->priv->play),
+ gst_structure_new ("video-size", "width",
+ G_TYPE_INT,
+ bvw->priv->video_width, "height",
+ G_TYPE_INT, bvw->priv->video_height, NULL));
+ gst_element_post_message (bvw->priv->play, msg);
+}
+
+static void
+got_time_tick (GstElement * play, gint64 time_nanos, BaconVideoWidget * bvw)
+{
+ gboolean seekable;
+
+ g_return_if_fail (bvw != NULL);
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+
+ bvw->priv->current_time = (gint64) time_nanos / GST_MSECOND;
+
+ if (bvw->priv->stream_length == 0) {
+ bvw->priv->current_position = 0;
+ } else {
+ bvw->priv->current_position =
+ (gdouble) bvw->priv->current_time / bvw->priv->stream_length;
+ }
+
+ if (bvw->priv->stream_length == 0) {
+ seekable = bacon_video_widget_is_seekable (bvw);
+ } else {
+ if (bvw->priv->seekable == -1)
+ g_object_notify (G_OBJECT (bvw), "seekable");
+ seekable = TRUE;
+ }
+
+ bvw->priv->is_live = (bvw->priv->stream_length == 0);
+
+/*
+ GST_INFO ("%" GST_TIME_FORMAT ",%" GST_TIME_FORMAT " %s",
+ GST_TIME_ARGS (bvw->priv->current_time),
+ GST_TIME_ARGS (bvw->priv->stream_length),
+ (seekable) ? "TRUE" : "FALSE");
+*/
+
+ g_signal_emit (bvw, bvw_signals[SIGNAL_TICK], 0,
+ bvw->priv->current_time, bvw->priv->stream_length,
+ bvw->priv->current_position, seekable);
+}
+
+static void
+bvw_set_device_on_element (BaconVideoWidget * bvw, GstElement * element)
+{
+ if (bvw->priv->media_device == NULL)
+ return;
+
+ if (g_object_class_find_property (G_OBJECT_GET_CLASS (element), "device")) {
+ GST_DEBUG ("Setting device to '%s'", bvw->priv->media_device);
+ g_object_set (element, "device", bvw->priv->media_device, NULL);
+ }
+}
+
+static void
+playbin_source_notify_cb (GObject * play, GParamSpec * p,
+ BaconVideoWidget * bvw)
+{
+ GObject *source = NULL;
+
+ /* CHECKME: do we really need these taglist frees here (tpm)? */
+ if (bvw->priv->tagcache) {
+ gst_tag_list_free (bvw->priv->tagcache);
+ bvw->priv->tagcache = NULL;
+ }
+ if (bvw->priv->audiotags) {
+ gst_tag_list_free (bvw->priv->audiotags);
+ bvw->priv->audiotags = NULL;
+ }
+ if (bvw->priv->videotags) {
+ gst_tag_list_free (bvw->priv->videotags);
+ bvw->priv->videotags = NULL;
+ }
+
+ g_object_get (play, "source", &source, NULL);
+
+ if (source) {
+ GST_DEBUG ("Got source of type %s", G_OBJECT_TYPE_NAME (source));
+ bvw_set_device_on_element (bvw, GST_ELEMENT (source));
+ g_object_unref (source);
+ }
+}
+
+static gboolean
+bvw_query_timeout (BaconVideoWidget * bvw)
+{
+ GstFormat fmt = GST_FORMAT_TIME;
+ gint64 prev_len = -1;
+ gint64 pos = -1, len = -1;
+
+ /* check length/pos of stream */
+ prev_len = bvw->priv->stream_length;
+ if (gst_element_query_duration (bvw->priv->play, &fmt, &len)) {
+ if (len != -1 && fmt == GST_FORMAT_TIME) {
+ bvw->priv->stream_length = len / GST_MSECOND;
+ if (bvw->priv->stream_length != prev_len) {
+ g_signal_emit (bvw, bvw_signals[SIGNAL_GOT_METADATA], 0, NULL);
+ }
+ }
+ } else {
+ GST_INFO ("could not get duration");
+ }
+
+ if (gst_element_query_position (bvw->priv->play, &fmt, &pos)) {
+ if (pos != -1 && fmt == GST_FORMAT_TIME) {
+ got_time_tick (GST_ELEMENT (bvw->priv->play), pos, bvw);
+ }
+ } else {
+ GST_INFO ("could not get position");
+ }
+
+ return TRUE;
+}
+
+static void
+caps_set (GObject * obj, GParamSpec * pspec, BaconVideoWidget * bvw)
+{
+ GstPad *pad = GST_PAD (obj);
+ GstStructure *s;
+ GstCaps *caps;
+
+ if (!(caps = gst_pad_get_negotiated_caps (pad)))
+ return;
+
+ /* Get video decoder caps */
+ s = gst_caps_get_structure (caps, 0);
+ if (s) {
+ /* We need at least width/height and framerate */
+ if (!
+ (gst_structure_get_fraction
+ (s, "framerate", &bvw->priv->video_fps_n, &bvw->priv->video_fps_d)
+ && gst_structure_get_int (s, "width", &bvw->priv->video_width)
+ && gst_structure_get_int (s, "height", &bvw->priv->video_height)))
+ return;
+
+ /* Get the movie PAR if available */
+ bvw->priv->movie_par = gst_structure_get_value (s, "pixel-aspect-ratio");
+
+ /* Now set for real */
+ bacon_video_widget_set_aspect_ratio (bvw, bvw->priv->ratio_type);
+ }
+
+ gst_caps_unref (caps);
+}
+
+static void
+parse_stream_info (BaconVideoWidget * bvw)
+{
+ GstPad *videopad = NULL;
+ gint n_audio, n_video;
+
+ g_object_get (G_OBJECT (bvw->priv->play), "n-audio", &n_audio,
+ "n-video", &n_video, NULL);
+
+ bvw->priv->media_has_video = FALSE;
+ if (n_video > 0) {
+ gint i;
+
+ bvw->priv->media_has_video = TRUE;
+ if (bvw->priv->video_window)
+ gdk_window_show (bvw->priv->video_window);
+
+ for (i = 0; i < n_video && videopad == NULL; i++)
+ g_signal_emit_by_name (bvw->priv->play, "get-video-pad", i, &videopad);
+ }
+
+ bvw->priv->media_has_audio = FALSE;
+ if (n_audio > 0) {
+ bvw->priv->media_has_audio = TRUE;
+ if (!bvw->priv->media_has_video && bvw->priv->video_window) {
+ gint flags;
+
+ g_object_get (bvw->priv->play, "flags", &flags, NULL);
+
+ gdk_window_hide (bvw->priv->video_window);
+ GTK_WIDGET_SET_FLAGS (GTK_WIDGET (bvw), GTK_DOUBLE_BUFFERED);
+ flags &= ~GST_PLAY_FLAGS_VIS;
+
+ g_object_set (bvw->priv->play, "flags", flags, NULL);
+ }
+ }
+
+ if (videopad) {
+ GstCaps *caps;
+
+ if ((caps = gst_pad_get_negotiated_caps (videopad))) {
+ caps_set (G_OBJECT (videopad), NULL, bvw);
+ gst_caps_unref (caps);
+ }
+ g_signal_connect (videopad, "notify::caps", G_CALLBACK (caps_set), bvw);
+ gst_object_unref (videopad);
+ }
+}
+
+static void
+playbin_stream_changed_cb (GstElement * obj, gpointer data)
+{
+ BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (data);
+ GstMessage *msg;
+
+ /* we're being called from the streaming thread, so don't do anything here */
+ GST_LOG ("streams have changed");
+ msg = gst_message_new_application (GST_OBJECT (bvw->priv->play),
+ gst_structure_new ("stream-changed", NULL));
+ gst_element_post_message (bvw->priv->play, msg);
+}
+
+static void
+bacon_video_widget_finalize (GObject * object)
+{
+ BaconVideoWidget *bvw = (BaconVideoWidget *) object;
+
+ GST_INFO ("finalizing");
+
+ if (bvw->priv->bus) {
+ /* make bus drop all messages to make sure none of our callbacks is ever
+ * called again (main loop might be run again to display error dialog) */
+ gst_bus_set_flushing (bvw->priv->bus, TRUE);
+
+ if (bvw->priv->sig_bus_sync)
+ g_signal_handler_disconnect (bvw->priv->bus, bvw->priv->sig_bus_sync);
+
+ if (bvw->priv->sig_bus_async)
+ g_signal_handler_disconnect (bvw->priv->bus, bvw->priv->sig_bus_async);
+
+ gst_object_unref (bvw->priv->bus);
+ bvw->priv->bus = NULL;
+ }
+
+ g_free (bvw->priv->media_device);
+ bvw->priv->media_device = NULL;
+
+ g_free (bvw->priv->mrl);
+ bvw->priv->mrl = NULL;
+
+
+
+
+
+ if (bvw->priv->play != NULL && GST_IS_ELEMENT (bvw->priv->play)) {
+ gst_element_set_state (bvw->priv->play, GST_STATE_NULL);
+ gst_object_unref (bvw->priv->play);
+ bvw->priv->play = NULL;
+ }
+
+ if (bvw->priv->update_id) {
+ g_source_remove (bvw->priv->update_id);
+ bvw->priv->update_id = 0;
+ }
+
+ if (bvw->priv->interface_update_id) {
+ g_source_remove (bvw->priv->interface_update_id);
+ bvw->priv->interface_update_id = 0;
+ }
+
+ if (bvw->priv->tagcache) {
+ gst_tag_list_free (bvw->priv->tagcache);
+ bvw->priv->tagcache = NULL;
+ }
+ if (bvw->priv->audiotags) {
+ gst_tag_list_free (bvw->priv->audiotags);
+ bvw->priv->audiotags = NULL;
+ }
+ if (bvw->priv->videotags) {
+ gst_tag_list_free (bvw->priv->videotags);
+ bvw->priv->videotags = NULL;
+ }
+
+ if (bvw->priv->cursor != NULL) {
+ gdk_cursor_unref (bvw->priv->cursor);
+ bvw->priv->cursor = NULL;
+ }
+
+ if (bvw->priv->eos_id != 0)
+ g_source_remove (bvw->priv->eos_id);
+
+ g_mutex_free (bvw->priv->lock);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+bacon_video_widget_set_property (GObject * object, guint property_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ BaconVideoWidget *bvw;
+
+ bvw = BACON_VIDEO_WIDGET (object);
+
+ switch (property_id) {
+ case PROP_LOGO_MODE:
+ bacon_video_widget_set_logo_mode (bvw, g_value_get_boolean (value));
+ break;
+ case PROP_EXPAND_LOGO:
+ bvw->priv->expand_logo = g_value_get_boolean (value);
+ break;
+ case PROP_SHOW_CURSOR:
+ bacon_video_widget_set_show_cursor (bvw, g_value_get_boolean (value));
+ break;
+ case PROP_VOLUME:
+ bacon_video_widget_set_volume (bvw, g_value_get_double (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+bacon_video_widget_get_property (GObject * object, guint property_id,
+ GValue * value, GParamSpec * pspec)
+{
+ BaconVideoWidget *bvw;
+
+ bvw = BACON_VIDEO_WIDGET (object);
+
+ switch (property_id) {
+ case PROP_LOGO_MODE:
+ g_value_set_boolean (value, bacon_video_widget_get_logo_mode (bvw));
+ break;
+ case PROP_EXPAND_LOGO:
+ g_value_set_boolean (value, bvw->priv->expand_logo);
+ break;
+ case PROP_POSITION:
+ g_value_set_int64 (value, bacon_video_widget_get_position (bvw));
+ break;
+ case PROP_STREAM_LENGTH:
+ g_value_set_int64 (value, bacon_video_widget_get_stream_length (bvw));
+ break;
+ case PROP_PLAYING:
+ g_value_set_boolean (value, bacon_video_widget_is_playing (bvw));
+ break;
+ case PROP_SEEKABLE:
+ g_value_set_boolean (value, bacon_video_widget_is_seekable (bvw));
+ break;
+ case PROP_SHOW_CURSOR:
+ g_value_set_boolean (value, bacon_video_widget_get_show_cursor (bvw));
+ break;
+ case PROP_VOLUME:
+ g_value_set_int (value, bacon_video_widget_get_volume (bvw));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+/* ============================================================= */
+/* */
+/* Public Methods */
+/* */
+/* ============================================================= */
+
+
+/**
+ * bacon_video_widget_get_backend_name:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Returns the name string for @bvw. For the GStreamer backend, it is the output
+ * of gst_version_string(). *
+ * Return value: the backend's name; free with g_free()
+ **/
+char *
+bacon_video_widget_get_backend_name (BaconVideoWidget * bvw)
+{
+ return gst_version_string ();
+}
+
+/**
+ * bacon_video_widget_get_subtitle:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Returns the index of the current subtitles.
+ *
+ * If the widget is not playing, %-2 will be returned. If no subtitles are
+ * being used, %-1 is returned.
+ *
+ * Return value: the subtitle index
+ **/
+int
+bacon_video_widget_get_subtitle (BaconVideoWidget * bvw)
+{
+ int subtitle = -1;
+ gint flags;
+
+ g_return_val_if_fail (bvw != NULL, -2);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), -2);
+ g_return_val_if_fail (bvw->priv->play != NULL, -2);
+
+ g_object_get (bvw->priv->play, "flags", &flags, NULL);
+
+ if ((flags & GST_PLAY_FLAGS_TEXT) == 0)
+ return -2;
+
+ g_object_get (G_OBJECT (bvw->priv->play), "current-text", &subtitle, NULL);
+
+ return subtitle;
+}
+
+/**
+ * bacon_video_widget_set_subtitle:
+ * @bvw: a #BaconVideoWidget
+ * @subtitle: a subtitle index
+ *
+ * Sets the subtitle index for @bvw. If @subtitle is %-1, no subtitles will
+ * be used.
+ **/
+void
+bacon_video_widget_set_subtitle (BaconVideoWidget * bvw, int subtitle)
+{
+ gint flags;
+
+ g_return_if_fail (bvw != NULL);
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+ g_return_if_fail (bvw->priv->play != NULL);
+
+ g_object_get (bvw->priv->play, "flags", &flags, NULL);
+
+ if (subtitle == -2) {
+ flags &= ~GST_PLAY_FLAGS_TEXT;
+ subtitle = -1;
+ } else {
+ flags |= GST_PLAY_FLAGS_TEXT;
+ }
+
+ g_object_set (bvw->priv->play, "flags", flags, "current-text", subtitle,
+ NULL);
+}
+
+/**
+ * bacon_video_widget_has_next_track:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Determines whether there is another track after the current one, typically
+ * as a chapter on a DVD.
+ *
+ * Return value: %TRUE if there is another track, %FALSE otherwise
+ **/
+gboolean
+bacon_video_widget_has_next_track (BaconVideoWidget * bvw)
+{
+ //FIXME
+ return TRUE;
+}
+
+/**
+ * bacon_video_widget_has_previous_track:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Determines whether there is another track before the current one, typically
+ * as a chapter on a DVD.
+ *
+ * Return value: %TRUE if there is another track, %FALSE otherwise
+ **/
+gboolean
+bacon_video_widget_has_previous_track (BaconVideoWidget * bvw)
+{
+ //FIXME
+ return TRUE;
+}
+
+static GList *
+get_lang_list_for_type (BaconVideoWidget * bvw, const gchar * type_name)
+{
+ GList *ret = NULL;
+ gint num = 0;
+
+ if (g_str_equal (type_name, "AUDIO")) {
+ gint i, n;
+
+ g_object_get (G_OBJECT (bvw->priv->play), "n-audio", &n, NULL);
+ if (n == 0)
+ return NULL;
+
+ for (i = 0; i < n; i++) {
+ GstTagList *tags = NULL;
+
+ g_signal_emit_by_name (G_OBJECT (bvw->priv->play), "get-audio-tags",
+ i, &tags);
+
+ if (tags) {
+ gchar *lc = NULL, *cd = NULL;
+
+ gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &lc);
+ gst_tag_list_get_string (tags, GST_TAG_CODEC, &lc);
+
+ if (lc) {
+ ret = g_list_prepend (ret, lc);
+ g_free (cd);
+ } else if (cd) {
+ ret = g_list_prepend (ret, cd);
+ } else {
+ ret =
+ g_list_prepend (ret, g_strdup_printf ("%s %d", type_name, num++));
+ }
+ gst_tag_list_free (tags);
+ } else {
+ ret = g_list_prepend (ret, g_strdup_printf ("%s %d", type_name, num++));
+ }
+ }
+ } else if (g_str_equal (type_name, "TEXT")) {
+ gint i, n = 0;
+
+ g_object_get (G_OBJECT (bvw->priv->play), "n-text", &n, NULL);
+ if (n == 0)
+ return NULL;
+
+ for (i = 0; i < n; i++) {
+ GstTagList *tags = NULL;
+
+ g_signal_emit_by_name (G_OBJECT (bvw->priv->play), "get-text-tags",
+ i, &tags);
+
+ if (tags) {
+ gchar *lc = NULL, *cd = NULL;
+
+ gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &lc);
+ gst_tag_list_get_string (tags, GST_TAG_CODEC, &lc);
+
+ if (lc) {
+ ret = g_list_prepend (ret, lc);
+ g_free (cd);
+ } else if (cd) {
+ ret = g_list_prepend (ret, cd);
+ } else {
+ ret =
+ g_list_prepend (ret, g_strdup_printf ("%s %d", type_name, num++));
+ }
+ gst_tag_list_free (tags);
+ } else {
+ ret = g_list_prepend (ret, g_strdup_printf ("%s %d", type_name, num++));
+ }
+ }
+ } else {
+ g_critical ("Invalid stream type '%s'", type_name);
+ return NULL;
+ }
+
+ return g_list_reverse (ret);
+}
+
+/**
+ * bacon_video_widget_get_subtitles:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Returns a list of subtitle tags, each in the form <literal>TEXT <replaceable>x</replaceable></literal>,
+ * where <replaceable>x</replaceable> is the subtitle index.
+ *
+ * Return value: a #GList of subtitle tags, or %NULL; free each element with g_free() and the list with g_list_free()
+ **/
+GList *
+bacon_video_widget_get_subtitles (BaconVideoWidget * bvw)
+{
+ GList *list;
+
+ g_return_val_if_fail (bvw != NULL, NULL);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), NULL);
+ g_return_val_if_fail (bvw->priv->play != NULL, NULL);
+
+ list = get_lang_list_for_type (bvw, "TEXT");
+
+ return list;
+}
+
+/**
+ * bacon_video_widget_get_languages:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Returns a list of audio language tags, each in the form <literal>AUDIO <replaceable>x</replaceable></literal>,
+ * where <replaceable>x</replaceable> is the language index.
+ *
+ * Return value: a #GList of audio language tags, or %NULL; free each element with g_free() and the list with g_list_free()
+ **/
+GList *
+bacon_video_widget_get_languages (BaconVideoWidget * bvw)
+{
+ g_return_val_if_fail (bvw != NULL, NULL);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), NULL);
+ g_return_val_if_fail (bvw->priv->play != NULL, NULL);
+
+ return get_lang_list_for_type (bvw, "AUDIO");
+}
+
+/**
+ * bacon_video_widget_get_language:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Returns the index of the current audio language.
+ *
+ * If the widget is not playing, or the default language is in use, %-2 will be returned.
+ *
+ * Return value: the audio language index
+ **/
+int
+bacon_video_widget_get_language (BaconVideoWidget * bvw)
+{
+ int language = -1;
+
+ g_return_val_if_fail (bvw != NULL, -2);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), -2);
+ g_return_val_if_fail (bvw->priv->play != NULL, -2);
+
+ g_object_get (G_OBJECT (bvw->priv->play), "current-audio", &language, NULL);
+
+ if (language == -1)
+ language = -2;
+
+ return language;
+}
+
+/**
+ * bacon_video_widget_set_language:
+ * @bvw: a #BaconVideoWidget
+ * @language: an audio language index
+ *
+ * Sets the audio language index for @bvw. If @language is %-1, the default language will
+ * be used.
+ **/
+void
+bacon_video_widget_set_language (BaconVideoWidget * bvw, int language)
+{
+ g_return_if_fail (bvw != NULL);
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+ g_return_if_fail (bvw->priv->play != NULL);
+
+ if (language == -1)
+ language = 0;
+ else if (language == -2)
+ language = -1;
+
+ GST_DEBUG ("setting language to %d", language);
+
+ g_object_set (bvw->priv->play, "current-audio", language, NULL);
+
+ g_object_get (bvw->priv->play, "current-audio", &language, NULL);
+ GST_DEBUG ("current-audio now: %d", language);
+
+ /* so it updates its metadata for the newly-selected stream */
+ g_signal_emit (bvw, bvw_signals[SIGNAL_GOT_METADATA], 0, NULL);
+ g_signal_emit (bvw, bvw_signals[SIGNAL_CHANNELS_CHANGE], 0);
+}
+
+static guint
+connection_speed_enum_to_kbps (gint speed)
+{
+ static const guint conv_table[] = { 14400, 19200, 28800, 33600, 34400, 56000,
+ 112000, 256000, 384000, 512000, 1536000, 10752000
+ };
+
+ g_return_val_if_fail (speed >= 0
+ && (guint) speed < G_N_ELEMENTS (conv_table), 0);
+
+ /* must round up so that the correct streams are chosen and not ignored
+ * due to rounding errors when doing kbps <=> bps */
+ return (conv_table[speed] / 1000) +
+ (((conv_table[speed] % 1000) != 0) ? 1 : 0);
+}
+
+/**
+ * bacon_video_widget_get_connection_speed:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Returns the current connection speed, where %0 is the lowest speed
+ * and %11 is the highest.
+ *
+ * Return value: the connection speed index
+ **/
+int
+bacon_video_widget_get_connection_speed (BaconVideoWidget * bvw)
+{
+ g_return_val_if_fail (bvw != NULL, 0);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 0);
+
+ return bvw->priv->connection_speed;
+}
+
+/**
+ * bacon_video_widget_set_connection_speed:
+ * @bvw: a #BaconVideoWidget
+ * @speed: the connection speed index
+ *
+ * Sets the connection speed from the given @speed index, where %0 is the lowest speed
+ * and %11 is the highest.
+ **/
+void
+bacon_video_widget_set_connection_speed (BaconVideoWidget * bvw, int speed)
+{
+ g_return_if_fail (bvw != NULL);
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+
+ if (bvw->priv->connection_speed != speed) {
+ bvw->priv->connection_speed = speed;
+ }
+
+ if (bvw->priv->play != NULL &&
+ g_object_class_find_property (G_OBJECT_GET_CLASS (bvw->priv->play),
+ "connection-speed")) {
+ guint kbps = connection_speed_enum_to_kbps (speed);
+
+ GST_LOG ("Setting connection speed %d (= %d kbps)", speed, kbps);
+ g_object_set (bvw->priv->play, "connection-speed", kbps, NULL);
+ }
+}
+
+
+static gint
+get_num_audio_channels (BaconVideoWidget * bvw)
+{
+ gint channels;
+
+ switch (bvw->priv->speakersetup) {
+ case BVW_AUDIO_SOUND_STEREO:
+ channels = 2;
+ break;
+ case BVW_AUDIO_SOUND_CHANNEL4:
+ channels = 4;
+ break;
+ case BVW_AUDIO_SOUND_CHANNEL5:
+ channels = 5;
+ break;
+ case BVW_AUDIO_SOUND_CHANNEL41:
+ /* so alsa has this as 5.1, but empty center speaker. We don't really
+ * do that yet. ;-). So we'll take the placebo approach. */
+ case BVW_AUDIO_SOUND_CHANNEL51:
+ channels = 6;
+ break;
+ case BVW_AUDIO_SOUND_AC3PASSTHRU:
+ default:
+ g_return_val_if_reached (-1);
+ }
+
+ return channels;
+}
+
+static GstCaps *
+fixate_to_num (const GstCaps * in_caps, gint channels)
+{
+ gint n, count;
+ GstStructure *s;
+ const GValue *v;
+ GstCaps *out_caps;
+
+ out_caps = gst_caps_copy (in_caps);
+
+ count = gst_caps_get_size (out_caps);
+ for (n = 0; n < count; n++) {
+ s = gst_caps_get_structure (out_caps, n);
+ v = gst_structure_get_value (s, "channels");
+ if (!v)
+ continue;
+
+ /* get channel count (or list of ~) */
+ gst_structure_fixate_field_nearest_int (s, "channels", channels);
+ }
+
+ return out_caps;
+}
+
+static void
+set_audio_filter (BaconVideoWidget * bvw)
+{
+ gint channels;
+ GstCaps *caps, *res;
+ GstPad *pad;
+
+ /* reset old */
+ g_object_set (bvw->priv->audio_capsfilter, "caps", NULL, NULL);
+
+ /* construct possible caps to filter down to our chosen caps */
+ /* Start with what the audio sink supports, but limit the allowed
+ * channel count to our speaker output configuration */
+ pad = gst_element_get_pad (bvw->priv->audio_capsfilter, "src");
+ caps = gst_pad_peer_get_caps (pad);
+ gst_object_unref (pad);
+
+ if ((channels = get_num_audio_channels (bvw)) == -1)
+ return;
+
+ res = fixate_to_num (caps, channels);
+ gst_caps_unref (caps);
+
+ /* set */
+ if (res && gst_caps_is_empty (res)) {
+ gst_caps_unref (res);
+ res = NULL;
+ }
+ g_object_set (bvw->priv->audio_capsfilter, "caps", res, NULL);
+
+ if (res) {
+ gst_caps_unref (res);
+ }
+
+ /* reset */
+ pad = gst_element_get_pad (bvw->priv->audio_capsfilter, "src");
+ gst_pad_set_caps (pad, NULL);
+ gst_object_unref (pad);
+}
+
+/**
+ * bacon_video_widget_get_audio_out_type:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Returns the current audio output type (e.g. how many speaker channels)
+ * from #BaconVideoWidgetAudioOutType.
+ *
+ * Return value: the audio output type, or %-1
+ **/
+BvwAudioOutType
+bacon_video_widget_get_audio_out_type (BaconVideoWidget * bvw)
+{
+ g_return_val_if_fail (bvw != NULL, -1);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), -1);
+
+ return bvw->priv->speakersetup;
+}
+
+/**
+ * bacon_video_widget_set_audio_out_type:
+ * @bvw: a #BaconVideoWidget
+ * @type: the new audio output type
+ *
+ * Sets the audio output type (number of speaker channels) in the video widget,
+ * and stores it in GConf.
+ *
+ * Return value: %TRUE on success, %FALSE otherwise
+ **/
+gboolean
+bacon_video_widget_set_audio_out_type (BaconVideoWidget * bvw,
+ BvwAudioOutType type)
+{
+ g_return_val_if_fail (bvw != NULL, FALSE);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
+
+ if (type == bvw->priv->speakersetup)
+ return FALSE;
+ else if (type == BVW_AUDIO_SOUND_AC3PASSTHRU)
+ return FALSE;
+
+ bvw->priv->speakersetup = type;
+
+
+ set_audio_filter (bvw);
+
+ return FALSE;
+}
+
+
+
+
+
+
+/* =========================================== */
+/* */
+/* Play/Pause, Stop */
+/* */
+/* =========================================== */
+
+static GError *
+bvw_error_from_gst_error (BaconVideoWidget * bvw, GstMessage * err_msg)
+{
+ const gchar *src_typename;
+ GError *ret = NULL;
+ GError *e = NULL;
+
+ GST_LOG ("resolving error message %" GST_PTR_FORMAT, err_msg);
+
+ src_typename = (err_msg->src) ? G_OBJECT_TYPE_NAME (err_msg->src) : NULL;
+
+ gst_message_parse_error (err_msg, &e, NULL);
+
+ if (is_error (e, RESOURCE, NOT_FOUND) || is_error (e, RESOURCE, OPEN_READ)) {
+#if 0
+ if (strchr (mrl, ':') &&
+ (g_str_has_prefix (mrl, "dvd") ||
+ g_str_has_prefix (mrl, "cd") || g_str_has_prefix (mrl, "vcd"))) {
+ ret = g_error_new_literal (BVW_ERROR, GST_ERROR_INVALID_DEVICE,
+ e->message);
+ } else {
+#endif
+ if (e->code == GST_RESOURCE_ERROR_NOT_FOUND) {
+ if (GST_IS_BASE_AUDIO_SINK (err_msg->src)) {
+ ret =
+ g_error_new_literal (BVW_ERROR, GST_ERROR_AUDIO_PLUGIN,
+ _
+ ("The requested audio output was not found. "
+ "Please select another audio output in the Multimedia "
+ "Systems Selector."));
+ } else {
+ ret =
+ g_error_new_literal (BVW_ERROR, GST_ERROR_FILE_NOT_FOUND,
+ _("Location not found."));
+ }
+ } else {
+ ret = g_error_new_literal (BVW_ERROR, GST_ERROR_FILE_PERMISSION,
+ _("Could not open location; "
+ "you might not have permission to open the file."));
+ }
+#if 0
+ }
+#endif
+ } else if (is_error (e, RESOURCE, BUSY)) {
+ if (GST_IS_VIDEO_SINK (err_msg->src)) {
+ /* a somewhat evil check, but hey.. */
+ ret = g_error_new_literal (BVW_ERROR,
+ GST_ERROR_VIDEO_PLUGIN,
+ _
+ ("The video output is in use by another application. "
+ "Please close other video applications, or select "
+ "another video output in the Multimedia Systems Selector."));
+ } else if (GST_IS_BASE_AUDIO_SINK (err_msg->src)) {
+ ret = g_error_new_literal (BVW_ERROR,
+ GST_ERROR_AUDIO_BUSY,
+ _
+ ("The audio output is in use by another application. "
+ "Please select another audio output in the Multimedia Systems Selector. "
+ "You may want to consider using a sound server."));
+ }
+ } else if (e->domain == GST_RESOURCE_ERROR) {
+ ret = g_error_new_literal (BVW_ERROR, GST_ERROR_FILE_GENERIC, e->message);
+ } else if (is_error (e, CORE, MISSING_PLUGIN) ||
+ is_error (e, STREAM, CODEC_NOT_FOUND)) {
+ if (bvw->priv->missing_plugins != NULL) {
+ gchar **descs, *msg = NULL;
+ guint num;
+
+ descs = bvw_get_missing_plugins_descriptions (bvw->priv->missing_plugins);
+ num = g_list_length (bvw->priv->missing_plugins);
+
+ if (is_error (e, CORE, MISSING_PLUGIN)) {
+ /* should be exactly one missing thing (source or converter) */
+ msg =
+ g_strdup_printf (_
+ ("The playback of this movie requires a %s "
+ "plugin which is not installed."), descs[0]);
+ } else {
+ gchar *desc_list;
+
+ desc_list = g_strjoinv ("\n", descs);
+ msg = g_strdup_printf (ngettext (_("The playback of this movie "
+ "requires a %s plugin which is not installed."),
+ _("The playback "
+ "of this movie requires the following decoders which are not "
+ "installed:\n\n%s"), num),
+ (num == 1) ? descs[0] : desc_list);
+ g_free (desc_list);
+ }
+ ret = g_error_new_literal (BVW_ERROR, GST_ERROR_CODEC_NOT_HANDLED, msg);
+ g_free (msg);
+ g_strfreev (descs);
+ } else {
+ GST_LOG ("no missing plugin messages, posting generic error");
+ ret = g_error_new_literal (BVW_ERROR, GST_ERROR_CODEC_NOT_HANDLED,
+ e->message);
+ }
+ } else if (is_error (e, STREAM, WRONG_TYPE) ||
+ is_error (e, STREAM, NOT_IMPLEMENTED)) {
+ if (src_typename) {
+ ret = g_error_new (BVW_ERROR, GST_ERROR_CODEC_NOT_HANDLED, "%s: %s",
+ src_typename, e->message);
+ } else {
+ ret = g_error_new_literal (BVW_ERROR, GST_ERROR_CODEC_NOT_HANDLED,
+ e->message);
+ }
+ } else if (is_error (e, STREAM, FAILED) &&
+ src_typename && strncmp (src_typename, "GstTypeFind", 11) == 0) {
+ ret = g_error_new_literal (BVW_ERROR, GST_ERROR_READ_ERROR,
+ _("Cannot play this file over the network. "
+ "Try downloading it to disk first."));
+ } else {
+ /* generic error, no code; take message */
+ ret = g_error_new_literal (BVW_ERROR, GST_ERROR_GENERIC, e->message);
+ }
+ g_error_free (e);
+ bvw_clear_missing_plugins_messages (bvw);
+
+ return ret;
+}
+
+
+static gboolean
+poll_for_state_change_full (BaconVideoWidget * bvw, GstElement * element,
+ GstState state, GstMessage ** err_msg, gint64 timeout)
+{
+ GstBus *bus;
+ GstMessageType events, saved_events;
+
+ g_assert (err_msg != NULL);
+
+ bus = gst_element_get_bus (element);
+
+ events = GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS;
+
+ saved_events = bvw->priv->ignore_messages_mask;
+
+ if (element != NULL && element == bvw->priv->play) {
+ /* we do want the main handler to process state changed messages for
+ * playbin as well, otherwise it won't hook up the timeout etc. */
+ bvw->priv->ignore_messages_mask |= (events ^ GST_MESSAGE_STATE_CHANGED);
+ } else {
+ bvw->priv->ignore_messages_mask |= events;
+ }
+
+ while (TRUE) {
+ GstMessage *message;
+ GstElement *src;
+
+ message = gst_bus_poll (bus, events, timeout);
+
+ if (!message)
+ goto timed_out;
+
+ src = (GstElement *) GST_MESSAGE_SRC (message);
+
+ switch (GST_MESSAGE_TYPE (message)) {
+ case GST_MESSAGE_STATE_CHANGED:
+ {
+ GstState old, new, pending;
+
+ if (src == element) {
+ gst_message_parse_state_changed (message, &old, &new, &pending);
+ if (new == state) {
+ gst_message_unref (message);
+ goto success;
+ }
+ }
+ break;
+ }
+ case GST_MESSAGE_ERROR:
+ {
+ bvw_error_msg (bvw, message);
+ *err_msg = message;
+ message = NULL;
+ goto error;
+ break;
+ }
+ case GST_MESSAGE_EOS:
+ {
+ GError *e = NULL;
+
+ gst_message_unref (message);
+ e = g_error_new_literal (BVW_ERROR, GST_ERROR_FILE_GENERIC,
+ _("Media file could not be played."));
+ *err_msg =
+ gst_message_new_error (GST_OBJECT (bvw->priv->play), e, NULL);
+ g_error_free (e);
+ goto error;
+ break;
+ }
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ gst_message_unref (message);
+ }
+
+ g_assert_not_reached ();
+
+success:
+ /* state change succeeded */
+ GST_DEBUG ("state change to %s succeeded",
+ gst_element_state_get_name (state));
+ bvw->priv->ignore_messages_mask = saved_events;
+ return TRUE;
+
+timed_out:
+ /* it's taking a long time to open -- just tell totem it was ok, this allows
+ * the user to stop the loading process with the normal stop button */
+ GST_DEBUG ("state change to %s timed out, returning success and handling "
+ "errors asynchronously", gst_element_state_get_name (state));
+ bvw->priv->ignore_messages_mask = saved_events;
+ return TRUE;
+
+error:
+ GST_DEBUG ("error while waiting for state change to %s: %" GST_PTR_FORMAT,
+ gst_element_state_get_name (state), *err_msg);
+ /* already set *err_msg */
+ bvw->priv->ignore_messages_mask = saved_events;
+ return FALSE;
+}
+
+/**
+ * bacon_video_widget_open:
+ * @bvw: a #BaconVideoWidget
+ * @mrl: an MRL
+ * @subtitle_uri: the URI of a subtitle file, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Opens the given @mrl in @bvw for playing. If @subtitle_uri is not %NULL, the given
+ * subtitle file is also loaded. Alternatively, the subtitle URI can be passed in @mrl
+ * by adding it after <literal>#subtitle:</literal>. For example:
+ * <literal>http://example.com/video.mpg#subtitle:/home/user/subtitle.ass</literal>.
+ *
+ * If there was a filesystem error, a %ERROR_GENERIC error will be returned. Otherwise,
+ * more specific #BvwError errors will be returned.
+ *
+ * On success, the MRL is loaded and waiting to be played with bacon_video_widget_play().
+ *
+ * Return value: %TRUE on success, %FALSE otherwise
+ **/
+
+gboolean
+bacon_video_widget_open (BaconVideoWidget * bvw,
+ const gchar * mrl, const gchar * subtitle_uri, GError ** error)
+{
+
+ GstMessage *err_msg = NULL;
+ GFile *file;
+ gboolean ret;
+ char *path;
+
+ g_return_val_if_fail (bvw != NULL, FALSE);
+ g_return_val_if_fail (mrl != NULL, FALSE);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
+ g_return_val_if_fail (bvw->priv->play != NULL, FALSE);
+
+
+ /* So we aren't closed yet... */
+ if (bvw->priv->mrl) {
+ bacon_video_widget_close (bvw);
+ }
+
+ GST_DEBUG ("mrl = %s", GST_STR_NULL (mrl));
+ GST_DEBUG ("subtitle_uri = %s", GST_STR_NULL (subtitle_uri));
+
+ /* this allows non-URI type of files in the thumbnailer and so on */
+ file = g_file_new_for_commandline_arg (mrl);
+
+
+ /* Only use the URI when FUSE isn't available for a file */
+ path = g_file_get_path (file);
+ if (path) {
+ bvw->priv->mrl = g_filename_to_uri (path, NULL, NULL);
+ g_free (path);
+ } else {
+ bvw->priv->mrl = g_strdup (mrl);
+ }
+
+ g_object_unref (file);
+
+ if (g_str_has_prefix (mrl, "icy:") != FALSE) {
+ /* Handle "icy://" URLs from QuickTime */
+ g_free (bvw->priv->mrl);
+ bvw->priv->mrl = g_strdup_printf ("http:%s", mrl + 4);
+ } else if (g_str_has_prefix (mrl, "icyx:") != FALSE) {
+ /* Handle "icyx://" URLs from Orban/Coding Technologies AAC/aacPlus Player */
+ g_free (bvw->priv->mrl);
+ bvw->priv->mrl = g_strdup_printf ("http:%s", mrl + 5);
+ } else if (g_str_has_prefix (mrl, "dvd:///")) {
+ /* this allows to play backups of dvds */
+ g_free (bvw->priv->mrl);
+ bvw->priv->mrl = g_strdup ("dvd://");
+ g_free (bvw->priv->media_device);
+ bvw->priv->media_device = g_strdup (mrl + strlen ("dvd://"));
+ } else if (g_str_has_prefix (mrl, "vcd:///")) {
+ /* this allows to play backups of vcds */
+ g_free (bvw->priv->mrl);
+ bvw->priv->mrl = g_strdup ("vcd://");
+ g_free (bvw->priv->media_device);
+ bvw->priv->media_device = g_strdup (mrl + strlen ("vcd://"));
+ }
+
+ bvw->priv->got_redirect = FALSE;
+ bvw->priv->media_has_video = FALSE;
+ bvw->priv->media_has_audio = FALSE;
+ bvw->priv->stream_length = 0;
+ bvw->priv->ignore_messages_mask = 0;
+
+ if (g_strrstr (bvw->priv->mrl, "#subtitle:")) {
+ gchar **uris;
+ gchar *subtitle_uri;
+
+ uris = g_strsplit (bvw->priv->mrl, "#subtitle:", 2);
+ /* Try to fix subtitle uri if needed */
+ if (uris[1][0] == '/') {
+ subtitle_uri = g_strdup_printf ("file://%s", uris[1]);
+ } else {
+ if (strchr (uris[1], ':')) {
+ subtitle_uri = g_strdup (uris[1]);
+ } else {
+ gchar *cur_dir = g_get_current_dir ();
+ if (!cur_dir) {
+ g_set_error_literal (error, BVW_ERROR, GST_ERROR_GENERIC,
+ _("Failed to retrieve working directory"));
+ return FALSE;
+ }
+ subtitle_uri = g_strdup_printf ("file://%s/%s", cur_dir, uris[1]);
+ g_free (cur_dir);
+ }
+ }
+ g_object_set (bvw->priv->play, "uri", bvw->priv->mrl,
+ "suburi", subtitle_uri, NULL);
+ g_free (subtitle_uri);
+ g_strfreev (uris);
+ } else {
+
+ g_object_set (bvw->priv->play, "uri", bvw->priv->mrl, NULL);
+ }
+
+ bvw->priv->seekable = -1;
+ bvw->priv->target_state = GST_STATE_PAUSED;
+ bvw_clear_missing_plugins_messages (bvw);
+
+ gst_element_set_state (bvw->priv->play, GST_STATE_PAUSED);
+
+ if (bvw->priv->use_type == BVW_USE_TYPE_AUDIO ||
+ bvw->priv->use_type == BVW_USE_TYPE_VIDEO) {
+ GST_INFO ("normal playback, handling all errors asynchroneously");
+ ret = TRUE;
+ } else {
+ /* used as thumbnailer or metadata extractor for properties dialog. In
+ * this case, wait for any state change to really finish and process any
+ * pending tag messages, so that the information is available right away */
+ GST_INFO ("waiting for state changed to PAUSED to complete");
+ ret = poll_for_state_change_full (bvw, bvw->priv->play,
+ GST_STATE_PAUSED, &err_msg, -1);
+
+ bvw_process_pending_tag_messages (bvw);
+ bacon_video_widget_get_stream_length (bvw);
+ GST_INFO ("stream length = %u", bvw->priv->stream_length);
+
+ /* even in case of an error (e.g. no decoders installed) we might still
+ * have useful metadata (like codec types, duration, etc.) */
+ g_signal_emit (bvw, bvw_signals[SIGNAL_GOT_METADATA], 0, NULL);
+ }
+
+ if (ret) {
+ g_signal_emit (bvw, bvw_signals[SIGNAL_CHANNELS_CHANGE], 0);
+ } else {
+ GST_INFO ("Error on open: %" GST_PTR_FORMAT, err_msg);
+ if (bvw_check_missing_plugins_error (bvw, err_msg)) {
+ /* totem will try to start playing, so ignore all messages on the bus */
+ bvw->priv->ignore_messages_mask |= GST_MESSAGE_ERROR;
+ GST_LOG ("missing plugins handled, ignoring error and returning TRUE");
+ gst_message_unref (err_msg);
+ err_msg = NULL;
+ ret = TRUE;
+ } else {
+ bvw->priv->ignore_messages_mask |= GST_MESSAGE_ERROR;
+ bvw_stop_play_pipeline (bvw);
+ g_free (bvw->priv->mrl);
+ bvw->priv->mrl = NULL;
+ }
+ }
+
+ /* When opening a new media we want to redraw ourselves */
+ gtk_widget_queue_draw (GTK_WIDGET (bvw));
+
+ if (err_msg != NULL) {
+ if (error) {
+ *error = bvw_error_from_gst_error (bvw, err_msg);
+
+ } else {
+ GST_WARNING ("Got error, but caller is not collecting error details!");
+ }
+ gst_message_unref (err_msg);
+ }
+
+
+ return ret;
+}
+
+/**
+ * bacon_video_widget_play:
+ * @bvw: a #BaconVideoWidget
+ * @error: a #GError, or %NULL
+ *
+ * Plays the currently-loaded video in @bvw.
+ *
+ * Errors from the GStreamer backend will be returned asynchronously via the
+ * #BaconVideoWidget::error signal, even if this function returns %TRUE.
+ *
+ * Return value: %TRUE on success, %FALSE otherwise
+ **/
+gboolean
+bacon_video_widget_play (BaconVideoWidget * bvw)
+{
+
+ GstState cur_state;
+
+ g_return_val_if_fail (bvw != NULL, FALSE);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
+ g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE);
+ g_return_val_if_fail (bvw->priv->mrl != NULL, FALSE);
+
+ bvw->priv->target_state = GST_STATE_PLAYING;
+
+ /* no need to actually go into PLAYING in capture/metadata mode (esp.
+ * not with sinks that don't sync to the clock), we'll get everything
+ * we need by prerolling the pipeline, and that is done in _open() */
+ if (bvw->priv->use_type == BVW_USE_TYPE_CAPTURE ||
+ bvw->priv->use_type == BVW_USE_TYPE_METADATA) {
+ return TRUE;
+ }
+
+ /* just lie and do nothing in this case */
+ gst_element_get_state (bvw->priv->play, &cur_state, NULL, 0);
+ if (bvw->priv->plugin_install_in_progress && cur_state != GST_STATE_PAUSED) {
+ GST_INFO ("plugin install in progress and nothing to play, doing nothing");
+ return TRUE;
+ }
+
+ GST_INFO ("play");
+ gst_element_set_state (bvw->priv->play, GST_STATE_PLAYING);
+
+ /* will handle all errors asynchroneously */
+ return TRUE;
+}
+
+/**
+ * bacon_video_widget_can_direct_seek:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Determines whether direct seeking is possible for the current stream.
+ *
+ * Return value: %TRUE if direct seeking is possible, %FALSE otherwise
+ **/
+gboolean
+bacon_video_widget_can_direct_seek (BaconVideoWidget * bvw)
+{
+ g_return_val_if_fail (bvw != NULL, FALSE);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
+ g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE);
+
+ if (bvw->priv->mrl == NULL)
+ return FALSE;
+
+ /* (instant seeking only make sense with video,
+ * hence no cdda:// here) */
+ if (g_str_has_prefix (bvw->priv->mrl, "file://") ||
+ g_str_has_prefix (bvw->priv->mrl, "dvd:/") ||
+ g_str_has_prefix (bvw->priv->mrl, "vcd:/"))
+ return TRUE;
+
+ return FALSE;
+}
+
+//If we want to seek throug a seekbar we want speed, so we use the KEY_UNIT flag
+//Sometimes accurate position is requested so we use the ACCURATE flag
+gboolean
+bacon_video_widget_seek_time (BaconVideoWidget * bvw, gint64 time,
+ gfloat rate, gboolean accurate)
+{
+
+
+ g_return_val_if_fail (bvw != NULL, FALSE);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
+ g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE);
+
+ GST_LOG ("Seeking to %" GST_TIME_FORMAT, GST_TIME_ARGS (time * GST_MSECOND));
+
+ if (time > bvw->priv->stream_length
+ && bvw->priv->stream_length > 0
+ && !g_str_has_prefix (bvw->priv->mrl, "dvd:")
+ && !g_str_has_prefix (bvw->priv->mrl, "vcd:")) {
+ if (bvw->priv->eos_id == 0)
+ bvw->priv->eos_id = g_idle_add (bvw_signal_eos_delayed, bvw);
+ return TRUE;
+ }
+
+
+ if (accurate) {
+ got_time_tick (bvw->priv->play, time * GST_MSECOND, bvw);
+ gst_element_seek (bvw->priv->play, rate,
+ GST_FORMAT_TIME,
+ GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
+ GST_SEEK_TYPE_SET, time * GST_MSECOND,
+ GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
+ } else {
+ /* Emit a time tick of where we are going, we are paused */
+ got_time_tick (bvw->priv->play, time * GST_MSECOND, bvw);
+ gst_element_seek (bvw->priv->play, rate,
+ GST_FORMAT_TIME,
+ GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
+ GST_SEEK_TYPE_SET, time * GST_MSECOND,
+ GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
+ }
+ return TRUE;
+}
+
+
+
+
+
+gboolean
+bacon_video_widget_seek (BaconVideoWidget * bvw, gdouble position, gfloat rate)
+{
+
+ gint64 seek_time, length_nanos;
+
+ g_return_val_if_fail (bvw != NULL, FALSE);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
+ g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE);
+
+ length_nanos = (gint64) (bvw->priv->stream_length * GST_MSECOND);
+ seek_time = (gint64) (length_nanos * position);
+
+ GST_LOG ("Seeking to %3.2f%% %" GST_TIME_FORMAT, position,
+ GST_TIME_ARGS (seek_time));
+
+ return bacon_video_widget_seek_time (bvw, seek_time / GST_MSECOND, rate,
+ FALSE);
+}
+
+gboolean
+bacon_video_widget_seek_in_segment (BaconVideoWidget * bvw, gint64 pos,
+ gfloat rate)
+{
+
+ g_return_val_if_fail (bvw != NULL, FALSE);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
+ g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE);
+
+ GST_LOG ("Segment seeking from %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (pos * GST_MSECOND));
+
+ if (pos > bvw->priv->stream_length
+ && bvw->priv->stream_length > 0
+ && !g_str_has_prefix (bvw->priv->mrl, "dvd:")
+ && !g_str_has_prefix (bvw->priv->mrl, "vcd:")) {
+ if (bvw->priv->eos_id == 0)
+ bvw->priv->eos_id = g_idle_add (bvw_signal_eos_delayed, bvw);
+ return TRUE;
+ }
+
+ got_time_tick (bvw->priv->play, pos * GST_MSECOND, bvw);
+ gst_element_seek (bvw->priv->play, rate,
+ GST_FORMAT_TIME,
+ GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_SEGMENT |
+ GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET,
+ pos * GST_MSECOND, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
+
+ return TRUE;
+}
+
+gboolean
+bacon_video_widget_set_rate_in_segment (BaconVideoWidget * bvw, gfloat rate,
+ gint64 stop)
+{
+ guint64 pos;
+
+ g_return_val_if_fail (bvw != NULL, FALSE);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
+ g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE);
+
+ pos = bacon_video_widget_get_accurate_current_time (bvw);
+ if (pos == 0)
+ return FALSE;
+
+ gst_element_seek (bvw->priv->play, rate,
+ GST_FORMAT_TIME,
+ GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE |
+ GST_SEEK_FLAG_SEGMENT, GST_SEEK_TYPE_SET,
+ pos * GST_MSECOND, GST_SEEK_TYPE_SET, stop * GST_MSECOND);
+
+ return TRUE;
+}
+
+gboolean
+bacon_video_widget_set_rate (BaconVideoWidget * bvw, gfloat rate)
+{
+ guint64 pos;
+
+ g_return_val_if_fail (bvw != NULL, FALSE);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
+ g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE);
+
+ pos = bacon_video_widget_get_accurate_current_time (bvw);
+ if (pos == 0)
+ return FALSE;
+
+ gst_element_seek (bvw->priv->play, rate,
+ GST_FORMAT_TIME,
+ GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
+ GST_SEEK_TYPE_SET,
+ pos * GST_MSECOND, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
+
+ return TRUE;
+}
+
+
+gboolean
+bacon_video_widget_new_file_seek (BaconVideoWidget * bvw, gint64 start,
+ gint64 stop, gfloat rate)
+{
+
+ g_return_val_if_fail (bvw != NULL, FALSE);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
+ g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE);
+
+ GST_LOG ("Segment seeking from %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (start * GST_MSECOND));
+
+ if (start > bvw->priv->stream_length
+ && bvw->priv->stream_length > 0
+ && !g_str_has_prefix (bvw->priv->mrl, "dvd:")
+ && !g_str_has_prefix (bvw->priv->mrl, "vcd:")) {
+ if (bvw->priv->eos_id == 0)
+ bvw->priv->eos_id = g_idle_add (bvw_signal_eos_delayed, bvw);
+ return TRUE;
+ }
+
+
+ GST_LOG ("Segment seeking from %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (start * GST_MSECOND));
+
+ //FIXME Needs to wait until GST_STATE_PAUSED
+ gst_element_get_state (bvw->priv->play, NULL, NULL, 0);
+
+ got_time_tick (bvw->priv->play, start * GST_MSECOND, bvw);
+ gst_element_seek (bvw->priv->play, rate,
+ GST_FORMAT_TIME,
+ GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_SEGMENT |
+ GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET,
+ start * GST_MSECOND, GST_SEEK_TYPE_SET, stop * GST_MSECOND);
+ gst_element_set_state (bvw->priv->play, GST_STATE_PLAYING);
+
+ return TRUE;
+}
+
+gboolean
+bacon_video_widget_segment_seek (BaconVideoWidget * bvw, gint64 start,
+ gint64 stop, gfloat rate)
+{
+
+ g_return_val_if_fail (bvw != NULL, FALSE);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
+ g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE);
+
+ GST_LOG ("Segment seeking from %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (start * GST_MSECOND));
+
+
+ if (start > bvw->priv->stream_length
+ && bvw->priv->stream_length > 0
+ && !g_str_has_prefix (bvw->priv->mrl, "dvd:")
+ && !g_str_has_prefix (bvw->priv->mrl, "vcd:")) {
+ if (bvw->priv->eos_id == 0)
+ bvw->priv->eos_id = g_idle_add (bvw_signal_eos_delayed, bvw);
+ return TRUE;
+ }
+
+ got_time_tick (bvw->priv->play, start * GST_MSECOND, bvw);
+ gst_element_seek (bvw->priv->play, rate,
+ GST_FORMAT_TIME,
+ GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_SEGMENT |
+ GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET,
+ start * GST_MSECOND, GST_SEEK_TYPE_SET, stop * GST_MSECOND);
+
+ return TRUE;
+}
+
+gboolean
+bacon_video_widget_seek_to_next_frame (BaconVideoWidget * bvw, gfloat rate,
+ gboolean in_segment)
+{
+ gint64 pos = -1;
+ gboolean ret;
+
+ g_return_val_if_fail (bvw != NULL, FALSE);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
+ g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE);
+
+ gst_element_send_event(bvw->priv->play,
+ gst_event_new_step (GST_FORMAT_BUFFERS, 1, 1.0, TRUE, FALSE));
+
+ pos = bacon_video_widget_get_accurate_current_time (bvw);
+ got_time_tick (GST_ELEMENT (bvw->priv->play), pos * GST_MSECOND, bvw);
+
+ gst_x_overlay_expose (bvw->priv->xoverlay);
+
+ return ret;
+}
+
+gboolean
+bacon_video_widget_seek_to_previous_frame (BaconVideoWidget * bvw,
+ gfloat rate, gboolean in_segment)
+{
+ gint fps;
+ gint64 pos;
+ gint64 final_pos;
+ guint8 seek_flags;
+ gboolean ret;
+
+ g_return_val_if_fail (bvw != NULL, FALSE);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
+ g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE);
+
+
+ //Round framerate to the nearest integer
+ fps = (bvw->priv->video_fps_n + bvw->priv->video_fps_d / 2) /
+ bvw->priv->video_fps_d;
+ pos = bacon_video_widget_get_accurate_current_time (bvw);
+ final_pos = pos * GST_MSECOND - 1 * GST_SECOND / fps;
+
+ if (pos == 0)
+ return FALSE;
+
+ if (bacon_video_widget_is_playing (bvw))
+ bacon_video_widget_pause (bvw);
+
+ seek_flags = GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE;
+ if (in_segment)
+ seek_flags = seek_flags | GST_SEEK_FLAG_SEGMENT;
+ ret = gst_element_seek (bvw->priv->play, rate,
+ GST_FORMAT_TIME, seek_flags, GST_SEEK_TYPE_SET,
+ final_pos, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
+ gst_x_overlay_expose (bvw->priv->xoverlay);
+
+ got_time_tick (GST_ELEMENT (bvw->priv->play), pos * GST_MSECOND, bvw);
+
+ return ret;
+}
+
+gboolean
+bacon_video_widget_segment_stop_update (BaconVideoWidget * bvw, gint64 stop,
+ gfloat rate)
+{
+ g_return_val_if_fail (bvw != NULL, FALSE);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
+ g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE);
+
+ gst_element_seek (bvw->priv->play, rate,
+ GST_FORMAT_TIME,
+ GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_SEGMENT |
+ GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET,
+ stop * GST_MSECOND - 1, GST_SEEK_TYPE_SET, stop * GST_MSECOND);
+
+ if (bacon_video_widget_is_playing (bvw))
+ bacon_video_widget_pause (bvw);
+
+ gst_x_overlay_expose (bvw->priv->xoverlay);
+
+ return TRUE;
+}
+
+gboolean
+bacon_video_widget_segment_start_update (BaconVideoWidget * bvw, gint64 start,
+ gfloat rate)
+{
+ g_return_val_if_fail (bvw != NULL, FALSE);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
+ g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE);
+
+ gst_element_seek (bvw->priv->play, rate,
+ GST_FORMAT_TIME,
+ GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_SEGMENT |
+ GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET,
+ start * GST_MSECOND, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
+
+ if (bacon_video_widget_is_playing (bvw))
+ bacon_video_widget_pause (bvw);
+
+ gst_x_overlay_expose (bvw->priv->xoverlay);
+
+ return TRUE;
+}
+
+
+static void
+bvw_stop_play_pipeline (BaconVideoWidget * bvw)
+{
+ GstState cur_state;
+
+ gst_element_get_state (bvw->priv->play, &cur_state, NULL, 0);
+ if (cur_state > GST_STATE_READY) {
+ GstMessage *msg;
+ GstBus *bus;
+
+ GST_INFO ("stopping");
+ gst_element_set_state (bvw->priv->play, GST_STATE_READY);
+
+ /* process all remaining state-change messages so everything gets
+ * cleaned up properly (before the state change to NULL flushes them) */
+ GST_INFO ("processing pending state-change messages");
+ bus = gst_element_get_bus (bvw->priv->play);
+ while ((msg = gst_bus_poll (bus, GST_MESSAGE_STATE_CHANGED, 0))) {
+ gst_bus_async_signal_func (bus, msg, NULL);
+ gst_message_unref (msg);
+ }
+ gst_object_unref (bus);
+ }
+
+ gst_element_set_state (bvw->priv->play, GST_STATE_NULL);
+ bvw->priv->target_state = GST_STATE_NULL;
+ bvw->priv->buffering = FALSE;
+ bvw->priv->plugin_install_in_progress = FALSE;
+ bvw->priv->ignore_messages_mask = 0;
+ GST_INFO ("stopped");
+}
+
+/**
+ * bacon_video_widget_stop:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Stops playing the current stream and resets to the first position in the stream.
+ **/
+void
+bacon_video_widget_stop (BaconVideoWidget * bvw)
+{
+ g_return_if_fail (bvw != NULL);
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+ g_return_if_fail (GST_IS_ELEMENT (bvw->priv->play));
+
+ GST_LOG ("Stopping");
+ bvw_stop_play_pipeline (bvw);
+
+ /* Reset position to 0 when stopping */
+ got_time_tick (GST_ELEMENT (bvw->priv->play), 0, bvw);
+}
+
+
+/**
+ * bacon_video_widget_close:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Closes the current stream and frees the resources associated with it.
+ **/
+void
+bacon_video_widget_close (BaconVideoWidget * bvw)
+{
+ g_return_if_fail (bvw != NULL);
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+ g_return_if_fail (GST_IS_ELEMENT (bvw->priv->play));
+
+ GST_LOG ("Closing");
+ bvw_stop_play_pipeline (bvw);
+
+ g_free (bvw->priv->mrl);
+ bvw->priv->mrl = NULL;
+ bvw->priv->is_live = FALSE;
+ bvw->priv->window_resized = FALSE;
+
+ g_object_notify (G_OBJECT (bvw), "seekable");
+ g_signal_emit (bvw, bvw_signals[SIGNAL_CHANNELS_CHANGE], 0);
+ got_time_tick (GST_ELEMENT (bvw->priv->play), 0, bvw);
+}
+
+
+void
+bacon_video_widget_redraw_last_frame (BaconVideoWidget * bvw)
+{
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+ g_return_if_fail (bvw->priv->xoverlay != NULL);
+
+ if (!bvw->priv->logo_mode && !bacon_video_widget_is_playing (bvw)) {
+ gst_x_overlay_expose (bvw->priv->xoverlay);
+ }
+}
+
+#if 0
+static void
+bvw_do_navigation_command (BaconVideoWidget * bvw, GstNavigationCommand command)
+{
+ GstNavigation *nav = bvw_get_navigation_iface (bvw);
+ if (nav == NULL)
+ return;
+
+ gst_navigation_send_command (nav, command);
+ gst_object_unref (GST_OBJECT (nav));
+}
+
+/**
+ * bacon_video_widget_dvd_event:
+ * @bvw: a #BaconVideoWidget
+ * @type: the type of DVD event to issue
+ *
+ * Issues a DVD navigation event to the video widget, such as one to skip to the
+ * next chapter, or navigate to the DVD title menu.
+ *
+ * This is a no-op if the current stream is not navigable.
+ **/
+void
+bacon_video_widget_dvd_event (BaconVideoWidget * bvw, BvwDVDEvent type)
+{
+ g_return_if_fail (bvw != NULL);
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+ g_return_if_fail (GST_IS_ELEMENT (bvw->priv->play));
+
+ switch (type) {
+ case BVW_DVD_ROOT_MENU:
+ bvw_do_navigation_command (bvw, GST_NAVIGATION_COMMAND_DVD_MENU);
+ break;
+ case BVW_DVD_TITLE_MENU:
+ bvw_do_navigation_command (bvw, GST_NAVIGATION_COMMAND_DVD_TITLE_MENU);
+ break;
+ case BVW_DVD_SUBPICTURE_MENU:
+ bvw_do_navigation_command (bvw,
+ GST_NAVIGATION_COMMAND_DVD_SUBPICTURE_MENU);
+ break;
+ case BVW_DVD_AUDIO_MENU:
+ bvw_do_navigation_command (bvw, GST_NAVIGATION_COMMAND_DVD_AUDIO_MENU);
+ break;
+ case BVW_DVD_ANGLE_MENU:
+ bvw_do_navigation_command (bvw, GST_NAVIGATION_COMMAND_DVD_ANGLE_MENU);
+ break;
+ case BVW_DVD_CHAPTER_MENU:
+ bvw_do_navigation_command (bvw, GST_NAVIGATION_COMMAND_DVD_CHAPTER_MENU);
+ break;
+ case BVW_DVD_NEXT_ANGLE:
+ bvw_do_navigation_command (bvw, GST_NAVIGATION_COMMAND_NEXT_ANGLE);
+ break;
+ case BVW_DVD_PREV_ANGLE:
+ bvw_do_navigation_command (bvw, GST_NAVIGATION_COMMAND_PREV_ANGLE);
+ break;
+ case BVW_DVD_ROOT_MENU_UP:
+ bvw_do_navigation_command (bvw, GST_NAVIGATION_COMMAND_UP);
+ break;
+ case BVW_DVD_ROOT_MENU_DOWN:
+ bvw_do_navigation_command (bvw, GST_NAVIGATION_COMMAND_DOWN);
+ break;
+ case BVW_DVD_ROOT_MENU_LEFT:
+ bvw_do_navigation_command (bvw, GST_NAVIGATION_COMMAND_LEFT);
+ break;
+ case BVW_DVD_ROOT_MENU_RIGHT:
+ bvw_do_navigation_command (bvw, GST_NAVIGATION_COMMAND_RIGHT);
+ break;
+ case BVW_DVD_ROOT_MENU_SELECT:
+ bvw_do_navigation_command (bvw, GST_NAVIGATION_COMMAND_ACTIVATE);
+ break;
+ case BVW_DVD_NEXT_CHAPTER:
+ case BVW_DVD_PREV_CHAPTER:
+ case BVW_DVD_NEXT_TITLE:
+ case BVW_DVD_PREV_TITLE:
+ {
+ const gchar *fmt_name;
+ GstFormat fmt;
+ gint64 val;
+ gint dir;
+
+ if (type == BVW_DVD_NEXT_CHAPTER || type == BVW_DVD_NEXT_TITLE)
+ dir = 1;
+ else
+ dir = -1;
+
+ if (type == BVW_DVD_NEXT_CHAPTER || type == BVW_DVD_PREV_CHAPTER)
+ fmt_name = "chapter";
+ else if (type == BVW_DVD_NEXT_TITLE || type == BVW_DVD_PREV_TITLE)
+ fmt_name = "title";
+ else
+ fmt_name = "angle";
+
+ fmt = gst_format_get_by_nick (fmt_name);
+ if (gst_element_query_position (bvw->priv->play, &fmt, &val)) {
+ GST_DEBUG ("current %s is: %" G_GINT64_FORMAT, fmt_name, val);
+ val += dir;
+ GST_DEBUG ("seeking to %s: %" G_GINT64_FORMAT, val);
+ gst_element_seek (bvw->priv->play, 1.0, fmt, GST_SEEK_FLAG_FLUSH,
+ GST_SEEK_TYPE_SET, val, GST_SEEK_TYPE_NONE, 0);
+ } else {
+ GST_DEBUG ("failed to query position (%s)", fmt_name);
+ }
+ break;
+ }
+ default:
+ GST_WARNING ("unhandled type %d", type);
+ break;
+ }
+}
+#endif
+
+/**
+ * bacon_video_widget_set_logo:
+ * @bvw: a #BaconVideoWidget
+ * @filename: the logo filename
+ *
+ * Sets the logo displayed on the video widget when no stream is loaded.
+ **/
+void
+bacon_video_widget_set_logo (BaconVideoWidget * bvw, gchar * filename)
+{
+ GError *error = NULL;
+
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+ g_return_if_fail (filename != NULL);
+
+ if (bvw->priv->logo_pixbuf != NULL)
+ g_object_unref (bvw->priv->logo_pixbuf);
+
+ bvw->priv->logo_pixbuf = gdk_pixbuf_new_from_file (filename, &error);
+
+ if (error) {
+ g_warning ("An error occurred trying to open logo %s: %s",
+ filename, error->message);
+ g_error_free (error);
+ }
+}
+
+/**
+ * bacon_video_widget_set_logo_pixbuf:
+ * @bvw: a #BaconVideoWidget
+ * @logo: the logo #GdkPixbuf
+ *
+ * Sets the logo displayed on the video widget when no stream is loaded,
+ * by passing in a #GdkPixbuf directly. @logo is reffed, so can be unreffed
+ * once this function call is complete.
+ **/
+void
+bacon_video_widget_set_logo_pixbuf (BaconVideoWidget * bvw, GdkPixbuf * logo)
+{
+ g_return_if_fail (bvw != NULL);
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+ g_return_if_fail (logo != NULL);
+
+ if (bvw->priv->logo_pixbuf != NULL)
+ g_object_unref (bvw->priv->logo_pixbuf);
+
+ g_object_ref (logo);
+ bvw->priv->logo_pixbuf = logo;
+}
+
+/**
+ * bacon_video_widget_set_logo_mode:
+ * @bvw: a #BaconVideoWidget
+ * @logo_mode: %TRUE to display the logo, %FALSE otherwise
+ *
+ * Sets whether to display a logo set with @bacon_video_widget_set_logo when
+ * no stream is loaded. If @logo_mode is %FALSE, nothing will be displayed
+ * and the video widget will take up no space. Otherwise, the logo will be
+ * displayed and will requisition a corresponding amount of space.
+ **/
+void
+bacon_video_widget_set_logo_mode (BaconVideoWidget * bvw, gboolean logo_mode)
+{
+ BaconVideoWidgetPrivate *priv;
+
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+ priv = bvw->priv;
+
+ logo_mode = logo_mode != FALSE;
+
+ if (priv->logo_mode != logo_mode) {
+ priv->logo_mode = logo_mode;
+
+ if (priv->video_window) {
+ if (logo_mode) {
+ gdk_window_hide (priv->video_window);
+ GTK_WIDGET_SET_FLAGS (GTK_WIDGET (bvw), GTK_DOUBLE_BUFFERED);
+ } else {
+ gdk_window_show (priv->video_window);
+ GTK_WIDGET_UNSET_FLAGS (GTK_WIDGET (bvw), GTK_DOUBLE_BUFFERED);
+ }
+ }
+
+ g_object_notify (G_OBJECT (bvw), "logo_mode");
+ g_object_notify (G_OBJECT (bvw), "seekable");
+
+ /* Queue a redraw of the widget */
+ gtk_widget_queue_draw (GTK_WIDGET (bvw));
+ }
+}
+
+/**
+ * bacon_video_widget_get_logo_mode
+ * @bvw: a #BaconVideoWidget
+ *
+ * Gets whether the logo is displayed when no stream is loaded.
+ *
+ * Return value: %TRUE if the logo is displayed, %FALSE otherwise
+ **/
+gboolean
+bacon_video_widget_get_logo_mode (BaconVideoWidget * bvw)
+{
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
+
+ return bvw->priv->logo_mode;
+}
+
+void
+bacon_video_widget_set_drawing_pixbuf (BaconVideoWidget * bvw,
+ GdkPixbuf * drawing)
+{
+ g_return_if_fail (bvw != NULL);
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+ g_return_if_fail (drawing != NULL);
+
+ if (bvw->priv->drawing_pixbuf != NULL)
+ g_object_unref (bvw->priv->drawing_pixbuf);
+
+ g_object_ref (drawing);
+ bvw->priv->drawing_pixbuf = drawing;
+}
+
+void
+bacon_video_widget_set_drawing_mode (BaconVideoWidget * bvw,
+ gboolean drawing_mode)
+{
+ g_return_if_fail (bvw != NULL);
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+
+ bvw->priv->drawing_mode = drawing_mode;
+}
+
+/**
+ * bacon_video_widget_pause:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Pauses the current stream in the video widget.
+ *
+ * If a live stream is being played, playback is stopped entirely.
+ **/
+void
+bacon_video_widget_pause (BaconVideoWidget * bvw)
+{
+ g_return_if_fail (bvw != NULL);
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+ g_return_if_fail (GST_IS_ELEMENT (bvw->priv->play));
+ g_return_if_fail (bvw->priv->mrl != NULL);
+
+ if (bvw->priv->is_live != FALSE) {
+ GST_LOG ("Stopping because we have a live stream");
+ bacon_video_widget_stop (bvw);
+ return;
+ }
+
+ GST_LOG ("Pausing");
+ gst_element_set_state (GST_ELEMENT (bvw->priv->play), GST_STATE_PAUSED);
+ bvw->priv->target_state = GST_STATE_PAUSED;
+}
+
+/**
+ * bacon_video_widget_set_subtitle_font:
+ * @bvw: a #BaconVideoWidget
+ * @font: a font description string
+ *
+ * Sets the font size and style in which to display subtitles.
+ *
+ * @font is a Pango font description string, as understood by
+ * pango_font_description_from_string().
+ **/
+void
+bacon_video_widget_set_subtitle_font (BaconVideoWidget * bvw,
+ const gchar * font)
+{
+ g_return_if_fail (bvw != NULL);
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+ g_return_if_fail (GST_IS_ELEMENT (bvw->priv->play));
+
+ if (!g_object_class_find_property
+ (G_OBJECT_GET_CLASS (bvw->priv->play), "subtitle-font-desc"))
+ return;
+ g_object_set (bvw->priv->play, "subtitle-font-desc", font, NULL);
+}
+
+/**
+ * bacon_video_widget_set_subtitle_encoding:
+ * @bvw: a #BaconVideoWidget
+ * @encoding: an encoding system
+ *
+ * Sets the encoding system for the subtitles, so that they can be decoded
+ * properly.
+ **/
+void
+bacon_video_widget_set_subtitle_encoding (BaconVideoWidget * bvw,
+ const char *encoding)
+{
+ g_return_if_fail (bvw != NULL);
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+ g_return_if_fail (GST_IS_ELEMENT (bvw->priv->play));
+
+ if (!g_object_class_find_property
+ (G_OBJECT_GET_CLASS (bvw->priv->play), "subtitle-encoding"))
+ return;
+ g_object_set (bvw->priv->play, "subtitle-encoding", encoding, NULL);
+}
+
+/**
+ * bacon_video_widget_can_set_volume:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Returns whether the volume level can be set, given the current settings.
+ *
+ * The volume cannot be set if the audio output type is set to
+ * %BVW_AUDIO_SOUND_AC3PASSTHRU.
+ *
+ * Return value: %TRUE if the volume can be set, %FALSE otherwise
+ **/
+gboolean
+bacon_video_widget_can_set_volume (BaconVideoWidget * bvw)
+{
+ g_return_val_if_fail (bvw != NULL, FALSE);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
+ g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE);
+
+ if (bvw->priv->speakersetup == BVW_AUDIO_SOUND_AC3PASSTHRU)
+ return FALSE;
+
+ return !bvw->priv->uses_fakesink;
+}
+
+/**
+ * bacon_video_widget_set_volume:
+ * @bvw: a #BaconVideoWidget
+ * @volume: the new volume level, as a percentage between %0 and %1
+ *
+ * Sets the volume level of the stream as a percentage between %0 and %1.
+ *
+ * If bacon_video_widget_can_set_volume() returns %FALSE, this is a no-op.
+ **/
+void
+bacon_video_widget_set_volume (BaconVideoWidget * bvw, double volume)
+{
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+ g_return_if_fail (GST_IS_ELEMENT (bvw->priv->play));
+
+ if (bacon_video_widget_can_set_volume (bvw) != FALSE) {
+ volume = CLAMP (volume, 0.0, 1.0);
+ g_object_set (bvw->priv->play, "volume", (gdouble) volume, NULL);
+ g_object_notify (G_OBJECT (bvw), "volume");
+ }
+}
+
+/**
+ * bacon_video_widget_get_volume:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Returns the current volume level, as a percentage between %0 and %1.
+ *
+ * Return value: the volume as a percentage between %0 and %1
+ **/
+double
+bacon_video_widget_get_volume (BaconVideoWidget * bvw)
+{
+ double vol;
+
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 0.0);
+ g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), 0.0);
+
+ g_object_get (G_OBJECT (bvw->priv->play), "volume", &vol, NULL);
+
+ return vol;
+}
+
+/**
+ * bacon_video_widget_set_fullscreen:
+ * @bvw: a #BaconVideoWidget
+ * @fullscreen: %TRUE to go fullscreen, %FALSE otherwise
+ *
+ * Sets whether the widget renders the stream in fullscreen mode.
+ *
+ * Fullscreen rendering is done only when possible, as xvidmode is required.
+ **/
+void
+bacon_video_widget_set_fullscreen (BaconVideoWidget * bvw, gboolean fullscreen)
+{
+ gboolean have_xvidmode;
+
+ g_return_if_fail (bvw != NULL);
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+
+ g_object_get (G_OBJECT (bvw->priv->bacon_resize),
+ "have-xvidmode", &have_xvidmode, NULL);
+
+ if (have_xvidmode == FALSE)
+ return;
+
+ bvw->priv->fullscreen_mode = fullscreen;
+
+ if (fullscreen == FALSE) {
+ bacon_resize_restore (bvw->priv->bacon_resize);
+ /* Turn fullscreen on when we have xvidmode */
+ } else if (have_xvidmode != FALSE) {
+ bacon_resize_resize (bvw->priv->bacon_resize);
+ }
+}
+
+
+/**
+ * bacon_video_widget_set_show_cursor:
+ * @bvw: a #BaconVideoWidget
+ * @show_cursor: %TRUE to show the cursor, %FALSE otherwise
+ *
+ * Sets whether the cursor should be shown when it is over the video
+ * widget. If @show_cursor is %FALSE, the cursor will be invisible
+ * when it is moved over the video widget.
+ **/
+void
+bacon_video_widget_set_show_cursor (BaconVideoWidget * bvw,
+ gboolean show_cursor)
+{
+ GdkWindow *window;
+
+ g_return_if_fail (bvw != NULL);
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+
+ bvw->priv->cursor_shown = show_cursor;
+ window = gtk_widget_get_window (GTK_WIDGET (bvw));
+
+ if (!window) {
+ return;
+ }
+
+ if (show_cursor == FALSE) {
+ totem_gdk_window_set_invisible_cursor (window);
+ } else {
+ gdk_window_set_cursor (window, bvw->priv->cursor);
+ }
+}
+
+/**
+ * bacon_video_widget_get_show_cursor:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Returns whether the cursor is shown when it is over the video widget.
+ *
+ * Return value: %TRUE if the cursor is shown, %FALSE otherwise
+ **/
+gboolean
+bacon_video_widget_get_show_cursor (BaconVideoWidget * bvw)
+{
+ g_return_val_if_fail (bvw != NULL, FALSE);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
+
+ return bvw->priv->cursor_shown;
+}
+
+/**
+ * bacon_video_widget_get_auto_resize:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Returns whether the widget will automatically resize to fit videos.
+ *
+ * Return value: %TRUE if the widget will resize, %FALSE otherwise
+ **/
+gboolean
+bacon_video_widget_get_auto_resize (BaconVideoWidget * bvw)
+{
+ g_return_val_if_fail (bvw != NULL, FALSE);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
+
+ return bvw->priv->auto_resize;
+}
+
+/**
+ * bacon_video_widget_set_auto_resize:
+ * @bvw: a #BaconVideoWidget
+ * @auto_resize: %TRUE to automatically resize for new videos, %FALSE otherwise
+ *
+ * Sets whether the widget should automatically resize to fit to new videos when
+ * they are loaded. Changes to this will take effect when the next media file is
+ * loaded.
+ **/
+void
+bacon_video_widget_set_auto_resize (BaconVideoWidget * bvw,
+ gboolean auto_resize)
+{
+ g_return_if_fail (bvw != NULL);
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+
+ bvw->priv->auto_resize = auto_resize;
+
+ /* this will take effect when the next media file loads */
+}
+
+/**
+ * bacon_video_widget_set_aspect_ratio:
+ * @bvw: a #BaconVideoWidget
+ * @ratio: the new aspect ratio
+ *
+ * Sets the aspect ratio used by the widget, from #BaconVideoWidgetAspectRatio.
+ *
+ * Changes to this take effect immediately.
+ **/
+void
+bacon_video_widget_set_aspect_ratio (BaconVideoWidget * bvw,
+ BvwAspectRatio ratio)
+{
+ g_return_if_fail (bvw != NULL);
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+
+ bvw->priv->ratio_type = ratio;
+ got_video_size (bvw);
+}
+
+/**
+ * bacon_video_widget_get_aspect_ratio:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Returns the current aspect ratio used by the widget, from
+ * #BaconVideoWidgetAspectRatio.
+ *
+ * Return value: the aspect ratio
+ **/
+BvwAspectRatio
+bacon_video_widget_get_aspect_ratio (BaconVideoWidget * bvw)
+{
+ g_return_val_if_fail (bvw != NULL, 0);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 0);
+
+ return bvw->priv->ratio_type;
+}
+
+/**
+ * bacon_video_widget_set_scale_ratio:
+ * @bvw: a #BaconVideoWidget
+ * @ratio: the new scale ratio
+ *
+ * Sets the ratio by which the widget will scale videos when they are
+ * displayed. If @ratio is set to %0, the highest ratio possible will
+ * be chosen.
+ **/
+void
+bacon_video_widget_set_scale_ratio (BaconVideoWidget * bvw, gfloat ratio)
+{
+ gint w, h;
+
+ g_return_if_fail (bvw != NULL);
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+ g_return_if_fail (GST_IS_ELEMENT (bvw->priv->play));
+
+ GST_DEBUG ("ratio = %.2f", ratio);
+
+ if (bvw->priv->video_window == NULL)
+ return;
+
+ get_media_size (bvw, &w, &h);
+
+
+ if (ratio == 0.0) {
+ if (totem_ratio_fits_screen (bvw->priv->video_window, w, h, 2.0))
+ ratio = 2.0;
+ else if (totem_ratio_fits_screen (bvw->priv->video_window, w, h, 1.0))
+ ratio = 1.0;
+ else if (totem_ratio_fits_screen (bvw->priv->video_window, w, h, 0.5))
+ ratio = 0.5;
+ else
+ return;
+ } else {
+ if (!totem_ratio_fits_screen (bvw->priv->video_window, w, h, ratio)) {
+ GST_DEBUG ("movie doesn't fit on screen @ %.1fx (%dx%d)", w, h, ratio);
+ return;
+ }
+ }
+ w = (gfloat) w *ratio;
+ h = (gfloat) h *ratio;
+
+ shrink_toplevel (bvw);
+
+ GST_DEBUG ("setting preferred size %dx%d", w, h);
+ totem_widget_set_preferred_size (GTK_WIDGET (bvw), w, h);
+}
+
+/**
+ * bacon_video_widget_set_zoom:
+ * @bvw: a #BaconVideoWidget
+ * @zoom: a percentage zoom factor
+ *
+ * Sets the zoom factor applied to the video when it is displayed,
+ * as an integeric percentage between %0 and %1
+ * (e.g. set @zoom to %1 to not zoom at all).
+ **/
+void
+bacon_video_widget_set_zoom (BaconVideoWidget * bvw, double zoom)
+{
+ g_return_if_fail (bvw != NULL);
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+
+ bvw->priv->zoom = zoom;
+ if (bvw->priv->video_window != NULL)
+ resize_video_window (bvw);
+}
+
+/**
+ * bacon_video_widget_get_zoom:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Returns the zoom factor applied to videos displayed by the widget,
+ * as an integeric percentage between %0 and %1
+ * (e.g. %1 means no zooming at all).
+ *
+ * Return value: the zoom factor
+ **/
+double
+bacon_video_widget_get_zoom (BaconVideoWidget * bvw)
+{
+ g_return_val_if_fail (bvw != NULL, 1.0);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 1.0);
+
+ return bvw->priv->zoom;
+}
+
+
+/* Search for the color balance channel corresponding to type and return it. */
+static GstColorBalanceChannel *
+bvw_get_color_balance_channel (GstColorBalance * color_balance,
+ BvwVideoProperty type)
+{
+ const GList *channels;
+
+ channels = gst_color_balance_list_channels (color_balance);
+
+ for (; channels != NULL; channels = channels->next) {
+ GstColorBalanceChannel *c = channels->data;
+
+ if (type == BVW_VIDEO_BRIGHTNESS && g_strrstr (c->label, "BRIGHTNESS"))
+ return g_object_ref (c);
+ else if (type == BVW_VIDEO_CONTRAST && g_strrstr (c->label, "CONTRAST"))
+ return g_object_ref (c);
+ else if (type == BVW_VIDEO_SATURATION && g_strrstr (c->label, "SATURATION"))
+ return g_object_ref (c);
+ else if (type == BVW_VIDEO_HUE && g_strrstr (c->label, "HUE"))
+ return g_object_ref (c);
+ }
+
+ return NULL;
+}
+
+/**
+ * bacon_video_widget_get_video_property:
+ * @bvw: a #BaconVideoWidget
+ * @type: the type of property
+ *
+ * Returns the given property of the video, such as its brightness or saturation.
+ *
+ * It is returned as a percentage in the full range of integer values; from %0
+ * to %G_MAXINT, where %G_MAXINT/2 is the default.
+ *
+ * Return value: the property's value, in the range %0 to %G_MAXINT
+ **/
+int
+bacon_video_widget_get_video_property (BaconVideoWidget * bvw,
+ BvwVideoProperty type)
+{
+ int ret;
+
+ g_return_val_if_fail (bvw != NULL, 65535 / 2);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 65535 / 2);
+
+ g_mutex_lock (bvw->priv->lock);
+
+ ret = 0;
+
+ if (bvw->priv->balance && GST_IS_COLOR_BALANCE (bvw->priv->balance)) {
+ GstColorBalanceChannel *found_channel = NULL;
+
+ found_channel = bvw_get_color_balance_channel (bvw->priv->balance, type);
+
+ if (found_channel && GST_IS_COLOR_BALANCE_CHANNEL (found_channel)) {
+ gint cur;
+
+ cur = gst_color_balance_get_value (bvw->priv->balance, found_channel);
+
+ GST_DEBUG ("channel %s: cur=%d, min=%d, max=%d",
+ found_channel->label, cur, found_channel->min_value,
+ found_channel->max_value);
+
+ ret = floor (0.5 +
+ ((double) cur - found_channel->min_value) * 65535 /
+ ((double) found_channel->max_value - found_channel->min_value));
+
+ GST_DEBUG ("channel %s: returning value %d", found_channel->label, ret);
+ g_object_unref (found_channel);
+ goto done;
+ } else {
+ ret = -1;
+ }
+ }
+
+done:
+
+ g_mutex_unlock (bvw->priv->lock);
+ return ret;
+}
+
+/**
+ * bacon_video_widget_set_video_property:
+ * @bvw: a #BaconVideoWidget
+ * @type: the type of property
+ * @value: the property's value, in the range %0 to %G_MAXINT
+ *
+ * Sets the given property of the video, such as its brightness or saturation.
+ *
+ * It should be given as a percentage in the full range of integer values; from %0
+ * to %G_MAXINT, where %G_MAXINT/2 is the default.
+ **/
+void
+bacon_video_widget_set_video_property (BaconVideoWidget * bvw,
+ BvwVideoProperty type, int value)
+{
+ g_return_if_fail (bvw != NULL);
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+
+ GST_DEBUG ("set video property type %d to value %d", type, value);
+
+ if (!(value <= 65535 && value >= 0))
+ return;
+
+ if (bvw->priv->balance && GST_IS_COLOR_BALANCE (bvw->priv->balance)) {
+ GstColorBalanceChannel *found_channel = NULL;
+
+ found_channel = bvw_get_color_balance_channel (bvw->priv->balance, type);
+
+ if (found_channel && GST_IS_COLOR_BALANCE_CHANNEL (found_channel)) {
+ int i_value;
+
+ i_value = floor (0.5 + value * ((double) found_channel->max_value -
+ found_channel->min_value) / 65535 + found_channel->min_value);
+
+ GST_DEBUG ("channel %s: set to %d/65535", found_channel->label, value);
+
+ gst_color_balance_set_value (bvw->priv->balance, found_channel, i_value);
+
+ GST_DEBUG ("channel %s: val=%d, min=%d, max=%d",
+ found_channel->label, i_value, found_channel->min_value,
+ found_channel->max_value);
+
+ g_object_unref (found_channel);
+ }
+ }
+}
+
+/**
+ * bacon_video_widget_get_position:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Returns the current position in the stream, as a value between
+ * %0 and %1.
+ *
+ * Return value: the current position, or %-1
+ **/
+double
+bacon_video_widget_get_position (BaconVideoWidget * bvw)
+{
+ g_return_val_if_fail (bvw != NULL, -1);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), -1);
+ return bvw->priv->current_position;
+}
+
+/**
+ * bacon_video_widget_get_current_time:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Returns the current position in the stream, as the time (in milliseconds)
+ * since the beginning of the stream.
+ *
+ * Return value: time since the beginning of the stream, in milliseconds, or %-1
+ **/
+gint64
+bacon_video_widget_get_current_time (BaconVideoWidget * bvw)
+{
+ g_return_val_if_fail (bvw != NULL, -1);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), -1);
+ return bvw->priv->current_time;
+}
+
+/**
+ * bacon_video_widget_get_accurate_current_time:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Returns the current position in the stream, as the time (in milliseconds)
+ * since the beginning of the stream.
+ *
+ * Return value: time since the beginning of the stream querying directly to the pipeline, in milliseconds, or %-1
+ **/
+gint64
+bacon_video_widget_get_accurate_current_time (BaconVideoWidget * bvw)
+{
+ GstFormat fmt;
+ gint64 pos;
+
+ g_return_val_if_fail (bvw != NULL, -1);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), -1);
+
+ fmt = GST_FORMAT_TIME;
+ pos = -1;
+
+ gst_element_query_position (bvw->priv->play, &fmt, &pos);
+
+ return pos / GST_MSECOND;
+
+}
+
+
+
+/**
+ * bacon_video_widget_get_stream_length:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Returns the total length of the stream, in milliseconds.
+ *
+ * Return value: the stream length, in milliseconds, or %-1
+ **/
+gint64
+bacon_video_widget_get_stream_length (BaconVideoWidget * bvw)
+{
+ g_return_val_if_fail (bvw != NULL, -1);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), -1);
+
+ if (bvw->priv->stream_length == 0 && bvw->priv->play != NULL) {
+ GstFormat fmt = GST_FORMAT_TIME;
+ gint64 len = -1;
+
+ if (gst_element_query_duration (bvw->priv->play, &fmt, &len)
+ && len != -1) {
+ bvw->priv->stream_length = len / GST_MSECOND;
+ }
+ }
+
+ return bvw->priv->stream_length;
+}
+
+/**
+ * bacon_video_widget_is_playing:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Returns whether the widget is currently playing a stream.
+ *
+ * Return value: %TRUE if a stream is playing, %FALSE otherwise
+ **/
+gboolean
+bacon_video_widget_is_playing (BaconVideoWidget * bvw)
+{
+ gboolean ret;
+
+ g_return_val_if_fail (bvw != NULL, FALSE);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
+ g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE);
+
+ ret = (bvw->priv->target_state == GST_STATE_PLAYING);
+ GST_LOG ("%splaying", (ret) ? "" : "not ");
+
+ return ret;
+}
+
+/**
+ * bacon_video_widget_is_seekable:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Returns whether seeking is possible in the current stream.
+ *
+ * If no stream is loaded, %FALSE is returned.
+ *
+ * Return value: %TRUE if the stream is seekable, %FALSE otherwise
+ **/
+gboolean
+bacon_video_widget_is_seekable (BaconVideoWidget * bvw)
+{
+ gboolean res;
+ gint old_seekable;
+
+ g_return_val_if_fail (bvw != NULL, FALSE);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
+ g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE);
+
+ if (bvw->priv->mrl == NULL)
+ return FALSE;
+
+ old_seekable = bvw->priv->seekable;
+
+ if (bvw->priv->seekable == -1) {
+ GstQuery *query;
+
+ query = gst_query_new_seeking (GST_FORMAT_TIME);
+ if (gst_element_query (bvw->priv->play, query)) {
+ gst_query_parse_seeking (query, NULL, &res, NULL, NULL);
+ bvw->priv->seekable = (res) ? 1 : 0;
+ } else {
+ GST_DEBUG ("seeking query failed");
+ }
+ gst_query_unref (query);
+ }
+
+ if (bvw->priv->seekable != -1) {
+ res = (bvw->priv->seekable != 0);
+ goto done;
+ }
+
+ /* try to guess from duration (this is very unreliable though) */
+ if (bvw->priv->stream_length == 0) {
+ res = (bacon_video_widget_get_stream_length (bvw) > 0);
+ } else {
+ res = (bvw->priv->stream_length > 0);
+ }
+
+done:
+
+ if (old_seekable != bvw->priv->seekable)
+ g_object_notify (G_OBJECT (bvw), "seekable");
+
+ GST_DEBUG ("stream is%s seekable", (res) ? "" : " not");
+ return res;
+}
+
+
+static struct _metadata_map_info
+{
+ BvwMetadataType type;
+ const gchar *str;
+} metadata_str_map[] = {
+ {
+ BVW_INFO_TITLE, "title"}, {
+ BVW_INFO_ARTIST, "artist"}, {
+ BVW_INFO_YEAR, "year"}, {
+ BVW_INFO_COMMENT, "comment"}, {
+ BVW_INFO_ALBUM, "album"}, {
+ BVW_INFO_DURATION, "duration"}, {
+ BVW_INFO_TRACK_NUMBER, "track-number"}, {
+ BVW_INFO_HAS_VIDEO, "has-video"}, {
+ BVW_INFO_DIMENSION_X, "dimension-x"}, {
+ BVW_INFO_DIMENSION_Y, "dimension-y"}, {
+ BVW_INFO_VIDEO_BITRATE, "video-bitrate"}, {
+ BVW_INFO_VIDEO_CODEC, "video-codec"}, {
+ BVW_INFO_FPS, "fps"}, {
+ BVW_INFO_HAS_AUDIO, "has-audio"}, {
+ BVW_INFO_AUDIO_BITRATE, "audio-bitrate"}, {
+ BVW_INFO_AUDIO_CODEC, "audio-codec"}, {
+ BVW_INFO_AUDIO_SAMPLE_RATE, "samplerate"}, {
+ BVW_INFO_AUDIO_CHANNELS, "channels"}
+};
+
+static const gchar *
+get_metadata_type_name (BvwMetadataType type)
+{
+ guint i;
+ for (i = 0; i < G_N_ELEMENTS (metadata_str_map); ++i) {
+ if (metadata_str_map[i].type == type)
+ return metadata_str_map[i].str;
+ }
+ return "unknown";
+}
+
+static gint
+bvw_get_current_stream_num (BaconVideoWidget * bvw, const gchar * stream_type)
+{
+ gchar *lower, *cur_prop_str;
+ gint stream_num = -1;
+
+ if (bvw->priv->play == NULL)
+ return stream_num;
+
+ lower = g_ascii_strdown (stream_type, -1);
+ cur_prop_str = g_strconcat ("current-", lower, NULL);
+ g_object_get (bvw->priv->play, cur_prop_str, &stream_num, NULL);
+ g_free (cur_prop_str);
+ g_free (lower);
+
+ GST_LOG ("current %s stream: %d", stream_type, stream_num);
+ return stream_num;
+}
+
+static GstTagList *
+bvw_get_tags_of_current_stream (BaconVideoWidget * bvw,
+ const gchar * stream_type)
+{
+ GstTagList *tags = NULL;
+ gint stream_num = -1;
+ gchar *lower, *cur_sig_str;
+
+ stream_num = bvw_get_current_stream_num (bvw, stream_type);
+ if (stream_num < 0)
+ return NULL;
+
+ lower = g_ascii_strdown (stream_type, -1);
+ cur_sig_str = g_strconcat ("get-", lower, "-tags", NULL);
+ g_signal_emit_by_name (bvw->priv->play, cur_sig_str, stream_num, &tags);
+ g_free (cur_sig_str);
+ g_free (lower);
+
+ GST_LOG ("current %s stream tags %" GST_PTR_FORMAT, stream_type, tags);
+ return tags;
+}
+
+static GstCaps *
+bvw_get_caps_of_current_stream (BaconVideoWidget * bvw,
+ const gchar * stream_type)
+{
+ GstCaps *caps = NULL;
+ gint stream_num = -1;
+ GstPad *current;
+ gchar *lower, *cur_sig_str;
+
+ stream_num = bvw_get_current_stream_num (bvw, stream_type);
+ if (stream_num < 0)
+ return NULL;
+
+ lower = g_ascii_strdown (stream_type, -1);
+ cur_sig_str = g_strconcat ("get-", lower, "-pad", NULL);
+ g_signal_emit_by_name (bvw->priv->play, cur_sig_str, stream_num, ¤t);
+ g_free (cur_sig_str);
+ g_free (lower);
+
+ if (current != NULL) {
+ caps = gst_pad_get_negotiated_caps (current);
+ gst_object_unref (current);
+ }
+ GST_LOG ("current %s stream caps: %" GST_PTR_FORMAT, stream_type, caps);
+ return caps;
+}
+
+static gboolean
+audio_caps_have_LFE (GstStructure * s)
+{
+ GstAudioChannelPosition *positions;
+ gint i, channels;
+
+ if (!gst_structure_get_value (s, "channel-positions") ||
+ !gst_structure_get_int (s, "channels", &channels)) {
+ return FALSE;
+ }
+
+ positions = gst_audio_get_channel_positions (s);
+ if (positions == NULL)
+ return FALSE;
+
+ for (i = 0; i < channels; ++i) {
+ if (positions[i] == GST_AUDIO_CHANNEL_POSITION_LFE) {
+ g_free (positions);
+ return TRUE;
+ }
+ }
+
+ g_free (positions);
+ return FALSE;
+}
+
+static void
+bacon_video_widget_get_metadata_string (BaconVideoWidget * bvw,
+ BvwMetadataType type, GValue * value)
+{
+ char *string = NULL;
+ gboolean res = FALSE;
+
+ g_value_init (value, G_TYPE_STRING);
+
+ if (bvw->priv->play == NULL) {
+ g_value_set_string (value, NULL);
+ return;
+ }
+
+ switch (type) {
+ case BVW_INFO_TITLE:
+ if (bvw->priv->tagcache != NULL) {
+ res = gst_tag_list_get_string_index (bvw->priv->tagcache,
+ GST_TAG_TITLE, 0, &string);
+ }
+ break;
+ case BVW_INFO_ARTIST:
+ if (bvw->priv->tagcache != NULL) {
+ res = gst_tag_list_get_string_index (bvw->priv->tagcache,
+ GST_TAG_ARTIST, 0, &string);
+ }
+ break;
+ case BVW_INFO_YEAR:
+ if (bvw->priv->tagcache != NULL) {
+ GDate *date;
+
+ if ((res = gst_tag_list_get_date (bvw->priv->tagcache,
+ GST_TAG_DATE, &date))) {
+ string = g_strdup_printf ("%d", g_date_get_year (date));
+ g_date_free (date);
+ }
+ }
+ break;
+ case BVW_INFO_COMMENT:
+ if (bvw->priv->tagcache != NULL) {
+ res = gst_tag_list_get_string_index (bvw->priv->tagcache,
+ GST_TAG_COMMENT, 0, &string);
+ }
+ break;
+ case BVW_INFO_ALBUM:
+ if (bvw->priv->tagcache != NULL) {
+ res = gst_tag_list_get_string_index (bvw->priv->tagcache,
+ GST_TAG_ALBUM, 0, &string);
+ }
+ break;
+ case BVW_INFO_VIDEO_CODEC:
+ {
+ GstTagList *tags;
+
+ /* try to get this from the stream info first */
+ if ((tags = bvw_get_tags_of_current_stream (bvw, "video"))) {
+ res = gst_tag_list_get_string (tags, GST_TAG_CODEC, &string);
+ gst_tag_list_free (tags);
+ }
+
+ /* if that didn't work, try the aggregated tags */
+ if (!res && bvw->priv->tagcache != NULL) {
+ res = gst_tag_list_get_string (bvw->priv->tagcache,
+ GST_TAG_VIDEO_CODEC, &string);
+ }
+ break;
+ }
+ case BVW_INFO_AUDIO_CODEC:
+ {
+ GstTagList *tags;
+
+ /* try to get this from the stream info first */
+ if ((tags = bvw_get_tags_of_current_stream (bvw, "audio"))) {
+ res = gst_tag_list_get_string (tags, GST_TAG_CODEC, &string);
+ gst_tag_list_free (tags);
+ }
+
+ /* if that didn't work, try the aggregated tags */
+ if (!res && bvw->priv->tagcache != NULL) {
+ res = gst_tag_list_get_string (bvw->priv->tagcache,
+ GST_TAG_AUDIO_CODEC, &string);
+ }
+ break;
+ }
+ case BVW_INFO_AUDIO_CHANNELS:
+ {
+ GstStructure *s;
+ GstCaps *caps;
+
+ caps = bvw_get_caps_of_current_stream (bvw, "audio");
+ if (caps) {
+ gint channels = 0;
+
+ s = gst_caps_get_structure (caps, 0);
+ if ((res = gst_structure_get_int (s, "channels", &channels))) {
+ /* FIXME: do something more sophisticated - but what? */
+ if (channels > 2 && audio_caps_have_LFE (s)) {
+ string = g_strdup_printf ("%s %d.1", _("Surround"), channels - 1);
+ } else if (channels == 1) {
+ string = g_strdup (_("Mono"));
+ } else if (channels == 2) {
+ string = g_strdup (_("Stereo"));
+ } else {
+ string = g_strdup_printf ("%d", channels);
+ }
+ }
+ gst_caps_unref (caps);
+ }
+ break;
+ }
+ default:
+ g_assert_not_reached ();
+ }
+
+ /* Remove line feeds */
+ if (string && strstr (string, "\n") != NULL)
+ g_strdelimit (string, "\n", ' ');
+
+ if (res && string && g_utf8_validate (string, -1, NULL)) {
+ g_value_take_string (value, string);
+ GST_DEBUG ("%s = '%s'", get_metadata_type_name (type), string);
+ } else {
+ g_value_set_string (value, NULL);
+ g_free (string);
+ }
+
+ return;
+}
+
+static void
+bacon_video_widget_get_metadata_int (BaconVideoWidget * bvw,
+ BvwMetadataType type, GValue * value)
+{
+ int integer = 0;
+
+ g_value_init (value, G_TYPE_INT);
+
+ if (bvw->priv->play == NULL) {
+ g_value_set_int (value, 0);
+ return;
+ }
+
+ switch (type) {
+ case BVW_INFO_DURATION:
+ integer = bacon_video_widget_get_stream_length (bvw) / 1000;
+ break;
+ case BVW_INFO_TRACK_NUMBER:
+ if (bvw->priv->tagcache == NULL)
+ break;
+ if (!gst_tag_list_get_uint (bvw->priv->tagcache,
+ GST_TAG_TRACK_NUMBER, (guint *) & integer))
+ integer = 0;
+ break;
+ case BVW_INFO_DIMENSION_X:
+ integer = bvw->priv->video_width;
+ break;
+ case BVW_INFO_DIMENSION_Y:
+ integer = bvw->priv->video_height;
+ break;
+ case BVW_INFO_FPS:
+ if (bvw->priv->video_fps_d > 0) {
+ /* Round up/down to the nearest integer framerate */
+ integer = (bvw->priv->video_fps_n + bvw->priv->video_fps_d / 2) /
+ bvw->priv->video_fps_d;
+ } else
+ integer = 0;
+ break;
+ case BVW_INFO_AUDIO_BITRATE:
+ if (bvw->priv->audiotags == NULL)
+ break;
+ if (gst_tag_list_get_uint (bvw->priv->audiotags, GST_TAG_BITRATE,
+ (guint *) & integer) ||
+ gst_tag_list_get_uint (bvw->priv->audiotags,
+ GST_TAG_NOMINAL_BITRATE, (guint *) & integer)) {
+ integer /= 1000;
+ }
+ break;
+ case BVW_INFO_VIDEO_BITRATE:
+ if (bvw->priv->videotags == NULL)
+ break;
+ if (gst_tag_list_get_uint (bvw->priv->videotags, GST_TAG_BITRATE,
+ (guint *) & integer) ||
+ gst_tag_list_get_uint (bvw->priv->videotags,
+ GST_TAG_NOMINAL_BITRATE, (guint *) & integer)) {
+ integer /= 1000;
+ }
+ break;
+ case BVW_INFO_AUDIO_SAMPLE_RATE:
+ {
+ GstStructure *s;
+ GstCaps *caps;
+
+ caps = bvw_get_caps_of_current_stream (bvw, "audio");
+ if (caps) {
+ s = gst_caps_get_structure (caps, 0);
+ gst_structure_get_int (s, "rate", &integer);
+ gst_caps_unref (caps);
+ }
+ break;
+ }
+ default:
+ g_assert_not_reached ();
+ }
+
+ g_value_set_int (value, integer);
+ GST_DEBUG ("%s = %d", get_metadata_type_name (type), integer);
+
+ return;
+}
+
+static void
+bacon_video_widget_get_metadata_bool (BaconVideoWidget * bvw,
+ BvwMetadataType type, GValue * value)
+{
+ gboolean boolean = FALSE;
+
+ g_value_init (value, G_TYPE_BOOLEAN);
+
+ if (bvw->priv->play == NULL) {
+ g_value_set_boolean (value, FALSE);
+ return;
+ }
+
+ GST_DEBUG ("tagcache = %" GST_PTR_FORMAT, bvw->priv->tagcache);
+ GST_DEBUG ("videotags = %" GST_PTR_FORMAT, bvw->priv->videotags);
+ GST_DEBUG ("audiotags = %" GST_PTR_FORMAT, bvw->priv->audiotags);
+
+ switch (type) {
+ case BVW_INFO_HAS_VIDEO:
+ boolean = bvw->priv->media_has_video;
+ /* if properties dialog, show the metadata we
+ * have even if we cannot decode the stream */
+ if (!boolean && bvw->priv->use_type == BVW_USE_TYPE_METADATA &&
+ bvw->priv->tagcache != NULL &&
+ gst_structure_has_field ((GstStructure *) bvw->priv->tagcache,
+ GST_TAG_VIDEO_CODEC)) {
+ boolean = TRUE;
+ }
+ break;
+ case BVW_INFO_HAS_AUDIO:
+ boolean = bvw->priv->media_has_audio;
+ /* if properties dialog, show the metadata we
+ * have even if we cannot decode the stream */
+ if (!boolean && bvw->priv->use_type == BVW_USE_TYPE_METADATA &&
+ bvw->priv->tagcache != NULL &&
+ gst_structure_has_field ((GstStructure *) bvw->priv->tagcache,
+ GST_TAG_AUDIO_CODEC)) {
+ boolean = TRUE;
+ }
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ g_value_set_boolean (value, boolean);
+ GST_DEBUG ("%s = %s", get_metadata_type_name (type),
+ (boolean) ? "yes" : "no");
+
+ return;
+}
+
+static void
+bvw_process_pending_tag_messages (BaconVideoWidget * bvw)
+{
+ GstMessageType events;
+ GstMessage *msg;
+ GstBus *bus;
+
+ /* process any pending tag messages on the bus NOW, so we can get to
+ * the information without/before giving control back to the main loop */
+
+ /* application message is for stream-info */
+ events = GST_MESSAGE_TAG | GST_MESSAGE_DURATION | GST_MESSAGE_APPLICATION;
+ bus = gst_element_get_bus (bvw->priv->play);
+ while ((msg = gst_bus_poll (bus, events, 0))) {
+ gst_bus_async_signal_func (bus, msg, NULL);
+ }
+ gst_object_unref (bus);
+}
+
+static GdkPixbuf *
+bacon_video_widget_get_metadata_pixbuf (BaconVideoWidget * bvw,
+ GstBuffer * buffer)
+{
+ GdkPixbufLoader *loader;
+ GdkPixbuf *pixbuf;
+
+ loader = gdk_pixbuf_loader_new ();
+ if (!gdk_pixbuf_loader_write (loader, buffer->data, buffer->size, NULL)) {
+ g_object_unref (loader);
+ return NULL;
+ }
+ if (!gdk_pixbuf_loader_close (loader, NULL)) {
+ g_object_unref (loader);
+ return NULL;
+ }
+
+ pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+ if (pixbuf)
+ g_object_ref (pixbuf);
+ g_object_unref (loader);
+ return pixbuf;
+}
+
+static const GValue *
+bacon_video_widget_get_best_image (BaconVideoWidget * bvw)
+{
+ const GValue *cover_value = NULL;
+ guint i;
+
+ for (i = 0;; i++) {
+ const GValue *value;
+ GstBuffer *buffer;
+ GstStructure *caps_struct;
+ int type;
+
+ value = gst_tag_list_get_value_index (bvw->priv->tagcache,
+ GST_TAG_IMAGE, i);
+ if (value == NULL)
+ break;
+
+ buffer = gst_value_get_buffer (value);
+
+ caps_struct = gst_caps_get_structure (buffer->caps, 0);
+ gst_structure_get_enum (caps_struct,
+ "image-type", GST_TYPE_TAG_IMAGE_TYPE, &type);
+ if (type == GST_TAG_IMAGE_TYPE_UNDEFINED) {
+ if (cover_value == NULL)
+ cover_value = value;
+ } else if (type == GST_TAG_IMAGE_TYPE_FRONT_COVER) {
+ cover_value = value;
+ break;
+ }
+ }
+
+ return cover_value;
+}
+
+/**
+ * bacon_video_widget_get_metadata:
+ * @bvw: a #BaconVideoWidget
+ * @type: the type of metadata to return
+ * @value: a #GValue
+ *
+ * Provides metadata of the given @type about the current stream in @value.
+ *
+ * Free the #GValue with g_value_unset().
+ **/
+void
+bacon_video_widget_get_metadata (BaconVideoWidget * bvw,
+ BvwMetadataType type, GValue * value)
+{
+ g_return_if_fail (bvw != NULL);
+ g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
+ g_return_if_fail (GST_IS_ELEMENT (bvw->priv->play));
+
+ switch (type) {
+ case BVW_INFO_TITLE:
+ case BVW_INFO_ARTIST:
+ case BVW_INFO_YEAR:
+ case BVW_INFO_COMMENT:
+ case BVW_INFO_ALBUM:
+ case BVW_INFO_VIDEO_CODEC:
+ bacon_video_widget_get_metadata_string (bvw, type, value);
+ break;
+ case BVW_INFO_AUDIO_CODEC:
+ bacon_video_widget_get_metadata_string (bvw, type, value);
+ break;
+ case BVW_INFO_AUDIO_CHANNELS:
+ bacon_video_widget_get_metadata_string (bvw, type, value);
+ break;
+ case BVW_INFO_DURATION:
+ bacon_video_widget_get_metadata_int (bvw, type, value);
+ break;
+ case BVW_INFO_DIMENSION_X:
+ bacon_video_widget_get_metadata_int (bvw, type, value);
+ break;
+ case BVW_INFO_DIMENSION_Y:
+ bacon_video_widget_get_metadata_int (bvw, type, value);
+ break;
+ case BVW_INFO_FPS:
+ bacon_video_widget_get_metadata_int (bvw, type, value);
+ break;
+ case BVW_INFO_AUDIO_BITRATE:
+ bacon_video_widget_get_metadata_int (bvw, type, value);
+ break;
+ case BVW_INFO_VIDEO_BITRATE:
+ bacon_video_widget_get_metadata_int (bvw, type, value);
+ break;
+ case BVW_INFO_TRACK_NUMBER:
+ case BVW_INFO_AUDIO_SAMPLE_RATE:
+ bacon_video_widget_get_metadata_int (bvw, type, value);
+ break;
+ case BVW_INFO_HAS_VIDEO:
+ bacon_video_widget_get_metadata_bool (bvw, type, value);
+ break;
+ case BVW_INFO_HAS_AUDIO:
+ bacon_video_widget_get_metadata_bool (bvw, type, value);
+ break;
+ case BVW_INFO_COVER:
+ {
+ const GValue *cover_value;
+
+ g_value_init (value, G_TYPE_OBJECT);
+
+ if (bvw->priv->tagcache == NULL)
+ break;
+ cover_value = bacon_video_widget_get_best_image (bvw);
+ if (!cover_value) {
+ cover_value = gst_tag_list_get_value_index (bvw->priv->tagcache,
+ GST_TAG_PREVIEW_IMAGE, 0);
+ }
+ if (cover_value) {
+ GstBuffer *buffer;
+ GdkPixbuf *pixbuf;
+
+ buffer = gst_value_get_buffer (cover_value);
+ pixbuf = bacon_video_widget_get_metadata_pixbuf (bvw, buffer);
+ if (pixbuf)
+ g_value_take_object (value, pixbuf);
+ }
+ }
+ break;
+ default:
+ g_return_if_reached ();
+ }
+
+ return;
+}
+
+/* Screenshot functions */
+
+/**
+ * bacon_video_widget_can_get_frames:
+ * @bvw: a #BaconVideoWidget
+ * @error: a #GError, or %NULL
+ *
+ * Determines whether individual frames from the current stream can
+ * be returned using bacon_video_widget_get_current_frame().
+ *
+ * Frames cannot be returned for audio-only streams, unless visualisations
+ * are enabled.
+ *
+ * Return value: %TRUE if frames can be captured, %FALSE otherwise
+ **/
+gboolean
+bacon_video_widget_can_get_frames (BaconVideoWidget * bvw, GError ** error)
+{
+ g_return_val_if_fail (bvw != NULL, FALSE);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
+ g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE);
+
+ /* check for version */
+ if (!g_object_class_find_property
+ (G_OBJECT_GET_CLASS (bvw->priv->play), "frame")) {
+ g_set_error_literal (error, BVW_ERROR, GST_ERROR_GENERIC,
+ _("Too old version of GStreamer installed."));
+ return FALSE;
+ }
+
+ /* check for video */
+ if (!bvw->priv->media_has_video) {
+ g_set_error_literal (error, BVW_ERROR, GST_ERROR_GENERIC,
+ _("Media contains no supported video streams."));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+destroy_pixbuf (guchar * pix, gpointer data)
+{
+ gst_buffer_unref (GST_BUFFER (data));
+}
+
+void
+bacon_video_widget_unref_pixbuf (GdkPixbuf * pixbuf)
+{
+ g_object_unref (pixbuf);
+}
+
+/**
+ * bacon_video_widget_get_current_frame:
+ * @bvw: a #BaconVideoWidget
+ *
+ * Returns a #GdkPixbuf containing the current frame from the playing
+ * stream. This will wait for any pending seeks to complete before
+ * capturing the frame.
+ *
+ * Return value: the current frame, or %NULL; unref with g_object_unref()
+ **/
+GdkPixbuf *
+bacon_video_widget_get_current_frame (BaconVideoWidget * bvw)
+{
+ GstStructure *s;
+ GstBuffer *buf = NULL;
+ GdkPixbuf *pixbuf;
+ GstCaps *to_caps;
+ gint outwidth = 0;
+ gint outheight = 0;
+
+ g_return_val_if_fail (bvw != NULL, NULL);
+ g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), NULL);
+ g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), NULL);
+
+ /*[> when used as thumbnailer, wait for pending seeks to complete <] */
+ /*if (bvw->priv->use_type == BVW_USE_TYPE_CAPTURE) */
+ /*{ */
+ /*gst_element_get_state (bvw->priv->play, NULL, NULL, -1); */
+ /*} */
+ gst_element_get_state (bvw->priv->play, NULL, NULL, -1);
+
+ /* no video info */
+ if (!bvw->priv->video_width || !bvw->priv->video_height) {
+ GST_DEBUG ("Could not take screenshot: %s", "no video info");
+ g_warning ("Could not take screenshot: %s", "no video info");
+ return NULL;
+ }
+
+ /* get frame */
+ g_object_get (bvw->priv->play, "frame", &buf, NULL);
+
+ if (!buf) {
+ GST_DEBUG ("Could not take screenshot: %s", "no last video frame");
+ g_warning ("Could not take screenshot: %s", "no last video frame");
+ return NULL;
+ }
+
+ if (GST_BUFFER_CAPS (buf) == NULL) {
+ GST_DEBUG ("Could not take screenshot: %s", "no caps on buffer");
+ g_warning ("Could not take screenshot: %s", "no caps on buffer");
+ return NULL;
+ }
+
+ /* convert to our desired format (RGB24) */
+ to_caps = gst_caps_new_simple ("video/x-raw-rgb",
+ "bpp", G_TYPE_INT, 24, "depth", G_TYPE_INT, 24,
+ /* Note: we don't ask for a specific width/height here, so that
+ * videoscale can adjust dimensions from a non-1/1 pixel aspect
+ * ratio to a 1/1 pixel-aspect-ratio */
+ "pixel-aspect-ratio", GST_TYPE_FRACTION, 1,
+ 1, "endianness", G_TYPE_INT, G_BIG_ENDIAN,
+ "red_mask", G_TYPE_INT, 0xff0000,
+ "green_mask", G_TYPE_INT, 0x00ff00,
+ "blue_mask", G_TYPE_INT, 0x0000ff, NULL);
+
+ if (bvw->priv->video_fps_n > 0 && bvw->priv->video_fps_d > 0) {
+ gst_caps_set_simple (to_caps, "framerate", GST_TYPE_FRACTION,
+ bvw->priv->video_fps_n, bvw->priv->video_fps_d, NULL);
+ }
+
+ GST_DEBUG ("frame caps: %" GST_PTR_FORMAT, GST_BUFFER_CAPS (buf));
+ GST_DEBUG ("pixbuf caps: %" GST_PTR_FORMAT, to_caps);
+
+ /* bvw_frame_conv_convert () takes ownership of the buffer passed */
+ buf = bvw_frame_conv_convert (buf, to_caps);
+
+ gst_caps_unref (to_caps);
+
+ if (!buf) {
+ GST_DEBUG ("Could not take screenshot: %s", "conversion failed");
+ g_warning ("Could not take screenshot: %s", "conversion failed");
+ return NULL;
+ }
+
+ if (!GST_BUFFER_CAPS (buf)) {
+ GST_DEBUG ("Could not take screenshot: %s", "no caps on output buffer");
+ g_warning ("Could not take screenshot: %s", "no caps on output buffer");
+ return NULL;
+ }
+
+ s = gst_caps_get_structure (GST_BUFFER_CAPS (buf), 0);
+ gst_structure_get_int (s, "width", &outwidth);
+ gst_structure_get_int (s, "height", &outheight);
+ g_return_val_if_fail (outwidth > 0 && outheight > 0, NULL);
+
+ /* create pixbuf from that - use our own destroy function */
+ pixbuf = gdk_pixbuf_new_from_data (GST_BUFFER_DATA (buf),
+ GDK_COLORSPACE_RGB, FALSE, 8, outwidth,
+ outheight, GST_ROUND_UP_4 (outwidth * 3), destroy_pixbuf, buf);
+
+ if (!pixbuf) {
+ GST_DEBUG ("Could not take screenshot: %s", "could not create pixbuf");
+ g_warning ("Could not take screenshot: %s", "could not create pixbuf");
+ gst_buffer_unref (buf);
+ }
+
+ return pixbuf;
+}
+
+
+/* =========================================== */
+/* */
+/* Widget typing & Creation */
+/* */
+/* =========================================== */
+
+G_DEFINE_TYPE (BaconVideoWidget, bacon_video_widget, GTK_TYPE_EVENT_BOX)
+/* applications must use exactly one of bacon_video_widget_get_option_group()
+ * OR bacon_video_widget_init_backend(), but not both */
+/**
+ * bacon_video_widget_get_option_group:
+ *
+ * Returns the #GOptionGroup containing command-line options for
+ * #BaconVideoWidget.
+ *
+ * Applications must call either this or bacon_video_widget_init_backend() exactly
+ * once; but not both.
+ *
+ * Return value: a #GOptionGroup giving command-line options for #BaconVideoWidget
+ **/
+ GOptionGroup *bacon_video_widget_get_option_group (void)
+{
+ return gst_init_get_option_group ();
+}
+
+/**
+ * bacon_video_widget_init_backend:
+ * @argc: pointer to application's argc
+ * @argv: pointer to application's argv
+ *
+ * Initialises #BaconVideoWidget's GStreamer backend. If this fails
+ * for the GStreamer backend, your application will be terminated.
+ *
+ * Applications must call either this or bacon_video_widget_get_option_group() exactly
+ * once; but not both.
+ **/
+void
+bacon_video_widget_init_backend (int *argc, char ***argv)
+{
+ gst_init (argc, argv);
+}
+
+GQuark
+bacon_video_widget_error_quark (void)
+{
+ static GQuark q; /* 0 */
+
+ if (G_UNLIKELY (q == 0)) {
+ q = g_quark_from_static_string ("bvw-error-quark");
+ }
+ return q;
+}
+
+/* fold function to pick the best colorspace element */
+static gboolean
+find_colorbalance_element (GstElement * element, GValue * ret, GstElement ** cb)
+{
+ GstColorBalanceClass *cb_class;
+
+ GST_DEBUG ("Checking element %s ...", GST_OBJECT_NAME (element));
+
+ if (!GST_IS_COLOR_BALANCE (element))
+ return TRUE;
+
+ GST_DEBUG ("Element %s is a color balance", GST_OBJECT_NAME (element));
+
+ cb_class = GST_COLOR_BALANCE_GET_CLASS (element);
+ if (GST_COLOR_BALANCE_TYPE (cb_class) == GST_COLOR_BALANCE_HARDWARE) {
+ gst_object_replace ((GstObject **) cb, (GstObject *) element);
+ /* shortcuts the fold */
+ return FALSE;
+ } else if (*cb == NULL) {
+ gst_object_replace ((GstObject **) cb, (GstObject *) element);
+ return TRUE;
+ } else {
+ return TRUE;
+ }
+}
+
+static gboolean
+bvw_update_interfaces_delayed (BaconVideoWidget * bvw)
+{
+ GST_DEBUG ("Delayed updating interface implementations");
+ g_mutex_lock (bvw->priv->lock);
+ bvw_update_interface_implementations (bvw);
+ bvw->priv->interface_update_id = 0;
+ g_mutex_unlock (bvw->priv->lock);
+
+ return FALSE;
+}
+
+/* Must be called with bvw->priv->lock held */
+static void
+bvw_update_interface_implementations (BaconVideoWidget * bvw)
+{
+ GstColorBalance *old_balance = bvw->priv->balance;
+ GstXOverlay *old_xoverlay = bvw->priv->xoverlay;
+ GstElement *video_sink = NULL;
+ GstElement *element = NULL;
+ GstIteratorResult ires;
+ GstIterator *iter;
+
+ if (g_thread_self () != gui_thread) {
+ if (bvw->priv->balance)
+ gst_object_unref (bvw->priv->balance);
+ bvw->priv->balance = NULL;
+ if (bvw->priv->xoverlay)
+ gst_object_unref (bvw->priv->xoverlay);
+ bvw->priv->xoverlay = NULL;
+ if (bvw->priv->navigation)
+ gst_object_unref (bvw->priv->navigation);
+ bvw->priv->navigation = NULL;
+
+ if (bvw->priv->interface_update_id)
+ g_source_remove (bvw->priv->interface_update_id);
+ bvw->priv->interface_update_id =
+ g_idle_add ((GSourceFunc) bvw_update_interfaces_delayed, bvw);
+ return;
+ }
+
+ g_object_get (bvw->priv->play, "video-sink", &video_sink, NULL);
+ g_assert (video_sink != NULL);
+
+ /* We try to get an element supporting XOverlay interface */
+ if (GST_IS_BIN (video_sink)) {
+ GST_DEBUG ("Retrieving xoverlay from bin ...");
+ element = gst_bin_get_by_interface (GST_BIN (video_sink),
+ GST_TYPE_X_OVERLAY);
+ } else {
+ element = gst_object_ref (video_sink);
+ }
+
+ if (GST_IS_X_OVERLAY (element)) {
+ GST_DEBUG ("Found xoverlay: %s", GST_OBJECT_NAME (element));
+ bvw->priv->xoverlay = GST_X_OVERLAY (element);
+ } else {
+ GST_DEBUG ("No xoverlay found");
+ if (element)
+ gst_object_unref (element);
+ bvw->priv->xoverlay = NULL;
+ }
+
+ /* Try to find the navigation interface */
+ if (GST_IS_BIN (video_sink)) {
+ GST_DEBUG ("Retrieving navigation from bin ...");
+ element = gst_bin_get_by_interface (GST_BIN (video_sink),
+ GST_TYPE_NAVIGATION);
+ } else {
+ element = gst_object_ref (video_sink);
+ }
+
+ if (GST_IS_NAVIGATION (element)) {
+ GST_DEBUG ("Found navigation: %s", GST_OBJECT_NAME (element));
+ bvw->priv->navigation = GST_NAVIGATION (element);
+ } else {
+ GST_DEBUG ("No navigation found");
+ if (element)
+ gst_object_unref (element);
+ bvw->priv->navigation = NULL;
+ }
+
+ /* Find best color balance element (using custom iterator so
+ * we can prefer hardware implementations to software ones) */
+
+ /* FIXME: this doesn't work reliably yet, most of the time
+ * the fold function doesn't even get called, while sometimes
+ * it does ... */
+ iter = gst_bin_iterate_all_by_interface (GST_BIN (bvw->priv->play),
+ GST_TYPE_COLOR_BALANCE);
+ /* naively assume no resync */
+ element = NULL;
+ ires = gst_iterator_fold (iter, (GstIteratorFoldFunction)
+ find_colorbalance_element, NULL, &element);
+ gst_iterator_free (iter);
+
+ if (element) {
+ bvw->priv->balance = GST_COLOR_BALANCE (element);
+ GST_DEBUG ("Best colorbalance found: %s",
+ GST_OBJECT_NAME (bvw->priv->balance));
+ } else if (GST_IS_COLOR_BALANCE (bvw->priv->xoverlay)) {
+ bvw->priv->balance = GST_COLOR_BALANCE (bvw->priv->xoverlay);
+ gst_object_ref (bvw->priv->balance);
+ GST_DEBUG ("Colorbalance backup found: %s",
+ GST_OBJECT_NAME (bvw->priv->balance));
+ } else {
+ GST_DEBUG ("No colorbalance found");
+ bvw->priv->balance = NULL;
+ }
+
+ if (old_xoverlay)
+ gst_object_unref (GST_OBJECT (old_xoverlay));
+
+ if (old_balance)
+ gst_object_unref (GST_OBJECT (old_balance));
+
+ gst_object_unref (video_sink);
+}
+
+
+static void
+bvw_element_msg_sync (GstBus * bus, GstMessage * msg, gpointer data)
+{
+
+ BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (data);
+
+ g_assert (msg->type == GST_MESSAGE_ELEMENT);
+
+ if (msg->structure == NULL)
+ return;
+
+ /* This only gets sent if we haven't set an ID yet. This is our last
+ * chance to set it before the video sink will create its own window */
+ if (gst_structure_has_name (msg->structure, "prepare-xwindow-id")) {
+ GST_INFO ("Handling sync prepare-xwindow-id message");
+
+ g_mutex_lock (bvw->priv->lock);
+ bvw_update_interface_implementations (bvw);
+ g_mutex_unlock (bvw->priv->lock);
+
+ if (bvw->priv->xoverlay == NULL) {
+ GstObject *sender = GST_MESSAGE_SRC (msg);
+ if (sender && GST_IS_X_OVERLAY (sender))
+ bvw->priv->xoverlay = GST_X_OVERLAY (gst_object_ref (sender));
+ }
+
+ g_return_if_fail (bvw->priv->xoverlay != NULL);
+ g_return_if_fail (bvw->priv->video_window != NULL);
+
+#ifdef WIN32
+ gst_x_overlay_set_xwindow_id (bvw->priv->xoverlay,
+ GDK_WINDOW_HWND (bvw->priv->video_window));
+#else
+ gst_x_overlay_set_xwindow_id (bvw->priv->xoverlay,
+ GDK_WINDOW_XID (bvw->priv->video_window));
+#endif
+
+ }
+}
+
+static void
+got_new_video_sink_bin_element (GstBin * video_sink, GstElement * element,
+ gpointer data)
+{
+ BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (data);
+
+ g_mutex_lock (bvw->priv->lock);
+ bvw_update_interface_implementations (bvw);
+ g_mutex_unlock (bvw->priv->lock);
+
+}
+
+/**
+ * bacon_video_widget_new:
+ * @width: initial or expected video width, in pixels, or %-1
+ * @height: initial or expected video height, in pixels, or %-1
+ * @type: the widget's use type
+ * @error: a #GError, or %NULL
+ *
+ * Creates a new #BaconVideoWidget for the purpose specified in @type.
+ *
+ * If @type is %BVW_USE_TYPE_VIDEO, the #BaconVideoWidget will be fully-featured; other
+ * values of @type will enable less functionality on the widget, which will come with
+ * corresponding decreases in the size of its memory footprint.
+ *
+ * @width and @height give the initial or expected video height. Set them to %-1 if the
+ * video size is unknown. For small videos, #BaconVideoWidget will be configured differently.
+ *
+ * A #BvwError will be returned on error.
+ *
+ * Return value: a new #BaconVideoWidget, or %NULL; destroy with gtk_widget_destroy()
+ **/
+GtkWidget *
+bacon_video_widget_new (int width, int height, BvwUseType type, GError ** err)
+{
+
+ BaconVideoWidget *bvw;
+ GstElement *audio_sink = NULL, *video_sink = NULL;
+ gchar *version_str;
+
+#ifndef GST_DISABLE_GST_INFO
+ if (_totem_gst_debug_cat == NULL) {
+ GST_DEBUG_CATEGORY_INIT (_totem_gst_debug_cat, "totem", 0,
+ "Totem GStreamer Backend");
+ }
+#endif
+
+ version_str = gst_version_string ();
+ GST_INFO ("Initialised %s", version_str);
+ g_free (version_str);
+
+ gst_pb_utils_init ();
+
+ bvw = g_object_new (bacon_video_widget_get_type (), NULL);
+
+ bvw->priv->use_type = type;
+
+ GST_INFO ("use_type = %d", type);
+
+ bvw->priv->play = gst_element_factory_make ("playbin2", "play");
+ if (!bvw->priv->play) {
+
+ g_set_error (err, BVW_ERROR, GST_ERROR_PLUGIN_LOAD,
+ _("Failed to create a GStreamer play object. "
+ "Please check your GStreamer installation."));
+ g_object_ref_sink (bvw);
+ g_object_unref (bvw);
+ return NULL;
+ }
+
+ bvw->priv->bus = gst_element_get_bus (bvw->priv->play);
+
+ gst_bus_add_signal_watch (bvw->priv->bus);
+
+ bvw->priv->sig_bus_async =
+ g_signal_connect (bvw->priv->bus, "message",
+ G_CALLBACK (bvw_bus_message_cb), bvw);
+
+ bvw->priv->speakersetup = BVW_AUDIO_SOUND_STEREO;
+ bvw->priv->media_device = g_strdup ("/dev/dvd");
+ bvw->priv->ratio_type = BVW_RATIO_AUTO;
+
+ bvw->priv->cursor_shown = TRUE;
+ bvw->priv->logo_mode = FALSE;
+ bvw->priv->auto_resize = FALSE;
+
+
+ if (type == BVW_USE_TYPE_VIDEO || type == BVW_USE_TYPE_AUDIO) {
+
+ audio_sink = gst_element_factory_make ("autoaudiosink", "audio-sink");
+
+ if (audio_sink == NULL) {
+ g_warning ("Could not create element 'autoaudiosink'");
+ }
+ } else {
+ audio_sink = gst_element_factory_make ("fakesink", "audio-fake-sink");
+ }
+
+ if (type == BVW_USE_TYPE_VIDEO) {
+ video_sink = gst_element_factory_make (DEFAULT_VIDEO_SINK, "video-sink");
+
+ if (video_sink == NULL) {
+ g_warning ("Could not create element '%s'", DEFAULT_VIDEO_SINK);
+ /* Try to fallback on ximagesink */
+ video_sink = gst_element_factory_make ("ximagesink", "video-sink");
+ }
+
+ } else {
+ video_sink = gst_element_factory_make ("fakesink", "video-fake-sink");
+ if (video_sink)
+ g_object_set (video_sink, "sync", TRUE, NULL);
+ }
+
+ if (video_sink) {
+ GstStateChangeReturn ret;
+
+ /* need to set bus explicitly as it's not in a bin yet and
+ * poll_for_state_change() needs one to catch error messages */
+ gst_element_set_bus (video_sink, bvw->priv->bus);
+ /* state change NULL => READY should always be synchronous */
+ ret = gst_element_set_state (video_sink, GST_STATE_READY);
+ if (ret == GST_STATE_CHANGE_FAILURE) {
+ /* Drop this video sink */
+ gst_element_set_state (video_sink, GST_STATE_NULL);
+ gst_object_unref (video_sink);
+ /* Try again with autovideosink */
+ video_sink = gst_element_factory_make ("autovideosink", "video-sink");
+ gst_element_set_bus (video_sink, bvw->priv->bus);
+ ret = gst_element_set_state (video_sink, GST_STATE_READY);
+ if (ret == GST_STATE_CHANGE_FAILURE) {
+ GstMessage *err_msg;
+
+ err_msg = gst_bus_poll (bvw->priv->bus, GST_MESSAGE_ERROR, 0);
+ if (err_msg == NULL) {
+ g_warning ("Should have gotten an error message, please file a bug.");
+ g_set_error (err, BVW_ERROR, GST_ERROR_VIDEO_PLUGIN,
+ _
+ ("Failed to open video output. It may not be available. "
+ "Please select another video output in the Multimedia "
+ "Systems Selector."));
+ } else if (err_msg) {
+ *err = bvw_error_from_gst_error (bvw, err_msg);
+ gst_message_unref (err_msg);
+ }
+ goto sink_error;
+ }
+ }
+ } else {
+ g_set_error (err, BVW_ERROR, GST_ERROR_VIDEO_PLUGIN,
+ _("Could not find the video output. "
+ "You may need to install additional GStreamer plugins, "
+ "or select another video output in the Multimedia Systems "
+ "Selector."));
+ goto sink_error;
+ }
+
+ if (audio_sink) {
+ GstStateChangeReturn ret;
+ GstBus *bus;
+
+ /* need to set bus explicitly as it's not in a bin yet and
+ * we need one to catch error messages */
+ bus = gst_bus_new ();
+ gst_element_set_bus (audio_sink, bus);
+
+ /* state change NULL => READY should always be synchronous */
+ ret = gst_element_set_state (audio_sink, GST_STATE_READY);
+ gst_element_set_bus (audio_sink, NULL);
+
+ if (ret == GST_STATE_CHANGE_FAILURE) {
+ /* doesn't work, drop this audio sink */
+ gst_element_set_state (audio_sink, GST_STATE_NULL);
+ gst_object_unref (audio_sink);
+ audio_sink = NULL;
+ /* Hopefully, fakesink should always work */
+ if (type != BVW_USE_TYPE_AUDIO)
+ audio_sink = gst_element_factory_make ("fakesink", "audio-sink");
+ if (audio_sink == NULL) {
+ GstMessage *err_msg;
+
+ err_msg = gst_bus_poll (bus, GST_MESSAGE_ERROR, 0);
+ if (err_msg == NULL) {
+ g_warning ("Should have gotten an error message, please file a bug.");
+ g_set_error (err, BVW_ERROR, GST_ERROR_AUDIO_PLUGIN,
+ _
+ ("Failed to open audio output. You may not have "
+ "permission to open the sound device, or the sound "
+ "server may not be running. "
+ "Please select another audio output in the Multimedia "
+ "Systems Selector."));
+ } else if (err) {
+ *err = bvw_error_from_gst_error (bvw, err_msg);
+ gst_message_unref (err_msg);
+ }
+ gst_object_unref (bus);
+ goto sink_error;
+ }
+ /* make fakesink sync to the clock like a real sink */
+ g_object_set (audio_sink, "sync", TRUE, NULL);
+ GST_INFO ("audio sink doesn't work, using fakesink instead");
+ bvw->priv->uses_fakesink = TRUE;
+ }
+ gst_object_unref (bus);
+ } else {
+ g_set_error (err, BVW_ERROR, GST_ERROR_AUDIO_PLUGIN,
+ _("Could not find the audio output. "
+ "You may need to install additional GStreamer plugins, or "
+ "select another audio output in the Multimedia Systems "
+ "Selector."));
+ goto sink_error;
+ }
+
+ /* set back to NULL to close device again in order to avoid interrupts
+ * being generated after startup while there's nothing to play yet */
+ gst_element_set_state (audio_sink, GST_STATE_NULL);
+
+ do {
+ GstElement *bin;
+ GstPad *pad;
+
+ bvw->priv->audio_capsfilter =
+ gst_element_factory_make ("capsfilter", "audiofilter");
+ bin = gst_bin_new ("audiosinkbin");
+ gst_bin_add_many (GST_BIN (bin), bvw->priv->audio_capsfilter,
+ audio_sink, NULL);
+ gst_element_link_pads (bvw->priv->audio_capsfilter, "src",
+ audio_sink, "sink");
+
+ pad = gst_element_get_pad (bvw->priv->audio_capsfilter, "sink");
+ gst_element_add_pad (bin, gst_ghost_pad_new ("sink", pad));
+ gst_object_unref (pad);
+
+ audio_sink = bin;
+ }
+ while (0);
+
+
+ /* now tell playbin */
+ g_object_set (bvw->priv->play, "video-sink", video_sink, NULL);
+ g_object_set (bvw->priv->play, "audio-sink", audio_sink, NULL);
+
+ g_signal_connect (bvw->priv->play, "notify::source",
+ G_CALLBACK (playbin_source_notify_cb), bvw);
+ g_signal_connect (bvw->priv->play, "video-changed",
+ G_CALLBACK (playbin_stream_changed_cb), bvw);
+ g_signal_connect (bvw->priv->play, "audio-changed",
+ G_CALLBACK (playbin_stream_changed_cb), bvw);
+ g_signal_connect (bvw->priv->play, "text-changed",
+ G_CALLBACK (playbin_stream_changed_cb), bvw);
+
+ /* assume we're always called from the main Gtk+ GUI thread */
+ gui_thread = g_thread_self ();
+
+ if (type == BVW_USE_TYPE_VIDEO) {
+ GstStateChangeReturn ret;
+
+ /* wait for video sink to finish changing to READY state,
+ * otherwise we won't be able to detect the colorbalance interface */
+ ret = gst_element_get_state (video_sink, NULL, NULL, 5 * GST_SECOND);
+
+ if (ret != GST_STATE_CHANGE_SUCCESS) {
+ GST_WARNING ("Timeout setting videosink to READY");
+ g_set_error (err, BVW_ERROR, GST_ERROR_VIDEO_PLUGIN,
+ _
+ ("Failed to open video output. It may not be available. "
+ "Please select another video output in the Multimedia Systems Selector."));
+ return NULL;
+ }
+ bvw_update_interface_implementations (bvw);
+
+ }
+
+ /* we want to catch "prepare-xwindow-id" element messages synchronously */
+ gst_bus_set_sync_handler (bvw->priv->bus, gst_bus_sync_signal_handler, bvw);
+
+ bvw->priv->sig_bus_sync =
+ g_signal_connect (bvw->priv->bus, "sync-message::element",
+ G_CALLBACK (bvw_element_msg_sync), bvw);
+
+ if (GST_IS_BIN (video_sink)) {
+ /* video sink bins like gconfvideosink might remove their children and
+ * create new ones when set to NULL state, and they are currently set
+ * to NULL state whenever playbin re-creates its internal video bin
+ * (it sets all elements to NULL state before gst_bin_remove()ing them) */
+ g_signal_connect (video_sink, "element-added",
+ G_CALLBACK (got_new_video_sink_bin_element), bvw);
+ }
+
+
+ return GTK_WIDGET (bvw);
+
+ /* errors */
+sink_error:
+ {
+ if (video_sink) {
+ gst_element_set_state (video_sink, GST_STATE_NULL);
+ gst_object_unref (video_sink);
+ }
+ if (audio_sink) {
+ gst_element_set_state (audio_sink, GST_STATE_NULL);
+ gst_object_unref (audio_sink);
+ }
+
+ g_object_ref (bvw);
+ g_object_ref_sink (G_OBJECT (bvw));
+ g_object_unref (bvw);
+
+ return NULL;
+ }
+}
diff --git a/libcesarplayer/src/bacon-video-widget.h b/libcesarplayer/src/bacon-video-widget.h
new file mode 100644
index 0000000..7f9e7a0
--- /dev/null
+++ b/libcesarplayer/src/bacon-video-widget.h
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2001,2002,2003,2004,2005 Bastien Nocera <hadess hadess net>
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Totem project hereby grant permission for non-gpl compatible GStreamer
+ * plugins to be used and distributed together with GStreamer and Totem. This
+ * permission are above and beyond the permissions granted by the GPL license
+ * Totem is covered by.
+ *
+ * Monday 7th February 2005: Christian Schaller: Add excemption clause.
+ * See license_change file for details.
+ *
+ */
+
+#ifndef HAVE_BACON_VIDEO_WIDGET_H
+#define HAVE_BACON_VIDEO_WIDGET_H
+
+#ifdef WIN32
+#define EXPORT __declspec (dllexport)
+#else
+#define EXPORT
+#endif
+
+
+#include <gtk/gtkbox.h>
+#include <gst/gst.h>
+/* for optical disc enumeration type */
+//#include "totem-disc.h"
+
+G_BEGIN_DECLS
+#define BACON_TYPE_VIDEO_WIDGET (bacon_video_widget_get_type ())
+#define BACON_VIDEO_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), bacon_video_widget_get_type (), BaconVideoWidget))
+#define BACON_VIDEO_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), bacon_video_widget_get_type (), BaconVideoWidgetClass))
+#define BACON_IS_VIDEO_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, bacon_video_widget_get_type ()))
+#define BACON_IS_VIDEO_WIDGET_CLASS(klass) (G_CHECK_INSTANCE_GET_CLASS ((klass), bacon_video_widget_get_type ()))
+#define BVW_ERROR bacon_video_widget_error_quark ()
+typedef struct BaconVideoWidgetPrivate BaconVideoWidgetPrivate;
+
+
+typedef struct
+{
+ GtkEventBox parent;
+ BaconVideoWidgetPrivate *priv;
+} BaconVideoWidget;
+
+typedef struct
+{
+ GtkEventBoxClass parent_class;
+
+ void (*error) (BaconVideoWidget * bvw, const char *message);
+ void (*eos) (BaconVideoWidget * bvw);
+ void (*got_metadata) (BaconVideoWidget * bvw);
+ void (*segment_done) (BaconVideoWidget * bvw);
+ void (*got_redirect) (BaconVideoWidget * bvw, const char *mrl);
+ void (*title_change) (BaconVideoWidget * bvw, const char *title);
+ void (*channels_change) (BaconVideoWidget * bvw);
+ void (*tick) (BaconVideoWidget * bvw, gint64 current_time,
+ gint64 stream_length, float current_position, gboolean seekable);
+ void (*buffering) (BaconVideoWidget * bvw, guint progress);
+ void (*state_change) (BaconVideoWidget * bvw, gboolean playing);
+ void (*got_duration) (BaconVideoWidget * bvw);
+ void (*ready_to_seek) (BaconVideoWidget * bvw);
+} BaconVideoWidgetClass;
+
+
+EXPORT GQuark
+bacon_video_widget_error_quark (void)
+ G_GNUC_CONST;
+ EXPORT GType bacon_video_widget_get_type (void) G_GNUC_CONST;
+ EXPORT GOptionGroup *bacon_video_widget_get_option_group (void);
+/* This can be used if the app does not use popt */
+ EXPORT void bacon_video_widget_init_backend (int *argc, char ***argv);
+
+/**
+ * BvwUseType:
+ * @BVW_USE_TYPE_VIDEO: fully-featured with video, audio, capture and metadata support
+ * @BVW_USE_TYPE_AUDIO: audio and metadata support
+ * @BVW_USE_TYPE_CAPTURE: capture support only
+ * @BVW_USE_TYPE_METADATA: metadata support only
+ *
+ * The purpose for which a #BaconVideoWidget will be used, as specified to
+ * bacon_video_widget_new(). This determines which features will be enabled
+ * in the created widget.
+ **/
+ typedef enum
+ {
+ BVW_USE_TYPE_VIDEO,
+ BVW_USE_TYPE_AUDIO,
+ BVW_USE_TYPE_CAPTURE,
+ BVW_USE_TYPE_METADATA
+ } BvwUseType;
+
+ EXPORT GtkWidget *bacon_video_widget_new (int width, int height,
+ BvwUseType type, GError ** error);
+
+ EXPORT char *bacon_video_widget_get_backend_name (BaconVideoWidget * bvw);
+
+/* Actions */
+ EXPORT gboolean bacon_video_widget_open (BaconVideoWidget * bvw,
+ const char *mrl, const char *subtitle_uri, GError ** error);
+ EXPORT gboolean bacon_video_widget_play (BaconVideoWidget * bvw);
+ EXPORT void bacon_video_widget_pause (BaconVideoWidget * bvw);
+ EXPORT gboolean bacon_video_widget_is_playing (BaconVideoWidget * bvw);
+
+/* Seeking and length */
+ EXPORT gboolean bacon_video_widget_is_seekable (BaconVideoWidget * bvw);
+ EXPORT gboolean bacon_video_widget_seek (BaconVideoWidget * bvw,
+ gdouble position, gfloat rate);
+ EXPORT gboolean bacon_video_widget_seek_time (BaconVideoWidget * bvw,
+ gint64 time, gfloat rate, gboolean accurate);
+ EXPORT gboolean bacon_video_widget_segment_seek (BaconVideoWidget * bvw,
+ gint64 start, gint64 stop, gfloat rate);
+ EXPORT gboolean bacon_video_widget_seek_in_segment (BaconVideoWidget *
+ bvw, gint64 pos, gfloat rate);
+ EXPORT gboolean bacon_video_widget_seek_to_next_frame (BaconVideoWidget *
+ bvw, gfloat rate, gboolean in_segment);
+ EXPORT gboolean
+ bacon_video_widget_seek_to_previous_frame (BaconVideoWidget * bvw,
+ gfloat rate, gboolean in_segment);
+ EXPORT gboolean bacon_video_widget_segment_stop_update (BaconVideoWidget
+ * bvw, gint64 stop, gfloat rate);
+ EXPORT gboolean bacon_video_widget_segment_start_update (BaconVideoWidget
+ * bvw, gint64 start, gfloat rate);
+ EXPORT gboolean bacon_video_widget_new_file_seek (BaconVideoWidget * bvw,
+ gint64 start, gint64 stop, gfloat rate);
+ EXPORT gboolean bacon_video_widget_can_direct_seek (BaconVideoWidget *
+ bvw);
+ EXPORT double bacon_video_widget_get_position (BaconVideoWidget * bvw);
+ EXPORT gint64 bacon_video_widget_get_current_time (BaconVideoWidget * bvw);
+ EXPORT gint64 bacon_video_widget_get_stream_length (BaconVideoWidget *
+ bvw);
+ EXPORT gint64
+ bacon_video_widget_get_accurate_current_time (BaconVideoWidget * bvw);
+ EXPORT gboolean bacon_video_widget_set_rate (BaconVideoWidget * bvw,
+ gfloat rate);
+ EXPORT gboolean bacon_video_widget_set_rate_in_segment (BaconVideoWidget
+ * bvw, gfloat rate, gint64 stop);
+
+
+
+ EXPORT void bacon_video_widget_stop (BaconVideoWidget * bvw);
+ EXPORT void bacon_video_widget_close (BaconVideoWidget * bvw);
+
+/* Audio volume */
+ EXPORT gboolean bacon_video_widget_can_set_volume (BaconVideoWidget * bvw);
+ EXPORT void bacon_video_widget_set_volume (BaconVideoWidget * bvw,
+ double volume);
+ EXPORT double bacon_video_widget_get_volume (BaconVideoWidget * bvw);
+
+/*Drawings Overlay*/
+ EXPORT void bacon_video_widget_set_drawing_pixbuf (BaconVideoWidget *
+ bvw, GdkPixbuf * drawing);
+ EXPORT void bacon_video_widget_set_drawing_mode (BaconVideoWidget * bvw,
+ gboolean drawing_mode);
+
+
+/* Properties */
+ EXPORT void bacon_video_widget_set_logo (BaconVideoWidget * bvw,
+ char *filename);
+ EXPORT void bacon_video_widget_set_logo_pixbuf (BaconVideoWidget * bvw,
+ GdkPixbuf * logo);
+ EXPORT void bacon_video_widget_set_logo_mode (BaconVideoWidget * bvw,
+ gboolean logo_mode);
+ EXPORT gboolean bacon_video_widget_get_logo_mode (BaconVideoWidget * bvw);
+
+ EXPORT void bacon_video_widget_set_fullscreen (BaconVideoWidget * bvw,
+ gboolean fullscreen);
+
+ EXPORT void bacon_video_widget_set_show_cursor (BaconVideoWidget * bvw,
+ gboolean show_cursor);
+ EXPORT gboolean bacon_video_widget_get_show_cursor (BaconVideoWidget *
+ bvw);
+
+ EXPORT gboolean bacon_video_widget_get_auto_resize (BaconVideoWidget *
+ bvw);
+ EXPORT void bacon_video_widget_set_auto_resize (BaconVideoWidget * bvw,
+ gboolean auto_resize);
+
+ EXPORT void bacon_video_widget_set_connection_speed (BaconVideoWidget *
+ bvw, int speed);
+ EXPORT int bacon_video_widget_get_connection_speed (BaconVideoWidget *
+ bvw);
+
+ EXPORT void bacon_video_widget_set_subtitle_font (BaconVideoWidget * bvw,
+ const char *font);
+ EXPORT void bacon_video_widget_set_subtitle_encoding (BaconVideoWidget *
+ bvw, const char *encoding);
+
+/* Metadata */
+/**
+ * BvwMetadataType:
+ * @BVW_INFO_TITLE: the stream's title
+ * @BVW_INFO_ARTIST: the artist who created the work
+ * @BVW_INFO_YEAR: the year in which the work was created
+ * @BVW_INFO_COMMENT: a comment attached to the stream
+ * @BVW_INFO_ALBUM: the album in which the work was released
+ * @BVW_INFO_DURATION: the stream's duration, in seconds
+ * @BVW_INFO_TRACK_NUMBER: the track number of the work on the album
+ * @BVW_INFO_COVER: a #GdkPixbuf of the cover artwork
+ * @BVW_INFO_HAS_VIDEO: whether the stream has video
+ * @BVW_INFO_DIMENSION_X: the video's width, in pixels
+ * @BVW_INFO_DIMENSION_Y: the video's height, in pixels
+ * @BVW_INFO_VIDEO_BITRATE: the video's bitrate, in kilobits per second
+ * @BVW_INFO_VIDEO_CODEC: the video's codec
+ * @BVW_INFO_FPS: the number of frames per second in the video
+ * @BVW_INFO_HAS_AUDIO: whether the stream has audio
+ * @BVW_INFO_AUDIO_BITRATE: the audio's bitrate, in kilobits per second
+ * @BVW_INFO_AUDIO_CODEC: the audio's codec
+ * @BVW_INFO_AUDIO_SAMPLE_RATE: the audio sample rate, in bits per second
+ * @BVW_INFO_AUDIO_CHANNELS: a string describing the number of audio channels in the stream
+ *
+ * The different metadata available for querying from a #BaconVideoWidget
+ * stream with bacon_video_widget_get_metadata().
+ **/
+ typedef enum
+ {
+ BVW_INFO_TITLE,
+ BVW_INFO_ARTIST,
+ BVW_INFO_YEAR,
+ BVW_INFO_COMMENT,
+ BVW_INFO_ALBUM,
+ BVW_INFO_DURATION,
+ BVW_INFO_TRACK_NUMBER,
+ BVW_INFO_COVER,
+ /* Video */
+ BVW_INFO_HAS_VIDEO,
+ BVW_INFO_DIMENSION_X,
+ BVW_INFO_DIMENSION_Y,
+ BVW_INFO_VIDEO_BITRATE,
+ BVW_INFO_VIDEO_CODEC,
+ BVW_INFO_FPS,
+ /* Audio */
+ BVW_INFO_HAS_AUDIO,
+ BVW_INFO_AUDIO_BITRATE,
+ BVW_INFO_AUDIO_CODEC,
+ BVW_INFO_AUDIO_SAMPLE_RATE,
+ BVW_INFO_AUDIO_CHANNELS
+ } BvwMetadataType;
+
+ EXPORT void bacon_video_widget_get_metadata (BaconVideoWidget * bvw,
+ BvwMetadataType type, GValue * value);
+
+
+/* Picture settings */
+/**
+ * BvwVideoProperty:
+ * @BVW_VIDEO_BRIGHTNESS: the video brightness
+ * @BVW_VIDEO_CONTRAST: the video contrast
+ * @BVW_VIDEO_SATURATION: the video saturation
+ * @BVW_VIDEO_HUE: the video hue
+ *
+ * The video properties queryable with bacon_video_widget_get_video_property(),
+ * and settable with bacon_video_widget_set_video_property().
+ **/
+ typedef enum
+ {
+ BVW_VIDEO_BRIGHTNESS,
+ BVW_VIDEO_CONTRAST,
+ BVW_VIDEO_SATURATION,
+ BVW_VIDEO_HUE
+ } BvwVideoProperty;
+
+/**
+ * BvwAspectRatio:
+ * @BVW_RATIO_AUTO: automatic
+ * @BVW_RATIO_SQUARE: square (1:1)
+ * @BVW_RATIO_FOURBYTHREE: four-by-three (4:3)
+ * @BVW_RATIO_ANAMORPHIC: anamorphic (16:9)
+ * @BVW_RATIO_DVB: DVB (20:9)
+ *
+ * The pixel aspect ratios available in which to display videos using
+ * @bacon_video_widget_set_aspect_ratio().
+ **/
+ typedef enum
+ {
+ BVW_RATIO_AUTO = 0,
+ BVW_RATIO_SQUARE = 1,
+ BVW_RATIO_FOURBYTHREE = 2,
+ BVW_RATIO_ANAMORPHIC = 3,
+ BVW_RATIO_DVB = 4
+ } BvwAspectRatio;
+
+ EXPORT gboolean bacon_video_widget_can_deinterlace (BaconVideoWidget *
+ bvw);
+ EXPORT void bacon_video_widget_set_deinterlacing (BaconVideoWidget * bvw,
+ gboolean deinterlace);
+ EXPORT gboolean bacon_video_widget_get_deinterlacing (BaconVideoWidget *
+ bvw);
+
+ EXPORT void bacon_video_widget_set_aspect_ratio (BaconVideoWidget * bvw,
+ BvwAspectRatio ratio);
+ EXPORT BvwAspectRatio bacon_video_widget_get_aspect_ratio
+ (BaconVideoWidget * bvw);
+
+ EXPORT void bacon_video_widget_set_scale_ratio (BaconVideoWidget * bvw,
+ float ratio);
+
+ EXPORT void bacon_video_widget_set_zoom (BaconVideoWidget * bvw,
+ double zoom);
+ EXPORT double bacon_video_widget_get_zoom (BaconVideoWidget * bvw);
+
+ EXPORT int bacon_video_widget_get_video_property (BaconVideoWidget * bvw,
+ BvwVideoProperty type);
+ EXPORT void bacon_video_widget_set_video_property (BaconVideoWidget *
+ bvw, BvwVideoProperty type, int value);
+
+/* DVD functions */
+/**
+ * BvwDVDEvent:
+ * @BVW_DVD_ROOT_MENU: root menu
+ * @BVW_DVD_TITLE_MENU: title menu
+ * @BVW_DVD_SUBPICTURE_MENU: subpicture menu (if available)
+ * @BVW_DVD_AUDIO_MENU: audio menu (if available)
+ * @BVW_DVD_ANGLE_MENU: angle menu (if available)
+ * @BVW_DVD_CHAPTER_MENU: chapter menu
+ * @BVW_DVD_NEXT_CHAPTER: the next chapter
+ * @BVW_DVD_PREV_CHAPTER: the previous chapter
+ * @BVW_DVD_NEXT_TITLE: the next title in the current chapter
+ * @BVW_DVD_PREV_TITLE: the previous title in the current chapter
+ * @BVW_DVD_NEXT_ANGLE: the next angle
+ * @BVW_DVD_PREV_ANGLE: the previous angle
+ * @BVW_DVD_ROOT_MENU_UP: go up in the menu
+ * @BVW_DVD_ROOT_MENU_DOWN: go down in the menu
+ * @BVW_DVD_ROOT_MENU_LEFT: go left in the menu
+ * @BVW_DVD_ROOT_MENU_RIGHT: go right in the menu
+ * @BVW_DVD_ROOT_MENU_SELECT: select the current menu entry
+ *
+ * The DVD navigation actions available to fire as DVD events to
+ * the #BaconVideoWidget.
+ **/
+ typedef enum
+ {
+ BVW_DVD_ROOT_MENU,
+ BVW_DVD_TITLE_MENU,
+ BVW_DVD_SUBPICTURE_MENU,
+ BVW_DVD_AUDIO_MENU,
+ BVW_DVD_ANGLE_MENU,
+ BVW_DVD_CHAPTER_MENU,
+ BVW_DVD_NEXT_CHAPTER,
+ BVW_DVD_PREV_CHAPTER,
+ BVW_DVD_NEXT_TITLE,
+ BVW_DVD_PREV_TITLE,
+ BVW_DVD_NEXT_ANGLE,
+ BVW_DVD_PREV_ANGLE,
+ BVW_DVD_ROOT_MENU_UP,
+ BVW_DVD_ROOT_MENU_DOWN,
+ BVW_DVD_ROOT_MENU_LEFT,
+ BVW_DVD_ROOT_MENU_RIGHT,
+ BVW_DVD_ROOT_MENU_SELECT
+ } BvwDVDEvent;
+
+ EXPORT void bacon_video_widget_dvd_event (BaconVideoWidget * bvw,
+ BvwDVDEvent type);
+ EXPORT GList *bacon_video_widget_get_languages (BaconVideoWidget * bvw);
+ EXPORT int bacon_video_widget_get_language (BaconVideoWidget * bvw);
+ EXPORT void bacon_video_widget_set_language (BaconVideoWidget * bvw,
+ int language);
+
+ EXPORT GList *bacon_video_widget_get_subtitles (BaconVideoWidget * bvw);
+ EXPORT int bacon_video_widget_get_subtitle (BaconVideoWidget * bvw);
+ EXPORT void bacon_video_widget_set_subtitle (BaconVideoWidget * bvw,
+ int subtitle);
+
+ EXPORT gboolean bacon_video_widget_has_next_track (BaconVideoWidget * bvw);
+ EXPORT gboolean bacon_video_widget_has_previous_track (BaconVideoWidget *
+ bvw);
+
+/* Screenshot functions */
+ EXPORT gboolean bacon_video_widget_can_get_frames (BaconVideoWidget *
+ bvw, GError ** error);
+ EXPORT GdkPixbuf *bacon_video_widget_get_current_frame (BaconVideoWidget
+ * bvw);
+ EXPORT void bacon_video_widget_unref_pixbuf (GdkPixbuf * pixbuf);
+
+/* Audio-out functions */
+/**
+ * BvwAudioOutType:
+ * @BVW_AUDIO_SOUND_STEREO: stereo output
+ * @BVW_AUDIO_SOUND_4CHANNEL: 4-channel output
+ * @BVW_AUDIO_SOUND_41CHANNEL: 4.1-channel output
+ * @BVW_AUDIO_SOUND_5CHANNEL: 5-channel output
+ * @BVW_AUDIO_SOUND_51CHANNEL: 5.1-channel output
+ * @BVW_AUDIO_SOUND_AC3PASSTHRU: AC3 passthrough output
+ *
+ * The audio output types available for use with bacon_video_widget_set_audio_out_type().
+ **/
+ typedef enum
+ {
+ BVW_AUDIO_SOUND_STEREO,
+ BVW_AUDIO_SOUND_CHANNEL4,
+ BVW_AUDIO_SOUND_CHANNEL41,
+ BVW_AUDIO_SOUND_CHANNEL5,
+ BVW_AUDIO_SOUND_CHANNEL51,
+ BVW_AUDIO_SOUND_AC3PASSTHRU
+ } BvwAudioOutType;
+
+ EXPORT BvwAudioOutType bacon_video_widget_get_audio_out_type
+ (BaconVideoWidget * bvw);
+ EXPORT gboolean bacon_video_widget_set_audio_out_type (BaconVideoWidget *
+ bvw, BvwAudioOutType type);
+
+G_END_DECLS
+#endif /* HAVE_BACON_VIDEO_WIDGET_H */
diff --git a/libcesarplayer/src/baconvideowidget-marshal.c b/libcesarplayer/src/baconvideowidget-marshal.c
new file mode 100644
index 0000000..8d8fd6f
--- /dev/null
+++ b/libcesarplayer/src/baconvideowidget-marshal.c
@@ -0,0 +1,247 @@
+
+#ifndef __baconvideowidget_marshal_MARSHAL_H__
+#define __baconvideowidget_marshal_MARSHAL_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#ifdef G_ENABLE_DEBUG
+#define g_marshal_value_peek_boolean(v) g_value_get_boolean (v)
+#define g_marshal_value_peek_char(v) g_value_get_char (v)
+#define g_marshal_value_peek_uchar(v) g_value_get_uchar (v)
+#define g_marshal_value_peek_int(v) g_value_get_int (v)
+#define g_marshal_value_peek_uint(v) g_value_get_uint (v)
+#define g_marshal_value_peek_long(v) g_value_get_long (v)
+#define g_marshal_value_peek_ulong(v) g_value_get_ulong (v)
+#define g_marshal_value_peek_int64(v) g_value_get_int64 (v)
+#define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v)
+#define g_marshal_value_peek_enum(v) g_value_get_enum (v)
+#define g_marshal_value_peek_flags(v) g_value_get_flags (v)
+#define g_marshal_value_peek_float(v) g_value_get_float (v)
+#define g_marshal_value_peek_double(v) g_value_get_double (v)
+#define g_marshal_value_peek_string(v) (char*) g_value_get_string (v)
+#define g_marshal_value_peek_param(v) g_value_get_param (v)
+#define g_marshal_value_peek_boxed(v) g_value_get_boxed (v)
+#define g_marshal_value_peek_pointer(v) g_value_get_pointer (v)
+#define g_marshal_value_peek_object(v) g_value_get_object (v)
+#define g_marshal_value_peek_variant(v) g_value_get_variant (v)
+#else /* !G_ENABLE_DEBUG */
+/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API.
+ * Do not access GValues directly in your code. Instead, use the
+ * g_value_get_*() functions
+ */
+#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int
+#define g_marshal_value_peek_char(v) (v)->data[0].v_int
+#define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint
+#define g_marshal_value_peek_int(v) (v)->data[0].v_int
+#define g_marshal_value_peek_uint(v) (v)->data[0].v_uint
+#define g_marshal_value_peek_long(v) (v)->data[0].v_long
+#define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong
+#define g_marshal_value_peek_int64(v) (v)->data[0].v_int64
+#define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64
+#define g_marshal_value_peek_enum(v) (v)->data[0].v_long
+#define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong
+#define g_marshal_value_peek_float(v) (v)->data[0].v_float
+#define g_marshal_value_peek_double(v) (v)->data[0].v_double
+#define g_marshal_value_peek_string(v) (v)->data[0].v_pointer
+#define g_marshal_value_peek_param(v) (v)->data[0].v_pointer
+#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer
+#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer
+#define g_marshal_value_peek_object(v) (v)->data[0].v_pointer
+#define g_marshal_value_peek_variant(v) (v)->data[0].v_pointer
+#endif /* !G_ENABLE_DEBUG */
+
+
+/* VOID:INT64,INT64,DOUBLE,BOOLEAN (./baconvideowidget-marshal.list:1) */
+extern void baconvideowidget_marshal_VOID__INT64_INT64_DOUBLE_BOOLEAN (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+void
+baconvideowidget_marshal_VOID__INT64_INT64_DOUBLE_BOOLEAN (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__INT64_INT64_DOUBLE_BOOLEAN) (gpointer data1,
+ gint64 arg_1,
+ gint64 arg_2,
+ gdouble arg_3,
+ gboolean arg_4,
+ gpointer data2);
+ register GMarshalFunc_VOID__INT64_INT64_DOUBLE_BOOLEAN callback;
+ register GCClosure *cc = (GCClosure*) closure;
+ register gpointer data1, data2;
+
+ g_return_if_fail (n_param_values == 5);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__INT64_INT64_DOUBLE_BOOLEAN) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_int64 (param_values + 1),
+ g_marshal_value_peek_int64 (param_values + 2),
+ g_marshal_value_peek_double (param_values + 3),
+ g_marshal_value_peek_boolean (param_values + 4),
+ data2);
+}
+
+/* VOID:STRING,BOOLEAN,BOOLEAN (./baconvideowidget-marshal.list:2) */
+extern void baconvideowidget_marshal_VOID__STRING_BOOLEAN_BOOLEAN (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+void
+baconvideowidget_marshal_VOID__STRING_BOOLEAN_BOOLEAN (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__STRING_BOOLEAN_BOOLEAN) (gpointer data1,
+ gpointer arg_1,
+ gboolean arg_2,
+ gboolean arg_3,
+ gpointer data2);
+ register GMarshalFunc_VOID__STRING_BOOLEAN_BOOLEAN callback;
+ register GCClosure *cc = (GCClosure*) closure;
+ register gpointer data1, data2;
+
+ g_return_if_fail (n_param_values == 4);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__STRING_BOOLEAN_BOOLEAN) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_string (param_values + 1),
+ g_marshal_value_peek_boolean (param_values + 2),
+ g_marshal_value_peek_boolean (param_values + 3),
+ data2);
+}
+
+/* BOOLEAN:BOXED,BOXED,BOOLEAN (./baconvideowidget-marshal.list:3) */
+extern void baconvideowidget_marshal_BOOLEAN__BOXED_BOXED_BOOLEAN (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+void
+baconvideowidget_marshal_BOOLEAN__BOXED_BOXED_BOOLEAN (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef gboolean (*GMarshalFunc_BOOLEAN__BOXED_BOXED_BOOLEAN) (gpointer data1,
+ gpointer arg_1,
+ gpointer arg_2,
+ gboolean arg_3,
+ gpointer data2);
+ register GMarshalFunc_BOOLEAN__BOXED_BOXED_BOOLEAN callback;
+ register GCClosure *cc = (GCClosure*) closure;
+ register gpointer data1, data2;
+ gboolean v_return;
+
+ g_return_if_fail (return_value != NULL);
+ g_return_if_fail (n_param_values == 4);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_BOOLEAN__BOXED_BOXED_BOOLEAN) (marshal_data ? marshal_data : cc->callback);
+
+ v_return = callback (data1,
+ g_marshal_value_peek_boxed (param_values + 1),
+ g_marshal_value_peek_boxed (param_values + 2),
+ g_marshal_value_peek_boolean (param_values + 3),
+ data2);
+
+ g_value_set_boolean (return_value, v_return);
+}
+
+/* VOID:INT64,INT64,FLOAT,BOOLEAN (./baconvideowidget-marshal.list:4) */
+extern void baconvideowidget_marshal_VOID__INT64_INT64_FLOAT_BOOLEAN (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+void
+baconvideowidget_marshal_VOID__INT64_INT64_FLOAT_BOOLEAN (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__INT64_INT64_FLOAT_BOOLEAN) (gpointer data1,
+ gint64 arg_1,
+ gint64 arg_2,
+ gfloat arg_3,
+ gboolean arg_4,
+ gpointer data2);
+ register GMarshalFunc_VOID__INT64_INT64_FLOAT_BOOLEAN callback;
+ register GCClosure *cc = (GCClosure*) closure;
+ register gpointer data1, data2;
+
+ g_return_if_fail (n_param_values == 5);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__INT64_INT64_FLOAT_BOOLEAN) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_int64 (param_values + 1),
+ g_marshal_value_peek_int64 (param_values + 2),
+ g_marshal_value_peek_float (param_values + 3),
+ g_marshal_value_peek_boolean (param_values + 4),
+ data2);
+}
+
+G_END_DECLS
+
+#endif /* __baconvideowidget_marshal_MARSHAL_H__ */
+
diff --git a/libcesarplayer/src/baconvideowidget-marshal.h b/libcesarplayer/src/baconvideowidget-marshal.h
new file mode 100644
index 0000000..035d853
--- /dev/null
+++ b/libcesarplayer/src/baconvideowidget-marshal.h
@@ -0,0 +1,44 @@
+
+#ifndef __baconvideowidget_marshal_MARSHAL_H__
+#define __baconvideowidget_marshal_MARSHAL_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/* VOID:INT64,INT64,DOUBLE,BOOLEAN (./baconvideowidget-marshal.list:1) */
+extern void baconvideowidget_marshal_VOID__INT64_INT64_DOUBLE_BOOLEAN (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID:STRING,BOOLEAN,BOOLEAN (./baconvideowidget-marshal.list:2) */
+extern void baconvideowidget_marshal_VOID__STRING_BOOLEAN_BOOLEAN (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* BOOLEAN:BOXED,BOXED,BOOLEAN (./baconvideowidget-marshal.list:3) */
+extern void baconvideowidget_marshal_BOOLEAN__BOXED_BOXED_BOOLEAN (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID:INT64,INT64,FLOAT,BOOLEAN (./baconvideowidget-marshal.list:4) */
+extern void baconvideowidget_marshal_VOID__INT64_INT64_FLOAT_BOOLEAN (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+G_END_DECLS
+
+#endif /* __baconvideowidget_marshal_MARSHAL_H__ */
+
diff --git a/libcesarplayer/src/common.h b/libcesarplayer/src/common.h
new file mode 100644
index 0000000..cb3ed97
--- /dev/null
+++ b/libcesarplayer/src/common.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2010 Andoni Morales Alastruey <ylatuya gmail com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+/**
+ * Error:
+ * @GST_ERROR_AUDIO_PLUGIN: Error loading audio output plugin or device.
+ * @GST_ERROR_NO_PLUGIN_FOR_FILE: A required GStreamer plugin or xine feature is missing.
+ * @GST_ERROR_VIDEO_PLUGIN: Error loading video output plugin or device.
+ * @GST_ERROR_AUDIO_BUSY: Audio output device is busy.
+ * @GST_ERROR_BROKEN_FILE: The movie file is broken and cannot be decoded.
+ * @GST_ERROR_FILE_GENERIC: A generic error for problems with movie files.
+ * @GST_ERROR_FILE_PERMISSION: Permission was refused to access the stream, or authentication was required.
+ * @GST_ERROR_FILE_ENCRYPTED: The stream is encrypted and cannot be played.
+ * @GST_ERROR_FILE_NOT_FOUND: The stream cannot be found.
+ * @GST_ERROR_DVD_ENCRYPTED: The DVD is encrypted and libdvdcss is not installed.
+ * @GST_ERROR_INVALID_DEVICE: The device given in an MRL (e.g. DVD drive or DVB tuner) did not exist.
+ * @GST_ERROR_DEVICE_BUSY: The device was busy.
+ * @GST_ERROR_UNKNOWN_HOST: The host for a given stream could not be resolved.
+ * @GST_ERROR_NETWORK_UNREACHABLE: The host for a given stream could not be reached.
+ * @GST_ERROR_CONNECTION_REFUSED: The server for a given stream refused the connection.
+ * @GST_ERROR_INVALID_LOCATION: An MRL was malformed, or CDDB playback was attempted (which is now unsupported).
+ * @GST_ERROR_GENERIC: A generic error occurred.
+ * @GST_ERROR_CODEC_NOT_HANDLED: The audio or video codec required by the stream is not supported.
+ * @GST_ERROR_AUDIO_ONLY: An audio-only stream could not be played due to missing audio output support.
+ * @GST_ERROR_CANNOT_CAPTURE: Error determining frame capture support for a video with bacon_video_widget_can_get_frames().
+ * @GST_ERROR_READ_ERROR: A generic error for problems reading streams.
+ * @GST_ERROR_PLUGIN_LOAD: A library or plugin could not be loaded.
+ * @GST_ERROR_EMPTY_FILE: A movie file was empty.
+ *
+ **/
+typedef enum
+{
+ /* Plugins */
+ GST_ERROR_AUDIO_PLUGIN,
+ GST_ERROR_NO_PLUGIN_FOR_FILE,
+ GST_ERROR_VIDEO_PLUGIN,
+ GST_ERROR_AUDIO_BUSY,
+ /* File */
+ GST_ERROR_BROKEN_FILE,
+ GST_ERROR_FILE_GENERIC,
+ GST_ERROR_FILE_PERMISSION,
+ GST_ERROR_FILE_ENCRYPTED,
+ GST_ERROR_FILE_NOT_FOUND,
+ /* Devices */
+ GST_ERROR_DVD_ENCRYPTED,
+ GST_ERROR_INVALID_DEVICE,
+ GST_ERROR_DEVICE_BUSY,
+ /* Network */
+ GST_ERROR_UNKNOWN_HOST,
+ GST_ERROR_NETWORK_UNREACHABLE,
+ GST_ERROR_CONNECTION_REFUSED,
+ /* Generic */
+ GST_ERROR_INVALID_LOCATION,
+ GST_ERROR_GENERIC,
+ GST_ERROR_CODEC_NOT_HANDLED,
+ GST_ERROR_AUDIO_ONLY,
+ GST_ERROR_CANNOT_CAPTURE,
+ GST_ERROR_READ_ERROR,
+ GST_ERROR_PLUGIN_LOAD,
+ GST_ERROR_EMPTY_FILE
+} Error;
+
+
+typedef enum
+{
+ VIDEO_ENCODER_MPEG4,
+ VIDEO_ENCODER_XVID,
+ VIDEO_ENCODER_THEORA,
+ VIDEO_ENCODER_H264,
+ VIDEO_ENCODER_MPEG2,
+ VIDEO_ENCODER_VP8
+} VideoEncoderType;
+
+typedef enum
+{
+ AUDIO_ENCODER_MP3,
+ AUDIO_ENCODER_AAC,
+ AUDIO_ENCODER_VORBIS
+} AudioEncoderType;
+
+typedef enum
+{
+ VIDEO_MUXER_AVI,
+ VIDEO_MUXER_MP4,
+ VIDEO_MUXER_MATROSKA,
+ VIDEO_MUXER_OGG,
+ VIDEO_MUXER_MPEG_PS,
+ VIDEO_MUXER_WEBM
+} VideoMuxerType;
diff --git a/libcesarplayer/src/gst-camera-capturer.c b/libcesarplayer/src/gst-camera-capturer.c
new file mode 100644
index 0000000..9502cdb
--- /dev/null
+++ b/libcesarplayer/src/gst-camera-capturer.c
@@ -0,0 +1,1783 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+* Gstreamer DV capturer
+* Copyright (C) Andoni Morales Alastruey 2008 <ylatuya gmail com>
+*
+* Gstreamer DV capturer is free software.
+*
+* You may 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.
+*
+* Gstreamer DV 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 foob. If not, write to:
+* The Free Software Foundation, Inc.,
+* 51 Franklin Street, Fifth Floor
+* Boston, MA 02110-1301, USA.
+*/
+
+#include <string.h>
+#include <stdio.h>
+
+#include <gst/interfaces/xoverlay.h>
+#include <gst/interfaces/propertyprobe.h>
+#include <gst/gst.h>
+#include <gst/video/video.h>
+
+#include "gst-camera-capturer.h"
+#include "gstscreenshot.h"
+
+/*Default video source*/
+#ifdef WIN32
+#define DVVIDEOSRC "dshowvideosrc"
+#define RAWVIDEOSRC "dshowvideosrc"
+#define AUDIOSRC "dshowaudiosrc"
+#else
+#define DVVIDEOSRC "dv1394src"
+#define RAWVIDEOSRC "gconfvideosrc"
+#define AUDIOSRC "gconfaudiosrc"
+#endif
+
+/* gtk+/gnome */
+#ifdef WIN32
+#include <gdk/gdkwin32.h>
+#else
+#include <gdk/gdkx.h>
+#endif
+
+#ifdef WIN32
+#define DEFAULT_SOURCE_TYPE GST_CAMERA_CAPTURE_SOURCE_TYPE_DSHOW
+#else
+#define DEFAULT_SOURCE_TYPE GST_CAMERA_CAPTURE_SOURCE_TYPE_RAW
+#endif
+
+typedef enum
+{
+ GST_CAMERABIN_FLAG_SOURCE_RESIZE = (1 << 0),
+ GST_CAMERABIN_FLAG_SOURCE_COLOR_CONVERSION = (1 << 1),
+ GST_CAMERABIN_FLAG_VIEWFINDER_COLOR_CONVERSION = (1 << 2),
+ GST_CAMERABIN_FLAG_VIEWFINDER_SCALE = (1 << 3),
+ GST_CAMERABIN_FLAG_AUDIO_CONVERSION = (1 << 4),
+ GST_CAMERABIN_FLAG_DISABLE_AUDIO = (1 << 5),
+ GST_CAMERABIN_FLAG_IMAGE_COLOR_CONVERSION = (1 << 6)
+} GstCameraBinFlags;
+
+/* Signals */
+enum
+{
+ SIGNAL_ERROR,
+ SIGNAL_EOS,
+ SIGNAL_STATE_CHANGED,
+ SIGNAL_DEVICE_CHANGE,
+ LAST_SIGNAL
+};
+
+/* Properties */
+enum
+{
+ PROP_0,
+ PROP_OUTPUT_HEIGHT,
+ PROP_OUTPUT_WIDTH,
+ PROP_VIDEO_BITRATE,
+ PROP_AUDIO_BITRATE,
+ PROP_AUDIO_ENABLED,
+ PROP_OUTPUT_FILE,
+ PROP_DEVICE_ID,
+};
+
+struct GstCameraCapturerPrivate
+{
+
+ /*Encoding properties */
+ gchar *output_file;
+ gchar *device_id;
+ guint output_height;
+ guint output_width;
+ guint output_fps_n;
+ guint output_fps_d;
+ guint audio_bitrate;
+ guint video_bitrate;
+ gboolean audio_enabled;
+ VideoEncoderType video_encoder_type;
+ AudioEncoderType audio_encoder_type;
+
+ /*Video input info */
+ gint video_width; /* Movie width */
+ gint video_height; /* Movie height */
+ const GValue *movie_par; /* Movie pixel aspect ratio */
+ gint video_width_pixels; /* Scaled movie width */
+ gint video_height_pixels; /* Scaled movie height */
+ gint video_fps_n;
+ gint video_fps_d;
+ gboolean media_has_video;
+ gboolean media_has_audio;
+ GstCameraCaptureSourceType source_type;
+
+ /* Snapshots */
+ GstBuffer *last_buffer;
+
+ /*GStreamer elements */
+ GstElement *main_pipeline;
+ GstElement *camerabin;
+ GstElement *videosrc;
+ GstElement *device_source;
+ GstElement *videofilter;
+ GstElement *audiosrc;
+ GstElement *videoenc;
+ GstElement *audioenc;
+ GstElement *videomux;
+
+ /*Overlay */
+ GstXOverlay *xoverlay; /* protect with lock */
+ guint interface_update_id; /* protect with lock */
+ GMutex *lock;
+
+ /*Videobox */
+ GdkWindow *video_window;
+ gboolean logo_mode;
+ GdkPixbuf *logo_pixbuf;
+ float zoom;
+
+ /*GStreamer bus */
+ GstBus *bus;
+ gulong sig_bus_async;
+ gulong sig_bus_sync;
+};
+
+static GtkWidgetClass *parent_class = NULL;
+
+static GThread *gui_thread;
+
+static int gcc_signals[LAST_SIGNAL] = { 0 };
+
+static void gcc_error_msg (GstCameraCapturer * gcc, GstMessage * msg);
+static void gcc_bus_message_cb (GstBus * bus, GstMessage * message,
+ gpointer data);
+static void gst_camera_capturer_get_property (GObject * object,
+ guint property_id, GValue * value, GParamSpec * pspec);
+static void gst_camera_capturer_set_property (GObject * object,
+ guint property_id, const GValue * value, GParamSpec * pspec);
+static void gcc_element_msg_sync (GstBus * bus, GstMessage * msg,
+ gpointer data);
+static void gcc_update_interface_implementations (GstCameraCapturer * gcc);
+static int gcc_get_video_stream_info (GstCameraCapturer * gcc);
+
+G_DEFINE_TYPE (GstCameraCapturer, gst_camera_capturer, GTK_TYPE_EVENT_BOX);
+
+static void
+gst_camera_capturer_init (GstCameraCapturer * object)
+{
+ GstCameraCapturerPrivate *priv;
+ object->priv = priv =
+ G_TYPE_INSTANCE_GET_PRIVATE (object, GST_TYPE_CAMERA_CAPTURER,
+ GstCameraCapturerPrivate);
+
+ GTK_WIDGET_SET_FLAGS (GTK_WIDGET (object), GTK_CAN_FOCUS);
+ GTK_WIDGET_UNSET_FLAGS (GTK_WIDGET (object), GTK_DOUBLE_BUFFERED);
+
+ priv->zoom = 1.0;
+ priv->output_height = 576;
+ priv->output_width = 720;
+ priv->output_fps_n = 25;
+ priv->output_fps_d = 1;
+ priv->audio_bitrate = 128;
+ priv->video_bitrate = 5000;
+ priv->last_buffer = NULL;
+ priv->source_type = GST_CAMERA_CAPTURE_SOURCE_TYPE_NONE;
+
+ priv->lock = g_mutex_new ();
+}
+
+void
+gst_camera_capturer_finalize (GObject * object)
+{
+ GstCameraCapturer *gcc = (GstCameraCapturer *) object;
+
+ GST_DEBUG_OBJECT (gcc, "Finalizing.");
+ if (gcc->priv->bus) {
+ /* make bus drop all messages to make sure none of our callbacks is ever
+ * called again (main loop might be run again to display error dialog) */
+ gst_bus_set_flushing (gcc->priv->bus, TRUE);
+
+ if (gcc->priv->sig_bus_async)
+ g_signal_handler_disconnect (gcc->priv->bus, gcc->priv->sig_bus_async);
+
+ if (gcc->priv->sig_bus_sync)
+ g_signal_handler_disconnect (gcc->priv->bus, gcc->priv->sig_bus_sync);
+
+ gst_object_unref (gcc->priv->bus);
+ gcc->priv->bus = NULL;
+ }
+
+ if (gcc->priv->output_file) {
+ g_free (gcc->priv->output_file);
+ gcc->priv->output_file = NULL;
+ }
+
+ if (gcc->priv->device_id) {
+ g_free (gcc->priv->device_id);
+ gcc->priv->device_id = NULL;
+ }
+
+ if (gcc->priv->logo_pixbuf) {
+ g_object_unref (gcc->priv->logo_pixbuf);
+ gcc->priv->logo_pixbuf = NULL;
+ }
+
+ if (gcc->priv->interface_update_id) {
+ g_source_remove (gcc->priv->interface_update_id);
+ gcc->priv->interface_update_id = 0;
+ }
+
+ if (gcc->priv->last_buffer != NULL)
+ gst_buffer_unref (gcc->priv->last_buffer);
+
+ if (gcc->priv->main_pipeline != NULL
+ && GST_IS_ELEMENT (gcc->priv->main_pipeline)) {
+ gst_element_set_state (gcc->priv->main_pipeline, GST_STATE_NULL);
+ gst_object_unref (gcc->priv->main_pipeline);
+ gcc->priv->main_pipeline = NULL;
+ }
+
+ g_mutex_free (gcc->priv->lock);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_camera_capturer_apply_resolution (GstCameraCapturer * gcc)
+{
+ GST_INFO_OBJECT (gcc, "Changed video resolution to %dx%d %d/%dfps",
+ gcc->priv->output_width, gcc->priv->output_height,
+ gcc->priv->output_fps_n, gcc->priv->output_fps_d);
+
+ g_signal_emit_by_name (G_OBJECT (gcc->priv->camerabin),
+ "set-video-resolution-fps", gcc->priv->output_width,
+ gcc->priv->output_height, gcc->priv->output_fps_n,
+ gcc->priv->output_fps_d);
+}
+
+static void
+gst_camera_capturer_set_video_bit_rate (GstCameraCapturer * gcc, gint bitrate)
+{
+ gcc->priv->video_bitrate = bitrate;
+ if (gcc->priv->video_encoder_type == VIDEO_ENCODER_MPEG4 ||
+ gcc->priv->video_encoder_type == VIDEO_ENCODER_XVID)
+ g_object_set (gcc->priv->videoenc, "bitrate", bitrate * 1000, NULL);
+ else
+ g_object_set (gcc->priv->videoenc, "bitrate", gcc->priv->video_bitrate,
+ NULL);
+ GST_INFO_OBJECT (gcc, "Changed video bitrate to :\n%d",
+ gcc->priv->video_bitrate);
+}
+
+static void
+gst_camera_capturer_set_audio_bit_rate (GstCameraCapturer * gcc, gint bitrate)
+{
+
+ gcc->priv->audio_bitrate = bitrate;
+ if (gcc->priv->audio_encoder_type == AUDIO_ENCODER_MP3)
+ g_object_set (gcc->priv->audioenc, "bitrate", bitrate, NULL);
+ else
+ g_object_set (gcc->priv->audioenc, "bitrate", 1000 * bitrate, NULL);
+ GST_INFO_OBJECT (gcc, "Changed audio bitrate to :\n%d",
+ gcc->priv->audio_bitrate);
+
+}
+
+static void
+gst_camera_capturer_set_audio_enabled (GstCameraCapturer * gcc,
+ gboolean enabled)
+{
+ gint flags;
+ gcc->priv->audio_enabled = enabled;
+
+ g_object_get (gcc->priv->main_pipeline, "flags", &flags, NULL);
+ if (!enabled) {
+ flags &= ~GST_CAMERABIN_FLAG_DISABLE_AUDIO;
+ GST_INFO_OBJECT (gcc, "Audio disabled");
+ } else {
+ flags |= GST_CAMERABIN_FLAG_DISABLE_AUDIO;
+ GST_INFO_OBJECT (gcc, "Audio enabled");
+ }
+}
+
+static void
+gst_camera_capturer_set_output_file (GstCameraCapturer * gcc,
+ const gchar * file)
+{
+ gcc->priv->output_file = g_strdup (file);
+ g_object_set (gcc->priv->camerabin, "filename", file, NULL);
+ GST_INFO_OBJECT (gcc, "Changed output filename to :\n%s", file);
+
+}
+
+static void
+gst_camera_capturer_set_device_id (GstCameraCapturer * gcc,
+ const gchar * device_id)
+{
+ gcc->priv->device_id = g_strdup (device_id);
+ GST_INFO_OBJECT (gcc, "Changed device id/name to :\n%s", device_id);
+}
+
+/***********************************
+*
+* GTK Widget
+*
+************************************/
+
+static void
+gst_camera_capturer_size_request (GtkWidget * widget,
+ GtkRequisition * requisition)
+{
+ requisition->width = 320;
+ requisition->height = 240;
+}
+
+static void
+get_media_size (GstCameraCapturer * gcc, gint * width, gint * height)
+{
+ if (gcc->priv->logo_mode) {
+ if (gcc->priv->logo_pixbuf) {
+ *width = gdk_pixbuf_get_width (gcc->priv->logo_pixbuf);
+ *height = gdk_pixbuf_get_height (gcc->priv->logo_pixbuf);
+ } else {
+ *width = 0;
+ *height = 0;
+ }
+ } else {
+
+ GValue *disp_par = NULL;
+ guint movie_par_n, movie_par_d, disp_par_n, disp_par_d, num, den;
+
+ /* Create and init the fraction value */
+ disp_par = g_new0 (GValue, 1);
+ g_value_init (disp_par, GST_TYPE_FRACTION);
+
+ /* Square pixel is our default */
+ gst_value_set_fraction (disp_par, 1, 1);
+
+ /* Now try getting display's pixel aspect ratio */
+ if (gcc->priv->xoverlay) {
+ GObjectClass *klass;
+ GParamSpec *pspec;
+
+ klass = G_OBJECT_GET_CLASS (gcc->priv->xoverlay);
+ pspec = g_object_class_find_property (klass, "pixel-aspect-ratio");
+
+ if (pspec != NULL) {
+ GValue disp_par_prop = { 0, };
+
+ g_value_init (&disp_par_prop, pspec->value_type);
+ g_object_get_property (G_OBJECT (gcc->priv->xoverlay),
+ "pixel-aspect-ratio", &disp_par_prop);
+
+ if (!g_value_transform (&disp_par_prop, disp_par)) {
+ GST_WARNING ("Transform failed, assuming pixel-aspect-ratio = 1/1");
+ gst_value_set_fraction (disp_par, 1, 1);
+ }
+
+ g_value_unset (&disp_par_prop);
+ }
+ }
+
+ disp_par_n = gst_value_get_fraction_numerator (disp_par);
+ disp_par_d = gst_value_get_fraction_denominator (disp_par);
+
+ GST_DEBUG_OBJECT (gcc, "display PAR is %d/%d", disp_par_n, disp_par_d);
+
+ /* Use the movie pixel aspect ratio if any */
+ if (gcc->priv->movie_par) {
+ movie_par_n = gst_value_get_fraction_numerator (gcc->priv->movie_par);
+ movie_par_d = gst_value_get_fraction_denominator (gcc->priv->movie_par);
+ } else {
+ /* Square pixels */
+ movie_par_n = 1;
+ movie_par_d = 1;
+ }
+
+ GST_DEBUG_OBJECT (gcc, "movie PAR is %d/%d", movie_par_n, movie_par_d);
+
+ if (gcc->priv->video_width == 0 || gcc->priv->video_height == 0) {
+ GST_DEBUG_OBJECT (gcc, "width and/or height 0, assuming 1/1 ratio");
+ num = 1;
+ den = 1;
+ } else if (!gst_video_calculate_display_ratio (&num, &den,
+ gcc->priv->video_width,
+ gcc->priv->video_height,
+ movie_par_n, movie_par_d, disp_par_n, disp_par_d)) {
+ GST_WARNING ("overflow calculating display aspect ratio!");
+ num = 1; /* FIXME: what values to use here? */
+ den = 1;
+ }
+
+ GST_DEBUG_OBJECT (gcc, "calculated scaling ratio %d/%d for video %dx%d",
+ num, den, gcc->priv->video_width, gcc->priv->video_height);
+
+ /* now find a width x height that respects this display ratio.
+ * prefer those that have one of w/h the same as the incoming video
+ * using wd / hd = num / den */
+
+ /* start with same height, because of interlaced video */
+ /* check hd / den is an integer scale factor, and scale wd with the PAR */
+ if (gcc->priv->video_height % den == 0) {
+ GST_DEBUG_OBJECT (gcc, "keeping video height");
+ gcc->priv->video_width_pixels =
+ (guint) gst_util_uint64_scale (gcc->priv->video_height, num, den);
+ gcc->priv->video_height_pixels = gcc->priv->video_height;
+ } else if (gcc->priv->video_width % num == 0) {
+ GST_DEBUG_OBJECT (gcc, "keeping video width");
+ gcc->priv->video_width_pixels = gcc->priv->video_width;
+ gcc->priv->video_height_pixels =
+ (guint) gst_util_uint64_scale (gcc->priv->video_width, den, num);
+ } else {
+ GST_DEBUG_OBJECT (gcc, "approximating while keeping video height");
+ gcc->priv->video_width_pixels =
+ (guint) gst_util_uint64_scale (gcc->priv->video_height, num, den);
+ gcc->priv->video_height_pixels = gcc->priv->video_height;
+ }
+ GST_DEBUG_OBJECT (gcc, "scaling to %dx%d", gcc->priv->video_width_pixels,
+ gcc->priv->video_height_pixels);
+
+ *width = gcc->priv->video_width_pixels;
+ *height = gcc->priv->video_height_pixels;
+
+ /* Free the PAR fraction */
+ g_value_unset (disp_par);
+ g_free (disp_par);
+
+ }
+}
+
+static void
+resize_video_window (GstCameraCapturer * gcc)
+{
+ const GtkAllocation *allocation;
+ gfloat width, height, ratio, x, y;
+ int w, h;
+
+ g_return_if_fail (gcc != NULL);
+ g_return_if_fail (GST_IS_CAMERA_CAPTURER (gcc));
+
+ allocation = >K_WIDGET (gcc)->allocation;
+
+ get_media_size (gcc, &w, &h);
+
+ if (!w || !h) {
+ w = allocation->width;
+ h = allocation->height;
+ }
+ width = w;
+ height = h;
+
+ /* calculate ratio for fitting video into the available space */
+ if ((gfloat) allocation->width / width > (gfloat) allocation->height / height) {
+ ratio = (gfloat) allocation->height / height;
+ } else {
+ ratio = (gfloat) allocation->width / width;
+ }
+
+ /* apply zoom factor */
+ ratio = ratio * gcc->priv->zoom;
+
+ width *= ratio;
+ height *= ratio;
+ x = (allocation->width - width) / 2;
+ y = (allocation->height - height) / 2;
+
+ gdk_window_move_resize (gcc->priv->video_window, x, y, width, height);
+ gtk_widget_queue_draw (GTK_WIDGET (gcc));
+}
+
+static void
+gst_camera_capturer_size_allocate (GtkWidget * widget,
+ GtkAllocation * allocation)
+{
+ GstCameraCapturer *gcc = GST_CAMERA_CAPTURER (widget);
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GST_IS_CAMERA_CAPTURER (widget));
+
+ widget->allocation = *allocation;
+
+ if (GTK_WIDGET_REALIZED (widget)) {
+ gdk_window_move_resize (gtk_widget_get_window (widget),
+ allocation->x, allocation->y, allocation->width, allocation->height);
+ resize_video_window (gcc);
+ }
+}
+
+static gboolean
+gst_camera_capturer_configure_event (GtkWidget * widget,
+ GdkEventConfigure * event, GstCameraCapturer * gcc)
+{
+ GstXOverlay *xoverlay = NULL;
+
+ g_return_val_if_fail (gcc != NULL, FALSE);
+ g_return_val_if_fail (GST_IS_CAMERA_CAPTURER (gcc), FALSE);
+
+ xoverlay = gcc->priv->xoverlay;
+
+ if (xoverlay != NULL && GST_IS_X_OVERLAY (xoverlay)) {
+ gst_x_overlay_expose (xoverlay);
+ }
+
+ return FALSE;
+}
+
+static void
+gst_camera_capturer_realize (GtkWidget * widget)
+{
+ GstCameraCapturer *gcc = GST_CAMERA_CAPTURER (widget);
+ GdkWindowAttr attributes;
+ gint attributes_mask, w, h;
+ GdkColor colour;
+ GdkWindow *window;
+ GdkEventMask event_mask;
+
+ event_mask = gtk_widget_get_events (widget)
+ | GDK_POINTER_MOTION_MASK | GDK_KEY_PRESS_MASK;
+ gtk_widget_set_events (widget, event_mask);
+
+ GTK_WIDGET_CLASS (parent_class)->realize (widget);
+
+ window = gtk_widget_get_window (widget);
+
+ /* Creating our video window */
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.x = 0;
+ attributes.y = 0;
+ attributes.width = widget->allocation.width;
+ attributes.height = widget->allocation.height;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.event_mask = gtk_widget_get_events (widget);
+ attributes.event_mask |= GDK_EXPOSURE_MASK |
+ GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_KEY_PRESS_MASK;
+ attributes_mask = GDK_WA_X | GDK_WA_Y;
+
+ gcc->priv->video_window = gdk_window_new (window,
+ &attributes, attributes_mask);
+ gdk_window_set_user_data (gcc->priv->video_window, widget);
+
+ gdk_color_parse ("black", &colour);
+ gdk_colormap_alloc_color (gtk_widget_get_colormap (widget),
+ &colour, TRUE, TRUE);
+ gdk_window_set_background (window, &colour);
+ gtk_widget_set_style (widget,
+ gtk_style_attach (gtk_widget_get_style (widget), window));
+
+ GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+
+ /* Connect to configure event on the top level window */
+ g_signal_connect (G_OBJECT (widget), "configure-event",
+ G_CALLBACK (gst_camera_capturer_configure_event), gcc);
+
+ /* nice hack to show the logo fullsize, while still being resizable */
+ get_media_size (GST_CAMERA_CAPTURER (widget), &w, &h);
+}
+
+static void
+gst_camera_capturer_unrealize (GtkWidget * widget)
+{
+ GstCameraCapturer *gcc = GST_CAMERA_CAPTURER (widget);
+
+ gdk_window_set_user_data (gcc->priv->video_window, NULL);
+ gdk_window_destroy (gcc->priv->video_window);
+ gcc->priv->video_window = NULL;
+
+ GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
+}
+
+static void
+gst_camera_capturer_show (GtkWidget * widget)
+{
+ GstCameraCapturer *gcc = GST_CAMERA_CAPTURER (widget);
+ GdkWindow *window;
+
+ window = gtk_widget_get_window (widget);
+ if (window)
+ gdk_window_show (window);
+ if (gcc->priv->video_window)
+ gdk_window_show (gcc->priv->video_window);
+
+ if (GTK_WIDGET_CLASS (parent_class)->show)
+ GTK_WIDGET_CLASS (parent_class)->show (widget);
+}
+
+static void
+gst_camera_capturer_hide (GtkWidget * widget)
+{
+ GstCameraCapturer *gcc = GST_CAMERA_CAPTURER (widget);
+ GdkWindow *window;
+
+ window = gtk_widget_get_window (widget);
+ if (window)
+ gdk_window_hide (window);
+ if (gcc->priv->video_window)
+ gdk_window_hide (gcc->priv->video_window);
+
+ if (GTK_WIDGET_CLASS (parent_class)->hide)
+ GTK_WIDGET_CLASS (parent_class)->hide (widget);
+}
+
+static gboolean
+gst_camera_capturer_expose_event (GtkWidget * widget, GdkEventExpose * event)
+{
+ GstCameraCapturer *gcc = GST_CAMERA_CAPTURER (widget);
+ GstXOverlay *xoverlay;
+ gboolean draw_logo;
+ GdkWindow *win;
+
+ if (event && event->count > 0)
+ return TRUE;
+
+ g_mutex_lock (gcc->priv->lock);
+ xoverlay = gcc->priv->xoverlay;
+ if (xoverlay == NULL) {
+ gcc_update_interface_implementations (gcc);
+ resize_video_window (gcc);
+ xoverlay = gcc->priv->xoverlay;
+ }
+ if (xoverlay != NULL)
+ gst_object_ref (xoverlay);
+
+ g_mutex_unlock (gcc->priv->lock);
+
+ if (xoverlay != NULL && GST_IS_X_OVERLAY (xoverlay)) {
+#ifdef WIN32
+ gst_x_overlay_set_xwindow_id (gcc->priv->xoverlay,
+ GDK_WINDOW_HWND (gcc->priv->video_window));
+#else
+ gst_x_overlay_set_xwindow_id (gcc->priv->xoverlay,
+ GDK_WINDOW_XID (gcc->priv->video_window));
+#endif
+ }
+
+ /* Start with a nice black canvas */
+ win = gtk_widget_get_window (widget);
+ gdk_draw_rectangle (win, gtk_widget_get_style (widget)->black_gc, TRUE, 0,
+ 0, widget->allocation.width, widget->allocation.height);
+
+ /* if there's only audio and no visualisation, draw the logo as well */
+ draw_logo = gcc->priv->media_has_audio && !gcc->priv->media_has_video;
+
+ if (gcc->priv->logo_mode || draw_logo) {
+ if (gcc->priv->logo_pixbuf != NULL) {
+ GdkPixbuf *frame;
+ guchar *pixels;
+ int rowstride;
+ gint width, height, alloc_width, alloc_height, logo_x, logo_y;
+ gfloat ratio;
+
+ /* Checking if allocated space is smaller than our logo */
+ width = gdk_pixbuf_get_width (gcc->priv->logo_pixbuf);
+ height = gdk_pixbuf_get_height (gcc->priv->logo_pixbuf);
+ alloc_width = widget->allocation.width;
+ alloc_height = widget->allocation.height;
+
+ if ((gfloat) alloc_width / width > (gfloat) alloc_height / height) {
+ ratio = (gfloat) alloc_height / height;
+ } else {
+ ratio = (gfloat) alloc_width / width;
+ }
+
+ width *= ratio;
+ height *= ratio;
+
+ logo_x = (alloc_width / 2) - (width / 2);
+ logo_y = (alloc_height / 2) - (height / 2);
+
+ /* Drawing our frame */
+ /* Scaling to available space */
+ frame = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ FALSE, 8, widget->allocation.width, widget->allocation.height);
+
+ gdk_pixbuf_composite (gcc->priv->logo_pixbuf,
+ frame,
+ 0, 0,
+ alloc_width, alloc_height,
+ logo_x, logo_y, ratio, ratio, GDK_INTERP_BILINEAR, 255);
+
+ rowstride = gdk_pixbuf_get_rowstride (frame);
+
+ pixels = gdk_pixbuf_get_pixels (frame) +
+ rowstride * event->area.y + event->area.x * 3;
+
+ gdk_draw_rgb_image_dithalign (widget->window,
+ widget->style->black_gc,
+ event->area.x, event->area.y,
+ event->area.width,
+ event->area.height,
+ GDK_RGB_DITHER_NORMAL, pixels,
+ rowstride, event->area.x, event->area.y);
+
+ g_object_unref (frame);
+ } else if (win) {
+ /* No pixbuf, just draw a black background then */
+ gdk_window_clear_area (win,
+ 0, 0, widget->allocation.width, widget->allocation.height);
+ }
+ } else {
+ /* no logo, pass the expose to gst */
+ if (xoverlay != NULL && GST_IS_X_OVERLAY (xoverlay)) {
+ gst_x_overlay_expose (xoverlay);
+ } else {
+ /* No xoverlay to expose yet */
+ gdk_window_clear_area (win,
+ 0, 0, widget->allocation.width, widget->allocation.height);
+ }
+ }
+ if (xoverlay != NULL)
+ gst_object_unref (xoverlay);
+
+ return TRUE;
+}
+
+static void
+gst_camera_capturer_set_property (GObject * object, guint property_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstCameraCapturer *gcc;
+
+ gcc = GST_CAMERA_CAPTURER (object);
+
+ switch (property_id) {
+ case PROP_OUTPUT_HEIGHT:
+ gcc->priv->output_height = g_value_get_uint (value);
+ gst_camera_capturer_apply_resolution (gcc);
+ break;
+ case PROP_OUTPUT_WIDTH:
+ gcc->priv->output_width = g_value_get_uint (value);
+ gst_camera_capturer_apply_resolution (gcc);
+ break;
+ case PROP_VIDEO_BITRATE:
+ gst_camera_capturer_set_video_bit_rate (gcc, g_value_get_uint (value));
+ break;
+ case PROP_AUDIO_BITRATE:
+ gst_camera_capturer_set_audio_bit_rate (gcc, g_value_get_uint (value));
+ break;
+ case PROP_AUDIO_ENABLED:
+ gst_camera_capturer_set_audio_enabled (gcc, g_value_get_boolean (value));
+ break;
+ case PROP_OUTPUT_FILE:
+ gst_camera_capturer_set_output_file (gcc, g_value_get_string (value));
+ break;
+ case PROP_DEVICE_ID:
+ gst_camera_capturer_set_device_id (gcc, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_camera_capturer_get_property (GObject * object, guint property_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstCameraCapturer *gcc;
+
+ gcc = GST_CAMERA_CAPTURER (object);
+
+ switch (property_id) {
+ case PROP_OUTPUT_HEIGHT:
+ g_value_set_uint (value, gcc->priv->output_height);
+ break;
+ case PROP_OUTPUT_WIDTH:
+ g_value_set_uint (value, gcc->priv->output_width);
+ break;
+ case PROP_AUDIO_BITRATE:
+ g_value_set_uint (value, gcc->priv->audio_bitrate);
+ break;
+ case PROP_VIDEO_BITRATE:
+ g_value_set_uint (value, gcc->priv->video_bitrate);
+ break;
+ case PROP_AUDIO_ENABLED:
+ g_value_set_boolean (value, gcc->priv->audio_enabled);
+ break;
+ case PROP_OUTPUT_FILE:
+ g_value_set_string (value, gcc->priv->output_file);
+ break;
+ case PROP_DEVICE_ID:
+ g_value_set_string (value, gcc->priv->device_id);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_camera_capturer_class_init (GstCameraCapturerClass * klass)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ object_class = (GObjectClass *) klass;
+ widget_class = (GtkWidgetClass *) klass;
+ parent_class = g_type_class_peek_parent (klass);
+
+ g_type_class_add_private (object_class, sizeof (GstCameraCapturerPrivate));
+
+ /* GtkWidget */
+ widget_class->size_request = gst_camera_capturer_size_request;
+ widget_class->size_allocate = gst_camera_capturer_size_allocate;
+ widget_class->realize = gst_camera_capturer_realize;
+ widget_class->unrealize = gst_camera_capturer_unrealize;
+ widget_class->show = gst_camera_capturer_show;
+ widget_class->hide = gst_camera_capturer_hide;
+ widget_class->expose_event = gst_camera_capturer_expose_event;
+
+ /* GObject */
+ object_class->set_property = gst_camera_capturer_set_property;
+ object_class->get_property = gst_camera_capturer_get_property;
+ object_class->finalize = gst_camera_capturer_finalize;
+
+ /* Properties */
+ g_object_class_install_property (object_class, PROP_OUTPUT_HEIGHT,
+ g_param_spec_uint ("output_height", NULL,
+ NULL, 0, 5600, 576, G_PARAM_READWRITE));
+ g_object_class_install_property (object_class, PROP_OUTPUT_WIDTH,
+ g_param_spec_uint ("output_width", NULL,
+ NULL, 0, 5600, 720, G_PARAM_READWRITE));
+ g_object_class_install_property (object_class, PROP_VIDEO_BITRATE,
+ g_param_spec_uint ("video_bitrate", NULL,
+ NULL, 100, G_MAXUINT, 1000, G_PARAM_READWRITE));
+ g_object_class_install_property (object_class, PROP_AUDIO_BITRATE,
+ g_param_spec_uint ("audio_bitrate", NULL,
+ NULL, 12, G_MAXUINT, 128, G_PARAM_READWRITE));
+ g_object_class_install_property (object_class, PROP_AUDIO_ENABLED,
+ g_param_spec_boolean ("audio_enabled", NULL,
+ NULL, FALSE, G_PARAM_READWRITE));
+ g_object_class_install_property (object_class, PROP_OUTPUT_FILE,
+ g_param_spec_string ("output_file", NULL,
+ NULL, FALSE, G_PARAM_READWRITE));
+ g_object_class_install_property (object_class, PROP_DEVICE_ID,
+ g_param_spec_string ("device_id", NULL, NULL, FALSE, G_PARAM_READWRITE));
+
+ /* Signals */
+ gcc_signals[SIGNAL_ERROR] =
+ g_signal_new ("error",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GstCameraCapturerClass, error),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING);
+
+ gcc_signals[SIGNAL_EOS] =
+ g_signal_new ("eos",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GstCameraCapturerClass, eos),
+ NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+
+ gcc_signals[SIGNAL_DEVICE_CHANGE] =
+ g_signal_new ("device-change",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GstCameraCapturerClass, device_change),
+ NULL, NULL, g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT);
+}
+
+void
+gst_camera_capturer_init_backend (int *argc, char ***argv)
+{
+ gst_init (argc, argv);
+}
+
+GQuark
+gst_camera_capturer_error_quark (void)
+{
+ static GQuark q; /* 0 */
+
+ if (G_UNLIKELY (q == 0)) {
+ q = g_quark_from_static_string ("gcc-error-quark");
+ }
+ return q;
+}
+
+gboolean
+gst_camera_capture_videosrc_buffer_probe (GstPad * pad, GstBuffer * buf,
+ gpointer data)
+{
+ GstCameraCapturer *gcc = GST_CAMERA_CAPTURER (data);
+
+ if (gcc->priv->last_buffer) {
+ gst_buffer_unref (gcc->priv->last_buffer);
+ gcc->priv->last_buffer = NULL;
+ }
+
+ gst_buffer_ref (buf);
+ gcc->priv->last_buffer = buf;
+
+ return TRUE;
+}
+
+static void
+cb_new_pad (GstElement * element, GstPad * pad, gpointer data)
+{
+ GstCaps *caps;
+ const gchar *mime;
+ GstElement *sink;
+ GstBin *bin = GST_BIN (data);
+
+ caps = gst_pad_get_caps (pad);
+ mime = gst_structure_get_name (gst_caps_get_structure (caps, 0));
+ if (g_strrstr (mime, "video")) {
+ sink = gst_bin_get_by_name (bin, "source_video_sink");
+ gst_pad_link (pad, gst_element_get_pad (sink, "sink"));
+ }
+ if (g_strrstr (mime, "audio")) {
+ /* Not implemented yet */
+ }
+}
+
+/* On linux GStreamer packages provided by distributions might still have the
+ * dv1394src clock bug and the dvdemuxer buffers duration bug. That's why we
+ * can't use decodebin2 and we need to force the use of ffdemux_dv */
+static GstElement *
+gst_camera_capture_create_dv1394_source_bin (GstCameraCapturer * gcc)
+{
+ GstElement *bin;
+ GstElement *demuxer;
+ GstElement *queue1;
+ GstElement *decoder;
+ GstElement *queue2;
+ GstElement *deinterlacer;
+ GstElement *colorspace;
+ GstElement *videorate;
+ GstElement *videoscale;
+ GstPad *src_pad;
+
+ bin = gst_bin_new ("videosource");
+ gcc->priv->device_source =
+ gst_element_factory_make (DVVIDEOSRC, "source_device");
+ demuxer = gst_element_factory_make ("ffdemux_dv", NULL);
+ queue1 = gst_element_factory_make ("queue", "source_video_sink");
+ decoder = gst_element_factory_make ("ffdec_dvvideo", NULL);
+ queue2 = gst_element_factory_make ("queue", NULL);
+ deinterlacer = gst_element_factory_make ("ffdeinterlace", NULL);
+ videorate = gst_element_factory_make ("videorate", NULL);
+ colorspace = gst_element_factory_make ("ffmpegcolorspace", NULL);
+ videoscale = gst_element_factory_make ("videoscale", NULL);
+
+ /* this property needs to be set before linking the element, where the device
+ * id configured in get_caps() */
+ g_object_set (G_OBJECT (gcc->priv->device_source), "guid",
+ g_ascii_strtoull (gcc->priv->device_id, NULL, 0), NULL);
+
+ gst_bin_add_many (GST_BIN (bin), gcc->priv->device_source, demuxer, queue1,
+ decoder, queue2, deinterlacer, colorspace, videorate, videoscale, NULL);
+ gst_element_link (gcc->priv->device_source, demuxer);
+ gst_element_link_many (queue1, decoder, queue2, deinterlacer, videorate,
+ colorspace, videoscale, NULL);
+
+ g_signal_connect (demuxer, "pad-added", G_CALLBACK (cb_new_pad), bin);
+
+ /* add ghostpad */
+ src_pad = gst_element_get_static_pad (videoscale, "src");
+ gst_element_add_pad (bin, gst_ghost_pad_new ("src", src_pad));
+ gst_object_unref (GST_OBJECT (src_pad));
+
+ return bin;
+}
+
+static GstElement *
+gst_camera_capture_create_dshow_source_bin (GstCameraCapturer * gcc)
+{
+ GstElement *bin;
+ GstElement *decoder;
+ GstElement *deinterlacer;
+ GstElement *colorspace;
+ GstElement *videorate;
+ GstElement *videoscale;
+ GstPad *src_pad;
+ GstCaps *source_caps;
+
+ bin = gst_bin_new ("videosource");
+ gcc->priv->device_source =
+ gst_element_factory_make (DVVIDEOSRC, "source_device");
+ decoder = gst_element_factory_make ("decodebin2", NULL);
+ colorspace = gst_element_factory_make ("ffmpegcolorspace",
+ "source_video_sink");
+ deinterlacer = gst_element_factory_make ("ffdeinterlace", NULL);
+ videorate = gst_element_factory_make ("videorate", NULL);
+ videoscale = gst_element_factory_make ("videoscale", NULL);
+
+ /* this property needs to be set before linking the element, where the device
+ * id configured in get_caps() */
+ g_object_set (G_OBJECT (gcc->priv->device_source), "device-name",
+ gcc->priv->device_id, NULL);
+
+ gst_bin_add_many (GST_BIN (bin), gcc->priv->device_source, decoder,
+ colorspace, deinterlacer, videorate, videoscale, NULL);
+ source_caps =
+ gst_caps_from_string ("video/x-dv, systemstream=true;"
+ "video/x-raw-rgb; video/x-raw-yuv");
+ gst_element_link_filtered (gcc->priv->device_source, decoder, source_caps);
+ gst_element_link_many (colorspace, deinterlacer, videorate, videoscale, NULL);
+
+ g_signal_connect (decoder, "pad-added", G_CALLBACK (cb_new_pad), bin);
+
+ /* add ghostpad */
+ src_pad = gst_element_get_static_pad (videoscale, "src");
+ gst_element_add_pad (bin, gst_ghost_pad_new ("src", src_pad));
+ gst_object_unref (GST_OBJECT (src_pad));
+
+ return bin;
+}
+
+gboolean
+gst_camera_capturer_set_source (GstCameraCapturer * gcc,
+ GstCameraCaptureSourceType source_type, GError ** err)
+{
+ GstPad *videosrcpad;
+
+ g_return_val_if_fail (gcc != NULL, FALSE);
+ g_return_val_if_fail (GST_IS_CAMERA_CAPTURER (gcc), FALSE);
+
+ if (gcc->priv->source_type == source_type)
+ return TRUE;
+ gcc->priv->source_type = source_type;
+
+ switch (gcc->priv->source_type) {
+ case GST_CAMERA_CAPTURE_SOURCE_TYPE_DV:
+ {
+ gcc->priv->videosrc = gst_camera_capture_create_dv1394_source_bin (gcc);
+ /*gcc->priv->audiosrc = gcc->priv->videosrc; */
+ break;
+ }
+ case GST_CAMERA_CAPTURE_SOURCE_TYPE_DSHOW:
+ {
+ gcc->priv->videosrc = gst_camera_capture_create_dshow_source_bin (gcc);
+ /*gcc->priv->audiosrc = gcc->priv->videosrc; */
+ break;
+ }
+ case GST_CAMERA_CAPTURE_SOURCE_TYPE_RAW:
+ default:
+ {
+ gchar *bin =
+ g_strdup_printf ("%s name=device_source ! videorate ! "
+ "ffmpegcolorspace ! videoscale", RAWVIDEOSRC);
+ gcc->priv->videosrc = gst_parse_bin_from_description (bin, TRUE, err);
+ gcc->priv->device_source =
+ gst_bin_get_by_name (GST_BIN (gcc->priv->videosrc), "device_source");
+ gcc->priv->audiosrc = gst_element_factory_make (AUDIOSRC, "audiosource");
+ break;
+ }
+ }
+ if (*err) {
+ GST_ERROR_OBJECT (gcc, "Error changing source: %s", (*err)->message);
+ return FALSE;
+ }
+
+ g_object_set (gcc->priv->camerabin, "video-source", gcc->priv->videosrc,
+ NULL);
+
+ /* Install pad probe to store the last buffer */
+ videosrcpad = gst_element_get_pad (gcc->priv->videosrc, "src");
+ gst_pad_add_buffer_probe (videosrcpad,
+ G_CALLBACK (gst_camera_capture_videosrc_buffer_probe), gcc);
+ return TRUE;
+}
+
+GstCameraCapturer *
+gst_camera_capturer_new (gchar * filename, GError ** err)
+{
+ GstCameraCapturer *gcc = NULL;
+ gchar *plugin;
+ gint flags = 0;
+
+ gcc = g_object_new (GST_TYPE_CAMERA_CAPTURER, NULL);
+
+ gcc->priv->main_pipeline = gst_pipeline_new ("main_pipeline");
+
+ if (!gcc->priv->main_pipeline) {
+ plugin = "pipeline";
+ goto missing_plugin;
+ }
+
+ /* Setup */
+ GST_INFO_OBJECT (gcc, "Initializing camerabin");
+ gcc->priv->camerabin = gst_element_factory_make ("camerabin", "camerabin");
+ gst_bin_add (GST_BIN (gcc->priv->main_pipeline), gcc->priv->camerabin);
+ if (!gcc->priv->camerabin) {
+ plugin = "camerabin";
+ goto missing_plugin;
+ }
+ GST_INFO_OBJECT (gcc, "Setting capture mode to \"video\"");
+ g_object_set (gcc->priv->camerabin, "mode", 1, NULL);
+
+
+ GST_INFO_OBJECT (gcc, "Disabling audio");
+ flags = GST_CAMERABIN_FLAG_DISABLE_AUDIO;
+#ifdef WIN32
+ flags |= GST_CAMERABIN_FLAG_VIEWFINDER_COLOR_CONVERSION;
+#endif
+ g_object_set (gcc->priv->camerabin, "flags", flags, NULL);
+
+ /* assume we're always called from the main Gtk+ GUI thread */
+ gui_thread = g_thread_self ();
+
+ /*Connect bus signals */
+ GST_INFO_OBJECT (gcc, "Connecting bus signals");
+ gcc->priv->bus = gst_element_get_bus (GST_ELEMENT (gcc->priv->main_pipeline));
+ gst_bus_add_signal_watch (gcc->priv->bus);
+ gcc->priv->sig_bus_async =
+ g_signal_connect (gcc->priv->bus, "message",
+ G_CALLBACK (gcc_bus_message_cb), gcc);
+
+ /* we want to catch "prepare-xwindow-id" element messages synchronously */
+ gst_bus_set_sync_handler (gcc->priv->bus, gst_bus_sync_signal_handler, gcc);
+
+ gcc->priv->sig_bus_sync =
+ g_signal_connect (gcc->priv->bus, "sync-message::element",
+ G_CALLBACK (gcc_element_msg_sync), gcc);
+
+ return gcc;
+
+/* Missing plugin */
+missing_plugin:
+ {
+ g_set_error (err, GCC_ERROR, GST_ERROR_PLUGIN_LOAD,
+ ("Failed to create a GStreamer element. "
+ "The element \"%s\" is missing. "
+ "Please check your GStreamer installation."), plugin);
+ g_object_ref_sink (gcc);
+ g_object_unref (gcc);
+ return NULL;
+ }
+}
+
+void
+gst_camera_capturer_run (GstCameraCapturer * gcc)
+{
+ GError *err = NULL;
+
+ g_return_if_fail (gcc != NULL);
+ g_return_if_fail (GST_IS_CAMERA_CAPTURER (gcc));
+
+ /* the source needs to be created before the 'device-is' is set
+ * because dshowsrcwrapper can't change the device-name after
+ * it has been linked for the first time */
+ if (!gcc->priv->videosrc)
+ gst_camera_capturer_set_source (gcc, gcc->priv->source_type, &err);
+ gst_element_set_state (gcc->priv->main_pipeline, GST_STATE_PLAYING);
+}
+
+void
+gst_camera_capturer_close (GstCameraCapturer * gcc)
+{
+ g_return_if_fail (gcc != NULL);
+ g_return_if_fail (GST_IS_CAMERA_CAPTURER (gcc));
+
+ gst_element_set_state (gcc->priv->main_pipeline, GST_STATE_NULL);
+}
+
+void
+gst_camera_capturer_start (GstCameraCapturer * gcc)
+{
+ g_return_if_fail (gcc != NULL);
+ g_return_if_fail (GST_IS_CAMERA_CAPTURER (gcc));
+
+ g_signal_emit_by_name (G_OBJECT (gcc->priv->camerabin), "capture-start", 0,
+ 0);
+}
+
+void
+gst_camera_capturer_toggle_pause (GstCameraCapturer * gcc)
+{
+ g_return_if_fail (gcc != NULL);
+ g_return_if_fail (GST_IS_CAMERA_CAPTURER (gcc));
+
+ g_signal_emit_by_name (G_OBJECT (gcc->priv->camerabin), "capture-pause", 0,
+ 0);
+}
+
+void
+gst_camera_capturer_stop (GstCameraCapturer * gcc)
+{
+ g_return_if_fail (gcc != NULL);
+ g_return_if_fail (GST_IS_CAMERA_CAPTURER (gcc));
+
+#ifdef WIN32
+ //On windows we can't handle device disconnections until dshowvideosrc
+ //supports it. When a device is disconnected, the source is locked
+ //in ::create(), blocking the streaming thread. We need to change its
+ //state to null, this way camerabin doesn't block in ::do_stop().
+ gst_element_set_state(gcc->priv->device_source, GST_STATE_NULL);
+#endif
+ g_signal_emit_by_name (G_OBJECT (gcc->priv->camerabin), "capture-stop", 0, 0);
+}
+
+gboolean
+gst_camera_capturer_set_video_encoder (GstCameraCapturer * gcc,
+ VideoEncoderType type, GError ** err)
+{
+ gchar *name = NULL;
+
+ g_return_val_if_fail (gcc != NULL, FALSE);
+ g_return_val_if_fail (GST_IS_CAMERA_CAPTURER (gcc), FALSE);
+
+ switch (type) {
+ case VIDEO_ENCODER_MPEG4:
+ gcc->priv->videoenc =
+ gst_element_factory_make ("ffenc_mpeg4", "video-encoder");
+ g_object_set (gcc->priv->videoenc, "pass", 512,
+ "max-key-interval", -1, NULL);
+ name = "FFmpeg mpeg4 video encoder";
+ break;
+
+ case VIDEO_ENCODER_XVID:
+ gcc->priv->videoenc =
+ gst_element_factory_make ("xvidenc", "video-encoder");
+ g_object_set (gcc->priv->videoenc, "pass", 1,
+ "profile", 146, "max-key-interval", -1, NULL);
+ name = "Xvid video encoder";
+ break;
+
+ case VIDEO_ENCODER_H264:
+ gcc->priv->videoenc =
+ gst_element_factory_make ("x264enc", "video-encoder");
+ g_object_set (gcc->priv->videoenc, "key-int-max", 25, "pass", 17, NULL);
+ name = "X264 video encoder";
+ break;
+
+ case VIDEO_ENCODER_THEORA:
+ gcc->priv->videoenc =
+ gst_element_factory_make ("theoraenc", "video-encoder");
+ g_object_set (gcc->priv->videoenc, "keyframe-auto", FALSE,
+ "keyframe-force", 25, NULL);
+ name = "Theora video encoder";
+ break;
+
+ case VIDEO_ENCODER_VP8:
+ default:
+ gcc->priv->videoenc =
+ gst_element_factory_make ("vp8enc", "video-encoder");
+ g_object_set (gcc->priv->videoenc, "speed", 2,
+ "max-keyframe-distance", 25, NULL);
+ name = "VP8 video encoder";
+ break;
+
+ }
+ if (!gcc->priv->videoenc) {
+ g_set_error (err,
+ GCC_ERROR,
+ GST_ERROR_PLUGIN_LOAD,
+ "Failed to create the %s element. "
+ "Please check your GStreamer installation.", name);
+ } else {
+ g_object_set (gcc->priv->camerabin, "video-encoder", gcc->priv->videoenc,
+ NULL);
+ gcc->priv->video_encoder_type = type;
+ }
+ return TRUE;
+}
+
+gboolean
+gst_camera_capturer_set_audio_encoder (GstCameraCapturer * gcc,
+ AudioEncoderType type, GError ** err)
+{
+ gchar *name = NULL;
+
+ g_return_val_if_fail (gcc != NULL, FALSE);
+ g_return_val_if_fail (GST_IS_CAMERA_CAPTURER (gcc), FALSE);
+
+ switch (type) {
+ case AUDIO_ENCODER_MP3:
+ gcc->priv->audioenc =
+ gst_element_factory_make ("lamemp3enc", "audio-encoder");
+ g_object_set (gcc->priv->audioenc, "target", 0, NULL);
+ name = "Mp3 audio encoder";
+ break;
+
+ case AUDIO_ENCODER_AAC:
+ gcc->priv->audioenc = gst_element_factory_make ("faac", "audio-encoder");
+ name = "AAC audio encoder";
+ break;
+
+ case AUDIO_ENCODER_VORBIS:
+ default:
+ gcc->priv->audioenc =
+ gst_element_factory_make ("vorbisenc", "audio-encoder");
+ name = "Vorbis audio encoder";
+ break;
+ }
+
+ if (!gcc->priv->audioenc) {
+ g_set_error (err,
+ GCC_ERROR,
+ GST_ERROR_PLUGIN_LOAD,
+ "Failed to create the %s element. "
+ "Please check your GStreamer installation.", name);
+ } else {
+ g_object_set (gcc->priv->camerabin, "audio-encoder", gcc->priv->audioenc,
+ NULL);
+ gcc->priv->audio_encoder_type = type;
+ }
+
+ return TRUE;
+}
+
+gboolean
+gst_camera_capturer_set_video_muxer (GstCameraCapturer * gcc,
+ VideoMuxerType type, GError ** err)
+{
+ gchar *name = NULL;
+
+ g_return_val_if_fail (gcc != NULL, FALSE);
+ g_return_val_if_fail (GST_IS_CAMERA_CAPTURER (gcc), FALSE);
+
+ switch (type) {
+ case VIDEO_MUXER_OGG:
+ name = "OGG muxer";
+ gcc->priv->videomux = gst_element_factory_make ("oggmux", "video-muxer");
+ break;
+ case VIDEO_MUXER_AVI:
+ name = "AVI muxer";
+ gcc->priv->videomux = gst_element_factory_make ("avimux", "video-muxer");
+ break;
+ case VIDEO_MUXER_MATROSKA:
+ name = "Matroska muxer";
+ gcc->priv->videomux =
+ gst_element_factory_make ("matroskamux", "video-muxer");
+ break;
+ case VIDEO_MUXER_MP4:
+ name = "MP4 muxer";
+ gcc->priv->videomux = gst_element_factory_make ("qtmux", "video-muxer");
+ break;
+ case VIDEO_MUXER_WEBM:
+ default:
+ name = "WebM muxer";
+ gcc->priv->videomux = gst_element_factory_make ("webmmux", "video-muxer");
+ break;
+ }
+
+ if (!gcc->priv->videomux) {
+ g_set_error (err,
+ GCC_ERROR,
+ GST_ERROR_PLUGIN_LOAD,
+ "Failed to create the %s element. "
+ "Please check your GStreamer installation.", name);
+ } else {
+ g_object_set (gcc->priv->camerabin, "video-muxer", gcc->priv->videomux,
+ NULL);
+ }
+
+ return TRUE;
+}
+
+static void
+gcc_bus_message_cb (GstBus * bus, GstMessage * message, gpointer data)
+{
+ GstCameraCapturer *gcc = (GstCameraCapturer *) data;
+ GstMessageType msg_type;
+
+ g_return_if_fail (gcc != NULL);
+ g_return_if_fail (GST_IS_CAMERA_CAPTURER (gcc));
+
+ msg_type = GST_MESSAGE_TYPE (message);
+
+ switch (msg_type) {
+ case GST_MESSAGE_ERROR:
+ {
+ if (gcc->priv->main_pipeline) {
+ gst_camera_capturer_stop (gcc);
+ gst_camera_capturer_close (gcc);
+ gst_element_set_state (gcc->priv->main_pipeline, GST_STATE_NULL);
+ }
+ gcc_error_msg (gcc, message);
+ break;
+ }
+
+ case GST_MESSAGE_WARNING:
+ {
+ GST_WARNING ("Warning message: %" GST_PTR_FORMAT, message);
+ break;
+ }
+
+ case GST_MESSAGE_EOS:
+ {
+ GST_INFO_OBJECT (gcc, "EOS message");
+ g_signal_emit (gcc, gcc_signals[SIGNAL_EOS], 0);
+ break;
+ }
+
+ case GST_MESSAGE_STATE_CHANGED:
+ {
+ GstState old_state, new_state;
+
+ gst_message_parse_state_changed (message, &old_state, &new_state, NULL);
+
+ if (old_state == new_state)
+ break;
+
+ /* we only care about playbin (pipeline) state changes */
+ if (GST_MESSAGE_SRC (message) != GST_OBJECT (gcc->priv->main_pipeline))
+ break;
+
+ if (old_state == GST_STATE_PAUSED && new_state == GST_STATE_PLAYING) {
+ gcc_get_video_stream_info (gcc);
+ resize_video_window (gcc);
+ gtk_widget_queue_draw (GTK_WIDGET (gcc));
+ }
+ }
+
+ case GST_MESSAGE_ELEMENT:
+ {
+ const GstStructure *s;
+ gint device_change = 0;
+
+ /* We only care about messages sent by the device source */
+ if (GST_MESSAGE_SRC (message) != GST_OBJECT (gcc->priv->device_source))
+ break;
+
+ s = gst_message_get_structure (message);
+ /* check if it's bus reset message and it contains the
+ * 'current-device-change' field */
+ if (g_strcmp0 (gst_structure_get_name (s), "ieee1394-bus-reset"))
+ break;
+ if (!gst_structure_has_field (s, "current-device-change"))
+ break;
+
+
+ /* emit a signal if the device was connected or disconnected */
+ gst_structure_get_int (s, "current-device-change", &device_change);
+
+ if (device_change != 0)
+ g_signal_emit (gcc, gcc_signals[SIGNAL_DEVICE_CHANGE], 0,
+ device_change);
+ break;
+ }
+
+ default:
+ GST_LOG ("Unhandled message: %" GST_PTR_FORMAT, message);
+ break;
+ }
+}
+
+static void
+gcc_error_msg (GstCameraCapturer * gcc, GstMessage * msg)
+{
+ GError *err = NULL;
+ gchar *dbg = NULL;
+
+ gst_message_parse_error (msg, &err, &dbg);
+ if (err) {
+ GST_ERROR ("message = %s", GST_STR_NULL (err->message));
+ GST_ERROR ("domain = %d (%s)", err->domain,
+ GST_STR_NULL (g_quark_to_string (err->domain)));
+ GST_ERROR ("code = %d", err->code);
+ GST_ERROR ("debug = %s", GST_STR_NULL (dbg));
+ GST_ERROR ("source = %" GST_PTR_FORMAT, msg->src);
+
+
+ g_message ("Error: %s\n%s\n", GST_STR_NULL (err->message),
+ GST_STR_NULL (dbg));
+ g_signal_emit (gcc, gcc_signals[SIGNAL_ERROR], 0, err->message);
+ g_error_free (err);
+ }
+ g_free (dbg);
+}
+
+static gboolean
+gcc_update_interfaces_delayed (GstCameraCapturer * gcc)
+{
+ GST_DEBUG_OBJECT (gcc, "Delayed updating interface implementations");
+ g_mutex_lock (gcc->priv->lock);
+ gcc_update_interface_implementations (gcc);
+ gcc->priv->interface_update_id = 0;
+ g_mutex_unlock (gcc->priv->lock);
+
+ return FALSE;
+}
+
+static void
+gcc_update_interface_implementations (GstCameraCapturer * gcc)
+{
+
+ GstElement *element = NULL;
+
+ if (g_thread_self () != gui_thread) {
+ if (gcc->priv->interface_update_id)
+ g_source_remove (gcc->priv->interface_update_id);
+ gcc->priv->interface_update_id =
+ g_idle_add ((GSourceFunc) gcc_update_interfaces_delayed, gcc);
+ return;
+ }
+
+ GST_INFO_OBJECT (gcc, "Retrieving xoverlay from bin ...");
+ element = gst_bin_get_by_interface (GST_BIN (gcc->priv->camerabin),
+ GST_TYPE_X_OVERLAY);
+
+ if (GST_IS_X_OVERLAY (element)) {
+ gcc->priv->xoverlay = GST_X_OVERLAY (element);
+ } else {
+ gcc->priv->xoverlay = NULL;
+ }
+}
+
+static void
+gcc_element_msg_sync (GstBus * bus, GstMessage * msg, gpointer data)
+{
+ GstCameraCapturer *gcc = GST_CAMERA_CAPTURER (data);
+
+ g_assert (msg->type == GST_MESSAGE_ELEMENT);
+
+ if (msg->structure == NULL)
+ return;
+
+ /* This only gets sent if we haven't set an ID yet. This is our last
+ * chance to set it before the video sink will create its own window */
+ if (gst_structure_has_name (msg->structure, "prepare-xwindow-id")) {
+ g_mutex_lock (gcc->priv->lock);
+ gcc_update_interface_implementations (gcc);
+ g_mutex_unlock (gcc->priv->lock);
+
+ if (gcc->priv->xoverlay == NULL) {
+ GstObject *sender = GST_MESSAGE_SRC (msg);
+ if (sender && GST_IS_X_OVERLAY (sender))
+ gcc->priv->xoverlay = GST_X_OVERLAY (gst_object_ref (sender));
+ }
+
+ g_return_if_fail (gcc->priv->xoverlay != NULL);
+ g_return_if_fail (gcc->priv->video_window != NULL);
+
+#ifdef WIN32
+ gst_x_overlay_set_xwindow_id (gcc->priv->xoverlay,
+ GDK_WINDOW_HWND (gcc->priv->video_window));
+#else
+ gst_x_overlay_set_xwindow_id (gcc->priv->xoverlay,
+ GDK_WINDOW_XID (gcc->priv->video_window));
+#endif
+ }
+}
+
+static int
+gcc_get_video_stream_info (GstCameraCapturer * gcc)
+{
+ GstPad *sourcepad;
+ GstCaps *caps;
+ GstStructure *s;
+
+ sourcepad = gst_element_get_pad (gcc->priv->videosrc, "src");
+ caps = gst_pad_get_negotiated_caps (sourcepad);
+
+ if (!(caps)) {
+ GST_WARNING_OBJECT (gcc, "Could not get stream info");
+ return -1;
+ }
+
+ /* Get the source caps */
+ s = gst_caps_get_structure (caps, 0);
+ if (s) {
+ /* We need at least width/height and framerate */
+ if (!
+ (gst_structure_get_fraction
+ (s, "framerate", &gcc->priv->video_fps_n, &gcc->priv->video_fps_d)
+ && gst_structure_get_int (s, "width", &gcc->priv->video_width)
+ && gst_structure_get_int (s, "height", &gcc->priv->video_height)))
+ return -1;
+ /* Get the source PAR if available */
+ gcc->priv->movie_par = gst_structure_get_value (s, "pixel-aspect-ratio");
+ }
+ return 1;
+}
+
+GList *
+gst_camera_capturer_enum_devices (gchar * device_name)
+{
+ GstElement *device;
+ GstPropertyProbe *probe;
+ GValueArray *va;
+ gchar *prop_name;
+ GList *list = NULL;
+ guint i = 0;
+
+ device = gst_element_factory_make (device_name, "source");
+ if (!device || !GST_IS_PROPERTY_PROBE (device))
+ goto finish;
+ gst_element_set_state (device, GST_STATE_READY);
+ gst_element_get_state (device, NULL, NULL, 5 * GST_SECOND);
+ probe = GST_PROPERTY_PROBE (device);
+
+ if (!g_strcmp0 (device_name, "dv1394src"))
+ prop_name = "guid";
+ else if (!g_strcmp0 (device_name, "v4l2src"))
+ prop_name = "device";
+ else
+ prop_name = "device-name";
+
+ va = gst_property_probe_get_values_name (probe, prop_name);
+ if (!va)
+ goto finish;
+
+ for (i = 0; i < va->n_values; ++i) {
+ GValue *v = g_value_array_get_nth (va, i);
+ GValue valstr = { 0, };
+
+ g_value_init (&valstr, G_TYPE_STRING);
+ if (!g_value_transform (v, &valstr))
+ continue;
+ list = g_list_append (list, g_value_dup_string (&valstr));
+ g_value_unset (&valstr);
+ }
+ g_value_array_free (va);
+
+finish:
+ {
+ gst_element_set_state (device, GST_STATE_NULL);
+ gst_object_unref (GST_OBJECT (device));
+ return list;
+ }
+}
+
+GList *
+gst_camera_capturer_enum_video_devices (void)
+{
+ return gst_camera_capturer_enum_devices (DVVIDEOSRC);
+}
+
+GList *
+gst_camera_capturer_enum_audio_devices (void)
+{
+ return gst_camera_capturer_enum_devices (AUDIOSRC);
+}
+
+gboolean
+gst_camera_capturer_can_get_frames (GstCameraCapturer * gcc, GError ** error)
+{
+ g_return_val_if_fail (gcc != NULL, FALSE);
+ g_return_val_if_fail (GST_IS_CAMERA_CAPTURER (gcc), FALSE);
+ g_return_val_if_fail (GST_IS_ELEMENT (gcc->priv->camerabin), FALSE);
+
+ /* check for video */
+ if (!gcc->priv->media_has_video) {
+ g_set_error_literal (error, GCC_ERROR, GST_ERROR_GENERIC,
+ "Media contains no supported video streams.");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void
+destroy_pixbuf (guchar * pix, gpointer data)
+{
+ gst_buffer_unref (GST_BUFFER (data));
+}
+
+void
+gst_camera_capturer_unref_pixbuf (GdkPixbuf * pixbuf)
+{
+ gdk_pixbuf_unref (pixbuf);
+}
+
+GdkPixbuf *
+gst_camera_capturer_get_current_frame (GstCameraCapturer * gcc)
+{
+ GstStructure *s;
+ GdkPixbuf *pixbuf;
+ GstBuffer *last_buffer;
+ GstBuffer *buf;
+ GstCaps *to_caps;
+ gint outwidth = 0;
+ gint outheight = 0;
+
+ g_return_val_if_fail (gcc != NULL, NULL);
+ g_return_val_if_fail (GST_IS_CAMERA_CAPTURER (gcc), NULL);
+ g_return_val_if_fail (GST_IS_ELEMENT (gcc->priv->camerabin), NULL);
+
+ gst_element_get_state (gcc->priv->camerabin, NULL, NULL, -1);
+
+ /* no video info */
+ if (!gcc->priv->video_width || !gcc->priv->video_height) {
+ GST_DEBUG_OBJECT (gcc, "Could not take screenshot: %s", "no video info");
+ g_warning ("Could not take screenshot: %s", "no video info");
+ return NULL;
+ }
+
+ /* get frame */
+ last_buffer = gcc->priv->last_buffer;
+ gst_buffer_ref (last_buffer);
+
+ if (!last_buffer) {
+ GST_DEBUG_OBJECT (gcc, "Could not take screenshot: %s",
+ "no last video frame");
+ g_warning ("Could not take screenshot: %s", "no last video frame");
+ return NULL;
+ }
+
+ if (GST_BUFFER_CAPS (last_buffer) == NULL) {
+ GST_DEBUG_OBJECT (gcc, "Could not take screenshot: %s",
+ "no caps on buffer");
+ g_warning ("Could not take screenshot: %s", "no caps on buffer");
+ return NULL;
+ }
+
+ /* convert to our desired format (RGB24) */
+ to_caps = gst_caps_new_simple ("video/x-raw-rgb",
+ "bpp", G_TYPE_INT, 24, "depth", G_TYPE_INT, 24,
+ /* Note: we don't ask for a specific width/height here, so that
+ * videoscale can adjust dimensions from a non-1/1 pixel aspect
+ * ratio to a 1/1 pixel-aspect-ratio */
+ "pixel-aspect-ratio", GST_TYPE_FRACTION, 1,
+ 1, "endianness", G_TYPE_INT, G_BIG_ENDIAN,
+ "red_mask", G_TYPE_INT, 0xff0000,
+ "green_mask", G_TYPE_INT, 0x00ff00,
+ "blue_mask", G_TYPE_INT, 0x0000ff, NULL);
+
+ if (gcc->priv->video_fps_n > 0 && gcc->priv->video_fps_d > 0) {
+ gst_caps_set_simple (to_caps, "framerate", GST_TYPE_FRACTION,
+ gcc->priv->video_fps_n, gcc->priv->video_fps_d, NULL);
+ }
+
+ GST_DEBUG_OBJECT (gcc, "frame caps: %" GST_PTR_FORMAT,
+ GST_BUFFER_CAPS (gcc->priv->last_buffer));
+ GST_DEBUG_OBJECT (gcc, "pixbuf caps: %" GST_PTR_FORMAT, to_caps);
+
+ /* bvw_frame_conv_convert () takes ownership of the buffer passed */
+ buf = bvw_frame_conv_convert (last_buffer, to_caps);
+
+ gst_caps_unref (to_caps);
+ gst_buffer_unref (last_buffer);
+
+ if (!buf) {
+ GST_DEBUG_OBJECT (gcc, "Could not take screenshot: %s",
+ "conversion failed");
+ g_warning ("Could not take screenshot: %s", "conversion failed");
+ return NULL;
+ }
+
+ if (!GST_BUFFER_CAPS (buf)) {
+ GST_DEBUG_OBJECT (gcc, "Could not take screenshot: %s",
+ "no caps on output buffer");
+ g_warning ("Could not take screenshot: %s", "no caps on output buffer");
+ return NULL;
+ }
+
+ s = gst_caps_get_structure (GST_BUFFER_CAPS (buf), 0);
+ gst_structure_get_int (s, "width", &outwidth);
+ gst_structure_get_int (s, "height", &outheight);
+ g_return_val_if_fail (outwidth > 0 && outheight > 0, NULL);
+
+ /* create pixbuf from that - we don't want to use the gstreamer's buffer
+ * because the GTK# bindings won't call the destroy funtion */
+ pixbuf = gdk_pixbuf_new_from_data (GST_BUFFER_DATA (buf),
+ GDK_COLORSPACE_RGB, FALSE, 8, outwidth,
+ outheight, GST_ROUND_UP_4 (outwidth * 3), destroy_pixbuf, buf);
+
+ if (!pixbuf) {
+ GST_DEBUG_OBJECT (gcc, "Could not take screenshot: %s",
+ "could not create pixbuf");
+ g_warning ("Could not take screenshot: %s", "could not create pixbuf");
+ }
+
+ return pixbuf;
+}
diff --git a/libcesarplayer/src/gst-camera-capturer.h b/libcesarplayer/src/gst-camera-capturer.h
new file mode 100644
index 0000000..0e83bb5
--- /dev/null
+++ b/libcesarplayer/src/gst-camera-capturer.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+ * Gstreamer DV capturer
+ * Copyright (C) Andoni Morales Alastruey 2008 <ylatuya gmail com>
+ *
+ * Gstreamer DV capturer is free software.
+ *
+ * You may 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.
+ *
+ * foob 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 foob. If not, write to:
+ * The Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _GST_CAMERA_CAPTURER_H_
+#define _GST_CAMERA_CAPTURER_H_
+
+#ifdef WIN32
+#define EXPORT __declspec (dllexport)
+#else
+#define EXPORT
+#endif
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include "common.h"
+
+G_BEGIN_DECLS
+#define GST_TYPE_CAMERA_CAPTURER (gst_camera_capturer_get_type ())
+#define GST_CAMERA_CAPTURER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CAMERA_CAPTURER, GstCameraCapturer))
+#define GST_CAMERA_CAPTURER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CAMERA_CAPTURER, GstCameraCapturerClass))
+#define GST_IS_CAMERA_CAPTURER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CAMERA_CAPTURER))
+#define GST_IS_CAMERA_CAPTURER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CAMERA_CAPTURER))
+#define GST_CAMERA_CAPTURER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CAMERA_CAPTURER, GstCameraCapturerClass))
+#define GCC_ERROR gst_camera_capturer_error_quark ()
+typedef struct _GstCameraCapturerClass GstCameraCapturerClass;
+typedef struct _GstCameraCapturer GstCameraCapturer;
+typedef struct GstCameraCapturerPrivate GstCameraCapturerPrivate;
+
+
+struct _GstCameraCapturerClass
+{
+ GtkHBoxClass parent_class;
+
+ void (*eos) (GstCameraCapturer * gcc);
+ void (*error) (GstCameraCapturer * gcc, const char *message);
+ void (*device_change) (GstCameraCapturer * gcc, gint *device_change);
+ void (*invalidsource) (GstCameraCapturer * gcc);
+};
+
+struct _GstCameraCapturer
+{
+ GtkEventBox parent;
+ GstCameraCapturerPrivate *priv;
+};
+
+typedef enum
+{
+ GST_CAMERA_CAPTURE_SOURCE_TYPE_NONE = 0,
+ GST_CAMERA_CAPTURE_SOURCE_TYPE_DV = 1,
+ GST_CAMERA_CAPTURE_SOURCE_TYPE_RAW = 2,
+ GST_CAMERA_CAPTURE_SOURCE_TYPE_DSHOW = 3
+} GstCameraCaptureSourceType;
+
+EXPORT GType
+gst_camera_capturer_get_type (void)
+ G_GNUC_CONST;
+
+ EXPORT void gst_camera_capturer_init_backend (int *argc, char ***argv);
+ EXPORT GstCameraCapturer *gst_camera_capturer_new (gchar * filename,
+ GError ** err);
+ EXPORT void gst_camera_capturer_run (GstCameraCapturer * gcc);
+ EXPORT void gst_camera_capturer_close (GstCameraCapturer * gcc);
+ EXPORT void gst_camera_capturer_start (GstCameraCapturer * gcc);
+ EXPORT void gst_camera_capturer_toggle_pause (GstCameraCapturer * gcc);
+ EXPORT void gst_camera_capturer_stop (GstCameraCapturer * gcc);
+ EXPORT gboolean gst_camera_capturer_set_video_encoder (GstCameraCapturer *
+ gcc, VideoEncoderType type, GError ** err);
+ EXPORT gboolean gst_camera_capturer_set_audio_encoder (GstCameraCapturer *
+ gcc, AudioEncoderType type, GError ** err);
+ EXPORT gboolean gst_camera_capturer_set_video_muxer (GstCameraCapturer *
+ gcc, VideoMuxerType type, GError ** err);
+ EXPORT gboolean gst_camera_capturer_set_source (GstCameraCapturer * gcc,
+ GstCameraCaptureSourceType type, GError ** err);
+ EXPORT GList *gst_camera_capturer_enum_audio_devices (void);
+ EXPORT GList *gst_camera_capturer_enum_video_devices (void);
+ EXPORT GdkPixbuf *gst_camera_capturer_get_current_frame (GstCameraCapturer
+ * gcc);
+ EXPORT void gst_camera_capturer_unref_pixbuf (GdkPixbuf * pixbuf);
+ EXPORT void gst_camera_capturer_finalize (GObject * object);
+
+G_END_DECLS
+#endif /* _GST_CAMERA_CAPTURER_H_ */
diff --git a/libcesarplayer/src/gst-smart-video-scaler.c b/libcesarplayer/src/gst-smart-video-scaler.c
new file mode 100644
index 0000000..520fcd5
--- /dev/null
+++ b/libcesarplayer/src/gst-smart-video-scaler.c
@@ -0,0 +1,313 @@
+/*
+ * gst-smart-video-scaler.c
+ * Copyright (C) Andoni Morales Alastruey 2009 <ylatuya gmail com>
+ *
+ * gst-smart-video-scaler.c 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.
+ *
+ * gst-smart-video-scaler.c 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, see <http://www.gnu.org/licenses/>.
+ */
+
+ /* This code is a port to C of the PiTiVi's smartvideoscaler */
+
+#include "gst-smart-video-scaler.h"
+#include <glib/gprintf.h>
+
+G_DEFINE_TYPE (GstSmartVideoScaler, gst_smart_video_scaler, GST_TYPE_BIN);
+
+
+struct _GstSmartVideoScalerPrivate
+{
+ gint widthin;
+ gint heightin;
+ GValue *parin;
+ GValue *darin;
+
+ GstCaps *capsout;
+ gint widthout;
+ gint heightout;
+ GValue *parout;
+ GValue *darout;
+
+ GstElement *videoscale;
+ GstElement *capsfilter;
+ GstElement *videobox;
+
+ GstPad *sink_pad;
+ GstPad *src_pad;
+};
+
+static void
+gst_smart_video_scaler_init (GstSmartVideoScaler * object)
+{
+ GstSmartVideoScalerPrivate *priv;
+ object->priv = priv =
+ G_TYPE_INSTANCE_GET_PRIVATE (object, GST_TYPE_SMART_VIDEO_SCALER,
+ GstSmartVideoScalerPrivate);
+
+ priv->parin = g_new0 (GValue, 1);
+ g_value_init (priv->parin, GST_TYPE_FRACTION);
+ gst_value_set_fraction (priv->parin, 1, 1);
+
+ priv->parout = g_new0 (GValue, 1);
+ g_value_init (priv->parout, GST_TYPE_FRACTION);
+ gst_value_set_fraction (priv->parout, 1, 1);
+
+ priv->darin = g_new0 (GValue, 1);
+ g_value_init (priv->darin, GST_TYPE_FRACTION);
+ gst_value_set_fraction (priv->darin, 1, 1);
+
+ priv->darout = g_new0 (GValue, 1);
+ g_value_init (priv->darout, GST_TYPE_FRACTION);
+ gst_value_set_fraction (priv->darout, 1, 1);
+
+ priv->widthin = -1;
+ priv->widthout = -1;
+ priv->heightin = -1;
+ priv->heightout = -1;
+}
+
+static void
+gst_smart_video_scaler_finalize (GObject * object)
+{
+ GstSmartVideoScaler *gsvs = (GstSmartVideoScaler *) object;
+
+ if (gsvs != NULL) {
+ gst_element_set_state (GST_ELEMENT (gsvs), GST_STATE_NULL);
+ gst_object_unref (gsvs);
+ gsvs->priv->videoscale = NULL;
+ gsvs->priv->videobox = NULL;
+ gsvs->priv->capsfilter = NULL;
+ }
+
+ g_free (gsvs->priv->parin);
+ g_free (gsvs->priv->parout);
+ g_free (gsvs->priv->darin);
+ g_free (gsvs->priv->darout);
+
+ G_OBJECT_CLASS (gst_smart_video_scaler_parent_class)->finalize (object);
+}
+
+static void
+gst_smart_video_scaler_class_init (GstSmartVideoScalerClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ //GstBinClass* parent_class = GST_BIN_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (GstSmartVideoScalerPrivate));
+
+ object_class->finalize = gst_smart_video_scaler_finalize;
+}
+
+static void
+gsvs_compute_and_set_values (GstSmartVideoScaler * gsvs)
+{
+ guint mayor, minor, micro, nano;
+ gint left, right, bottom, top;
+ gchar astr[100];
+ gfloat fdarin, fdarout, fparin, fparout;
+ GstCaps *caps;
+
+ /*Calculate the new values to set on capsfilter and videobox. */
+ if (gsvs->priv->widthin == -1 || gsvs->priv->heightin == -1
+ || gsvs->priv->widthout == -1 || gsvs->priv->heightout == -1) {
+ /* FIXME : should we reset videobox/capsfilter properties here ? */
+ GST_ERROR
+ ("We don't have input and output caps, we can't calculate videobox values");
+ return;
+ }
+
+ fdarin =
+ (gfloat) gst_value_get_fraction_numerator (gsvs->priv->darin) /
+ (gfloat) gst_value_get_fraction_denominator (gsvs->priv->darin);
+ fdarout =
+ (gfloat) gst_value_get_fraction_numerator (gsvs->priv->darout) /
+ (gfloat) gst_value_get_fraction_denominator (gsvs->priv->darout);
+ fparin =
+ (gfloat) gst_value_get_fraction_numerator (gsvs->priv->parin) /
+ (gfloat) gst_value_get_fraction_denominator (gsvs->priv->parin);
+ fparout =
+ (gfloat) gst_value_get_fraction_numerator (gsvs->priv->parout) /
+ (gfloat) gst_value_get_fraction_denominator (gsvs->priv->parout);
+ GST_INFO ("incoming width/height/PAR/DAR : %d/%d/%f/%f",
+ gsvs->priv->widthin, gsvs->priv->heightin, fparin, fdarin);
+ GST_INFO ("outgoing width/height/PAR/DAR : %d/%d/%f/%f",
+ gsvs->priv->widthout, gsvs->priv->heightout, fparout, fdarout);
+
+ /* for core <= 0.10.22 we always set caps != any, see 574805 for the
+ details */
+
+ gst_version (&mayor, &minor, µ, &nano);
+ if (fdarin == fdarout && (mayor >= 0 && minor >= 10 && micro >= 23)) {
+ GST_INFO
+ ("We have same input and output caps, resetting capsfilter and videobox settings");
+ /* same DAR, set inputcaps on capsfilter, reset videobox values */
+ caps = gst_caps_new_any ();
+ left = 0;
+ right = 0;
+ top = 0;
+ bottom = 0;
+ } else {
+ gint par_d, par_n, dar_d, dar_n;
+ gint extra;
+ gchar scaps[200];
+
+ par_n = gst_value_get_fraction_numerator (gsvs->priv->parout);
+ par_d = gst_value_get_fraction_denominator (gsvs->priv->parout);
+ dar_n = gst_value_get_fraction_numerator (gsvs->priv->darin);
+ dar_d = gst_value_get_fraction_denominator (gsvs->priv->darin);
+
+ if (fdarin > fdarout) {
+ gint newheight;
+
+ GST_INFO
+ ("incoming DAR is greater that ougoing DAR. Adding top/bottom borders");
+ /* width, PAR stays the same as output
+ calculate newheight = (PAR * widthout) / DAR */
+ newheight = (par_n * gsvs->priv->widthout * dar_d) / (par_d * dar_n);
+ GST_INFO ("newheight should be %d", newheight);
+ extra = gsvs->priv->heightout - newheight;
+ top = extra / 2;
+ bottom = extra - top; /* compensate for odd extra */
+ left = right = 0;
+ /*calculate filter caps */
+ g_sprintf (astr, "width=%d,height=%d", gsvs->priv->widthout, newheight);
+ } else {
+ gint newwidth;
+
+ GST_INFO
+ ("incoming DAR is smaller than outgoing DAR. Adding left/right borders");
+ /* height, PAR stays the same as output
+ calculate newwidth = (DAR * heightout) / PAR */
+ newwidth = (dar_n * gsvs->priv->heightout * par_n) / (dar_d * par_n);
+ GST_INFO ("newwidth should be %d", newwidth);
+ extra = gsvs->priv->widthout - newwidth;
+ left = extra / 2;
+ right = extra - left; /* compensate for odd extra */
+ top = bottom = 0;
+ /* calculate filter caps */
+ g_sprintf (astr, "width=%d,height=%d", newwidth, gsvs->priv->heightout);
+ }
+ g_sprintf (scaps, "video/x-raw-yuv,%s;video/x-raw-rgb,%s", astr, astr);
+ caps = gst_caps_from_string (scaps);
+ }
+
+ /* set properties on elements */
+ GST_INFO ("About to set left/right/top/bottom : %d/%d/%d/%d", -left, -right,
+ -top, -bottom);
+ g_object_set (G_OBJECT (gsvs->priv->videobox), "left", -left, NULL);
+ g_object_set (G_OBJECT (gsvs->priv->videobox), "right", -right, NULL);
+ g_object_set (G_OBJECT (gsvs->priv->videobox), "top", -top, NULL);
+ g_object_set (G_OBJECT (gsvs->priv->videobox), "bottom", -bottom, NULL);
+ GST_INFO ("Settings filter caps %s", gst_caps_to_string (caps));
+ g_object_set (G_OBJECT (gsvs->priv->capsfilter), "caps", caps, NULL);
+ gst_caps_unref (caps);
+}
+
+static void
+gsvs_get_value_from_caps (GstCaps * caps, gboolean force, gint * width,
+ gint * height, GValue ** par, GValue ** dar)
+{
+ GstStructure *str = NULL;
+ const GValue *tmp_par = NULL;
+ gint num, denom;
+
+ *width = -1;
+ *height = -1;
+ gst_value_set_fraction (*par, 1, 1);
+ gst_value_set_fraction (*dar, 1, 1);
+
+ if (force || (caps && gst_caps_is_fixed (caps))) {
+ str = gst_caps_get_structure (caps, 0);
+ gst_structure_get_int (str, "width", &(*width));
+ gst_structure_get_int (str, "height", &(*height));
+
+ if (g_strrstr (gst_structure_get_name (str), "pixel-aspect-ratio")) {
+ tmp_par = gst_structure_get_value (str, "pixel-aspect-ratio");
+ gst_value_set_fraction (*par,
+ gst_value_get_fraction_numerator (tmp_par),
+ gst_value_get_fraction_denominator (tmp_par));
+ }
+ num = gst_value_get_fraction_numerator (*par);
+ denom = gst_value_get_fraction_denominator (*par);
+ gst_value_set_fraction (*dar, *width * num, *height * denom);
+ }
+}
+
+static gboolean
+gsvs_sink_set_caps (GstPad * pad, GstCaps * caps)
+{
+ GstSmartVideoScaler *gsvs =
+ GST_SMART_VIDEO_SCALER (gst_element_get_parent
+ (gst_pad_get_parent (pad)));
+ GstPad *videoscale_pad = NULL;
+
+ videoscale_pad = gst_element_get_static_pad (GST_ELEMENT (gsvs), "sink");
+
+ if (!gst_pad_set_caps (videoscale_pad, caps))
+ return FALSE;
+
+ gsvs_get_value_from_caps (caps, FALSE, &gsvs->priv->widthin,
+ &gsvs->priv->heightin, &gsvs->priv->parin, &gsvs->priv->darin);
+ gsvs_compute_and_set_values (gsvs);
+
+ return TRUE;
+}
+
+void
+gst_smart_video_scaler_set_caps (GstSmartVideoScaler * gsvs, GstCaps * caps)
+{
+ g_return_if_fail (GST_IS_SMART_VIDEO_SCALER (gsvs));
+
+ gsvs_get_value_from_caps (caps, FALSE, &gsvs->priv->widthout,
+ &gsvs->priv->heightout, &gsvs->priv->parout, &gsvs->priv->darout);
+
+}
+
+GstSmartVideoScaler *
+gst_smart_video_scaler_new ()
+{
+ GstSmartVideoScaler *gsvs = NULL;
+ GstPad *sink_pad;
+ GstPad *src_pad;
+
+ gsvs = g_object_new (GST_TYPE_SMART_VIDEO_SCALER, NULL);
+
+ /*Create bin elements */
+ gsvs->priv->videoscale =
+ gst_element_factory_make ("videoscale", "smart-videoscale");
+ g_object_set (G_OBJECT (gsvs->priv->videoscale), "method", 1, NULL);
+ gsvs->priv->capsfilter =
+ gst_element_factory_make ("capsfilter", "smart-capsfilter");
+ gsvs->priv->videobox =
+ gst_element_factory_make ("videobox", "smart-videobox");
+
+ /*Add and link elements */
+ gst_bin_add_many (GST_BIN (gsvs), gsvs->priv->videoscale,
+ gsvs->priv->capsfilter, gsvs->priv->videobox, NULL);
+ gst_element_link_many (gsvs->priv->videoscale, gsvs->priv->capsfilter,
+ gsvs->priv->videobox, NULL);
+
+ /*Create bin sink pad */
+ sink_pad = gst_element_get_static_pad (gsvs->priv->videoscale, "sink");
+ gst_pad_set_active (sink_pad, TRUE);
+ gst_element_add_pad (GST_ELEMENT (gsvs),
+ gst_ghost_pad_new ("sink", sink_pad));
+
+ /*Creat bin src pad */
+ src_pad = gst_element_get_static_pad (gsvs->priv->videobox, "src");
+ gst_pad_set_active (src_pad, TRUE);
+ gst_element_add_pad (GST_ELEMENT (gsvs), gst_ghost_pad_new ("src", src_pad));
+
+ gst_pad_set_setcaps_function (sink_pad, gsvs_sink_set_caps);
+
+ return gsvs;
+}
diff --git a/libcesarplayer/src/gst-smart-video-scaler.h b/libcesarplayer/src/gst-smart-video-scaler.h
new file mode 100644
index 0000000..58325d5
--- /dev/null
+++ b/libcesarplayer/src/gst-smart-video-scaler.h
@@ -0,0 +1,63 @@
+/*
+ * gst-smart-video-scaler.c
+ * Copyright (C) Andoni Morales Alastruey 2009 <ylatuya gmail com>
+ *
+ * gst-smart-video-scaler.c 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.
+ *
+ * gst-smart-video-scaler.c 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _GST_SMART_VIDEO_SCALER_H_
+#define _GST_SMART_VIDEO_SCALER_H_
+
+#include <glib-object.h>
+#include <gst/gst.h>
+
+#ifdef WIN32
+#define EXPORT __declspec (dllexport)
+#else
+#define EXPORT
+#endif
+
+G_BEGIN_DECLS
+#define GST_TYPE_SMART_VIDEO_SCALER (gst_smart_video_scaler_get_type ())
+#define GST_SMART_VIDEO_SCALER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_SMART_VIDEO_SCALER, GstSmartVideoScaler))
+#define GST_SMART_VIDEO_SCALER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_SMART_VIDEO_SCALER, GstSmartVideoScalerClass))
+#define GST_IS_SMART_VIDEO_SCALER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_SMART_VIDEO_SCALER))
+#define GST_IS_SMART_VIDEO_SCALER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_SMART_VIDEO_SCALER))
+#define GST_SMART_VIDEO_SCALER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_SMART_VIDEO_SCALER, GstSmartVideoScalerClass))
+typedef struct _GstSmartVideoScalerClass GstSmartVideoScalerClass;
+typedef struct _GstSmartVideoScaler GstSmartVideoScaler;
+typedef struct _GstSmartVideoScalerPrivate GstSmartVideoScalerPrivate;
+
+struct _GstSmartVideoScalerClass
+{
+ GstBinClass parent_class;
+};
+
+struct _GstSmartVideoScaler
+{
+ GstBin parent_instance;
+ GstSmartVideoScalerPrivate *priv;
+
+
+};
+
+EXPORT GType
+gst_smart_video_scaler_get_type (void)
+ G_GNUC_CONST;
+ EXPORT GstSmartVideoScaler *gst_smart_video_scaler_new ();
+ EXPORT void gst_smart_video_scaler_set_caps (GstSmartVideoScaler * gsvs,
+ GstCaps * caps);
+
+G_END_DECLS
+#endif /* _GST_SMART_VIDEO_SCALER_H_ */
diff --git a/libcesarplayer/src/gst-video-editor.c b/libcesarplayer/src/gst-video-editor.c
new file mode 100644
index 0000000..922ed83
--- /dev/null
+++ b/libcesarplayer/src/gst-video-editor.c
@@ -0,0 +1,1348 @@
+ /*GStreamer Video Editor Based On GNonlin
+ * Copyright (C) 2007-2009 Andoni Morales Alastruey <ylatuya gmail com>
+ *
+ * This program is free software.
+ *
+ * You may 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.
+ *
+ * Gstreamer Video Transcoderis 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 foob. If not, write to:
+ * The Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <gst/gst.h>
+#include "gst-video-editor.h"
+
+
+#define DEFAULT_VIDEO_ENCODER "vp8enc"
+#define DEFAULT_AUDIO_ENCODER "vorbisenc"
+#define DEFAULT_VIDEO_MUXER "matroskamux"
+#define FONT_SIZE_FACTOR 0.05
+#define LAME_CAPS "audio/x-raw-int, rate=44100, channels=2, endianness=1234, signed=true, width=16, depth=16"
+#define VORBIS_CAPS "audio/x-raw-float, rate=44100, channels=2, endianness=1234, signed=true, width=32, depth=32"
+#define FAAC_CAPS "audio/x-raw-int, rate=44100, channels=2, endianness=1234, signed=true, width=16, depth=16"
+#define AC3_CAPS "audio/x-raw-int, rate=44100, channels=2, endianness=1234, signed=true, width=16, depth=16"
+
+#define TIMEOUT 50
+
+/* Signals */
+enum
+{
+ SIGNAL_ERROR,
+ SIGNAL_EOS,
+ SIGNAL_PERCENT_COMPLETED,
+ LAST_SIGNAL
+};
+
+/* Properties */
+enum
+{
+ PROP_0,
+ PROP_ENABLE_AUDIO,
+ PROP_ENABLE_TITLE,
+ PROP_VIDEO_BITRATE,
+ PROP_AUDIO_BITRATE,
+ PROP_HEIGHT,
+ PROP_WIDTH,
+ PROP_OUTPUT_FILE
+};
+
+struct GstVideoEditorPrivate
+{
+ gint segments;
+ gint active_segment;
+ gint64 *stop_times;
+ GList *titles;
+ GList *gnl_video_filesources;
+ GList *gnl_audio_filesources;
+ gint64 duration;
+
+ /* Properties */
+ gboolean audio_enabled;
+ gboolean title_enabled;
+ gchar *output_file;
+ gint audio_bitrate;
+ gint video_bitrate;
+ gint width;
+ gint height;
+
+ /* Bins */
+ GstElement *main_pipeline;
+ GstElement *vencode_bin;
+ GstElement *aencode_bin;
+
+ /* Source */
+ GstElement *gnl_video_composition;
+ GstElement *gnl_audio_composition;
+
+ /* Video */
+ GstElement *identity;
+ GstElement *ffmpegcolorspace;
+ GstElement *videorate;
+ GstElement *textoverlay;
+ GstElement *videoscale;
+ GstElement *capsfilter;
+ GstElement *queue;
+ GstElement *video_encoder;
+ VideoEncoderType video_encoder_type;
+
+ /* Audio */
+ GstElement *audioidentity;
+ GstElement *audioconvert;
+ GstElement *audioresample;
+ GstElement *audiocapsfilter;
+ GstElement *audioqueue;
+ GstElement *audioencoder;
+
+ /* Sink */
+ GstElement *muxer;
+ GstElement *file_sink;
+
+ GstBus *bus;
+ gulong sig_bus_async;
+
+ gint update_id;
+};
+
+static int gve_signals[LAST_SIGNAL] = { 0 };
+
+static void gve_error_msg (GstVideoEditor * gve, GstMessage * msg);
+static void new_decoded_pad_cb (GstElement * object, GstPad * arg0,
+ gpointer user_data);
+static void gve_bus_message_cb (GstBus * bus, GstMessage * message,
+ gpointer data);
+static void gst_video_editor_get_property (GObject * object,
+ guint property_id, GValue * value, GParamSpec * pspec);
+static void gst_video_editor_set_property (GObject * object,
+ guint property_id, const GValue * value, GParamSpec * pspec);
+static gboolean gve_query_timeout (GstVideoEditor * gve);
+static void gve_apply_new_caps (GstVideoEditor * gve);
+static void gve_rewrite_headers (GstVideoEditor * gve);
+G_DEFINE_TYPE (GstVideoEditor, gst_video_editor, G_TYPE_OBJECT);
+
+
+
+/* =========================================== */
+/* */
+/* Class Initialization/Finalization */
+/* */
+/* =========================================== */
+
+static void
+gst_video_editor_init (GstVideoEditor * object)
+{
+ GstVideoEditorPrivate *priv;
+ object->priv = priv =
+ G_TYPE_INSTANCE_GET_PRIVATE (object, GST_TYPE_VIDEO_EDITOR,
+ GstVideoEditorPrivate);
+
+ priv->output_file = "new_video.avi";
+
+ priv->audio_bitrate = 128000;
+ priv->video_bitrate = 5000;
+ priv->height = 540;
+ priv->width = 720;
+ priv->title_enabled = TRUE;
+ priv->audio_enabled = TRUE;
+
+ priv->duration = 0;
+ priv->segments = 0;
+ priv->gnl_video_filesources = NULL;
+ priv->gnl_audio_filesources = NULL;
+ priv->titles = NULL;
+ priv->stop_times = (gint64 *) malloc (200 * sizeof (gint64));
+
+ priv->update_id = 0;
+}
+
+static void
+gst_video_editor_finalize (GObject * object)
+{
+ GstVideoEditor *gve = (GstVideoEditor *) object;
+
+ if (gve->priv->bus) {
+ /* make bus drop all messages to make sure none of our callbacks is ever
+ called again (main loop might be run again to display error dialog) */
+ gst_bus_set_flushing (gve->priv->bus, TRUE);
+
+ if (gve->priv->sig_bus_async)
+ g_signal_handler_disconnect (gve->priv->bus, gve->priv->sig_bus_async);
+ gst_object_unref (gve->priv->bus);
+ gve->priv->bus = NULL;
+ }
+
+ if (gve->priv->main_pipeline != NULL
+ && GST_IS_ELEMENT (gve->priv->main_pipeline)) {
+ gst_element_set_state (gve->priv->main_pipeline, GST_STATE_NULL);
+ gst_object_unref (gve->priv->main_pipeline);
+ gve->priv->main_pipeline = NULL;
+ }
+
+ g_free (gve->priv->output_file);
+ g_list_free (gve->priv->gnl_video_filesources);
+ g_list_free (gve->priv->gnl_audio_filesources);
+ g_free (gve->priv->stop_times);
+ g_list_free (gve->priv->titles);
+
+ G_OBJECT_CLASS (gst_video_editor_parent_class)->finalize (object);
+}
+
+
+static void
+gst_video_editor_class_init (GstVideoEditorClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gst_video_editor_finalize;
+
+ g_type_class_add_private (object_class, sizeof (GstVideoEditorPrivate));
+
+ /* GObject */
+ object_class->set_property = gst_video_editor_set_property;
+ object_class->get_property = gst_video_editor_get_property;
+ object_class->finalize = gst_video_editor_finalize;
+
+ /* Properties */
+ g_object_class_install_property (object_class, PROP_ENABLE_AUDIO,
+ g_param_spec_boolean ("enable-audio", NULL,
+ NULL, TRUE, G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_ENABLE_TITLE,
+ g_param_spec_boolean ("enable-title", NULL,
+ NULL, TRUE, G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_VIDEO_BITRATE,
+ g_param_spec_int ("video_bitrate", NULL,
+ NULL, 100, G_MAXINT, 10000, G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_AUDIO_BITRATE,
+ g_param_spec_int ("audio_bitrate", NULL,
+ NULL, 12000, G_MAXINT, 128000, G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_HEIGHT,
+ g_param_spec_int ("height", NULL, NULL,
+ 240, 1080, 480, G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_WIDTH,
+ g_param_spec_int ("width", NULL, NULL, 320,
+ 1920, 720, G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_OUTPUT_FILE,
+ g_param_spec_string ("output_file", NULL, NULL, "", G_PARAM_READWRITE));
+
+ /* Signals */
+ gve_signals[SIGNAL_ERROR] =
+ g_signal_new ("error",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GstVideoEditorClass, error),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING);
+
+ gve_signals[SIGNAL_PERCENT_COMPLETED] =
+ g_signal_new ("percent_completed",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GstVideoEditorClass, percent_completed),
+ NULL, NULL, g_cclosure_marshal_VOID__FLOAT, G_TYPE_NONE, 1, G_TYPE_FLOAT);
+}
+
+/* =========================================== */
+/* */
+/* Properties */
+/* */
+/* =========================================== */
+
+static void
+gst_video_editor_set_enable_audio (GstVideoEditor * gve, gboolean audio_enabled)
+{
+ GstState cur_state;
+
+ gst_element_get_state (gve->priv->main_pipeline, &cur_state, NULL, 0);
+
+ if (cur_state > GST_STATE_READY) {
+ GST_WARNING ("Could not enable/disable audio. Pipeline is playing");
+ return;
+ }
+
+ if (!gve->priv->audio_enabled && audio_enabled) {
+ /* Audio needs to be enabled and is disabled */
+ gst_bin_add_many (GST_BIN (gve->priv->main_pipeline),
+ gve->priv->gnl_audio_composition, gve->priv->aencode_bin, NULL);
+ gst_element_link (gve->priv->aencode_bin, gve->priv->muxer);
+ gst_element_set_state (gve->priv->gnl_audio_composition, cur_state);
+ gst_element_set_state (gve->priv->aencode_bin, cur_state);
+ gve_rewrite_headers (gve);
+ gve->priv->audio_enabled = TRUE;
+ GST_INFO ("Audio enabled");
+ } else if (gve->priv->audio_enabled && !audio_enabled) {
+ /* Audio is enabled and needs to be disabled) */
+ gst_element_unlink_many (gve->priv->gnl_audio_composition,
+ gve->priv->aencode_bin, gve->priv->muxer, NULL);
+ gst_element_set_state (gve->priv->gnl_audio_composition, GST_STATE_NULL);
+ gst_element_set_state (gve->priv->aencode_bin, GST_STATE_NULL);
+ gst_object_ref (gve->priv->gnl_audio_composition);
+ gst_object_ref (gve->priv->aencode_bin);
+ gst_bin_remove_many (GST_BIN (gve->priv->main_pipeline),
+ gve->priv->gnl_audio_composition, gve->priv->aencode_bin, NULL);
+ gve_rewrite_headers (gve);
+ gve->priv->audio_enabled = FALSE;
+ GST_INFO ("Audio disabled");
+ }
+}
+
+static void
+gst_video_editor_set_enable_title (GstVideoEditor * gve, gboolean title_enabled)
+{
+ gve->priv->title_enabled = title_enabled;
+ g_object_set (G_OBJECT (gve->priv->textoverlay), "silent",
+ !gve->priv->title_enabled, NULL);
+}
+
+static void
+gst_video_editor_set_video_bit_rate (GstVideoEditor * gve, gint bitrate)
+{
+ GstState cur_state;
+
+ gve->priv->video_bitrate = bitrate;
+ gst_element_get_state (gve->priv->video_encoder, &cur_state, NULL, 0);
+ if (cur_state <= GST_STATE_READY) {
+ if (gve->priv->video_encoder_type == VIDEO_ENCODER_THEORA ||
+ gve->priv->video_encoder_type == VIDEO_ENCODER_H264)
+ g_object_set (gve->priv->video_encoder, "bitrate", bitrate, NULL);
+ else
+ g_object_set (gve->priv->video_encoder, "bitrate", bitrate * 1000, NULL);
+ GST_INFO ("Encoding video bitrate changed to :%d (kbps)\n", bitrate);
+ }
+}
+
+static void
+gst_video_editor_set_audio_bit_rate (GstVideoEditor * gve, gint bitrate)
+{
+ GstState cur_state;
+
+ gve->priv->audio_bitrate = bitrate;
+ gst_element_get_state (gve->priv->audioencoder, &cur_state, NULL, 0);
+ if (cur_state <= GST_STATE_READY) {
+ g_object_set (gve->priv->audioencoder, "bitrate", bitrate, NULL);
+ GST_INFO ("Encoding audio bitrate changed to :%d (bps)\n", bitrate);
+ }
+}
+
+static void
+gst_video_editor_set_width (GstVideoEditor * gve, gint width)
+{
+ gve->priv->width = width;
+ gve_apply_new_caps (gve);
+}
+
+static void
+gst_video_editor_set_height (GstVideoEditor * gve, gint height)
+{
+ gve->priv->height = height;
+ gve_apply_new_caps (gve);
+}
+
+static void
+gst_video_editor_set_output_file (GstVideoEditor * gve, const char *output_file)
+{
+ GstState cur_state;
+
+ gve->priv->output_file = g_strdup (output_file);
+ gst_element_get_state (gve->priv->file_sink, &cur_state, NULL, 0);
+ if (cur_state <= GST_STATE_READY) {
+ gst_element_set_state (gve->priv->file_sink, GST_STATE_NULL);
+ g_object_set (gve->priv->file_sink, "location", gve->priv->output_file,
+ NULL);
+ GST_INFO ("Ouput File changed to :%s\n", gve->priv->output_file);
+ }
+}
+
+static void
+gst_video_editor_set_property (GObject * object, guint property_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstVideoEditor *gve;
+
+ gve = GST_VIDEO_EDITOR (object);
+
+ switch (property_id) {
+ case PROP_ENABLE_AUDIO:
+ gst_video_editor_set_enable_audio (gve, g_value_get_boolean (value));
+ break;
+ case PROP_ENABLE_TITLE:
+ gst_video_editor_set_enable_title (gve, g_value_get_boolean (value));
+ break;
+ case PROP_VIDEO_BITRATE:
+ gst_video_editor_set_video_bit_rate (gve, g_value_get_int (value));
+ break;
+ case PROP_AUDIO_BITRATE:
+ gst_video_editor_set_audio_bit_rate (gve, g_value_get_int (value));
+ break;
+ case PROP_WIDTH:
+ gst_video_editor_set_width (gve, g_value_get_int (value));
+ break;
+ case PROP_HEIGHT:
+ gst_video_editor_set_height (gve, g_value_get_int (value));
+ break;
+ case PROP_OUTPUT_FILE:
+ gst_video_editor_set_output_file (gve, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_video_editor_get_property (GObject * object, guint property_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstVideoEditor *gve;
+
+ gve = GST_VIDEO_EDITOR (object);
+
+ switch (property_id) {
+ case PROP_ENABLE_AUDIO:
+ g_value_set_boolean (value, gve->priv->audio_enabled);
+ break;
+ case PROP_ENABLE_TITLE:
+ g_value_set_boolean (value, gve->priv->title_enabled);
+ break;
+ case PROP_AUDIO_BITRATE:
+ g_value_set_int (value, gve->priv->audio_bitrate);
+ break;
+ case PROP_VIDEO_BITRATE:
+ g_value_set_int (value, gve->priv->video_bitrate);
+ break;
+ case PROP_WIDTH:
+ g_value_set_int (value, gve->priv->width);
+ break;
+ case PROP_HEIGHT:
+ g_value_set_int (value, gve->priv->height);
+ break;
+ case PROP_OUTPUT_FILE:
+ g_value_set_string (value, gve->priv->output_file);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+/* =========================================== */
+/* */
+/* Private Methods */
+/* */
+/* =========================================== */
+
+static void
+gve_rewrite_headers (GstVideoEditor * gve)
+{
+ gst_element_set_state (gve->priv->muxer, GST_STATE_NULL);
+ gst_element_set_state (gve->priv->file_sink, GST_STATE_NULL);
+ gst_element_set_state (gve->priv->file_sink, GST_STATE_READY);
+ gst_element_set_state (gve->priv->muxer, GST_STATE_READY);
+}
+
+static void
+gve_set_tick_timeout (GstVideoEditor * gve, guint msecs)
+{
+ g_return_if_fail (msecs > 0);
+
+ GST_INFO ("adding tick timeout (at %ums)", msecs);
+ gve->priv->update_id =
+ g_timeout_add (msecs, (GSourceFunc) gve_query_timeout, gve);
+}
+
+static void
+gve_apply_new_caps (GstVideoEditor * gve)
+{
+ GstCaps *caps;
+ gchar *font;
+
+ caps = gst_caps_new_simple ("video/x-raw-yuv",
+ "width", G_TYPE_INT, gve->priv->width,
+ "height", G_TYPE_INT, gve->priv->height,
+ "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1,
+ "framerate", GST_TYPE_FRACTION, 25, 1, NULL);
+
+ g_object_set (G_OBJECT (gve->priv->capsfilter), "caps", caps, NULL);
+ font =
+ g_strdup_printf ("sans bold %d",
+ (int) (gve->priv->height * FONT_SIZE_FACTOR));
+ g_object_set (G_OBJECT (gve->priv->textoverlay), "font-desc", font, NULL);
+ g_free (font);
+ gst_caps_unref (caps);
+}
+
+static void
+gve_create_video_encode_bin (GstVideoEditor * gve)
+{
+ GstPad *sinkpad = NULL;
+ GstPad *srcpad = NULL;
+
+ if (gve->priv->vencode_bin != NULL)
+ return;
+
+ gve->priv->vencode_bin = gst_element_factory_make ("bin", "vencodebin");
+ gve->priv->identity = gst_element_factory_make ("identity", "identity");
+ gve->priv->ffmpegcolorspace =
+ gst_element_factory_make ("ffmpegcolorspace", "ffmpegcolorspace");
+ gve->priv->videorate = gst_element_factory_make ("videorate", "videorate");
+ gve->priv->videoscale = gst_element_factory_make ("videoscale", "videoscale");
+ gve->priv->capsfilter = gst_element_factory_make ("capsfilter", "capsfilter");
+ gve->priv->textoverlay =
+ gst_element_factory_make ("textoverlay", "textoverlay");
+ gve->priv->queue = gst_element_factory_make ("queue", "video-encode-queue");
+ gve->priv->video_encoder =
+ gst_element_factory_make (DEFAULT_VIDEO_ENCODER, "video-encoder");
+
+ g_object_set (G_OBJECT (gve->priv->identity), "single-segment", TRUE, NULL);
+ g_object_set (G_OBJECT (gve->priv->textoverlay), "font-desc",
+ "sans bold 20", "shaded-background", TRUE, "valignment", 2,
+ "halignment", 2, NULL);
+ g_object_set (G_OBJECT (gve->priv->videoscale), "add-borders", TRUE, NULL);
+ g_object_set (G_OBJECT (gve->priv->video_encoder), "bitrate",
+ gve->priv->video_bitrate, NULL);
+
+ /*Add and link elements */
+ gst_bin_add_many (GST_BIN (gve->priv->vencode_bin),
+ gve->priv->identity,
+ gve->priv->ffmpegcolorspace,
+ gve->priv->videorate,
+ gve->priv->videoscale,
+ gve->priv->capsfilter,
+ gve->priv->textoverlay, gve->priv->queue, gve->priv->video_encoder, NULL);
+ gst_element_link_many (gve->priv->identity,
+ gve->priv->ffmpegcolorspace,
+ gve->priv->videoscale,
+ gve->priv->videorate,
+ gve->priv->capsfilter,
+ gve->priv->textoverlay, gve->priv->queue, gve->priv->video_encoder, NULL);
+
+ /*Create bin sink pad */
+ sinkpad = gst_element_get_static_pad (gve->priv->identity, "sink");
+ gst_pad_set_active (sinkpad, TRUE);
+ gst_element_add_pad (GST_ELEMENT (gve->priv->vencode_bin),
+ gst_ghost_pad_new ("sink", sinkpad));
+
+ /*Creat bin src pad */
+ srcpad = gst_element_get_static_pad (gve->priv->video_encoder, "src");
+ gst_pad_set_active (srcpad, TRUE);
+ gst_element_add_pad (GST_ELEMENT (gve->priv->vencode_bin),
+ gst_ghost_pad_new ("src", srcpad));
+
+ g_object_unref (srcpad);
+ g_object_unref (sinkpad);
+}
+
+static void
+gve_create_audio_encode_bin (GstVideoEditor * gve)
+{
+ GstPad *sinkpad = NULL;
+ GstPad *srcpad = NULL;
+
+ if (gve->priv->aencode_bin != NULL)
+ return;
+
+ gve->priv->aencode_bin = gst_element_factory_make ("bin", "aencodebin");
+ gve->priv->audioidentity =
+ gst_element_factory_make ("identity", "audio-identity");
+ gve->priv->audioconvert =
+ gst_element_factory_make ("audioconvert", "audioconvert");
+ gve->priv->audioresample =
+ gst_element_factory_make ("audioresample", "audioresample");
+ gve->priv->audiocapsfilter =
+ gst_element_factory_make ("capsfilter", "audiocapsfilter");
+ gve->priv->audioqueue = gst_element_factory_make ("queue", "audio-queue");
+ gve->priv->audioencoder =
+ gst_element_factory_make (DEFAULT_AUDIO_ENCODER, "audio-encoder");
+
+
+ g_object_set (G_OBJECT (gve->priv->audioidentity), "single-segment", TRUE,
+ NULL);
+ g_object_set (G_OBJECT (gve->priv->audiocapsfilter), "caps",
+ gst_caps_from_string (VORBIS_CAPS), NULL);
+ g_object_set (G_OBJECT (gve->priv->audioencoder), "bitrate",
+ gve->priv->audio_bitrate, NULL);
+
+ /*Add and link elements */
+ gst_bin_add_many (GST_BIN (gve->priv->aencode_bin),
+ gve->priv->audioidentity,
+ gve->priv->audioconvert,
+ gve->priv->audioresample,
+ gve->priv->audiocapsfilter,
+ gve->priv->audioqueue, gve->priv->audioencoder, NULL);
+
+ gst_element_link_many (gve->priv->audioidentity,
+ gve->priv->audioconvert,
+ gve->priv->audioresample,
+ gve->priv->audiocapsfilter,
+ gve->priv->audioqueue, gve->priv->audioencoder, NULL);
+
+ /*Create bin sink pad */
+ sinkpad = gst_element_get_static_pad (gve->priv->audioidentity, "sink");
+ gst_pad_set_active (sinkpad, TRUE);
+ gst_element_add_pad (GST_ELEMENT (gve->priv->aencode_bin),
+ gst_ghost_pad_new ("sink", sinkpad));
+
+ /*Creat bin src pad */
+ srcpad = gst_element_get_static_pad (gve->priv->audioencoder, "src");
+ gst_pad_set_active (srcpad, TRUE);
+ gst_element_add_pad (GST_ELEMENT (gve->priv->aencode_bin),
+ gst_ghost_pad_new ("src", srcpad));
+
+ g_object_unref (srcpad);
+ g_object_unref (sinkpad);
+}
+
+GQuark
+gst_video_editor_error_quark (void)
+{
+ static GQuark q; /* 0 */
+
+ if (G_UNLIKELY (q == 0)) {
+ q = g_quark_from_static_string ("gve-error-quark");
+ }
+ return q;
+}
+
+/* =========================================== */
+/* */
+/* Callbacks */
+/* */
+/* =========================================== */
+
+static void
+new_decoded_pad_cb (GstElement * object, GstPad * pad, gpointer user_data)
+{
+ GstCaps *caps = NULL;
+ GstStructure *str = NULL;
+ GstPad *videopad = NULL;
+ GstPad *audiopad = NULL;
+ GstVideoEditor *gve = NULL;
+
+ g_return_if_fail (GST_IS_VIDEO_EDITOR (user_data));
+ gve = GST_VIDEO_EDITOR (user_data);
+
+ /* check media type */
+ caps = gst_pad_get_caps (pad);
+ str = gst_caps_get_structure (caps, 0);
+
+ if (g_strrstr (gst_structure_get_name (str), "video")) {
+ gst_element_set_state (gve->priv->vencode_bin, GST_STATE_PLAYING);
+ videopad =
+ gst_element_get_compatible_pad (gve->priv->vencode_bin, pad, NULL);
+ /* only link once */
+ if (GST_PAD_IS_LINKED (videopad)) {
+ g_object_unref (videopad);
+ gst_caps_unref (caps);
+ return;
+ }
+ /* link 'n play */
+ GST_INFO ("Found video stream...%" GST_PTR_FORMAT, caps);
+ gst_pad_link (pad, videopad);
+ g_object_unref (videopad);
+ }
+
+ else if (g_strrstr (gst_structure_get_name (str), "audio")) {
+ gst_element_set_state (gve->priv->aencode_bin, GST_STATE_PLAYING);
+ audiopad =
+ gst_element_get_compatible_pad (gve->priv->aencode_bin, pad, NULL);
+ /* only link once */
+ if (GST_PAD_IS_LINKED (audiopad)) {
+ g_object_unref (audiopad);
+ gst_caps_unref (caps);
+ return;
+ }
+ /* link 'n play */
+ GST_INFO ("Found audio stream...%" GST_PTR_FORMAT, caps);
+ gst_pad_link (pad, audiopad);
+ g_object_unref (audiopad);
+ }
+
+ gst_caps_unref (caps);
+}
+
+static void
+gve_bus_message_cb (GstBus * bus, GstMessage * message, gpointer data)
+{
+ GstVideoEditor *gve = (GstVideoEditor *) data;
+ GstMessageType msg_type;
+
+ g_return_if_fail (gve != NULL);
+ g_return_if_fail (GST_IS_VIDEO_EDITOR (gve));
+
+ msg_type = GST_MESSAGE_TYPE (message);
+
+ switch (msg_type) {
+ case GST_MESSAGE_ERROR:
+ gve_error_msg (gve, message);
+ if (gve->priv->main_pipeline)
+ gst_element_set_state (gve->priv->main_pipeline, GST_STATE_READY);
+ break;
+ case GST_MESSAGE_WARNING:
+ GST_WARNING ("Warning message: %" GST_PTR_FORMAT, message);
+ break;
+
+ case GST_MESSAGE_STATE_CHANGED:
+ {
+ GstState old_state, new_state;
+ gchar *src_name;
+
+ gst_message_parse_state_changed (message, &old_state, &new_state, NULL);
+
+ if (old_state == new_state)
+ break;
+
+ /* we only care about playbin (pipeline) state changes */
+ if (GST_MESSAGE_SRC (message) != GST_OBJECT (gve->priv->main_pipeline))
+ break;
+
+ src_name = gst_object_get_name (message->src);
+
+ GST_INFO ("%s changed state from %s to %s", src_name,
+ gst_element_state_get_name (old_state),
+ gst_element_state_get_name (new_state));
+ g_free (src_name);
+
+ if (new_state == GST_STATE_PLAYING)
+ gve_set_tick_timeout (gve, TIMEOUT);
+ if (old_state == GST_STATE_PAUSED && new_state == GST_STATE_READY) {
+ if (gve->priv->update_id > 0) {
+ g_source_remove (gve->priv->update_id);
+ gve->priv->update_id = 0;
+ }
+ }
+ if (old_state == GST_STATE_NULL && new_state == GST_STATE_READY)
+ GST_DEBUG_BIN_TO_DOT_FILE (GST_BIN (gve->priv->main_pipeline),
+ GST_DEBUG_GRAPH_SHOW_ALL, "gst-camera-capturer-null-to-ready");
+ if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED)
+ GST_DEBUG_BIN_TO_DOT_FILE (GST_BIN (gve->priv->main_pipeline),
+ GST_DEBUG_GRAPH_SHOW_ALL, "gst-camera-capturer-ready-to-paused");
+ break;
+ }
+ case GST_MESSAGE_EOS:
+ if (gve->priv->update_id > 0) {
+ g_source_remove (gve->priv->update_id);
+ gve->priv->update_id = 0;
+ }
+ gst_element_set_state (gve->priv->main_pipeline, GST_STATE_NULL);
+ g_signal_emit (gve, gve_signals[SIGNAL_PERCENT_COMPLETED], 0, (gfloat) 1);
+ gve->priv->active_segment = 0;
+ /* Close file sink properly */
+ g_object_set (G_OBJECT (gve->priv->file_sink), "location", "", NULL);
+ break;
+ default:
+ GST_LOG ("Unhandled message: %" GST_PTR_FORMAT, message);
+ break;
+ }
+}
+
+static void
+gve_error_msg (GstVideoEditor * gve, GstMessage * msg)
+{
+ GError *err = NULL;
+ gchar *dbg = NULL;
+
+ gst_message_parse_error (msg, &err, &dbg);
+ if (err) {
+ GST_ERROR ("message = %s", GST_STR_NULL (err->message));
+ GST_ERROR ("domain = %d (%s)", err->domain,
+ GST_STR_NULL (g_quark_to_string (err->domain)));
+ GST_ERROR ("code = %d", err->code);
+ GST_ERROR ("debug = %s", GST_STR_NULL (dbg));
+ GST_ERROR ("source = %" GST_PTR_FORMAT, msg->src);
+
+ g_message ("Error: %s\n%s\n", GST_STR_NULL (err->message),
+ GST_STR_NULL (dbg));
+ g_signal_emit (gve, gve_signals[SIGNAL_ERROR], 0, err->message);
+ g_error_free (err);
+ }
+ g_free (dbg);
+}
+
+static gboolean
+gve_query_timeout (GstVideoEditor * gve)
+{
+ GstFormat fmt = GST_FORMAT_TIME;
+ gint64 pos = -1;
+ gchar *title;
+ gint64 stop_time = gve->priv->stop_times[gve->priv->active_segment];
+
+ if (gst_element_query_position (gve->priv->main_pipeline, &fmt, &pos)) {
+ if (pos != -1 && fmt == GST_FORMAT_TIME) {
+ g_signal_emit (gve,
+ gve_signals[SIGNAL_PERCENT_COMPLETED],
+ 0, (float) pos / (float) gve->priv->duration);
+ }
+ } else {
+ GST_INFO ("could not get position");
+ }
+
+ if (gst_element_query_position (gve->priv->video_encoder, &fmt, &pos)) {
+ if (stop_time - pos <= 0) {
+ gve->priv->active_segment++;
+ title =
+ (gchar *) g_list_nth_data (gve->priv->titles,
+ gve->priv->active_segment);
+ g_object_set (G_OBJECT (gve->priv->textoverlay), "text", title, NULL);
+ }
+ }
+
+ return TRUE;
+}
+
+/* =========================================== */
+/* */
+/* Public Methods */
+/* */
+/* =========================================== */
+
+
+void
+gst_video_editor_add_segment (GstVideoEditor * gve, gchar * file,
+ gint64 start, gint64 duration, gdouble rate,
+ gchar * title, gboolean hasAudio)
+{
+ GstState cur_state;
+ GstElement *gnl_filesource = NULL;
+ GstElement *audiotestsrc = NULL;
+ GstCaps *filter = NULL;
+ gchar *element_name = "";
+ gint64 final_duration;
+
+ g_return_if_fail (GST_IS_VIDEO_EDITOR (gve));
+
+ gst_element_get_state (gve->priv->main_pipeline, &cur_state, NULL, 0);
+ if (cur_state > GST_STATE_READY) {
+ GST_WARNING ("Segments can only be added for a state <= GST_STATE_READY");
+ return;
+ }
+
+ final_duration = GST_MSECOND * duration / rate;
+
+ /* Video */
+ filter = gst_caps_from_string ("video/x-raw-rgb;video/x-raw-yuv");
+ element_name = g_strdup_printf ("gnlvideofilesource%d", gve->priv->segments);
+ gnl_filesource = gst_element_factory_make ("gnlfilesource", element_name);
+ g_object_set (G_OBJECT (gnl_filesource), "location", file,
+ "media-start", GST_MSECOND * start,
+ "media-duration", GST_MSECOND * duration,
+ "start", gve->priv->duration,
+ "duration", final_duration, "caps", filter, NULL);
+ if (gve->priv->segments == 0) {
+ g_object_set (G_OBJECT (gve->priv->textoverlay), "text", title, NULL);
+ }
+ gst_bin_add (GST_BIN (gve->priv->gnl_video_composition), gnl_filesource);
+ gve->priv->gnl_video_filesources =
+ g_list_append (gve->priv->gnl_video_filesources, gnl_filesource);
+
+ /* Audio */
+ if (hasAudio && rate == 1) {
+ element_name =
+ g_strdup_printf ("gnlaudiofilesource%d", gve->priv->segments);
+ gnl_filesource = gst_element_factory_make ("gnlfilesource", element_name);
+ g_object_set (G_OBJECT (gnl_filesource), "location", file, NULL);
+ } else {
+ /* If the file doesn't contain audio, something must be playing */
+ /* We use an audiotestsrc mutted and with a low priority */
+ element_name =
+ g_strdup_printf ("gnlaudiofakesource%d", gve->priv->segments);
+ gnl_filesource = gst_element_factory_make ("gnlsource", element_name);
+ element_name = g_strdup_printf ("audiotestsource%d", gve->priv->segments);
+ audiotestsrc = gst_element_factory_make ("audiotestsrc", element_name);
+ g_object_set (G_OBJECT (audiotestsrc), "volume", (double) 0, NULL);
+ gst_bin_add (GST_BIN (gnl_filesource), audiotestsrc);
+ }
+ filter = gst_caps_from_string ("audio/x-raw-float;audio/x-raw-int");
+ g_object_set (G_OBJECT (gnl_filesource),
+ "media-start", GST_MSECOND * start,
+ "media-duration", GST_MSECOND * duration,
+ "start", gve->priv->duration,
+ "duration", final_duration, "caps", filter, NULL);
+ gst_bin_add (GST_BIN (gve->priv->gnl_audio_composition), gnl_filesource);
+ gve->priv->gnl_audio_filesources =
+ g_list_append (gve->priv->gnl_audio_filesources, gnl_filesource);
+
+ gve->priv->duration += final_duration;
+ gve->priv->segments++;
+
+ gve->priv->titles = g_list_append (gve->priv->titles, title);
+ gve->priv->stop_times[gve->priv->segments - 1] = gve->priv->duration;
+
+ GST_INFO ("New segment: start={%" GST_TIME_FORMAT "} duration={%"
+ GST_TIME_FORMAT "} ", GST_TIME_ARGS (start * GST_MSECOND),
+ GST_TIME_ARGS (duration * GST_MSECOND));
+ g_free (element_name);
+}
+
+void
+gst_video_editor_clear_segments_list (GstVideoEditor * gve)
+{
+ GList *tmp = NULL;
+
+ g_return_if_fail (GST_IS_VIDEO_EDITOR (gve));
+
+ gst_video_editor_cancel (gve);
+
+ tmp = gve->priv->gnl_video_filesources;
+
+ for (; tmp; tmp = g_list_next (tmp)) {
+ GstElement *object = (GstElement *) tmp->data;
+ if (object)
+ gst_element_set_state (object, GST_STATE_NULL);
+ gst_bin_remove (GST_BIN (gve->priv->gnl_video_composition), object);
+ }
+
+ tmp = gve->priv->gnl_audio_filesources;
+
+ for (; tmp; tmp = g_list_next (tmp)) {
+ GstElement *object = (GstElement *) tmp->data;
+ if (object)
+ gst_element_set_state (object, GST_STATE_NULL);
+ gst_bin_remove (GST_BIN (gve->priv->gnl_audio_composition), object);
+ }
+
+ g_list_free (tmp);
+ g_list_free (gve->priv->gnl_video_filesources);
+ g_list_free (gve->priv->gnl_audio_filesources);
+ g_free (gve->priv->stop_times);
+ g_list_free (gve->priv->titles);
+
+ gve->priv->gnl_video_filesources = NULL;
+ gve->priv->gnl_audio_filesources = NULL;
+ gve->priv->stop_times = (gint64 *) malloc (200 * sizeof (gint64));
+ gve->priv->titles = NULL;
+
+ gve->priv->duration = 0;
+ gve->priv->active_segment = 0;
+}
+
+
+void
+gst_video_editor_set_video_encoder (GstVideoEditor * gve, gchar ** err,
+ VideoEncoderType codec)
+{
+ GstElement *encoder = NULL;
+ GstState cur_state;
+ GstPad *srcpad;
+ GstPad *oldsrcpad;
+ gchar *encoder_name = "";
+ gchar *error;
+
+ g_return_if_fail (GST_IS_VIDEO_EDITOR (gve));
+
+ gst_element_get_state (gve->priv->main_pipeline, &cur_state, NULL, 0);
+
+ if (cur_state > GST_STATE_READY)
+ goto wrong_state;
+
+ switch (codec) {
+ case VIDEO_ENCODER_H264:
+ encoder_name = "x264enc";
+ encoder = gst_element_factory_make (encoder_name, encoder_name);
+ g_object_set (G_OBJECT (encoder), "pass", 17, NULL); //Variable Bitrate-Pass 1
+ break;
+ case VIDEO_ENCODER_MPEG4:
+ encoder_name = "xvidenc";
+ encoder = gst_element_factory_make (encoder_name, encoder_name);
+ g_object_set (G_OBJECT (encoder), "pass", 1, NULL); //Variable Bitrate-Pass 1
+ break;
+ case VIDEO_ENCODER_XVID:
+ encoder_name = "ffenc_mpeg4";
+ encoder = gst_element_factory_make (encoder_name, encoder_name);
+ g_object_set (G_OBJECT (encoder), "pass", 512, NULL); //Variable Bitrate-Pass 1
+ break;
+ case VIDEO_ENCODER_MPEG2:
+ encoder_name = "mpeg2enc";
+ encoder = gst_element_factory_make (encoder_name, encoder_name);
+ g_object_set (G_OBJECT (encoder), "format", 9, NULL); //DVD compilant
+ g_object_set (G_OBJECT (encoder), "framerate", 3, NULL); //25 FPS (PAL/SECAM)
+ break;
+ case VIDEO_ENCODER_THEORA:
+ encoder_name = "theoraenc";
+ encoder = gst_element_factory_make (encoder_name, encoder_name);
+ break;
+ case VIDEO_ENCODER_VP8:
+ encoder_name = "vp8enc";
+ encoder = gst_element_factory_make (encoder_name, encoder_name);
+ break;
+ }
+
+ if (!encoder)
+ goto no_encoder;
+
+ if (!g_strcmp0
+ (gst_element_get_name (gve->priv->video_encoder), encoder_name))
+ goto same_encoder;
+
+ gve->priv->video_encoder_type = codec;
+
+ /*Remove old encoder element */
+ gst_element_unlink (gve->priv->queue, gve->priv->video_encoder);
+ gst_element_unlink (gve->priv->vencode_bin, gve->priv->muxer);
+ gst_element_set_state (gve->priv->video_encoder, GST_STATE_NULL);
+ gst_bin_remove (GST_BIN (gve->priv->vencode_bin), gve->priv->video_encoder);
+
+ /*Add new encoder element */
+ gve->priv->video_encoder = encoder;
+ if (codec == VIDEO_ENCODER_THEORA || codec == VIDEO_ENCODER_H264)
+ g_object_set (G_OBJECT (gve->priv->video_encoder), "bitrate",
+ gve->priv->video_bitrate, NULL);
+ else
+ g_object_set (G_OBJECT (gve->priv->video_encoder), "bitrate",
+ gve->priv->video_bitrate * 1000, NULL);
+
+ /*Add first to the encoder bin */
+ gst_bin_add (GST_BIN (gve->priv->vencode_bin), gve->priv->video_encoder);
+ gst_element_link (gve->priv->queue, gve->priv->video_encoder);
+ /*Remove old encoder bin's src pad */
+ oldsrcpad = gst_element_get_static_pad (gve->priv->vencode_bin, "src");
+ gst_pad_set_active (oldsrcpad, FALSE);
+ gst_element_remove_pad (gve->priv->vencode_bin, oldsrcpad);
+ /*Create new encoder bin's src pad */
+ srcpad = gst_element_get_static_pad (gve->priv->video_encoder, "src");
+ gst_pad_set_active (srcpad, TRUE);
+ gst_element_add_pad (gve->priv->vencode_bin,
+ gst_ghost_pad_new ("src", srcpad));
+ gst_element_link (gve->priv->vencode_bin, gve->priv->muxer);
+
+ gve_rewrite_headers (gve);
+ return;
+
+wrong_state:
+ {
+ GST_WARNING
+ ("The video encoder cannot be changed for a state <= GST_STATE_READY");
+ return;
+ }
+no_encoder:
+ {
+ error =
+ g_strdup_printf
+ ("The %s encoder element is not avalaible. Check your GStreamer installation",
+ encoder_name);
+ GST_ERROR (error);
+ *err = g_strdup (error);
+ g_free (error);
+ return;
+ }
+same_encoder:
+ {
+ GST_WARNING
+ ("The video encoder is not changed because it is already in use.");
+ gst_object_unref (encoder);
+ return;
+ }
+}
+
+void
+gst_video_editor_set_audio_encoder (GstVideoEditor * gve, gchar ** err,
+ AudioEncoderType codec)
+{
+ GstElement *encoder = NULL;
+ GstState cur_state;
+ GstPad *srcpad;
+ GstPad *oldsrcpad;
+ gchar *encoder_name = "";
+ gchar *error;
+
+ g_return_if_fail (GST_IS_VIDEO_EDITOR (gve));
+
+ gst_element_get_state (gve->priv->main_pipeline, &cur_state, NULL, 0);
+
+ if (cur_state > GST_STATE_READY)
+ goto wrong_state;
+
+ switch (codec) {
+ case AUDIO_ENCODER_AAC:
+ encoder_name = "faac";
+ encoder = gst_element_factory_make (encoder_name, encoder_name);
+ g_object_set (G_OBJECT (gve->priv->audiocapsfilter), "caps",
+ gst_caps_from_string (FAAC_CAPS), NULL);
+ break;
+ case AUDIO_ENCODER_MP3:
+ encoder_name = "lame";
+ encoder = gst_element_factory_make (encoder_name, encoder_name);
+ g_object_set (G_OBJECT (encoder), "vbr", 4, NULL); //Variable Bitrate
+ g_object_set (G_OBJECT (gve->priv->audiocapsfilter), "caps",
+ gst_caps_from_string (LAME_CAPS), NULL);
+ break;
+ case AUDIO_ENCODER_VORBIS:
+ encoder_name = "vorbisenc";
+ encoder = gst_element_factory_make (encoder_name, encoder_name);
+ g_object_set (G_OBJECT (gve->priv->audiocapsfilter), "caps",
+ gst_caps_from_string (VORBIS_CAPS), NULL);
+ break;
+ default:
+ gst_video_editor_set_enable_audio (gve, FALSE);
+ break;
+ }
+ if (!encoder)
+ goto no_encoder;
+
+ if (!g_strcmp0 (gst_element_get_name (gve->priv->audioencoder), encoder_name))
+ goto same_encoder;
+
+ /*Remove old encoder element */
+ gst_element_unlink (gve->priv->audioqueue, gve->priv->audioencoder);
+ if (gve->priv->audio_enabled)
+ gst_element_unlink (gve->priv->aencode_bin, gve->priv->muxer);
+ gst_element_set_state (gve->priv->audioencoder, GST_STATE_NULL);
+ gst_bin_remove (GST_BIN (gve->priv->aencode_bin), gve->priv->audioencoder);
+
+ /*Add new encoder element */
+ gve->priv->audioencoder = encoder;
+ if (codec == AUDIO_ENCODER_MP3)
+ g_object_set (G_OBJECT (gve->priv->audioencoder), "bitrate",
+ gve->priv->audio_bitrate / 1000, NULL);
+ else
+ g_object_set (G_OBJECT (gve->priv->audioencoder), "bitrate",
+ gve->priv->audio_bitrate, NULL);
+ /*Add first to the encoder bin */
+ gst_bin_add (GST_BIN (gve->priv->aencode_bin), gve->priv->audioencoder);
+ gst_element_link (gve->priv->audioqueue, gve->priv->audioencoder);
+ /*Remove old encoder bin's src pad */
+ oldsrcpad = gst_element_get_static_pad (gve->priv->aencode_bin, "src");
+ gst_pad_set_active (oldsrcpad, FALSE);
+ gst_element_remove_pad (gve->priv->aencode_bin, oldsrcpad);
+ /*Create new encoder bin's src pad */
+ srcpad = gst_element_get_static_pad (gve->priv->audioencoder, "src");
+ gst_pad_set_active (srcpad, TRUE);
+ gst_element_add_pad (gve->priv->aencode_bin,
+ gst_ghost_pad_new ("src", srcpad));
+ if (gve->priv->audio_enabled)
+ gst_element_link (gve->priv->aencode_bin, gve->priv->muxer);
+ gve_rewrite_headers (gve);
+ return;
+
+wrong_state:
+ {
+ GST_WARNING
+ ("The audio encoder cannot be changed for a state <= GST_STATE_READY");
+ return;
+ }
+no_encoder:
+ {
+ error =
+ g_strdup_printf
+ ("The %s encoder element is not avalaible. Check your GStreamer installation",
+ encoder_name);
+ GST_ERROR (error);
+ *err = g_strdup (error);
+ g_free (error);
+ return;
+ }
+same_encoder:
+ {
+ GST_WARNING
+ ("The audio encoder is not changed because it is already in use.");
+ gst_object_unref (encoder);
+ return;
+ }
+}
+
+void
+gst_video_editor_set_video_muxer (GstVideoEditor * gve, gchar ** err,
+ VideoMuxerType muxerType)
+{
+ GstElement *muxer = NULL;
+ GstState cur_state;
+ gchar *muxer_name = "";
+ gchar *error;
+
+ g_return_if_fail (GST_IS_VIDEO_EDITOR (gve));
+
+ gst_element_get_state (gve->priv->main_pipeline, &cur_state, NULL, 0);
+
+ if (cur_state > GST_STATE_READY)
+ goto wrong_state;
+
+ switch (muxerType) {
+ case VIDEO_MUXER_MATROSKA:
+ muxer_name = "matroskamux";
+ muxer = gst_element_factory_make ("matroskamux", muxer_name);
+ break;
+ case VIDEO_MUXER_AVI:
+ muxer_name = "avimux";
+ muxer = gst_element_factory_make ("avimux", muxer_name);
+ break;
+ case VIDEO_MUXER_OGG:
+ muxer_name = "oggmux";
+ muxer = gst_element_factory_make ("oggmux", muxer_name);
+ break;
+ case VIDEO_MUXER_MP4:
+ muxer_name = "qtmux";
+ muxer = gst_element_factory_make ("qtmux", muxer_name);
+ break;
+ case VIDEO_MUXER_MPEG_PS:
+ muxer_name = "ffmux_dvd";
+ //We don't want to mux anything yet as ffmux_dvd is buggy
+ //FIXME: Until we don't have audio save the mpeg-ps stream without mux.
+ muxer = gst_element_factory_make ("ffmux_dvd", muxer_name);
+ break;
+ case VIDEO_MUXER_WEBM:
+ muxer_name = "webmmux";
+ muxer = gst_element_factory_make ("webmmux", muxer_name);
+ break;
+ }
+
+ if (!muxer)
+ goto no_muxer;
+
+ if (!g_strcmp0 (gst_element_get_name (gve->priv->muxer), muxer_name))
+ goto same_muxer;
+
+ gst_element_unlink (gve->priv->vencode_bin, gve->priv->muxer);
+ if (gve->priv->audio_enabled)
+ gst_element_unlink (gve->priv->aencode_bin, gve->priv->muxer);
+ gst_element_unlink (gve->priv->muxer, gve->priv->file_sink);
+ gst_element_set_state (gve->priv->muxer, GST_STATE_NULL);
+ gst_bin_remove (GST_BIN (gve->priv->main_pipeline), gve->priv->muxer);
+ gst_bin_add (GST_BIN (gve->priv->main_pipeline), muxer);
+ gst_element_link_many (gve->priv->vencode_bin, muxer,
+ gve->priv->file_sink, NULL);
+ if (gve->priv->audio_enabled)
+ gst_element_link (gve->priv->aencode_bin, muxer);
+ gve->priv->muxer = muxer;
+ gve_rewrite_headers (gve);
+ return;
+
+wrong_state:
+ {
+ GST_WARNING
+ ("The video muxer cannot be changed for a state <= GST_STATE_READY");
+ return;
+ }
+no_muxer:
+ {
+ error =
+ g_strdup_printf
+ ("The %s muxer element is not avalaible. Check your GStreamer installation",
+ muxer_name);
+ GST_ERROR (error);
+ *err = g_strdup (error);
+ g_free (error);
+ return;
+ }
+same_muxer:
+ {
+ GST_WARNING
+ ("Not changing the video muxer as the new one is the same in use.");
+ gst_object_unref (muxer);
+ return;
+ }
+}
+
+void
+gst_video_editor_start (GstVideoEditor * gve)
+{
+ g_return_if_fail (GST_IS_VIDEO_EDITOR (gve));
+
+ gst_element_set_state (gve->priv->main_pipeline, GST_STATE_PLAYING);
+ g_signal_emit (gve, gve_signals[SIGNAL_PERCENT_COMPLETED], 0, (gfloat) 0);
+}
+
+void
+gst_video_editor_cancel (GstVideoEditor * gve)
+{
+ g_return_if_fail (GST_IS_VIDEO_EDITOR (gve));
+
+ if (gve->priv->update_id > 0) {
+ g_source_remove (gve->priv->update_id);
+ gve->priv->update_id = 0;
+ }
+ gst_element_set_state (gve->priv->main_pipeline, GST_STATE_NULL);
+ g_signal_emit (gve, gve_signals[SIGNAL_PERCENT_COMPLETED], 0, (gfloat) - 1);
+}
+
+void
+gst_video_editor_init_backend (int *argc, char ***argv)
+{
+ gst_init (argc, argv);
+}
+
+GstVideoEditor *
+gst_video_editor_new (GError ** err)
+{
+ GstVideoEditor *gve = NULL;
+
+ gve = g_object_new (GST_TYPE_VIDEO_EDITOR, NULL);
+
+ gve->priv->main_pipeline = gst_pipeline_new ("main_pipeline");
+
+ if (!gve->priv->main_pipeline) {
+ g_set_error (err, GVC_ERROR, GST_ERROR_PLUGIN_LOAD,
+ ("Failed to create a GStreamer Bin. "
+ "Please check your GStreamer installation."));
+ g_object_ref_sink (gve);
+ g_object_unref (gve);
+ return NULL;
+ }
+
+ /* Create elements */
+ gve->priv->gnl_video_composition =
+ gst_element_factory_make ("gnlcomposition", "gnl-video-composition");
+ gve->priv->gnl_audio_composition =
+ gst_element_factory_make ("gnlcomposition", "gnl-audio-composition");
+ if (!gve->priv->gnl_video_composition || !gve->priv->gnl_audio_composition) {
+ g_set_error (err, GVC_ERROR, GST_ERROR_PLUGIN_LOAD,
+ ("Failed to create a Gnonlin element. "
+ "Please check your GStreamer installation."));
+ g_object_ref_sink (gve);
+ g_object_unref (gve);
+ return NULL;
+ }
+
+ gve->priv->muxer =
+ gst_element_factory_make (DEFAULT_VIDEO_MUXER, "videomuxer");
+ gve->priv->file_sink = gst_element_factory_make ("filesink", "filesink");
+ gve_create_video_encode_bin (gve);
+ gve_create_audio_encode_bin (gve);
+
+ /* Set elements properties */
+ g_object_set (G_OBJECT (gve->priv->file_sink), "location",
+ gve->priv->output_file, NULL);
+
+ /* Link elements */
+ gst_bin_add_many (GST_BIN (gve->priv->main_pipeline),
+ gve->priv->gnl_video_composition,
+ gve->priv->gnl_audio_composition,
+ gve->priv->vencode_bin,
+ gve->priv->aencode_bin, gve->priv->muxer, gve->priv->file_sink, NULL);
+
+ gst_element_link_many (gve->priv->vencode_bin,
+ gve->priv->muxer, gve->priv->file_sink, NULL);
+ gst_element_link (gve->priv->aencode_bin, gve->priv->muxer);
+
+ /*Connect bus signals */
+ /*Wait for a "new-decoded-pad" message to link the composition with
+ the encoder tail */
+ gve->priv->bus = gst_element_get_bus (GST_ELEMENT (gve->priv->main_pipeline));
+ g_signal_connect (gve->priv->gnl_video_composition, "pad-added",
+ G_CALLBACK (new_decoded_pad_cb), gve);
+ g_signal_connect (gve->priv->gnl_audio_composition, "pad-added",
+ G_CALLBACK (new_decoded_pad_cb), gve);
+
+ gst_bus_add_signal_watch (gve->priv->bus);
+ gve->priv->sig_bus_async = g_signal_connect (gve->priv->bus, "message",
+ G_CALLBACK (gve_bus_message_cb), gve);
+
+ gst_element_set_state (gve->priv->main_pipeline, GST_STATE_READY);
+
+ return gve;
+}
diff --git a/libcesarplayer/src/gst-video-editor.h b/libcesarplayer/src/gst-video-editor.h
new file mode 100644
index 0000000..85d7819
--- /dev/null
+++ b/libcesarplayer/src/gst-video-editor.h
@@ -0,0 +1,85 @@
+/*
+ * Gstreamer Video Editor
+ * Copyright (C) 2007-2009 Andoni Morales Alastruey <ylatuya gmail com>
+ *
+ * Gstreamer Video Editor is free software.
+ *
+ * You may 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.
+ *
+ * foob 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 foob. If not, write to:
+ * The Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _GST_VIDEO_EDITOR_H_
+#define _GST_VIDEO_EDITOR_H_
+
+#ifdef WIN32
+#define EXPORT __declspec (dllexport)
+#else
+#define EXPORT
+#endif
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "common.h"
+
+G_BEGIN_DECLS
+#define GST_TYPE_VIDEO_EDITOR (gst_video_editor_get_type ())
+#define GST_VIDEO_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_VIDEO_EDITOR, GstVideoEditor))
+#define GST_VIDEO_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_VIDEO_EDITOR, GstVideoEditorClass))
+#define GST_IS_VIDEO_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_VIDEO_EDITOR))
+#define GST_IS_VIDEO_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_VIDEO_EDITOR))
+#define GST_VIDEO_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_VIDEO_EDITOR, GstVideoEditorClass))
+#define GVC_ERROR gst_video_editor_error_quark ()
+typedef struct _GstVideoEditorClass GstVideoEditorClass;
+typedef struct _GstVideoEditor GstVideoEditor;
+typedef struct GstVideoEditorPrivate GstVideoEditorPrivate;
+
+
+struct _GstVideoEditorClass
+{
+ GtkHBoxClass parent_class;
+
+ void (*error) (GstVideoEditor * gve, const char *message);
+ void (*percent_completed) (GstVideoEditor * gve, float percent);
+};
+
+struct _GstVideoEditor
+{
+ GtkHBox parent_instance;
+ GstVideoEditorPrivate *priv;
+};
+
+EXPORT GType
+gst_video_editor_get_type (void)
+ G_GNUC_CONST;
+
+ EXPORT void gst_video_editor_init_backend (int *argc, char ***argv);
+ EXPORT GstVideoEditor *gst_video_editor_new (GError ** err);
+ EXPORT void gst_video_editor_start (GstVideoEditor * gve);
+ EXPORT void gst_video_editor_cancel (GstVideoEditor * gve);
+ EXPORT void gst_video_editor_set_video_encoder (GstVideoEditor * gve,
+ gchar ** err, VideoEncoderType codec);
+ EXPORT void gst_video_editor_set_audio_encoder (GstVideoEditor * gve,
+ gchar ** err, AudioEncoderType codec);
+ EXPORT void gst_video_editor_set_video_muxer (GstVideoEditor * gve,
+ gchar ** err, VideoMuxerType codec);
+ EXPORT void gst_video_editor_clear_segments_list (GstVideoEditor * gve);
+ EXPORT void gst_video_editor_add_segment (GstVideoEditor * gve,
+ gchar * file, gint64 start,
+ gint64 duration, gdouble rate, gchar * title, gboolean hasAudio);
+
+G_END_DECLS
+#endif /* _GST_VIDEO_EDITOR_H_ */
diff --git a/libcesarplayer/src/gstscreenshot.c b/libcesarplayer/src/gstscreenshot.c
new file mode 100644
index 0000000..f1df208
--- /dev/null
+++ b/libcesarplayer/src/gstscreenshot.c
@@ -0,0 +1,203 @@
+/* Small helper element for format conversion
+ * (c) 2004 Ronald Bultje <rbultje ronald bitfreak net>
+ *
+ * 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.
+ *
+ * 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gst.h>
+#include <string.h>
+
+#include "gstscreenshot.h"
+
+GST_DEBUG_CATEGORY_EXTERN (_totem_gst_debug_cat);
+#define GST_CAT_DEFAULT _totem_gst_debug_cat
+
+static void
+feed_fakesrc (GstElement * src, GstBuffer * buf, GstPad * pad, gpointer data)
+{
+ GstBuffer *in_buf = GST_BUFFER (data);
+
+ g_assert (GST_BUFFER_SIZE (buf) >= GST_BUFFER_SIZE (in_buf));
+ g_assert (!GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_READONLY));
+
+ gst_buffer_set_caps (buf, GST_BUFFER_CAPS (in_buf));
+
+ memcpy (GST_BUFFER_DATA (buf), GST_BUFFER_DATA (in_buf),
+ GST_BUFFER_SIZE (in_buf));
+
+ GST_BUFFER_SIZE (buf) = GST_BUFFER_SIZE (in_buf);
+
+ GST_DEBUG ("feeding buffer %p, size %u, caps %" GST_PTR_FORMAT,
+ buf, GST_BUFFER_SIZE (buf), GST_BUFFER_CAPS (buf));
+}
+
+static void
+save_result (GstElement * sink, GstBuffer * buf, GstPad * pad, gpointer data)
+{
+ GstBuffer **p_buf = (GstBuffer **) data;
+
+ *p_buf = gst_buffer_ref (buf);
+
+ GST_DEBUG ("received converted buffer %p with caps %" GST_PTR_FORMAT,
+ *p_buf, GST_BUFFER_CAPS (*p_buf));
+}
+
+static gboolean
+create_element (const gchar * factory_name, GstElement ** element,
+ GError ** err)
+{
+ *element = gst_element_factory_make (factory_name, NULL);
+ if (*element)
+ return TRUE;
+
+ if (err && *err == NULL) {
+ *err = g_error_new (GST_CORE_ERROR, GST_CORE_ERROR_MISSING_PLUGIN,
+ "cannot create element '%s' - please check your GStreamer installation",
+ factory_name);
+ }
+
+ return FALSE;
+}
+
+/* takes ownership of the input buffer */
+GstBuffer *
+bvw_frame_conv_convert (GstBuffer * buf, GstCaps * to_caps)
+{
+ GstElement *src, *csp, *filter1, *vscale, *filter2, *sink, *pipeline;
+ GstMessage *msg;
+ GstBuffer *result = NULL;
+ GError *error = NULL;
+ GstBus *bus;
+ GstCaps *to_caps_no_par;
+
+ g_return_val_if_fail (GST_BUFFER_CAPS (buf) != NULL, NULL);
+
+ /* videoscale is here to correct for the pixel-aspect-ratio for us */
+ GST_DEBUG ("creating elements");
+ if (!create_element ("fakesrc", &src, &error) ||
+ !create_element ("ffmpegcolorspace", &csp, &error) ||
+ !create_element ("videoscale", &vscale, &error) ||
+ !create_element ("capsfilter", &filter1, &error) ||
+ !create_element ("capsfilter", &filter2, &error) ||
+ !create_element ("fakesink", &sink, &error)) {
+ g_warning ("Could not take screenshot: %s", error->message);
+ g_error_free (error);
+ return NULL;
+ }
+
+ pipeline = gst_pipeline_new ("screenshot-pipeline");
+ if (pipeline == NULL) {
+ g_warning ("Could not take screenshot: %s", "no pipeline (unknown error)");
+ return NULL;
+ }
+
+ GST_DEBUG ("adding elements");
+ gst_bin_add_many (GST_BIN (pipeline), src, csp, filter1, vscale, filter2,
+ sink, NULL);
+
+ g_signal_connect (src, "handoff", G_CALLBACK (feed_fakesrc), buf);
+
+ /* set to 'fixed' sizetype */
+ g_object_set (src, "sizemax", GST_BUFFER_SIZE (buf), "sizetype", 2,
+ "num-buffers", 1, "signal-handoffs", TRUE, NULL);
+
+ /* adding this superfluous capsfilter makes linking cheaper */
+ to_caps_no_par = gst_caps_copy (to_caps);
+ gst_structure_remove_field (gst_caps_get_structure (to_caps_no_par, 0),
+ "pixel-aspect-ratio");
+ g_object_set (filter1, "caps", to_caps_no_par, NULL);
+ gst_caps_unref (to_caps_no_par);
+
+ g_object_set (filter2, "caps", to_caps, NULL);
+
+ g_signal_connect (sink, "handoff", G_CALLBACK (save_result), &result);
+
+ g_object_set (sink, "preroll-queue-len", 1, "signal-handoffs", TRUE, NULL);
+
+ /* FIXME: linking is still way too expensive, profile this properly */
+ GST_DEBUG ("linking src->csp");
+ if (!gst_element_link_pads (src, "src", csp, "sink"))
+ return NULL;
+
+ GST_DEBUG ("linking csp->filter1");
+ if (!gst_element_link_pads (csp, "src", filter1, "sink"))
+ return NULL;
+
+ GST_DEBUG ("linking filter1->vscale");
+ if (!gst_element_link_pads (filter1, "src", vscale, "sink"))
+ return NULL;
+
+ GST_DEBUG ("linking vscale->capsfilter");
+ if (!gst_element_link_pads (vscale, "src", filter2, "sink"))
+ return NULL;
+
+ GST_DEBUG ("linking capsfilter->sink");
+ if (!gst_element_link_pads (filter2, "src", sink, "sink"))
+ return NULL;
+
+ GST_DEBUG ("running conversion pipeline");
+ gst_element_set_state (pipeline, GST_STATE_PLAYING);
+
+ bus = gst_element_get_bus (pipeline);
+ msg =
+ gst_bus_poll (bus, GST_MESSAGE_ERROR | GST_MESSAGE_EOS, 25 * GST_SECOND);
+
+ if (msg) {
+ switch (GST_MESSAGE_TYPE (msg)) {
+ case GST_MESSAGE_EOS:
+ {
+ if (result) {
+ GST_DEBUG ("conversion successful: result = %p", result);
+ } else {
+ GST_WARNING ("EOS but no result frame?!");
+ }
+ break;
+ }
+ case GST_MESSAGE_ERROR:
+ {
+ gchar *dbg = NULL;
+
+ gst_message_parse_error (msg, &error, &dbg);
+ if (error) {
+ g_warning ("Could not take screenshot: %s", error->message);
+ GST_DEBUG ("%s [debug: %s]", error->message, GST_STR_NULL (dbg));
+ g_error_free (error);
+ } else {
+ g_warning ("Could not take screenshot (and NULL error!)");
+ }
+ g_free (dbg);
+ result = NULL;
+ break;
+ }
+ default:
+ {
+ g_return_val_if_reached (NULL);
+ }
+ }
+ } else {
+ g_warning ("Could not take screenshot: %s", "timeout during conversion");
+ result = NULL;
+ }
+
+ gst_element_set_state (pipeline, GST_STATE_NULL);
+ gst_object_unref (pipeline);
+
+ return result;
+}
diff --git a/libcesarplayer/src/gstscreenshot.h b/libcesarplayer/src/gstscreenshot.h
new file mode 100644
index 0000000..4e20bd1
--- /dev/null
+++ b/libcesarplayer/src/gstscreenshot.h
@@ -0,0 +1,29 @@
+/* Small helper element for format conversion
+ * (c) 2004 Ronald Bultje <rbultje ronald bitfreak net>
+ *
+ * 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.
+ *
+ * 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __BVW_FRAME_CONV_H__
+#define __BVW_FRAME_CONV_H__
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+ GstBuffer * bvw_frame_conv_convert (GstBuffer * buf, GstCaps * to);
+
+G_END_DECLS
+#endif /* __BVW_FRAME_CONV_H__ */
diff --git a/libcesarplayer/src/gstvideowidget.c b/libcesarplayer/src/gstvideowidget.c
new file mode 100644
index 0000000..170ab16
--- /dev/null
+++ b/libcesarplayer/src/gstvideowidget.c
@@ -0,0 +1,1195 @@
+/* GStreamer
+ * Copyright (C) 1999,2000,2001,2002 Erik Walthinsen <omega cse ogi edu>
+ * 2000,2001,2002 Wim Taymans <wtay chello be>
+ * 2002 Steve Baker <steve stevebaker org>
+ * 2003 Julien Moutte <julien moutte net>
+ *
+ * gstvideowidget.c: Video widget for gst xvideosink window
+ *
+ * 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.
+ *
+ * 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library 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.
+ */
+
+#include "gstvideowidget.h"
+
+/* Signals and Args */
+
+enum
+{
+ ARG_0,
+ ARG_SCALE_FACTOR,
+ ARG_AUTO_RESIZE,
+ ARG_VISIBLE_CURSOR,
+ ARG_LOGO_FOCUSED,
+ ARG_EVENT_CATCHER,
+ ARG_SOURCE_WIDTH,
+ ARG_SOURCE_HEIGHT,
+ ARG_LOGO
+};
+
+struct _GstVideoWidgetPrivate
+{
+
+ GdkWindow *event_window;
+ GdkWindow *video_window;
+
+ GdkPixbuf *logo_pixbuf;
+
+ guint video_window_width;
+ guint video_window_height;
+
+ guint source_width;
+ guint source_height;
+
+ gint width_mini;
+ gint height_mini;
+
+ gboolean auto_resize;
+ gboolean cursor_visible;
+ gboolean event_catcher;
+ gboolean logo_focused;
+
+ gboolean scale_override;
+ gfloat scale_factor;
+};
+
+static GtkWidgetClass *parent_class = NULL;
+
+/* ============================================================= */
+/* */
+/* Private Methods */
+/* */
+/* ============================================================= */
+
+/* =========================================== */
+/* */
+/* Tool Box */
+/* */
+/* =========================================== */
+
+/* Method updating cursor status depending on widget flag */
+
+static void
+gst_video_widget_update_cursor (GstVideoWidget * vw)
+{
+ GtkWidget *widget;
+
+ g_return_if_fail (vw != NULL);
+ g_return_if_fail (GST_IS_VIDEO_WIDGET (vw));
+
+ widget = GTK_WIDGET (vw);
+
+ if (widget->window == NULL)
+ return;
+
+ if (vw->priv->cursor_visible)
+ gdk_window_set_cursor (widget->window, NULL);
+ else {
+
+ GdkBitmap *empty_bitmap;
+ GdkColor useless;
+ GdkCursor *cursor;
+ char invisible_cursor_bits[] = { 0x0 };
+
+ useless.red = useless.green = useless.blue = useless.pixel = 0;
+
+ empty_bitmap = gdk_bitmap_create_from_data (widget->window,
+ invisible_cursor_bits, 1, 1);
+
+ cursor = gdk_cursor_new_from_pixmap (empty_bitmap,
+ empty_bitmap, &useless, &useless, 0, 0);
+
+ gdk_window_set_cursor (widget->window, cursor);
+
+ gdk_cursor_unref (cursor);
+
+ g_object_unref (empty_bitmap);
+ }
+}
+
+/*
+ * Method reordering event window and video window
+ * depending on event_catcher flag
+ */
+
+static void
+gst_video_widget_reorder_windows (GstVideoWidget * vw)
+{
+ g_return_if_fail (vw != NULL);
+ g_return_if_fail (GST_IS_VIDEO_WIDGET (vw));
+
+ if ((vw->priv->logo_focused) && (GDK_IS_WINDOW (vw->priv->video_window))) {
+ gdk_window_hide (vw->priv->video_window);
+ } else if ((!vw->priv->logo_focused) &&
+ (GDK_IS_WINDOW (vw->priv->video_window))) {
+ gdk_window_show (vw->priv->video_window);
+ }
+
+ if (GDK_IS_WINDOW (vw->priv->video_window))
+ gdk_window_raise (vw->priv->video_window);
+
+ if (vw->priv->event_catcher) {
+ if (GDK_IS_WINDOW (vw->priv->event_window))
+ gdk_window_raise (vw->priv->event_window);
+ }
+
+ gtk_widget_queue_draw (GTK_WIDGET (vw));
+}
+
+/* =========================================== */
+/* */
+/* Event Handlers, Callbacks */
+/* */
+/* =========================================== */
+
+/* Realizing GstVideoWidget */
+
+static void
+gst_video_widget_realize (GtkWidget * widget)
+{
+ GstVideoWidget *vw = GST_VIDEO_WIDGET (widget);
+ GdkWindowAttr attributes;
+ gint attributes_mask;
+
+ g_return_if_fail (vw != NULL);
+
+ GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+
+ /* Creating our widget's window */
+
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.x = widget->allocation.x;
+ attributes.y = widget->allocation.y;
+ attributes.width = widget->allocation.width;
+ attributes.height = widget->allocation.height;
+ 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;
+
+ widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
+ &attributes, attributes_mask);
+
+ gdk_window_set_user_data (widget->window, widget);
+
+ /* Creating our video window */
+
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.x = 0;
+ attributes.y = 0;
+ attributes.width = widget->allocation.width;
+ attributes.height = widget->allocation.height;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.event_mask = GDK_EXPOSURE_MASK;
+
+ attributes_mask = GDK_WA_X | GDK_WA_Y;
+
+ vw->priv->video_window = gdk_window_new (widget->window,
+ &attributes, attributes_mask);
+
+ gdk_window_set_user_data (vw->priv->video_window, widget);
+
+ gdk_window_show (vw->priv->video_window);
+
+ /* Creating our event window */
+
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.x = 0;
+ attributes.y = 0;
+ attributes.width = widget->allocation.width;
+ attributes.height = widget->allocation.height;
+ attributes.wclass = GDK_INPUT_ONLY;
+ attributes.event_mask = GDK_ALL_EVENTS_MASK;
+
+ attributes_mask = GDK_WA_X | GDK_WA_Y;
+
+ vw->priv->event_window = gdk_window_new (widget->window,
+ &attributes, attributes_mask);
+
+ gdk_window_set_user_data (vw->priv->event_window, widget);
+
+ gdk_window_show (vw->priv->event_window);
+
+ widget->style = gtk_style_attach (widget->style, widget->window);
+
+ gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
+
+ gst_video_widget_update_cursor (vw);
+
+}
+
+/* Unrealizing GstVideoWidget */
+
+static void
+gst_video_widget_unrealize (GtkWidget * widget)
+{
+ GstVideoWidget *vw;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GST_IS_VIDEO_WIDGET (widget));
+
+ vw = GST_VIDEO_WIDGET (widget);
+
+ GTK_WIDGET_UNSET_FLAGS (widget, GTK_REALIZED);
+
+ /* Hide all windows */
+
+ if (GTK_WIDGET_MAPPED (widget))
+ gtk_widget_unmap (widget);
+
+ GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED);
+
+ /* Destroying event window */
+
+ if (GDK_IS_WINDOW (vw->priv->event_window)) {
+ gdk_window_set_user_data (vw->priv->event_window, NULL);
+ gdk_window_destroy (vw->priv->event_window);
+ vw->priv->event_window = NULL;
+ }
+
+ if (GDK_IS_WINDOW (vw->priv->video_window)) {
+ gdk_window_set_user_data (vw->priv->video_window, NULL);
+ gdk_window_destroy (vw->priv->video_window);
+ vw->priv->video_window = NULL;
+ }
+
+ /* Chaining up */
+
+ if (GTK_WIDGET_CLASS (parent_class)->unrealize)
+ GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
+}
+
+/* GstVideoWidget got exposed */
+
+static gint
+gst_video_widget_expose (GtkWidget * widget, GdkEventExpose * event)
+{
+ GstVideoWidget *vw;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GST_IS_VIDEO_WIDGET (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ vw = GST_VIDEO_WIDGET (widget);
+
+
+ if (GTK_WIDGET_VISIBLE (widget) && GTK_WIDGET_MAPPED (widget)) {
+ if ((vw->priv->logo_focused) && (vw->priv->logo_pixbuf)) {
+ GdkPixbuf *frame;
+ guchar *pixels;
+ int rowstride;
+ gint width, height, alloc_width, alloc_height, logo_x, logo_y;
+ gfloat width_ratio, height_ratio;
+
+ frame = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ FALSE, 8, widget->allocation.width, widget->allocation.height);
+
+ width = gdk_pixbuf_get_width (vw->priv->logo_pixbuf);
+ height = gdk_pixbuf_get_height (vw->priv->logo_pixbuf);
+ alloc_width = widget->allocation.width;
+ alloc_height = widget->allocation.height;
+
+ /* Checking if allocated space is smaller than our logo */
+
+ if ((alloc_width < width) || (alloc_height < height)) {
+ width_ratio = (gfloat) alloc_width / (gfloat) width;
+ height_ratio = (gfloat) alloc_height / (gfloat) height;
+ width_ratio = MIN (width_ratio, height_ratio);
+ height_ratio = width_ratio;
+ } else
+ width_ratio = height_ratio = 1.0;
+
+ logo_x = (alloc_width / 2) - (width * width_ratio / 2);
+ logo_y = (alloc_height / 2) - (height * height_ratio / 2);
+
+ /* Scaling to available space */
+
+ gdk_pixbuf_composite (vw->priv->logo_pixbuf,
+ frame,
+ 0, 0,
+ alloc_width, alloc_height,
+ logo_x, logo_y, width_ratio, height_ratio, GDK_INTERP_BILINEAR, 255);
+
+ /* Drawing our frame */
+
+ rowstride = gdk_pixbuf_get_rowstride (frame);
+
+ pixels = gdk_pixbuf_get_pixels (frame) +
+ rowstride * event->area.y + event->area.x * 3;
+
+ gdk_draw_rgb_image_dithalign (widget->window,
+ widget->style->black_gc,
+ event->area.x, event->area.y,
+ event->area.width, event->area.height,
+ GDK_RGB_DITHER_NORMAL, pixels,
+ rowstride, event->area.x, event->area.y);
+
+ g_object_unref (frame);
+ } else {
+ gdk_draw_rectangle (widget->window, widget->style->black_gc, TRUE,
+ event->area.x, event->area.y, event->area.width, event->area.height);
+ }
+
+ }
+
+ if (GDK_IS_WINDOW (vw->priv->video_window)) {
+ gint pos_x, pos_y, width, height, depth;
+
+ gdk_window_get_geometry (vw->priv->video_window, &pos_x, &pos_y,
+ &width, &height, &depth);
+ if ((width != vw->priv->video_window_width) ||
+ (height != vw->priv->video_window_height)) {
+ gtk_widget_queue_resize (widget);
+ }
+ }
+
+ return FALSE;
+}
+
+/* Size request for our widget */
+
+static void
+gst_video_widget_size_request (GtkWidget * widget, GtkRequisition * requisition)
+{
+ GstVideoWidget *vw;
+ gint width, height;
+ gfloat temp;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GST_IS_VIDEO_WIDGET (widget));
+
+ vw = GST_VIDEO_WIDGET (widget);
+
+ if ((!vw->priv->auto_resize) && (!vw->priv->scale_override)) {
+ requisition->width = vw->priv->width_mini;
+ requisition->height = vw->priv->height_mini;
+ return;
+ }
+
+ if ((vw->priv->source_width) &&
+ (vw->priv->source_height) && (vw->priv->scale_factor)) {
+ temp = (vw->priv->scale_factor * vw->priv->source_width + 0.5);
+ width = (gint) temp > G_MAXINT ? G_MAXINT : (gint) temp;
+ temp = (vw->priv->scale_factor * vw->priv->source_height + 0.5);
+ height = (gint) temp > G_MAXINT ? G_MAXINT : (gint) temp;
+
+ /* don't make us want to be bigger than the screen */
+
+ if (width > gdk_screen_width ()) {
+ height = height * gdk_screen_width () / width;
+ width = gdk_screen_width ();
+ }
+ if (height > gdk_screen_height ()) {
+ width = width * gdk_screen_height () / height;
+ height = gdk_screen_height ();
+ }
+ } else {
+ if (vw->priv->logo_pixbuf) {
+ width = gdk_pixbuf_get_width (vw->priv->logo_pixbuf);
+ height = gdk_pixbuf_get_height (vw->priv->logo_pixbuf);
+ vw->priv->width_mini = width;
+ vw->priv->height_mini = height;
+ } else {
+ width = 100;
+ height = 100;
+ }
+ }
+
+ if (width < vw->priv->width_mini)
+ width = vw->priv->width_mini;
+ if (height < vw->priv->height_mini)
+ height = vw->priv->height_mini;
+
+ requisition->width = width;
+ requisition->height = height;
+}
+
+/* Allocate method for our widget */
+
+static void
+gst_video_widget_allocate (GtkWidget * widget, GtkAllocation * allocation)
+{
+ GstVideoWidget *vw;
+ gfloat temp, scale_factor = 1.0;
+ gint width, height;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GST_IS_VIDEO_WIDGET (widget));
+
+ vw = GST_VIDEO_WIDGET (widget);
+
+ /* Choosing best ratio */
+
+ if (vw->priv->scale_override) {
+ /* Enforcing scale ratio taking the scale ratio from the priv structure */
+ scale_factor = vw->priv->scale_factor;
+ } else if (!vw->priv->auto_resize) {
+ /* Calculating optimal ratio to fit the allocated window */
+ if ((vw->priv->source_width) && (vw->priv->source_height)
+ && (GDK_IS_WINDOW (vw->priv->video_window))) {
+ gfloat w_ratio, h_ratio;
+
+ w_ratio = (gfloat) allocation->width / vw->priv->source_width;
+ h_ratio = (gfloat) allocation->height / vw->priv->source_height;
+
+ scale_factor = MIN (w_ratio, h_ratio);
+ }
+ } else {
+ scale_factor = 1.0;
+ }
+
+ /* Calculating width & height with optimal ratio */
+
+ temp = (scale_factor * vw->priv->source_width + 0.5);
+ width = (gint) temp > G_MAXINT ? G_MAXINT : (gint) temp;
+ temp = (scale_factor * vw->priv->source_height + 0.5);
+ height = (gint) temp > G_MAXINT ? G_MAXINT : (gint) temp;
+
+ /* When auto resizing we change allocation to our geometry except if our
+ geometry violates the minimum one */
+ if (vw->priv->auto_resize) {
+ if (width < vw->priv->width_mini) {
+ allocation->width = vw->priv->width_mini;
+ } else {
+ allocation->width = width;
+ }
+ if (height < vw->priv->height_mini) {
+ allocation->height = vw->priv->height_mini;
+ } else {
+ allocation->height = height;
+ }
+ } else { /* We do not want to shrink under our minimum geometry */
+ if (allocation->width < vw->priv->width_mini)
+ allocation->width = vw->priv->width_mini;
+ if (allocation->height < vw->priv->height_mini)
+ allocation->height = vw->priv->height_mini;
+ }
+
+ widget->allocation = *allocation;
+
+ if (GTK_WIDGET_REALIZED (widget)) {
+ gdk_window_move_resize (widget->window,
+ allocation->x, allocation->y, allocation->width, allocation->height);
+
+ if (GDK_IS_WINDOW (vw->priv->event_window))
+ gdk_window_move_resize (vw->priv->event_window,
+ 0, 0, allocation->width, allocation->height);
+
+ /* X windows can only be resized to 1,1 not 0,0 so we have to handle
+ the case where source size is 0,0 (not set basically). So instead
+ of setting the video window to 0,0 we set it to 1,1 to match with
+ what X will really do. If we don't do that we have an infinite loop
+ with the expose event reacting on video window geometry changes. */
+ if (!width)
+ width = 1;
+ if (!height)
+ height = 1;
+
+ vw->priv->video_window_width = width;
+ vw->priv->video_window_height = height;
+
+ if (GDK_IS_WINDOW (vw->priv->video_window)) {
+ gint video_x, video_y;
+
+ video_x = (allocation->width / 2) - (width / 2);
+ video_y = (allocation->height / 2) - (height / 2);
+
+ gdk_window_move_resize (vw->priv->video_window,
+ video_x, video_y, width, height);
+ }
+ }
+}
+
+/* GstVideoWidget methods to set properties */
+
+static void
+gst_video_widget_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstVideoWidget *vw;
+
+ g_return_if_fail (object != NULL);
+
+ vw = GST_VIDEO_WIDGET (object);
+
+ switch (prop_id) {
+ case ARG_SCALE_FACTOR:
+ vw->priv->scale_factor = g_value_get_float (value);
+ vw->priv->scale_override = TRUE;
+ gtk_widget_queue_resize (GTK_WIDGET (vw));
+ break;
+ case ARG_AUTO_RESIZE:
+ vw->priv->auto_resize = g_value_get_boolean (value);
+ gtk_widget_queue_resize (GTK_WIDGET (vw));
+ break;
+ case ARG_VISIBLE_CURSOR:
+ vw->priv->cursor_visible = g_value_get_boolean (value);
+ gst_video_widget_update_cursor (vw);
+ break;
+ case ARG_LOGO_FOCUSED:
+ vw->priv->logo_focused = g_value_get_boolean (value);
+ gst_video_widget_reorder_windows (vw);
+ break;
+ case ARG_EVENT_CATCHER:
+ vw->priv->event_catcher = g_value_get_boolean (value);
+ gst_video_widget_reorder_windows (vw);
+ break;
+ case ARG_SOURCE_WIDTH:
+ vw->priv->source_width = g_value_get_int (value);
+ break;
+ case ARG_SOURCE_HEIGHT:
+ vw->priv->source_height = g_value_get_int (value);
+ break;
+ case ARG_LOGO:
+ {
+ GdkPixbuf *image;
+
+ image = (GdkPixbuf *) g_value_get_object (value);
+
+ gst_video_widget_set_logo (vw, image);
+ }
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+/* GstVideoWidget methods to get properties */
+
+static void
+gst_video_widget_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstVideoWidget *vw;
+
+ g_return_if_fail (object != NULL);
+
+ vw = GST_VIDEO_WIDGET (object);
+
+ switch (prop_id) {
+ case ARG_SCALE_FACTOR:
+ g_value_set_float (value, vw->priv->scale_factor);
+ break;
+ case ARG_AUTO_RESIZE:
+ g_value_set_boolean (value, vw->priv->auto_resize);
+ break;
+ case ARG_VISIBLE_CURSOR:
+ g_value_set_boolean (value, vw->priv->cursor_visible);
+ break;
+ case ARG_LOGO_FOCUSED:
+ g_value_set_boolean (value, vw->priv->logo_focused);
+ break;
+ case ARG_EVENT_CATCHER:
+ g_value_set_boolean (value, vw->priv->event_catcher);
+ break;
+ case ARG_SOURCE_WIDTH:
+ g_value_set_int (value, vw->priv->source_width);
+ break;
+ case ARG_SOURCE_HEIGHT:
+ g_value_set_int (value, vw->priv->source_height);
+ break;
+ case ARG_LOGO:
+ g_value_set_object (value, (GObject *) vw->priv->logo_pixbuf);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+/* =========================================== */
+/* */
+/* Init & Class init */
+/* */
+/* =========================================== */
+
+/* Class initialization for GstVideoWidget */
+
+static void
+gst_video_widget_class_init (GstVideoWidgetClass * klass)
+{
+ GObjectClass *gobject_class;
+ GtkWidgetClass *widget_class;
+
+ gobject_class = (GObjectClass *) klass;
+ widget_class = (GtkWidgetClass *) klass;
+
+ parent_class = gtk_type_class (gtk_widget_get_type ());
+
+ gobject_class->set_property = gst_video_widget_set_property;
+ gobject_class->get_property = gst_video_widget_get_property;
+
+ g_object_class_install_property (gobject_class,
+ ARG_SCALE_FACTOR,
+ g_param_spec_float ("scale_factor",
+ "scale factor",
+ "size the video should be scaled to",
+ 0, G_MAXFLOAT / G_MAXINT, 1, G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ ARG_AUTO_RESIZE,
+ g_param_spec_boolean ("auto_resize",
+ "auto resize",
+ "Is the video widget resizing automatically",
+ FALSE, G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ ARG_VISIBLE_CURSOR,
+ g_param_spec_boolean ("visible_cursor",
+ "visible cursor",
+ "Is the mouse pointer (cursor) visible or not",
+ TRUE, G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ ARG_LOGO_FOCUSED,
+ g_param_spec_boolean ("logo_focused",
+ "logo is focused",
+ "Is the logo focused or not", TRUE, G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ ARG_EVENT_CATCHER,
+ g_param_spec_boolean ("event_catcher",
+ "Event catcher",
+ "Should the widget catch events over the video window",
+ TRUE, G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ ARG_SOURCE_WIDTH,
+ g_param_spec_int ("source_width",
+ "video source width",
+ "Video playback source width", 0, G_MAXINT, 1, G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ ARG_SOURCE_HEIGHT,
+ g_param_spec_int ("source_height",
+ "video source height",
+ "Video playback source height", 0, G_MAXINT, 1, G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ ARG_LOGO,
+ g_param_spec_object ("logo",
+ "Logo",
+ "Picture that should appear as a logo when no video",
+ gdk_pixbuf_get_type (), G_PARAM_READWRITE));
+
+ widget_class->realize = gst_video_widget_realize;
+ widget_class->unrealize = gst_video_widget_unrealize;
+ widget_class->expose_event = gst_video_widget_expose;
+ widget_class->size_request = gst_video_widget_size_request;
+ widget_class->size_allocate = gst_video_widget_allocate;
+
+}
+
+/* Initing our widget */
+
+static void
+gst_video_widget_init (GstVideoWidget * vw)
+{
+
+ vw->priv = g_new0 (GstVideoWidgetPrivate, 1);
+
+ GTK_WIDGET_SET_FLAGS (GTK_WIDGET (vw), GTK_CAN_FOCUS);
+ GTK_WIDGET_UNSET_FLAGS (GTK_WIDGET (vw), GTK_DOUBLE_BUFFERED);
+
+ vw->priv->source_width = 0;
+ vw->priv->source_height = 0;
+ vw->priv->width_mini = 0;
+ vw->priv->height_mini = 0;
+ vw->priv->scale_factor = 1.0;
+ vw->priv->auto_resize = FALSE;
+ vw->priv->scale_override = FALSE;
+ vw->priv->cursor_visible = TRUE;
+ vw->priv->event_catcher = TRUE;
+ vw->priv->logo_focused = FALSE;
+ vw->priv->event_window = NULL;
+ vw->priv->video_window = NULL;
+ vw->priv->logo_pixbuf = NULL;
+}
+
+/* ============================================================= */
+/* */
+/* Public Methods */
+/* */
+/* ============================================================= */
+
+GdkWindow *
+gst_video_widget_get_video_window (GstVideoWidget * vw)
+{
+ g_return_val_if_fail (vw != NULL, NULL);
+ g_return_val_if_fail (GST_IS_VIDEO_WIDGET (vw), NULL);
+ return vw->priv->video_window;
+}
+
+/**
+ * gst_video_widget_set_source_size:
+ * @vw: a #GstVideoWidget
+ * @width: a #guint indicating source video's width.
+ * @height: a #guint indicating source video's height.
+ *
+ * Set video source size of a #GstVideoWidget and queue a resize request
+ * for the widget.
+ *
+ * The #GstVideoWidget must have already been created
+ * before you can make this call.
+ *
+ * Remember you can set these values trough "source_width" and "source_height"
+ * properties, but you will have to queue the resize request yourself.
+ *
+ * Return value: a #gboolean indicating wether the call succeeded or not.
+ **/
+gboolean
+gst_video_widget_set_source_size (GstVideoWidget * vw, guint width,
+ guint height)
+{
+ g_return_val_if_fail (vw != NULL, FALSE);
+ g_return_val_if_fail (GST_IS_VIDEO_WIDGET (vw), FALSE);
+
+
+ if (vw->priv->source_width != width || vw->priv->source_height != height) {
+ vw->priv->source_width = width;
+ vw->priv->source_height = height;
+ gtk_widget_queue_resize (GTK_WIDGET (vw));
+ }
+
+ return TRUE;
+}
+
+/**
+ * gst_video_widget_get_source_size:
+ * @socket_: a #GstVideoWidget
+ * @width: a pointer to a #guint where source video's width will be put.
+ * @height: a pointer to a #guint where source video's height will be put.
+ *
+ * Fills @width and @height with source video's dimensions.
+ *
+ * Remember you can get these values trough "source_width" and "source_height"
+ * properties.
+ *
+ * Return value: a #gboolean indicating wether the call succeeded or not.
+ **/
+gboolean
+gst_video_widget_get_source_size (GstVideoWidget * vw, guint * width,
+ guint * height)
+{
+ g_return_val_if_fail (vw != NULL, FALSE);
+ g_return_val_if_fail (GST_IS_VIDEO_WIDGET (vw), FALSE);
+
+ *width = vw->priv->source_width;
+ *height = vw->priv->source_height;
+
+ return TRUE;
+}
+
+/**
+ * gst_video_widget_set_minimum_size:
+ * @vw: a #GstVideoWidget
+ * @width: a #gint indicating minimum width.
+ * @height: a #gint indicating minimum height.
+ *
+ * Set minimum size of a #GstVideoWidget and queue a resize request
+ * for the widget. This method is usefull when the #GstVideoWidget is set
+ * to auto resize, it won't go under this size.
+ *
+ * The #GstVideoWidget must have already been created
+ * before you can make this call.
+ *
+ * Remember you can set these values trough "width_mini" and "height_mini"
+ * properties, but you will have to queue the resize request yourself.
+ *
+ * Return value: a #gboolean indicating wether the call succeeded or not.
+ **/
+gboolean
+gst_video_widget_set_minimum_size (GstVideoWidget * vw, gint width, gint height)
+{
+ g_return_val_if_fail (vw != NULL, FALSE);
+ g_return_val_if_fail (GST_IS_VIDEO_WIDGET (vw), FALSE);
+
+ vw->priv->width_mini = width;
+ vw->priv->height_mini = height;
+ gtk_widget_queue_resize (GTK_WIDGET (vw));
+
+ return TRUE;
+}
+
+/**
+ * gst_video_widget_get_minimum_size:
+ * @socket_: a #GstVideoWidget
+ * @width: a pointer to a #gint where minimum width will be put.
+ * @height: a pointer to a #gint where minimum height will be put.
+ *
+ * Fills @width and @height with minimum dimensions.
+ *
+ * Remember you can get these values trough "width_mini" and "height_mini"
+ * properties.
+ *
+ * Return value: a #gboolean indicating wether the call succeeded or not.
+ **/
+gboolean
+gst_video_widget_get_minimum_size (GstVideoWidget * vw, gint * width,
+ gint * height)
+{
+ g_return_val_if_fail (vw != NULL, FALSE);
+ g_return_val_if_fail (GST_IS_VIDEO_WIDGET (vw), FALSE);
+
+ *width = vw->priv->width_mini;
+ *height = vw->priv->height_mini;
+
+ return TRUE;
+}
+
+/**
+ * gst_video_widget_set_cursor_visible:
+ * @vw: a #GstVideoWidget
+ * @visible: a #gboolean indicating wether or not the cursor should be visible.
+ *
+ * Set cursor visible or not over embeded video window.
+ *
+ * Remember you can set this flag trough "visible_cursor" property.
+ *
+ * Return value: a #gboolean indicating wether the call succeeded or not.
+ **/
+gboolean
+gst_video_widget_set_cursor_visible (GstVideoWidget * vw, gboolean visible)
+{
+ g_return_val_if_fail (vw != NULL, FALSE);
+ g_return_val_if_fail (GST_IS_VIDEO_WIDGET (vw), FALSE);
+
+ vw->priv->cursor_visible = visible;
+
+ gst_video_widget_update_cursor (vw);
+
+ return TRUE;
+}
+
+/**
+ * gst_video_widget_get_cursor_visible:
+ * @vw: a #GstVideoWidget
+ *
+ * Get cursor visible status over embeded video window.
+ *
+ * Remember you can get this flag trough "visible_cursor" property.
+ *
+ * Return value: a #gboolean indicating wether the cursor is visible or not.
+ **/
+gboolean
+gst_video_widget_get_cursor_visible (GstVideoWidget * vw)
+{
+ g_return_val_if_fail (vw != NULL, FALSE);
+ g_return_val_if_fail (GST_IS_VIDEO_WIDGET (vw), FALSE);
+ return vw->priv->cursor_visible;
+}
+
+/**
+ * gst_video_widget_set_logo_focus:
+ * @vw: a #GstVideoWidget
+ * @visible: a #gboolean indicating wether or not the logo should have focus.
+ *
+ * Set logo's focus over embeded video window.
+ *
+ * Remember you can set this flag trough "logo_focused" property.
+ *
+ * Return value: a #gboolean indicating wether the call succeeded or not.
+ **/
+gboolean
+gst_video_widget_set_logo_focus (GstVideoWidget * vw, gboolean focused)
+{
+ g_return_val_if_fail (vw != NULL, FALSE);
+ g_return_val_if_fail (GST_IS_VIDEO_WIDGET (vw), FALSE);
+
+ vw->priv->logo_focused = focused;
+
+ gst_video_widget_reorder_windows (vw);
+
+ return TRUE;
+}
+
+/**
+ * gst_video_widget_get_logo_focus:
+ * @vw: a #GstVideoWidget
+ *
+ * Get logo focus status over embeded video window.
+ *
+ * Remember you can get this flag trough "logo_focused" property.
+ *
+ * Return value: a #gboolean indicating wether the logo has focus or not.
+ **/
+gboolean
+gst_video_widget_get_logo_focus (GstVideoWidget * vw)
+{
+ g_return_val_if_fail (vw != NULL, FALSE);
+ g_return_val_if_fail (GST_IS_VIDEO_WIDGET (vw), FALSE);
+ return vw->priv->logo_focused;
+}
+
+/**
+ * gst_video_widget_set_event_catcher:
+ * @vw: a #GstVideoWidget
+ * @event_catcher: a #gboolean indicating wether the widget should catch events
+ * over the embeded window or not.
+ *
+ * Set a #GstVideoWidget to catch events over the embeded window or not.
+ *
+ * Remember you can set this flag trough the "event_catcher" property.
+ *
+ * Return value: a #gboolean indicating wether the call succeeded or not.
+ **/
+gboolean
+gst_video_widget_set_event_catcher (GstVideoWidget * vw, gboolean event_catcher)
+{
+ g_return_val_if_fail (vw != NULL, FALSE);
+ g_return_val_if_fail (GST_IS_VIDEO_WIDGET (vw), FALSE);
+
+ vw->priv->event_catcher = event_catcher;
+
+ gst_video_widget_reorder_windows (vw);
+
+ return TRUE;
+}
+
+/**
+ * gst_video_widget_get_event_catcher:
+ * @vw: a #GstVideoWidget
+ *
+ * Get event catcher status from a #GstVideoWidget to know if the widget
+ * catch events over embeded window or not.
+ *
+ * Remember you can get this flag trough "event_catcher" property.
+ *
+ * Return value: a #gboolean indicating wether the widget catch events or not.
+ **/
+gboolean
+gst_video_widget_get_event_catcher (GstVideoWidget * vw)
+{
+ g_return_val_if_fail (vw != NULL, FALSE);
+ g_return_val_if_fail (GST_IS_VIDEO_WIDGET (vw), FALSE);
+ return vw->priv->event_catcher;
+}
+
+/**
+ * gst_video_widget_set_auto_resize:
+ * @vw: a #GstVideoWidget
+ * @resize: a #gboolean indicating auto resize mode that will be used by @vw.
+ *
+ * Set if a #GstVideoWidget will auto resize or not.
+ *
+ * Remember you can set this flag trough the "auto_resize" property.
+ *
+ * Return value: a #gboolean indicating wether the call succeeded or not.
+ **/
+gboolean
+gst_video_widget_set_auto_resize (GstVideoWidget * vw, gboolean resize)
+{
+ g_return_val_if_fail (vw != NULL, FALSE);
+ g_return_val_if_fail (GST_IS_VIDEO_WIDGET (vw), FALSE);
+ vw->priv->auto_resize = resize;
+ gtk_widget_queue_resize (GTK_WIDGET (vw));
+ return TRUE;
+}
+
+/**
+ * gst_video_widget_get_auto_resize:
+ * @vw: a #GstVideoWidget
+ *
+ * Get used auto resize mode for a #GstVideoWidget.
+ *
+ * Remember you can get this value trough "auto_resize" property.
+ *
+ * Return value: a #gboolean indicating wether the video widget
+ * is in auto_resize mode or not.
+ **/
+gboolean
+gst_video_widget_get_auto_resize (GstVideoWidget * vw)
+{
+ g_return_val_if_fail (vw != NULL, FALSE);
+ g_return_val_if_fail (GST_IS_VIDEO_WIDGET (vw), FALSE);
+ return vw->priv->auto_resize;
+}
+
+/**
+ * gst_video_widget_get_scale_override:
+ * @vw: a #GstVideoWidget
+ *
+ * Get scale override mode for a #GstVideoWidget.
+ *
+ * Remember you can get this value trough "scale_override" property.
+ *
+ * Return value: a #gboolean indicating if scale ratio is enforced or not.
+ **/
+gboolean
+gst_video_widget_get_scale_override (GstVideoWidget * vw)
+{
+ g_return_val_if_fail (vw != NULL, FALSE);
+ g_return_val_if_fail (GST_IS_VIDEO_WIDGET (vw), FALSE);
+ return vw->priv->scale_override;
+}
+
+/**
+ * gst_video_widget_set_scale_override:
+ * @vw: a #GstVideoWidget
+ * @override: a #gboolean indicating scale override mode that will
+ * be used by @vw.
+ *
+ * Set scale override mode for a #GstVideoWidget.
+ *
+ * Remember you can set this flag trough the "scale_override" property.
+ *
+ * Return value: a #gboolean indicating wether the call succeeded or not.
+ **/
+gboolean
+gst_video_widget_set_scale_override (GstVideoWidget * vw, gboolean override)
+{
+ g_return_val_if_fail (vw != NULL, FALSE);
+ g_return_val_if_fail (GST_IS_VIDEO_WIDGET (vw), FALSE);
+ vw->priv->scale_override = override;
+ gtk_widget_queue_resize (GTK_WIDGET (vw));
+ return TRUE;
+}
+
+/**
+ * gst_video_widget_set_scale:
+ * @vw: a #GstVideoWidget
+ * @scale: a #gfloat indicating scale factor that will be used by @vw.
+ *
+ * Set a scale factor for a #GstVideoWidget.
+ *
+ * Remember you can set this flag trough the "scale_factor" property.
+ *
+ * Return value: a #gboolean indicating wether the call succeeded or not.
+ **/
+gboolean
+gst_video_widget_set_scale (GstVideoWidget * vw, gfloat scale)
+{
+ g_return_val_if_fail (vw != NULL, FALSE);
+ g_return_val_if_fail (GST_IS_VIDEO_WIDGET (vw), FALSE);
+ vw->priv->scale_factor = scale;
+ if (vw->priv->scale_override)
+ gtk_widget_queue_resize (GTK_WIDGET (vw));
+ return TRUE;
+}
+
+/**
+ * gst_video_widget_get_scale:
+ * @vw: a #GstVideoWidget
+ *
+ * Get used scale factor for a #GstVideoWidget.
+ *
+ * Remember you can get this value trough "scale_factor" property.
+ *
+ * Return value: a #gfloat indicating scale factor used.
+ **/
+gfloat
+gst_video_widget_get_scale (GstVideoWidget * vw)
+{
+ g_return_val_if_fail (vw != NULL, FALSE);
+ g_return_val_if_fail (GST_IS_VIDEO_WIDGET (vw), FALSE);
+ return vw->priv->scale_factor;
+}
+
+/**
+ * gst_video_widget_set_logo:
+ * @vw: a #GstVideoWidget.
+ * @logo: a #GdkPixbuf to set as the image for the logo.
+ *
+ * Sets the image of @vw to the given @logo pixbuf.
+ *
+ * Warning @logo should not be freed after this call unless you destroyed
+ * widget. Indeed no copy is done. We use your #GdkPixbuf !
+ **/
+void
+gst_video_widget_set_logo (GstVideoWidget * vw, GdkPixbuf * logo_pixbuf)
+{
+ g_return_if_fail (vw != NULL);
+ g_return_if_fail (GST_IS_VIDEO_WIDGET (vw));
+
+ if (logo_pixbuf == vw->priv->logo_pixbuf)
+ return;
+
+ if (vw->priv->logo_pixbuf)
+ g_object_unref (vw->priv->logo_pixbuf);
+
+ vw->priv->logo_pixbuf = logo_pixbuf;
+}
+
+/**
+ * gst_video_widget_get_logo:
+ * @vw: a #GstVideoWidget.
+ * @returns: the #GdkPixbuf set as logo of @vw.
+ *
+ * Gets the logo of @vw.
+ **/
+GdkPixbuf *
+gst_video_widget_get_logo (GstVideoWidget * vw)
+{
+ g_return_val_if_fail (vw != NULL, NULL);
+ g_return_val_if_fail (GST_IS_VIDEO_WIDGET (vw), NULL);
+ return vw->priv->logo_pixbuf;
+}
+
+void
+gst_video_widget_force_expose (GtkWidget * widget, GdkEventExpose * event)
+{
+ gst_video_widget_expose (widget, event);
+}
+
+/* =========================================== */
+/* */
+/* Widget typing & Creation */
+/* */
+/* =========================================== */
+
+/* Get type function for GstVideoWidget */
+
+GType
+gst_video_widget_get_type (void)
+{
+ static GType vw_type = 0;
+
+ if (!vw_type) {
+ static const GTypeInfo vw_info = {
+ sizeof (GstVideoWidgetClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gst_video_widget_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL /* class_data */ ,
+ sizeof (GstVideoWidget),
+ 0 /* n_preallocs */ ,
+ (GInstanceInitFunc) gst_video_widget_init,
+ };
+ vw_type = g_type_register_static (GTK_TYPE_WIDGET,
+ "GstVideoWidget", &vw_info, (GTypeFlags) 0);
+ }
+ return vw_type;
+}
+
+
+/**
+ * gst_video_widget_new:
+ *
+ * Create a new empty #GstVideoWidget.
+ *
+ * Return value: the new #GstVideoWidget.
+ **/
+GtkWidget *
+gst_video_widget_new (void)
+{
+ GstVideoWidget *widget = g_object_new (GST_TYPE_VIDEO_WIDGET, NULL);
+
+ return GTK_WIDGET (widget);
+}
diff --git a/libcesarplayer/src/gstvideowidget.h b/libcesarplayer/src/gstvideowidget.h
new file mode 100644
index 0000000..12d2e02
--- /dev/null
+++ b/libcesarplayer/src/gstvideowidget.h
@@ -0,0 +1,125 @@
+/* GStreamer
+ * Copyright (C) 1999,2000,2001,2002 Erik Walthinsen <omega cse ogi edu>
+ * 2000,2001,2002 Wim Taymans <wtay chello be>
+ * 2002 Steve Baker <steve stevebaker org>
+ * 2003 Julien Moutte <julien moutte net>
+ *
+ * gstvideowidget.h: Video widget for gst xvideosink window
+ *
+ * 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.
+ *
+ * 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library 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 __GST_VIDEO_WIDGET_H__
+#define __GST_VIDEO_WIDGET_H__
+
+#if BUILDING_DLL
+# define DLLIMPORT __declspec (dllexport)
+#else /* Not BUILDING_DLL */
+# define DLLIMPORT __declspec (dllimport)
+#endif /* Not BUILDING_DLL */
+
+#include <gtk/gtkwidget.h>
+#include <gdk/gdk.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#define GST_TYPE_VIDEO_WIDGET (gst_video_widget_get_type ())
+#define GST_VIDEO_WIDGET(obj) (GTK_CHECK_CAST ((obj), GST_TYPE_VIDEO_WIDGET, GstVideoWidget))
+#define GST_VIDEO_WIDGET_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), GST_TYPE_VIDEO_WIDGET, GstVideoWidgetClass))
+#define GST_IS_VIDEO_WIDGET(obj) (GTK_CHECK_TYPE ((obj), GST_TYPE_VIDEO_WIDGET))
+#define GST_IS_VIDEO_WIDGET_CLASS(obj) (GTK_CHECK_CLASS_TYPE ((klass), GST_TYPE_VIDEO_WIDGET))
+
+typedef struct _GstVideoWidget GstVideoWidget;
+typedef struct _GstVideoWidgetClass GstVideoWidgetClass;
+typedef struct _GstVideoWidgetPrivate GstVideoWidgetPrivate;
+
+struct _GstVideoWidget
+{
+
+ GtkWidget parent;
+
+ GstVideoWidgetPrivate *priv;
+};
+
+struct _GstVideoWidgetClass
+{
+
+ GtkWidgetClass parent_class;
+
+};
+
+GType gst_video_widget_get_type (void);
+
+GtkWidget *gst_video_widget_new (void);
+
+/* Set/Get video source size */
+
+gboolean gst_video_widget_set_source_size (GstVideoWidget * vw,
+ guint width, guint height);
+
+gboolean gst_video_widget_get_source_size (GstVideoWidget * vw,
+ guint * width, guint * height);
+
+/* Set/Get minimum video widget size */
+
+gboolean gst_video_widget_set_minimum_size (GstVideoWidget * vw,
+ gint width, gint height);
+
+gboolean gst_video_widget_get_minimum_size (GstVideoWidget * vw,
+ gint * width, gint * height);
+
+/* Set/Get mouse pointer visible or not */
+
+gboolean gst_video_widget_set_cursor_visible (GstVideoWidget * vw,
+ gboolean visible);
+gboolean gst_video_widget_get_cursor_visible (GstVideoWidget * vw);
+
+/* Set/Get focus on logo or not */
+
+gboolean gst_video_widget_set_logo_focus (GstVideoWidget * vw,
+ gboolean focused);
+gboolean gst_video_widget_get_logo_focus (GstVideoWidget * vw);
+
+/* Set/Get if the widget should catch events over embeded window */
+
+gboolean gst_video_widget_set_event_catcher (GstVideoWidget * vw,
+ gboolean event_catcher);
+gboolean gst_video_widget_get_event_catcher (GstVideoWidget * vw);
+
+/* Set/Get auto resize mode used by the widget */
+
+gboolean gst_video_widget_set_auto_resize (GstVideoWidget * vw,
+ gboolean resize);
+gboolean gst_video_widget_get_auto_resize (GstVideoWidget * vw);
+
+/* Set/Get scale factor used by the widget */
+
+gboolean gst_video_widget_get_scale_override (GstVideoWidget * vw);
+gboolean gst_video_widget_set_scale_override (GstVideoWidget * vw,
+ gboolean override);
+gboolean gst_video_widget_set_scale (GstVideoWidget * vw, gfloat scale);
+gfloat gst_video_widget_get_scale (GstVideoWidget * vw);
+
+GdkWindow *gst_video_widget_get_video_window (GstVideoWidget * vw);
+
+/* Set/Get the GdkPixbuf used for logo display */
+
+void gst_video_widget_set_logo (GstVideoWidget * vw, GdkPixbuf * logo_pixbuf);
+GdkPixbuf *gst_video_widget_get_logo (GstVideoWidget * vw);
+
+/*Force the expose callback*/
+void gst_video_widget_force_expose (GtkWidget * widget, GdkEventExpose * event);
+
+#endif /* __GST_VIDEO_WIDGET_H__ */
diff --git a/libcesarplayer/src/macros.h b/libcesarplayer/src/macros.h
new file mode 100644
index 0000000..7d44dde
--- /dev/null
+++ b/libcesarplayer/src/macros.h
@@ -0,0 +1,39 @@
+/* -*- mode: C; c-file-style: "gnu" -*- */
+/*
+ * Copyright (C) 2006 Peter Johanson <peter peterjohanson com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __MACROS_H__
+#define __MACROS_H__
+
+#if BUILDING_DLL
+# define DLLIMPORT __declspec (dllexport)
+#else /* Not BUILDING_DLL */
+# define DLLIMPORT __declspec (dllimport)
+#endif /* Not BUILDING_DLL */
+
+#ifdef UNUSED
+#elif defined(__GNUC__)
+# define UNUSED(x) UNUSED_ ##x __attribute__((unused))
+#elif defined(__LCLINT__)
+# define UNUSED(x) /* unused@*/ x
+#else
+# define UNUSED(x) x
+#endif
+
+#endif /* __MACROS_H__ */
diff --git a/libcesarplayer/src/main.c b/libcesarplayer/src/main.c
new file mode 100644
index 0000000..a2726fc
--- /dev/null
+++ b/libcesarplayer/src/main.c
@@ -0,0 +1,95 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+ * main.c
+ * Copyright (C) Andoni Morales Alastruey 2008 <ylatuya gmail com>
+ *
+ * main.c 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * main.c 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, see <http://www.gnu.org/licenses/>.
+ */
+
+
+
+
+#include <gtk/gtk.h>
+#include "gst-video-capturer.h"
+#include <stdlib.h>
+#include <unistd.h>
+
+
+static int i = 0;
+static gboolean
+window_state_event (GtkWidget * widget, GdkEventWindowState * event,
+ gpointer gvc)
+{
+ i++;
+ g_print ("%d\n", i);
+ if (i == 3) {
+ gst_video_capturer_rec (GST_VIDEO_CAPTURER (gvc));
+
+ }
+ if (i == 5)
+ gst_video_capturer_stop (GST_VIDEO_CAPTURER (gvc));
+ return TRUE;
+}
+
+GtkWidget *
+create_window (GstVideoCapturer * gvc)
+{
+ GtkWidget *window;
+
+
+
+
+ /* Create a new window */
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title (GTK_WINDOW (window), "Capturer");
+
+ g_signal_connect (G_OBJECT (window), "window-state-event",
+ G_CALLBACK (window_state_event), gvc);
+
+
+ return window;
+}
+
+
+int
+main (int argc, char *argv[])
+{
+ GtkWidget *window;
+ GstVideoCapturer *gvc;
+ GError *error = NULL;
+
+
+ gtk_init (&argc, &argv);
+
+ /*Create GstVideoCapturer */
+ gst_video_capturer_init_backend (&argc, &argv);
+ gvc = gst_video_capturer_new (GVC_USE_TYPE_DEVICE_CAPTURE, &error);
+ //gvc = gst_video_capturer_new (GVC_USE_TYPE_VIDEO_TRANSCODE, &error );
+ //g_object_set(gvc,"input_file","/home/andoni/Escritorio/RC Polo vs CD Complutense.avi",NULL);
+ g_object_set (gvc, "output_file", "/home/andoni/jander.avi", NULL);
+ //gvc = gst_video_capturer_new (GVC_USE_TYPE_TEST, &error );
+
+ window = create_window (gvc);
+
+ gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (gvc));
+ gtk_widget_show (GTK_WIDGET (gvc));
+ gtk_widget_show (window);
+
+
+
+
+ gtk_main ();
+
+ return 0;
+}
diff --git a/libcesarplayer/src/video-utils.c b/libcesarplayer/src/video-utils.c
new file mode 100644
index 0000000..ef8d619
--- /dev/null
+++ b/libcesarplayer/src/video-utils.c
@@ -0,0 +1,236 @@
+
+
+#include "video-utils.h"
+
+#include <glib/gi18n.h>
+#include <libintl.h>
+
+#include <gdk/gdk.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+
+void
+totem_gdk_window_set_invisible_cursor (GdkWindow * window)
+{
+ GdkCursor *cursor;
+#ifdef WIN32
+ cursor = gdk_cursor_new (GDK_X_CURSOR);
+#else
+ cursor = gdk_cursor_new (GDK_BLANK_CURSOR);
+#endif
+ gdk_window_set_cursor (window, cursor);
+ gdk_cursor_unref (cursor);
+}
+
+void
+totem_gdk_window_set_waiting_cursor (GdkWindow * window)
+{
+ GdkCursor *cursor;
+
+ cursor = gdk_cursor_new (GDK_WATCH);
+ gdk_window_set_cursor (window, cursor);
+ gdk_cursor_unref (cursor);
+
+ gdk_flush ();
+}
+
+gboolean
+totem_display_is_local (void)
+{
+ const char *name, *work;
+ int display, screen;
+ gboolean has_hostname;
+
+ name = gdk_display_get_name (gdk_display_get_default ());
+ if (name == NULL)
+ return TRUE;
+
+ work = strstr (name, ":");
+ if (work == NULL)
+ return TRUE;
+
+ has_hostname = (work - name) > 0;
+
+ /* Get to the character after the colon */
+ work++;
+ if (*work == '\0')
+ return TRUE;
+
+ if (sscanf (work, "%d.%d", &display, &screen) != 2)
+ return TRUE;
+
+ if (has_hostname == FALSE)
+ return TRUE;
+
+ if (display < 10)
+ return TRUE;
+
+ return FALSE;
+}
+
+char *
+totem_time_to_string (gint64 msecs)
+{
+ int sec, min, hour, time;
+
+ time = (int) (msecs / 1000);
+ sec = time % 60;
+ time = time - sec;
+ min = (time % (60 * 60)) / 60;
+ time = time - (min * 60);
+ hour = time / (60 * 60);
+
+ if (hour > 0) {
+ /* hour:minutes:seconds */
+ /* Translators: This is a time format, like "9:05:02" for 9
+ * hours, 5 minutes, and 2 seconds. You may change ":" to
+ * the separator that your locale uses or use "%Id" instead
+ * of "%d" if your locale uses localized digits.
+ */
+ return g_strdup_printf (C_ ("long time format", "%d:%02d:%02d"), hour,
+ min, sec);
+ }
+
+ /* minutes:seconds */
+ /* Translators: This is a time format, like "5:02" for 5
+ * minutes and 2 seconds. You may change ":" to the
+ * separator that your locale uses or use "%Id" instead of
+ * "%d" if your locale uses localized digits.
+ */
+ return g_strdup_printf (C_ ("short time format", "%d:%02d"), min, sec);
+}
+
+gint64
+totem_string_to_time (const char *time_string)
+{
+ int sec, min, hour, args;
+
+ args =
+ sscanf (time_string, C_ ("long time format", "%d:%02d:%02d"), &hour, &min,
+ &sec);
+
+ if (args == 3) {
+ /* Parsed all three arguments successfully */
+ return (hour * (60 * 60) + min * 60 + sec) * 1000;
+ } else if (args == 2) {
+ /* Only parsed the first two arguments; treat hour and min as min and sec, respectively */
+ return (hour * 60 + min) * 1000;
+ } else if (args == 1) {
+ /* Only parsed the first argument; treat hour as sec */
+ return hour * 1000;
+ } else {
+ /* Error! */
+ return -1;
+ }
+}
+
+char *
+totem_time_to_string_text (gint64 msecs)
+{
+ char *secs, *mins, *hours, *string;
+ int sec, min, hour, time;
+
+ time = (int) (msecs / 1000);
+ sec = time % 60;
+ time = time - sec;
+ min = (time % (60 * 60)) / 60;
+ time = time - (min * 60);
+ hour = time / (60 * 60);
+
+ hours = g_strdup_printf (ngettext ("%d hour", "%d hours", hour), hour);
+
+ mins = g_strdup_printf (ngettext ("%d minute", "%d minutes", min), min);
+
+ secs = g_strdup_printf (ngettext ("%d second", "%d seconds", sec), sec);
+
+ if (hour > 0) {
+ /* hour:minutes:seconds */
+ string = g_strdup_printf (_("%s %s %s"), hours, mins, secs);
+ } else if (min > 0) {
+ /* minutes:seconds */
+ string = g_strdup_printf (_("%s %s"), mins, secs);
+ } else if (sec > 0) {
+ /* seconds */
+ string = g_strdup_printf (_("%s"), secs);
+ } else {
+ /* 0 seconds */
+ string = g_strdup (_("0 seconds"));
+ }
+
+ g_free (hours);
+ g_free (mins);
+ g_free (secs);
+
+ return string;
+}
+
+typedef struct _TotemPrefSize
+{
+ gint width, height;
+ gulong sig_id;
+} TotemPrefSize;
+
+static gboolean
+cb_unset_size (gpointer data)
+{
+ GtkWidget *widget = data;
+
+ gtk_widget_queue_resize_no_redraw (widget);
+
+ return FALSE;
+}
+
+static void
+cb_set_preferred_size (GtkWidget * widget, GtkRequisition * req, gpointer data)
+{
+ TotemPrefSize *size = data;
+
+ req->width = size->width;
+ req->height = size->height;
+
+ g_signal_handler_disconnect (widget, size->sig_id);
+ g_free (size);
+ g_idle_add (cb_unset_size, widget);
+}
+
+void
+totem_widget_set_preferred_size (GtkWidget * widget, gint width, gint height)
+{
+ TotemPrefSize *size = g_new (TotemPrefSize, 1);
+
+ size->width = width;
+ size->height = height;
+ size->sig_id = g_signal_connect (widget, "size-request",
+ G_CALLBACK (cb_set_preferred_size), size);
+
+ gtk_widget_queue_resize (widget);
+}
+
+gboolean
+totem_ratio_fits_screen (GdkWindow * video_window, int video_width,
+ int video_height, gfloat ratio)
+{
+ GdkRectangle fullscreen_rect;
+ int new_w, new_h;
+ GdkScreen *screen;
+
+ if (video_width <= 0 || video_height <= 0)
+ return TRUE;
+
+ new_w = video_width * ratio;
+ new_h = video_height * ratio;
+
+ screen = gdk_drawable_get_screen (GDK_DRAWABLE (video_window));
+ gdk_screen_get_monitor_geometry (screen,
+ gdk_screen_get_monitor_at_window
+ (screen, video_window), &fullscreen_rect);
+
+ if (new_w > (fullscreen_rect.width - 128) ||
+ new_h > (fullscreen_rect.height - 128)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/libcesarplayer/src/video-utils.h b/libcesarplayer/src/video-utils.h
new file mode 100644
index 0000000..db17113
--- /dev/null
+++ b/libcesarplayer/src/video-utils.h
@@ -0,0 +1,19 @@
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#define TOTEM_OBJECT_HAS_SIGNAL(obj, name) (g_signal_lookup (name, g_type_from_name (G_OBJECT_TYPE_NAME (obj))) != 0)
+
+void totem_gdk_window_set_invisible_cursor (GdkWindow * window);
+void totem_gdk_window_set_waiting_cursor (GdkWindow * window);
+
+gboolean totem_display_is_local (void);
+
+char *totem_time_to_string (gint64 msecs);
+gint64 totem_string_to_time (const char *time_string);
+char *totem_time_to_string_text (gint64 msecs);
+
+void totem_widget_set_preferred_size (GtkWidget * widget, gint width,
+ gint height);
+gboolean totem_ratio_fits_screen (GdkWindow * window, int video_width,
+ int video_height, gfloat ratio);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]