[chronojump] Add libcesarplayer to chronojump



commit e2a974acda24b737efbf12de4f0eab91b6ca9c82
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 = &GTK_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),
+          &GTK_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, &current);
+  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 = &GTK_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, &micro, &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]