[clutter-gst/wip/separate-logic: 2/2] video-texture: Separate out the playbin2 logic.



commit 76c6c8694a1de567168625550329b7ee948e096e
Author: Damien Lespiau <damien lespiau intel com>
Date:   Thu Aug 18 14:25:04 2011 +0100

    video-texture: Separate out the playbin2 logic.
    
    The end goal is to be able to reuse the logic around playbin2 in a
    player that has nothing to do with Clutter. For this we remove all the
    code dealing with playbin2 from ClutterGstVideoTexture and we put it
    into a ClutterGstPlayer interface (internal atm).

 clutter-gst/Makefile.am                 |    2 +
 clutter-gst/clutter-gst-player.c        | 1858 +++++++++++++++++++++++++++
 clutter-gst/clutter-gst-player.h        |  100 ++
 clutter-gst/clutter-gst-video-texture.c | 2098 +++++--------------------------
 4 files changed, 2244 insertions(+), 1814 deletions(-)
---
diff --git a/clutter-gst/Makefile.am b/clutter-gst/Makefile.am
index e73ce3e..8424d5d 100644
--- a/clutter-gst/Makefile.am
+++ b/clutter-gst/Makefile.am
@@ -32,6 +32,7 @@ source_h = 					\
 source_priv_h =					\
 	$(srcdir)/clutter-gst-debug.h		\
 	$(srcdir)/clutter-gst-marshal.h		\
+	$(srcdir)/clutter-gst-player.h		\
 	$(srcdir)/clutter-gst-private.h		\
 	$(srcdir)/clutter-gst-shaders.h		\
 	$(NULL)
@@ -39,6 +40,7 @@ source_priv_h =					\
 source_c = 					\
 	$(srcdir)/clutter-gst-debug.c		\
 	$(srcdir)/clutter-gst-marshal.c		\
+	$(srcdir)/clutter-gst-player.c		\
 	$(srcdir)/clutter-gst-video-sink.c	\
 	$(srcdir)/clutter-gst-video-texture.c	\
         $(srcdir)/clutter-gst-util.c		\
diff --git a/clutter-gst/clutter-gst-player.c b/clutter-gst/clutter-gst-player.c
new file mode 100644
index 0000000..939ea01
--- /dev/null
+++ b/clutter-gst/clutter-gst-player.c
@@ -0,0 +1,1858 @@
+/*
+ * Clutter-GStreamer.
+ *
+ * GStreamer integration library for Clutter.
+ *
+ * clutter-gst-player.c - Wrap some convenience functions around playbin2
+ *
+ * Authored By Damien Lespiau <damien lespiau intel com>
+ *
+ * Copyright (C) 2011 Intel Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include <gst/video/video.h>
+#include <gst/tag/tag.h>
+#include <gst/interfaces/streamvolume.h>
+
+#include "clutter-gst-debug.h"
+#include "clutter-gst-enum-types.h"
+#include "clutter-gst-marshal.h"
+#include "clutter-gst-player.h"
+#include "clutter-gst-private.h"
+
+typedef ClutterGstPlayerIface       ClutterGstPlayerInterface;
+
+G_DEFINE_INTERFACE_WITH_CODE (ClutterGstPlayer, clutter_gst_player, G_TYPE_OBJECT,
+                              g_type_interface_add_prerequisite (g_define_type_id,
+                                                                 CLUTTER_TYPE_MEDIA))
+
+#define PLAYER_GET_PRIVATE(player)                              \
+  (g_object_get_qdata (G_OBJECT (player),                       \
+                       clutter_gst_player_private_quark))
+#define PLAYER_SET_PRIVATE(player,private)                      \
+  (g_object_set_qdata (G_OBJECT (player),                       \
+                       clutter_gst_player_private_quark,        \
+                       private))
+
+#define PLAYER_GET_CLASS_PRIVATE(player)                     \
+  (g_type_get_qdata (G_OBJECT_TYPE (player),                 \
+                     clutter_gst_player_class_quark))
+
+/* idle timeouts (in ms) */
+#define TICK_TIMEOUT        500
+#define BUFFERING_TIMEOUT   250
+
+enum
+{
+  DOWNLOAD_BUFFERING,
+
+  LAST_SIGNAL
+};
+
+enum
+{
+  PROP_0,
+
+  /* ClutterMedia properties */
+  PROP_URI,
+  PROP_PLAYING,
+  PROP_PROGRESS,
+  PROP_SUBTITLE_URI,
+  PROP_SUBTITLE_FONT_NAME,
+  PROP_AUDIO_VOLUME,
+  PROP_CAN_SEEK,
+  PROP_BUFFER_FILL,
+  PROP_DURATION,
+
+  /* ClutterGstPlayer properties */
+  PROP_IDLE,
+  PROP_USER_AGENT,
+  PROP_SEEK_FLAGS,
+  PROP_AUDIO_STREAMS,
+  PROP_AUDIO_STREAM
+};
+
+struct _ClutterGstPlayerIfacePrivate
+{
+  void       (*set_property)		(GObject        *object,
+                                         guint           property_id,
+                                         const GValue   *value,
+                                         GParamSpec     *pspec);
+  void       (*get_property)		(GObject        *object,
+                                         guint           property_id,
+                                         GValue         *value,
+                                         GParamSpec     *pspec);
+  void       (*dispose)			(GObject        *object);
+};
+
+typedef struct _ClutterGstPlayerPrivate ClutterGstPlayerPrivate;
+
+/* Elements don't expose header files */
+typedef enum {
+  GST_PLAY_FLAG_VIDEO         = (1 << 0),
+  GST_PLAY_FLAG_AUDIO         = (1 << 1),
+  GST_PLAY_FLAG_TEXT          = (1 << 2),
+  GST_PLAY_FLAG_VIS           = (1 << 3),
+  GST_PLAY_FLAG_SOFT_VOLUME   = (1 << 4),
+  GST_PLAY_FLAG_NATIVE_AUDIO  = (1 << 5),
+  GST_PLAY_FLAG_NATIVE_VIDEO  = (1 << 6),
+  GST_PLAY_FLAG_DOWNLOAD      = (1 << 7),
+  GST_PLAY_FLAG_BUFFERING     = (1 << 8),
+  GST_PLAY_FLAG_DEINTERLACE   = (1 << 9)
+} GstPlayFlags;
+
+struct _ClutterGstPlayerPrivate
+{
+  GObject parent;
+
+  GstElement *pipeline;
+  GstBus *bus;
+
+  gchar *uri;
+
+  guint is_idle : 1;
+  guint can_seek : 1;
+  guint in_seek : 1;
+  guint is_changing_uri : 1;
+  guint in_error : 1;
+  guint in_eos : 1;
+  guint in_download_buffering : 1;
+  /* when in progressive download, we use the buffer-fill property to signal
+   * that we have enough data to play the stream. This flag allows to send
+   * the notify that buffer-fill is 1.0 only once */
+  guint virtual_stream_buffer_signalled : 1;
+
+  gdouble stacked_progress;
+
+  gdouble target_progress;
+  GstState target_state;
+
+  guint tick_timeout_id;
+  guint buffering_timeout_id;
+
+  /* This is a cubic volume, suitable for use in a UI cf. StreamVolume doc */
+  gdouble volume;
+
+  gdouble buffer_fill;
+  gdouble duration;
+  gchar *font_name;
+  gchar *user_agent;
+
+  GstSeekFlags seek_flags;    /* flags for the seek in set_progress(); */
+
+  GstElement *download_buffering_element;
+
+  GList *audio_streams;
+};
+
+static GQuark clutter_gst_player_private_quark = 0;
+static GQuark clutter_gst_player_class_quark = 0;
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+static gboolean player_buffering_timeout (gpointer data);
+
+/* Logic */
+
+#ifdef CLUTTER_GST_ENABLE_DEBUG
+gchar *
+list_to_string (GList *list)
+{
+  GString *string;
+  GList *l;
+  gint n, i;
+
+  if (!list)
+    return g_strdup ("<empty list>");
+
+  string = g_string_new (NULL);
+  n = g_list_length (list);
+  for (i = 0, l = list; i < n - 1; i++, l = g_list_next (l))
+    g_string_append_printf (string, "%s, ", (gchar *) l->data);
+
+  g_string_append_printf (string, "%s", (gchar *) l->data);
+
+  return g_string_free (string, FALSE);
+}
+#endif
+
+static const gchar *
+gst_state_to_string (GstState state)
+{
+  switch (state)
+    {
+    case GST_STATE_VOID_PENDING:
+      return "pending";
+    case GST_STATE_NULL:
+      return "null";
+    case GST_STATE_READY:
+      return "ready";
+    case GST_STATE_PAUSED:
+      return "paused";
+    case GST_STATE_PLAYING:
+      return "playing";
+    }
+
+  return "Unknown state";
+}
+
+static void
+free_string_list (GList **listp)
+{
+  GList *l;
+
+  l = *listp;
+  while (l)
+    {
+      g_free (l->data);
+      l = g_list_delete_link (l, l);
+    }
+
+  *listp = NULL;
+}
+
+static gboolean
+tick_timeout (gpointer data)
+{
+  GObject *player = data;
+
+  g_object_notify (player, "progress");
+
+  return TRUE;
+}
+
+static void
+player_set_user_agent (ClutterGstPlayer *player,
+                       const gchar      *user_agent)
+{
+  ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
+  GstElement *source;
+  GParamSpec *pspec;
+
+  if (user_agent == NULL)
+    return;
+
+  g_object_get (priv->pipeline, "source", &source, NULL);
+  if (source == NULL)
+    return;
+
+  pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (source),
+                                        "user-agent");
+  if (pspec == NULL)
+    return;
+
+  CLUTTER_GST_NOTE (MEDIA, "setting user agent: %s", user_agent);
+
+  g_object_set (source, "user-agent", user_agent, NULL);
+}
+
+static void
+autoload_subtitle (ClutterGstPlayer *player,
+                   const gchar      *uri)
+{
+  ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
+  gchar *path, *dot, *subtitle_path;
+  GFile *video;
+  guint i;
+
+  static const char subtitles_extensions[][4] =
+    {
+      "sub", "SUB",
+      "srt", "SRT",
+      "smi", "SMI",
+      "ssa", "SSA",
+      "ass", "ASS",
+      "asc", "ASC"
+    };
+
+  /* do not try to look for subtitle files if the video file is not mounted
+   * locally */
+  if (!g_str_has_prefix (uri, "file://"))
+    return;
+
+  /* Retrieve the absolute path of the video file */
+  video = g_file_new_for_uri (uri);
+  path = g_file_get_path (video);
+  g_object_unref (video);
+  if (path == NULL)
+    return;
+
+  /* Put a '\0' after the dot of the extension */
+  dot = strrchr (path, '.');
+  if (dot == NULL) {
+    g_free (path);
+    return;
+  }
+  *++dot = '\0';
+
+  /* we can't use path as the temporary buffer for the paths of the potential
+   * subtitle files as we may not have enough room there */
+  subtitle_path = g_malloc (strlen (path) + 1 + 4);
+  strcpy (subtitle_path, path);
+
+  /* reuse dot to point to the first byte of the extension of subtitle_path */
+  dot = subtitle_path + (dot - path);
+
+  for (i = 0; i < G_N_ELEMENTS (subtitles_extensions); i++)
+    {
+      GFile *candidate;
+
+      memcpy (dot, subtitles_extensions[i], 4);
+      candidate = g_file_new_for_path (subtitle_path);
+      if (g_file_query_exists (candidate, NULL))
+        {
+          gchar *suburi;
+
+          suburi = g_file_get_uri (candidate);
+
+          CLUTTER_GST_NOTE (MEDIA, "found subtitle: %s", suburi);
+
+          g_object_set (priv->pipeline, "suburi", suburi, NULL);
+          g_free (suburi);
+
+          g_object_unref (candidate);
+          break;
+        }
+
+      g_object_unref (candidate);
+    }
+
+  g_free (path);
+  g_free (subtitle_path);
+}
+
+static void
+set_subtitle_uri (ClutterGstPlayer *player,
+                  const gchar      *uri)
+{
+  ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
+
+  if (!priv->pipeline)
+    return;
+
+  CLUTTER_GST_NOTE (MEDIA, "setting subtitle URI: %s", uri);
+
+  g_object_set (priv->pipeline, "suburi", uri, NULL);
+}
+
+static void
+player_configure_buffering_timeout (ClutterGstPlayer *player,
+                                    guint             ms)
+{
+  ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
+
+  if (priv->buffering_timeout_id)
+    {
+      g_source_remove (priv->buffering_timeout_id);
+      priv->buffering_timeout_id = 0;
+    }
+
+  if (ms)
+    {
+      priv->buffering_timeout_id =
+        g_timeout_add (ms, player_buffering_timeout, player);
+    }
+}
+
+static void
+player_clear_download_buffering (ClutterGstPlayer *player)
+{
+  ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
+
+  if (priv->download_buffering_element)
+    {
+      g_object_unref (priv->download_buffering_element);
+      priv->download_buffering_element = NULL;
+    }
+  player_configure_buffering_timeout (player, 0);
+  priv->in_download_buffering = FALSE;
+  priv->virtual_stream_buffer_signalled = 0;
+}
+
+static void
+set_uri (ClutterGstPlayer *player,
+         const gchar      *uri)
+{
+  ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
+  GObject *self = G_OBJECT (player);
+  GstState state, pending;
+
+  CLUTTER_GST_NOTE (MEDIA, "setting uri %s", uri);
+
+  if (!priv->pipeline)
+    return;
+
+  g_free (priv->uri);
+
+  priv->in_eos = FALSE;
+  priv->in_error = FALSE;
+
+  if (uri)
+    {
+      priv->uri = g_strdup (uri);
+
+      /* Ensure the tick timeout is installed.
+       *
+       * We also have it installed in PAUSED state, because
+       * seeks etc may have a delayed effect on the position.
+       */
+      if (priv->tick_timeout_id == 0)
+        {
+          priv->tick_timeout_id =
+            g_timeout_add (TICK_TIMEOUT, tick_timeout, self);
+        }
+
+      /* try to load subtitles based on the uri of the file */
+      set_subtitle_uri (player, NULL);
+      autoload_subtitle (player, uri);
+
+      /* reset the states of download buffering */
+      player_clear_download_buffering (player);
+    }
+  else
+    {
+      priv->uri = NULL;
+
+      set_subtitle_uri (player, NULL);
+
+      if (priv->tick_timeout_id)
+	{
+	  g_source_remove (priv->tick_timeout_id);
+	  priv->tick_timeout_id = 0;
+	}
+
+      if (priv->buffering_timeout_id)
+        {
+          g_source_remove (priv->buffering_timeout_id);
+          priv->buffering_timeout_id = 0;
+        }
+
+      if (priv->download_buffering_element)
+        {
+          g_object_unref (priv->download_buffering_element);
+          priv->download_buffering_element = NULL;
+        }
+
+    }
+
+  priv->can_seek = FALSE;
+  priv->duration = 0.0;
+  priv->stacked_progress = 0.0;
+  priv->target_progress = 0.0;
+
+  CLUTTER_GST_NOTE (MEDIA, "setting URI: %s", uri);
+
+  if (uri)
+    {
+      gst_element_get_state (priv->pipeline, &state, &pending, 0);
+      if (pending)
+        state = pending;
+
+      gst_element_set_state (priv->pipeline, GST_STATE_NULL);
+
+      g_object_set (priv->pipeline, "uri", uri, NULL);
+
+      gst_element_set_state (priv->pipeline, state);
+
+      priv->is_changing_uri = TRUE;
+    }
+  else
+    {
+      priv->is_idle = TRUE;
+      gst_element_set_state (priv->pipeline, GST_STATE_NULL);
+      g_object_notify (G_OBJECT (player), "idle");
+    }
+
+  /*
+   * Emit notifications for all these to make sure UI is not showing
+   * any properties of the old URI.
+   */
+  g_object_notify (self, "uri");
+  g_object_notify (self, "can-seek");
+  g_object_notify (self, "duration");
+  g_object_notify (self, "progress");
+
+  free_string_list (&priv->audio_streams);
+  CLUTTER_GST_NOTE (AUDIO_STREAM, "audio-streams changed");
+  g_object_notify (self, "audio-streams");
+}
+
+static void
+set_playing (ClutterGstPlayer *player,
+             gboolean          playing)
+{
+  ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
+
+  if (!priv->pipeline)
+    return;
+
+  CLUTTER_GST_NOTE (MEDIA, "set playing: %d", playing);
+
+  priv->in_error = FALSE;
+  priv->in_eos = FALSE;
+
+  priv->target_state = playing ? GST_STATE_PLAYING : GST_STATE_PAUSED;
+
+  if (priv->uri)
+    {
+      priv->in_seek = FALSE;
+
+      gst_element_set_state (priv->pipeline, priv->target_state);
+    }
+  else
+    {
+      if (playing)
+       g_warning ("Unable to start playing: no URI is set");
+    }
+
+  g_object_notify (G_OBJECT (player), "playing");
+  g_object_notify (G_OBJECT (player), "progress");
+}
+
+static gboolean
+get_playing (ClutterGstPlayer *player)
+{
+  ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
+  GstState state, pending;
+  gboolean playing;
+
+  if (!priv->pipeline)
+    return FALSE;
+
+  gst_element_get_state (priv->pipeline, &state, &pending, 0);
+
+  if (pending)
+    playing = (pending == GST_STATE_PLAYING);
+  else
+    playing = (state == GST_STATE_PLAYING);
+
+  CLUTTER_GST_NOTE (MEDIA, "get playing: %d", playing);
+
+  return playing;
+}
+
+static void
+set_progress (ClutterGstPlayer *player,
+              gdouble           progress)
+{
+  ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
+  GstQuery *duration_q;
+  gint64 position;
+
+  if (!priv->pipeline)
+    return;
+
+  CLUTTER_GST_NOTE (MEDIA, "set progress: %.02f", progress);
+
+  priv->in_eos = FALSE;
+  priv->target_progress = progress;
+
+  if (priv->in_download_buffering)
+    {
+      /* we clear the virtual_stream_buffer_signalled flag as it's likely we
+       * need to buffer again */
+      priv->virtual_stream_buffer_signalled = 0;
+    }
+
+  if (priv->in_seek || priv->is_idle || priv->is_changing_uri)
+    {
+      /* We can't seek right now, let's save the position where we
+         want to seek and do that later. */
+      CLUTTER_GST_NOTE (MEDIA,
+                        "already seeking/idleing. stacking progress point.");
+      priv->stacked_progress = progress;
+      return;
+    }
+
+  duration_q = gst_query_new_duration (GST_FORMAT_TIME);
+
+  if (gst_element_query (priv->pipeline, duration_q))
+    {
+      gint64 duration = 0;
+
+      gst_query_parse_duration (duration_q, NULL, &duration);
+
+      position = progress * duration;
+    }
+  else
+    position = 0;
+
+  gst_query_unref (duration_q);
+
+  gst_element_seek (priv->pipeline,
+		    1.0,
+		    GST_FORMAT_TIME,
+		    GST_SEEK_FLAG_FLUSH | priv->seek_flags,
+		    GST_SEEK_TYPE_SET,
+		    position,
+		    GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
+
+  priv->in_seek = TRUE;
+  priv->stacked_progress = 0.0;
+
+  CLUTTER_GST_NOTE (MEDIA, "set progress (seeked): %.02f", progress);
+}
+
+static gdouble
+get_progress (ClutterGstPlayer *player)
+{
+  ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
+  GstQuery *position_q, *duration_q;
+  gdouble progress;
+
+  if (!priv->pipeline)
+    return 0.0;
+
+  /* when hitting an error or after an EOS, playbin2 has some weird values when
+   * querying the duration and progress. We default to 0.0 on error and 1.0 on
+   * EOS */
+  if (priv->in_error)
+    {
+      CLUTTER_GST_NOTE (MEDIA, "get progress (error): 0.0");
+      return 0.0;
+    }
+
+  if (priv->in_eos)
+    {
+      CLUTTER_GST_NOTE (MEDIA, "get progress (eos): 1.0");
+      return 1.0;
+    }
+
+  /* When seeking, the progress returned by playbin2 is 0.0. We want that to be
+   * the last known position instead as returning 0.0 will have some ugly
+   * effects, say on a progress bar getting updated from the progress tick. */
+  if (priv->in_seek || priv->is_changing_uri)
+    {
+      CLUTTER_GST_NOTE (MEDIA, "get progress (target): %.02f",
+                        priv->target_progress);
+      return priv->target_progress;
+    }
+
+  position_q = gst_query_new_position (GST_FORMAT_TIME);
+  duration_q = gst_query_new_duration (GST_FORMAT_TIME);
+
+  if (gst_element_query (priv->pipeline, position_q) &&
+      gst_element_query (priv->pipeline, duration_q))
+    {
+      gint64 position, duration;
+
+      position = duration = 0;
+
+      gst_query_parse_position (position_q, NULL, &position);
+      gst_query_parse_duration (duration_q, NULL, &duration);
+
+      progress = CLAMP ((gdouble) position / (gdouble) duration, 0.0, 1.0);
+    }
+  else
+    progress = 0.0;
+
+  gst_query_unref (position_q);
+  gst_query_unref (duration_q);
+
+  CLUTTER_GST_NOTE (MEDIA, "get progress (pipeline): %.02f", progress);
+
+  return progress;
+}
+
+static void
+set_subtitle_font_name (ClutterGstPlayer *player,
+                        const gchar      *font_name)
+{
+  ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
+
+  if (!priv->pipeline)
+    return;
+
+  CLUTTER_GST_NOTE (MEDIA, "setting subtitle font to %s", font_name);
+
+  g_free (priv->font_name);
+  priv->font_name = g_strdup (font_name);
+  g_object_set (priv->pipeline, "subtitle-font-desc", font_name, NULL);
+}
+
+static void
+set_audio_volume (ClutterGstPlayer *player,
+                  gdouble           volume)
+{
+  ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
+
+    if (!priv->pipeline)
+      return;
+
+  CLUTTER_GST_NOTE (MEDIA, "set volume: %.02f", volume);
+
+  volume = CLAMP (volume, 0.0, 1.0);
+  gst_stream_volume_set_volume (GST_STREAM_VOLUME (priv->pipeline),
+				GST_STREAM_VOLUME_FORMAT_CUBIC,
+				volume);
+  g_object_notify (G_OBJECT (player), "audio-volume");
+}
+
+static gdouble
+get_audio_volume (ClutterGstPlayer *player)
+{
+  ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
+
+  if (!priv->pipeline)
+    return 0.0;
+
+  CLUTTER_GST_NOTE (MEDIA, "get volume: %.02f", priv->volume);
+
+  return priv->volume;
+}
+
+static gboolean
+player_buffering_timeout (gpointer data)
+{
+  ClutterGstPlayer *player = (ClutterGstPlayer *) data;
+  ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
+  gdouble start_d, stop_d, seconds_buffered;
+  gint64 start, stop, left;
+  GstState current_state;
+  GstElement *element;
+  GstQuery *query;
+  gboolean res;
+
+  element = priv->download_buffering_element;
+  if (element == NULL)
+    element = priv->pipeline;
+
+  /* queue2 only knows about _PERCENT and _BYTES */
+  query = gst_query_new_buffering (GST_FORMAT_PERCENT);
+  res = gst_element_query (element, query);
+
+  if (res == FALSE)
+    {
+      priv->buffering_timeout_id = 0;
+      player_clear_download_buffering (player);
+      return FALSE;
+    }
+
+  /* signal the current range */
+  gst_query_parse_buffering_stats (query, NULL, NULL, NULL, &left);
+  gst_query_parse_buffering_range (query, NULL, &start, &stop, NULL);
+
+  CLUTTER_GST_NOTE (BUFFERING,
+                    "start %" G_GINT64_FORMAT ", stop %" G_GINT64_FORMAT
+                    ", buffering left %" G_GINT64_FORMAT, start, stop, left);
+
+  start_d = (gdouble) start / GST_FORMAT_PERCENT_MAX;
+  stop_d = (gdouble) stop / GST_FORMAT_PERCENT_MAX;
+
+  g_signal_emit (player, signals[DOWNLOAD_BUFFERING], 0, start_d, stop_d);
+
+  /* handle the "virtual stream buffer" and the associated pipeline state.
+   * We pause the pipeline until 2s of content is buffered. With the current
+   * implementation of queue2, start is always 0, so even when we seek in
+   * the stream the start position of the download-buffering signal is
+   * always 0.0. FIXME: look at gst_query_parse_nth_buffering_range () */
+  seconds_buffered = priv->duration * (stop_d - start_d);
+  priv->buffer_fill = seconds_buffered / 2.0;
+  priv->buffer_fill = CLAMP (priv->buffer_fill, 0.0, 1.0);
+
+  if (priv->buffer_fill != 1.0 || !priv->virtual_stream_buffer_signalled)
+    {
+      CLUTTER_GST_NOTE (BUFFERING, "buffer holds %0.2fs of data, buffer-fill "
+                        "is %.02f", seconds_buffered, priv->buffer_fill);
+
+      g_object_notify (G_OBJECT (player), "buffer-fill");
+
+      if (priv->buffer_fill == 1.0)
+        priv->virtual_stream_buffer_signalled = 1;
+    }
+
+  gst_element_get_state (priv->pipeline, &current_state, NULL, 0);
+  if (priv->buffer_fill < 1.0)
+    {
+      if (current_state != GST_STATE_PAUSED)
+        {
+          CLUTTER_GST_NOTE (BUFFERING, "pausing the pipeline");
+          gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
+        }
+    }
+  else
+    {
+      if (current_state != priv->target_state)
+        {
+          CLUTTER_GST_NOTE (BUFFERING, "restoring the pipeline");
+          gst_element_set_state (priv->pipeline, priv->target_state);
+        }
+    }
+
+  /* the file has finished downloading */
+  if (left == G_GINT64_CONSTANT (0))
+    {
+      priv->buffering_timeout_id = 0;
+
+      player_clear_download_buffering (player);
+      gst_query_unref (query);
+      return FALSE;
+    }
+
+  gst_query_unref (query);
+  return TRUE;
+}
+
+static void
+bus_message_error_cb (GstBus           *bus,
+                      GstMessage       *message,
+                      ClutterGstPlayer *player)
+{
+  ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
+
+  gst_element_set_state (priv->pipeline, GST_STATE_NULL);
+}
+
+static void
+bus_message_eos_cb (GstBus           *bus,
+                    GstMessage       *message,
+                    ClutterGstPlayer *player)
+{
+  ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
+
+  gst_element_set_state (priv->pipeline, GST_STATE_READY);
+  priv->in_eos = TRUE;
+}
+
+static void
+bus_message_buffering_cb (GstBus           *bus,
+                          GstMessage       *message,
+                          ClutterGstPlayer *player)
+{
+  ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
+  GstBufferingMode mode;
+  GstState current_state;
+  gint buffer_percent;
+
+  gst_message_parse_buffering_stats (message, &mode, NULL, NULL, NULL);
+
+  if (mode != GST_BUFFERING_DOWNLOAD)
+    priv->in_download_buffering = FALSE;
+
+  switch (mode)
+    {
+    case GST_BUFFERING_STREAM:
+      gst_message_parse_buffering (message, &buffer_percent);
+      priv->buffer_fill = CLAMP ((gdouble) buffer_percent / 100.0, 0.0, 1.0);
+
+      CLUTTER_GST_NOTE (BUFFERING, "buffer-fill: %.02f", priv->buffer_fill);
+
+      /* The playbin2 documentation says that we need to pause the pipeline
+       * when there's not enough data yet. We try to limit the calls to
+       * gst_element_set_state() */
+      gst_element_get_state (priv->pipeline, &current_state, NULL, 0);
+
+      if (priv->buffer_fill < 1.0)
+        {
+          if (current_state != GST_STATE_PAUSED)
+            {
+              CLUTTER_GST_NOTE (BUFFERING, "pausing the pipeline");
+              gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
+            }
+        }
+      else
+        {
+          if (current_state != priv->target_state)
+            {
+              CLUTTER_GST_NOTE (BUFFERING, "restoring the pipeline");
+              gst_element_set_state (priv->pipeline, priv->target_state);
+            }
+        }
+
+      g_object_notify (G_OBJECT (player), "buffer-fill");
+      break;
+
+    case GST_BUFFERING_DOWNLOAD:
+      /* we rate limit the messages from GStreamer for a usage in a UI (we
+       * don't want *that* many updates). This is done by installing an idle
+       * handler querying the buffer range and sending a signal from there */
+
+      if (priv->in_download_buffering)
+        break;
+
+      /* install the querying idle handler the first time we receive a download
+       * buffering message */
+      player_configure_buffering_timeout (player, BUFFERING_TIMEOUT);
+
+      /* pause the stream. the idle timeout will set the target state when
+       * having received enough data. We'll use buffer_fill as a "virtual
+       * stream buffer" to signal the application we're buffering until we
+       * can play back from the downloaded stream. */
+      gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
+      priv->buffer_fill = 0.0;
+      g_object_notify (G_OBJECT (player), "buffer-fill");
+
+      priv->download_buffering_element = g_object_ref (message->src);
+      priv->in_download_buffering = TRUE;
+      priv->virtual_stream_buffer_signalled = 0;
+      break;
+
+    case GST_BUFFERING_TIMESHIFT:
+    case GST_BUFFERING_LIVE:
+    default:
+      g_warning ("Buffering mode %d not handled", mode);
+      break;
+    }
+}
+
+static void
+on_source_changed (GstElement       *pipeline,
+                   GParamSpec       *pspec,
+                   ClutterGstPlayer *player)
+{
+  ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
+
+  player_set_user_agent (player, priv->user_agent);
+}
+
+static void
+query_duration (ClutterGstPlayer *player)
+{
+  ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
+  gboolean success;
+  GstFormat format = GST_FORMAT_TIME;
+  gint64 duration;
+  gdouble new_duration, difference;
+
+  success = gst_element_query_duration (priv->pipeline,
+                                        &format,
+                                        &duration);
+  if (G_UNLIKELY (success != TRUE))
+    return;
+
+  new_duration = (gdouble) duration / GST_SECOND;
+
+  /* while we store the new duration if it sligthly changes, the duration
+   * signal is sent only if the new duration is at least one second different
+   * from the old one (as the duration signal is mainly used to update the
+   * time displayed in a UI */
+  difference = ABS (priv->duration - new_duration);
+  if (difference > 1e-3)
+    {
+      CLUTTER_GST_NOTE (MEDIA, "duration: %.02f", new_duration);
+      priv->duration = new_duration;
+
+      if (difference > 1.0)
+        g_object_notify (G_OBJECT (player), "duration");
+    }
+}
+
+static void
+bus_message_duration_cb (GstBus           *bus,
+                         GstMessage       *message,
+                         ClutterGstPlayer *player)
+{
+  gint64 duration;
+
+  /* GstElements send a duration message on the bus with GST_CLOCK_TIME_NONE
+   * as duration to signal a new duration */
+  gst_message_parse_duration (message, NULL, &duration);
+  if (G_UNLIKELY (duration != GST_CLOCK_TIME_NONE))
+    return;
+
+  query_duration (player);
+}
+
+static void
+bus_message_state_change_cb (GstBus           *bus,
+                             GstMessage       *message,
+                             ClutterGstPlayer *player)
+{
+  ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
+  GstState old_state, new_state;
+  gpointer src;
+
+  src = GST_MESSAGE_SRC (message);
+  if (src != priv->pipeline)
+    return;
+
+  gst_message_parse_state_changed (message, &old_state, &new_state, NULL);
+
+  CLUTTER_GST_NOTE (MEDIA, "state change:  %s -> %s",
+                    gst_state_to_string (old_state),
+                    gst_state_to_string (new_state));
+
+  if (old_state == new_state)
+    return;
+
+  if (old_state == GST_STATE_READY &&
+      new_state == GST_STATE_PAUSED)
+    {
+      GstQuery *query;
+
+      /* Determine whether we can seek */
+      query = gst_query_new_seeking (GST_FORMAT_TIME);
+
+      if (gst_element_query (priv->pipeline, query))
+        {
+          gboolean can_seek = FALSE;
+
+          gst_query_parse_seeking (query, NULL, &can_seek,
+                                   NULL,
+                                   NULL);
+
+          priv->can_seek = (can_seek == TRUE) ? TRUE : FALSE;
+        }
+      else
+        {
+	  /* could not query for ability to seek by querying the
+           * pipeline; let's crudely try by using the URI
+	   */
+	  if (priv->uri && g_str_has_prefix (priv->uri, "http://";))
+            priv->can_seek = FALSE;
+          else
+            priv->can_seek = TRUE;
+	}
+
+      gst_query_unref (query);
+
+      CLUTTER_GST_NOTE (MEDIA, "can-seek: %d", priv->can_seek);
+
+      g_object_notify (G_OBJECT (player), "can-seek");
+
+      query_duration (player);
+    }
+
+  /* is_idle controls the drawing with the idle material */
+  if (new_state == GST_STATE_NULL)
+    {
+      priv->is_idle = TRUE;
+      g_object_notify (G_OBJECT (player), "idle");
+    }
+  else if (new_state == GST_STATE_PLAYING)
+    {
+      priv->is_idle = FALSE;
+      priv->is_changing_uri = FALSE;
+      g_object_notify (G_OBJECT (player), "idle");
+    }
+
+  if (!priv->is_idle)
+    {
+      if (priv->stacked_progress)
+        {
+          set_progress (player, priv->stacked_progress);
+        }
+    }
+}
+
+static void
+bus_message_async_done_cb (GstBus           *bus,
+                           GstMessage       *message,
+                           ClutterGstPlayer *player)
+{
+  ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
+
+  if (priv->in_seek)
+    {
+      g_object_notify (G_OBJECT (player), "progress");
+
+      priv->in_seek = FALSE;
+
+      if (priv->stacked_progress)
+        {
+          set_progress (player, priv->stacked_progress);
+        }
+    }
+}
+
+static gboolean
+on_volume_changed_main_context (gpointer data)
+{
+  ClutterGstPlayer *player = CLUTTER_GST_PLAYER (data);
+  ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
+  gdouble volume;
+
+  volume =
+    gst_stream_volume_get_volume (GST_STREAM_VOLUME (priv->pipeline),
+                                  GST_STREAM_VOLUME_FORMAT_CUBIC);
+  priv->volume = volume;
+
+  g_object_notify (G_OBJECT (player), "audio-volume");
+
+  return FALSE;
+}
+
+/* playbin2 proxies the volume property change notification directly from
+ * the element having the "volume" property. This means this callback is
+ * called from the thread that runs the element, potentially different from
+ * the main thread */
+static void
+on_volume_changed (GstElement       *pipeline,
+		   GParamSpec       *pspec,
+		   ClutterGstPlayer *player)
+{
+  g_idle_add (on_volume_changed_main_context, player);
+}
+
+static GList *
+get_tags (GstElement  *pipeline,
+          const gchar *property_name,
+          const gchar *action_signal)
+{
+  GList *ret = NULL;
+  gint num = 1, i, n;
+
+  g_object_get (G_OBJECT (pipeline), property_name, &n, NULL);
+  if (n == 0)
+    return NULL;
+
+  for (i = 0; i < n; i++)
+    {
+      GstTagList *tags = NULL;
+      gchar *description = NULL;
+
+      g_signal_emit_by_name (G_OBJECT (pipeline), action_signal, i, &tags);
+
+      if (tags)
+        {
+
+          gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &description);
+
+          if (description)
+            {
+              const gchar *language = gst_tag_get_language_name (description);
+
+              if (language)
+                {
+                  g_free (description);
+                  description = g_strdup (language);
+                }
+            }
+
+          if (!description)
+            gst_tag_list_get_string (tags, GST_TAG_CODEC, &description);
+
+          gst_tag_list_free (tags);
+        }
+
+      if (!description)
+        description = g_strdup_printf ("Audio Track #%d", num++);
+
+      ret = g_list_prepend (ret, description);
+
+    }
+
+  return g_list_reverse (ret);
+}
+
+static gboolean
+are_lists_equal (GList *list1,
+                 GList *list2)
+{
+  GList *l1, *l2;
+
+  l1 = list1;
+  l2 = list2;
+
+  while (l1)
+    {
+      const gchar *str1, *str2;
+
+      if (l2 == NULL)
+        return FALSE;
+
+      str1 = l1->data;
+      str2 = l2->data;
+
+      if (g_strcmp0 (str1, str2) != 0)
+        return FALSE;
+
+      l1 = g_list_next (l1);
+      l2 = g_list_next (l2);
+    }
+
+  return l2 == NULL;
+}
+
+static gboolean
+on_audio_changed_main_context (gpointer data)
+{
+  ClutterGstPlayer *player = CLUTTER_GST_PLAYER (data);
+  ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
+  GList *audio_streams;
+
+  audio_streams = get_tags (priv->pipeline, "n-audio", "get-audio-tags");
+
+  if (!are_lists_equal (priv->audio_streams, audio_streams))
+    {
+      free_string_list (&priv->audio_streams);
+      priv->audio_streams = audio_streams;
+
+      CLUTTER_GST_NOTE (AUDIO_STREAM, "audio-streams changed");
+
+      g_object_notify (G_OBJECT (player), "audio-streams");
+    }
+  else
+    {
+      free_string_list (&audio_streams);
+    }
+
+  return FALSE;
+}
+
+/* same explanation as for notify::volume's usage of g_idle_add() */
+static void
+on_audio_changed (GstElement       *pipeline,
+                  ClutterGstPlayer *player)
+{
+  g_idle_add (on_audio_changed_main_context, player);
+}
+
+static void
+on_audio_tags_changed (GstElement       *pipeline,
+                       gint              stream,
+                       ClutterGstPlayer *player)
+{
+  g_idle_add (on_audio_changed_main_context, player);
+}
+
+static gboolean
+on_current_audio_changed_main_context (gpointer data)
+{
+  ClutterGstPlayer *player = CLUTTER_GST_PLAYER (data);
+
+  CLUTTER_GST_NOTE (AUDIO_STREAM, "audio stream changed");
+  g_object_notify (G_OBJECT (player), "audio-stream");
+
+  return FALSE;
+}
+
+static void
+on_current_audio_changed (GstElement       *pipeline,
+                          GParamSpec       *pspec,
+                          ClutterGstPlayer *player)
+{
+  g_idle_add (on_current_audio_changed_main_context, player);
+}
+
+/* GObject's magic/madness */
+
+static void
+clutter_gst_player_deinit (ClutterGstPlayer *player)
+{
+  /* TODO */
+}
+
+static void
+clutter_gst_player_dispose (GObject *object)
+{
+  ClutterGstPlayer *player = CLUTTER_GST_PLAYER (object);
+  ClutterGstPlayerIfacePrivate *iface_priv = PLAYER_GET_CLASS_PRIVATE (object);
+
+  clutter_gst_player_deinit (player);
+
+  iface_priv->dispose (object);
+}
+
+static void
+clutter_gst_player_set_property (GObject      *object,
+                                 guint         property_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+  ClutterGstPlayer *player = CLUTTER_GST_PLAYER (object);
+  ClutterGstPlayerIfacePrivate *iface_priv;
+
+  switch (property_id)
+    {
+    case PROP_URI:
+      set_uri (player, g_value_get_string (value));
+      break;
+
+    case PROP_PLAYING:
+      set_playing (player, g_value_get_boolean (value));
+      break;
+
+    case PROP_PROGRESS:
+      set_progress (player, g_value_get_double (value));
+      break;
+
+    case PROP_SUBTITLE_URI:
+      set_subtitle_uri (player, g_value_get_string (value));
+      break;
+
+    case PROP_SUBTITLE_FONT_NAME:
+      set_subtitle_font_name (player, g_value_get_string (value));
+      break;
+
+    case PROP_AUDIO_VOLUME:
+      set_audio_volume (player, g_value_get_double (value));
+      break;
+
+    case PROP_USER_AGENT:
+      clutter_gst_player_set_user_agent (player,
+                                         g_value_get_string (value));
+      break;
+
+    case PROP_SEEK_FLAGS:
+      clutter_gst_player_set_seek_flags (player,
+                                         g_value_get_flags (value));
+      break;
+
+    case PROP_AUDIO_STREAM:
+      clutter_gst_player_set_audio_stream (player,
+                                           g_value_get_int (value));
+      break;
+
+    default:
+      iface_priv = PLAYER_GET_CLASS_PRIVATE (object);
+      iface_priv->set_property (object, property_id, value, pspec);
+    }
+}
+
+
+static void
+clutter_gst_player_get_property (GObject    *object,
+                                 guint       property_id,
+                                 GValue     *value,
+                                 GParamSpec *pspec)
+{
+  ClutterGstPlayer *player = CLUTTER_GST_PLAYER (object);
+  ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
+  ClutterGstPlayerIfacePrivate *iface_priv;
+  gchar *str;
+
+  switch (property_id)
+    {
+    case PROP_URI:
+      g_value_set_string (value, priv->uri);
+      break;
+
+    case PROP_PLAYING:
+      g_value_set_boolean (value, get_playing (player));
+      break;
+
+    case PROP_PROGRESS:
+      g_value_set_double (value, get_progress (player));
+      break;
+
+    case PROP_SUBTITLE_URI:
+      g_object_get (priv->pipeline, "suburi", &str, NULL);
+      g_value_take_string (value, str);
+      break;
+
+    case PROP_SUBTITLE_FONT_NAME:
+      g_value_set_string (value, priv->font_name);
+      break;
+
+    case PROP_AUDIO_VOLUME:
+      g_value_set_double (value, get_audio_volume (player));
+      break;
+
+    case PROP_CAN_SEEK:
+      g_value_set_boolean (value, priv->can_seek);
+      break;
+
+    case PROP_BUFFER_FILL:
+      g_value_set_double (value, priv->buffer_fill);
+      break;
+
+    case PROP_DURATION:
+      g_value_set_double (value, priv->duration);
+      break;
+
+    case PROP_IDLE:
+      g_value_set_boolean (value, priv->is_idle);
+      break;
+
+    case PROP_USER_AGENT:
+      {
+        gchar *user_agent;
+
+        user_agent = clutter_gst_player_get_user_agent (player);
+        g_value_take_string (value, user_agent);
+      }
+      break;
+
+    case PROP_SEEK_FLAGS:
+      {
+        ClutterGstSeekFlags seek_flags;
+
+        seek_flags = clutter_gst_player_get_seek_flags (player);
+        g_value_set_flags (value, seek_flags);
+      }
+      break;
+
+    case PROP_AUDIO_STREAMS:
+      g_value_set_pointer (value, priv->audio_streams);
+      break;
+
+    case PROP_AUDIO_STREAM:
+      {
+        gint index_;
+
+        index_ = clutter_gst_player_get_audio_stream (player);
+        g_value_set_int (value, index_);
+      }
+      break;
+
+    default:
+      iface_priv = PLAYER_GET_CLASS_PRIVATE (object);
+      iface_priv->get_property (object, property_id, value, pspec);
+    }
+}
+
+void
+clutter_gst_player_class_init (GObjectClass *object_class)
+{
+  ClutterGstPlayerIfacePrivate *priv;
+
+  priv = g_new0 (ClutterGstPlayerIfacePrivate, 1);
+  g_type_set_qdata (G_OBJECT_CLASS_TYPE (object_class),
+                    clutter_gst_player_class_quark,
+                    priv);
+
+  /* Save object's methods we want to override */
+  priv->set_property = object_class->set_property;
+  priv->get_property = object_class->get_property;
+  priv->dispose      = object_class->dispose;
+
+  /* Replace by our methods */
+  object_class->dispose      = clutter_gst_player_dispose;
+  object_class->set_property = clutter_gst_player_set_property;
+  object_class->get_property = clutter_gst_player_get_property;
+
+  /* Override ClutterMedia's properties */
+  g_object_class_override_property (object_class,
+                                    PROP_URI, "uri");
+  g_object_class_override_property (object_class,
+                                    PROP_PLAYING, "playing");
+  g_object_class_override_property (object_class,
+                                    PROP_PROGRESS, "progress");
+  g_object_class_override_property (object_class,
+                                    PROP_SUBTITLE_URI, "subtitle-uri");
+  g_object_class_override_property (object_class,
+                                    PROP_SUBTITLE_FONT_NAME,
+                                    "subtitle-font-name");
+  g_object_class_override_property (object_class,
+                                    PROP_AUDIO_VOLUME, "audio-volume");
+  g_object_class_override_property (object_class,
+                                    PROP_CAN_SEEK, "can-seek");
+  g_object_class_override_property (object_class,
+                                    PROP_DURATION, "duration");
+  g_object_class_override_property (object_class,
+                                    PROP_BUFFER_FILL, "buffer-fill");
+
+  /* Override ClutterGstPlayer's properties */
+  g_object_class_override_property (object_class,
+                                    PROP_IDLE, "idle");
+  g_object_class_override_property (object_class,
+                                    PROP_USER_AGENT, "user-agent");
+  g_object_class_override_property (object_class,
+                                    PROP_SEEK_FLAGS, "seek-flags");
+  g_object_class_override_property (object_class,
+                                    PROP_AUDIO_STREAMS, "audio-streams");
+  g_object_class_override_property (object_class,
+                                    PROP_AUDIO_STREAM, "audio-stream");
+}
+
+static GstElement *
+get_pipeline (void)
+{
+  GstElement *pipeline, *audio_sink;
+
+  pipeline = gst_element_factory_make ("playbin2", "pipeline");
+  if (!pipeline)
+    {
+      g_critical ("Unable to create playbin2 element");
+      return NULL;
+    }
+
+  audio_sink = gst_element_factory_make ("gconfaudiosink", "audio-sink");
+  if (!audio_sink)
+    {
+      audio_sink = gst_element_factory_make ("autoaudiosink", "audio-sink");
+      if (!audio_sink)
+	{
+	  audio_sink = gst_element_factory_make ("alsasink", "audio-sink");
+	  g_warning ("Could not create a GST audio_sink. "
+		     "Audio unavailable.");
+
+          /* do we even need to bother? */
+	  if (!audio_sink)
+	    audio_sink = gst_element_factory_make ("fakesink", "audio-sink");
+	}
+    }
+
+  g_object_set (G_OBJECT (pipeline),
+                "audio-sink", audio_sink,
+                "subtitle-font-desc", "Sans 16",
+                NULL);
+
+  return pipeline;
+}
+
+gboolean
+clutter_gst_player_init (ClutterGstPlayer *player)
+{
+  ClutterGstPlayerPrivate *priv;
+
+  g_return_val_if_fail (CLUTTER_GST_IS_PLAYER (player), FALSE);
+
+  priv = PLAYER_GET_PRIVATE (player);
+  if (priv)
+    return TRUE;
+
+  priv = g_slice_new0 (ClutterGstPlayerPrivate);
+  PLAYER_SET_PRIVATE (player, priv);
+
+  priv->in_seek = FALSE;
+  priv->is_changing_uri = FALSE;
+  priv->in_download_buffering = FALSE;
+
+  priv->pipeline = get_pipeline ();
+  if (!priv->pipeline)
+    {
+      g_critical ("Unable to create pipeline");
+      return FALSE;
+    }
+
+  g_signal_connect (priv->pipeline, "notify::source",
+                    G_CALLBACK (on_source_changed), player);
+
+  /* We default to not playing until someone calls set_playing(TRUE) */
+  priv->target_state = GST_STATE_PAUSED;
+
+  /* Default to a fast seek, ie. same effect than set_seek_flags (NONE); */
+  priv->seek_flags = GST_SEEK_FLAG_KEY_UNIT;
+
+  priv->bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
+
+  gst_bus_add_signal_watch (priv->bus);
+
+  g_signal_connect_object (priv->bus, "message::error",
+			   G_CALLBACK (bus_message_error_cb),
+			   player, 0);
+  g_signal_connect_object (priv->bus, "message::eos",
+			   G_CALLBACK (bus_message_eos_cb),
+			   player, 0);
+  g_signal_connect_object (priv->bus, "message::buffering",
+			   G_CALLBACK (bus_message_buffering_cb),
+			   player, 0);
+  g_signal_connect_object (priv->bus, "message::duration",
+			   G_CALLBACK (bus_message_duration_cb),
+			   player, 0);
+  g_signal_connect_object (priv->bus, "message::state-changed",
+			   G_CALLBACK (bus_message_state_change_cb),
+			   player, 0);
+  g_signal_connect_object (priv->bus, "message::async-done",
+                           G_CALLBACK (bus_message_async_done_cb),
+                           player, 0);
+
+  g_signal_connect (priv->pipeline, "notify::volume",
+		    G_CALLBACK (on_volume_changed),
+                    player);
+
+  g_signal_connect (priv->pipeline, "audio-changed",
+                    G_CALLBACK (on_audio_changed),
+                    player);
+  g_signal_connect (priv->pipeline, "audio-tags-changed",
+                    G_CALLBACK (on_audio_tags_changed),
+                    player);
+  g_signal_connect (priv->pipeline, "notify::current-audio",
+                    G_CALLBACK (on_current_audio_changed),
+                    player);
+
+  gst_object_unref (GST_OBJECT (priv->bus));
+
+  return TRUE;
+}
+
+static void
+clutter_gst_player_default_init (ClutterGstPlayerIface *iface)
+{
+  GParamSpec *pspec;
+
+  pspec = g_param_spec_string ("idle",
+                               "Idle",
+                               "Idle state of the player's pipeline",
+                               NULL,
+                               CLUTTER_GST_PARAM_READABLE);
+  g_object_interface_install_property (iface, pspec);
+
+  pspec = g_param_spec_string ("user-agent",
+                               "User Agent",
+                               "User Agent used with network protocols",
+                               NULL,
+                               CLUTTER_GST_PARAM_READWRITE);
+  g_object_interface_install_property (iface, pspec);
+
+  pspec = g_param_spec_flags ("seek-flags",
+                              "Seek Flags",
+                              "Flags to use when seeking",
+                              CLUTTER_GST_TYPE_SEEK_FLAGS,
+                              CLUTTER_GST_SEEK_FLAG_NONE,
+                              CLUTTER_GST_PARAM_READWRITE);
+  g_object_interface_install_property (iface, pspec);
+
+  pspec = g_param_spec_pointer ("audio-streams",
+                                "Audio Streams",
+                                "List of the audio streams of the media",
+                                CLUTTER_GST_PARAM_READABLE);
+  g_object_interface_install_property (iface, pspec);
+
+  pspec = g_param_spec_int ("audio-stream",
+                            "Audio Stream",
+                            "Index of the current audio stream",
+                            -1, G_MAXINT, -1,
+                            CLUTTER_GST_PARAM_READWRITE);
+  g_object_interface_install_property (iface, pspec);
+
+  /* Signals */
+  signals[DOWNLOAD_BUFFERING] =
+    g_signal_new ("download-buffering",
+                  CLUTTER_GST_TYPE_PLAYER,
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (ClutterGstPlayerIface,
+                                   download_buffering),
+                  NULL, NULL,
+                  _clutter_gst_marshal_VOID__DOUBLE_DOUBLE,
+                  G_TYPE_NONE, 2, G_TYPE_DOUBLE, G_TYPE_DOUBLE);
+
+  /* Setup a quark for per instance private data */
+  if (!clutter_gst_player_private_quark)
+    {
+      clutter_gst_player_private_quark =
+        g_quark_from_static_string ("clutter-gst-player-private-quark");
+      clutter_gst_player_class_quark =
+        g_quark_from_static_string ("clutter-gst-player-class-quark");
+    }
+}
+
+GstElement *
+clutter_gst_player_get_pipeline (ClutterGstPlayer *player)
+{
+  ClutterGstPlayerPrivate *priv;
+
+  g_return_val_if_fail (CLUTTER_GST_IS_PLAYER (player), NULL);
+
+  priv = PLAYER_GET_PRIVATE (player);
+
+  return priv->pipeline;
+}
+
+gchar *
+clutter_gst_player_get_user_agent (ClutterGstPlayer *player)
+{
+  ClutterGstPlayerPrivate *priv;
+  GstElement *source;
+  GParamSpec *pspec;
+  gchar *user_agent;
+
+  g_return_val_if_fail (CLUTTER_GST_IS_PLAYER (player), NULL);
+
+  priv = PLAYER_GET_PRIVATE (player);
+
+  /* If the user has set a custom user agent, we just return it even if it is
+   * not used by the current source element of the pipeline */
+  if (priv->user_agent)
+    return g_strdup (priv->user_agent);
+
+  /* If not, we try to retrieve the user agent used by the current source */
+  g_object_get (priv->pipeline, "source", &source, NULL);
+  if (source == NULL)
+    return NULL;
+
+  pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (source),
+                                        "user-agent");
+  if (pspec == NULL)
+    return NULL;
+
+  g_object_get (source, "user-agent", &user_agent, NULL);
+
+  return user_agent;
+}
+
+void
+clutter_gst_player_set_user_agent (ClutterGstPlayer *player,
+                                   const gchar      *user_agent)
+{
+  ClutterGstPlayerPrivate *priv;
+
+  g_return_if_fail (CLUTTER_GST_IS_PLAYER (player));
+
+  priv = PLAYER_GET_PRIVATE (player);
+
+  g_free (priv->user_agent);
+  if (user_agent)
+    priv->user_agent = g_strdup (user_agent);
+  else
+    priv->user_agent = NULL;
+
+  player_set_user_agent (player, user_agent);
+}
+
+ClutterGstSeekFlags
+clutter_gst_player_get_seek_flags (ClutterGstPlayer *player)
+{
+  ClutterGstPlayerPrivate *priv;
+
+  g_return_val_if_fail (CLUTTER_GST_IS_PLAYER (player),
+                        CLUTTER_GST_SEEK_FLAG_NONE);
+
+  priv = PLAYER_GET_PRIVATE (player);
+
+  if (priv->seek_flags == GST_SEEK_FLAG_ACCURATE)
+    return CLUTTER_GST_SEEK_FLAG_ACCURATE;
+  else
+    return CLUTTER_GST_SEEK_FLAG_NONE;
+}
+
+void
+clutter_gst_player_set_seek_flags (ClutterGstPlayer    *player,
+                                   ClutterGstSeekFlags  flags)
+{
+  ClutterGstPlayerPrivate *priv;
+
+  g_return_if_fail (CLUTTER_GST_IS_PLAYER (player));
+
+  priv = PLAYER_GET_PRIVATE (player);
+
+  if (flags == CLUTTER_GST_SEEK_FLAG_NONE)
+    priv->seek_flags = GST_SEEK_FLAG_KEY_UNIT;
+  else if (flags & CLUTTER_GST_SEEK_FLAG_ACCURATE)
+    priv->seek_flags = GST_SEEK_FLAG_ACCURATE;
+}
+
+ClutterGstBufferingMode
+clutter_gst_player_get_buffering_mode (ClutterGstPlayer *player)
+{
+  ClutterGstPlayerPrivate *priv;
+  GstPlayFlags flags;
+
+  g_return_val_if_fail (CLUTTER_GST_IS_PLAYER (player),
+                        CLUTTER_GST_BUFFERING_MODE_STREAM);
+
+  priv = PLAYER_GET_PRIVATE (player);
+
+  g_object_get (G_OBJECT (priv->pipeline), "flags", &flags, NULL);
+
+  if (flags & GST_PLAY_FLAG_DOWNLOAD)
+    return CLUTTER_GST_BUFFERING_MODE_DOWNLOAD;
+
+  return CLUTTER_GST_BUFFERING_MODE_STREAM;
+}
+
+void
+clutter_gst_player_set_buffering_mode (ClutterGstPlayer        *player,
+                                       ClutterGstBufferingMode  mode)
+{
+  ClutterGstPlayerPrivate *priv;
+  GstPlayFlags flags;
+
+  g_return_if_fail (CLUTTER_GST_IS_PLAYER (player));
+
+  priv = PLAYER_GET_PRIVATE (player);
+
+  g_object_get (G_OBJECT (priv->pipeline), "flags", &flags, NULL);
+
+  switch (mode)
+    {
+    case CLUTTER_GST_BUFFERING_MODE_STREAM:
+      flags &= ~GST_PLAY_FLAG_DOWNLOAD;
+      break;
+
+    case CLUTTER_GST_BUFFERING_MODE_DOWNLOAD:
+      flags |= GST_PLAY_FLAG_DOWNLOAD;
+      break;
+
+    default:
+      g_warning ("Unexpected buffering mode %d", mode);
+      break;
+    }
+
+  g_object_set (G_OBJECT (priv->pipeline), "flags", flags, NULL);
+}
+
+GList *
+clutter_gst_player_get_audio_streams (ClutterGstPlayer *player)
+{
+  ClutterGstPlayerPrivate *priv;
+
+  g_return_val_if_fail (CLUTTER_GST_IS_PLAYER (player), NULL);
+
+  priv = PLAYER_GET_PRIVATE (player);
+
+  if (CLUTTER_GST_DEBUG_ENABLED (AUDIO_STREAM))
+    {
+      gchar *streams;
+
+      streams = list_to_string (priv->audio_streams);
+      CLUTTER_GST_NOTE (AUDIO_STREAM, "audio streams: %s", streams);
+      g_free (streams);
+    }
+
+  return priv->audio_streams;
+}
+
+gint
+clutter_gst_player_get_audio_stream (ClutterGstPlayer *player)
+{
+  ClutterGstPlayerPrivate *priv;
+  gint index_ = -1;
+
+  g_return_val_if_fail (CLUTTER_GST_IS_PLAYER (player), -1);
+
+  priv = PLAYER_GET_PRIVATE (player);
+
+  g_object_get (G_OBJECT (priv->pipeline),
+                "current-audio", &index_,
+                NULL);
+
+  CLUTTER_GST_NOTE (AUDIO_STREAM, "audio stream is #%d", index_);
+
+  return index_;
+}
+
+void
+clutter_gst_player_set_audio_stream (ClutterGstPlayer *player,
+                                     gint              index_)
+{
+  ClutterGstPlayerPrivate *priv;
+
+  g_return_if_fail (CLUTTER_GST_IS_PLAYER (player));
+
+  priv = PLAYER_GET_PRIVATE (player);
+
+  g_return_if_fail (index_ >= 0 &&
+                    index_ < g_list_length (priv->audio_streams));
+
+  CLUTTER_GST_NOTE (AUDIO_STREAM, "set audio audio stream to #%d", index_);
+
+  g_object_set (G_OBJECT (priv->pipeline),
+                "current-audio", index_,
+                NULL);
+}
+
+gboolean
+clutter_gst_player_get_idle (ClutterGstPlayer *player)
+{
+  ClutterGstPlayerPrivate *priv;
+
+  g_return_val_if_fail (CLUTTER_GST_IS_PLAYER (player), TRUE);
+
+  priv = PLAYER_GET_PRIVATE (player);
+
+  return priv->is_idle;
+}
diff --git a/clutter-gst/clutter-gst-player.h b/clutter-gst/clutter-gst-player.h
new file mode 100644
index 0000000..3bab429
--- /dev/null
+++ b/clutter-gst/clutter-gst-player.h
@@ -0,0 +1,100 @@
+/*
+ * Clutter-GStreamer.
+ *
+ * GStreamer integration library for Clutter.
+ *
+ * clutter-gst-player.c - Wrap some convenience functions around playbin2
+ *
+ * Authored By Damien Lespiau <damien lespiau intel com>
+ *
+ * Copyright (C) 2011 Intel Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __CLUTTER_GST_PLAYER_H__
+#define __CLUTTER_GST_PLAYER_H__
+
+#include <glib-object.h>
+
+#include <clutter/clutter.h>
+
+#include <clutter-gst/clutter-gst-types.h>
+
+G_BEGIN_DECLS
+
+#define CLUTTER_GST_TYPE_PLAYER clutter_gst_player_get_type()
+
+#define CLUTTER_GST_PLAYER(obj)                                         \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj),                                   \
+                               CLUTTER_GST_TYPE_PLAYER,                 \
+                               ClutterGstPlayer))
+#define CLUTTER_GST_IS_PLAYER(obj)                              \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj),                           \
+                               CLUTTER_GST_TYPE_PLAYER))
+#define CLUTTER_GST_PLAYER_GET_INTERFACE(obj)                           \
+  (G_TYPE_INSTANCE_GET_INTERFACE ((obj),                                \
+                                  CLUTTER_GST_TYPE_PLAYER,              \
+                                  ClutterGstPlayerIface))
+
+typedef struct _ClutterGstPlayer ClutterGstPlayer;
+typedef struct _ClutterGstPlayerIface ClutterGstPlayerIface;
+typedef struct _ClutterGstPlayerIfacePrivate ClutterGstPlayerIfacePrivate;
+
+
+struct _ClutterGstPlayerIface
+{
+  /*< private >*/
+  GTypeInterface base_iface;
+
+  ClutterGstPlayerIfacePrivate *priv;
+
+  /*< public >*/
+  void (* download_buffering) (ClutterGstPlayer *player,
+			       gdouble           start,
+			       gdouble           stop);
+};
+
+GType clutter_gst_player_get_type (void) G_GNUC_CONST;
+
+void                      clutter_gst_player_class_init          (GObjectClass *object_class);
+
+gboolean                  clutter_gst_player_init                (ClutterGstPlayer        *player);
+
+GstElement *		  clutter_gst_player_get_pipeline        (ClutterGstPlayer        *player);
+
+gchar *			  clutter_gst_player_get_user_agent      (ClutterGstPlayer        *player);
+void			  clutter_gst_player_set_user_agent      (ClutterGstPlayer        *player,
+                                                                  const gchar             *user_agent);
+
+ClutterGstSeekFlags	  clutter_gst_player_get_seek_flags      (ClutterGstPlayer        *player);
+void			  clutter_gst_player_set_seek_flags      (ClutterGstPlayer        *player,
+                                                                  ClutterGstSeekFlags      flags);
+
+ClutterGstBufferingMode	  clutter_gst_player_get_buffering_mode  (ClutterGstPlayer        *player);
+void			  clutter_gst_player_set_buffering_mode  (ClutterGstPlayer        *player,
+                                                                  ClutterGstBufferingMode  mode);
+
+GList *                   clutter_gst_player_get_audio_streams   (ClutterGstPlayer        *player);
+gint                      clutter_gst_player_get_audio_stream    (ClutterGstPlayer        *player);
+void                      clutter_gst_player_set_audio_stream    (ClutterGstPlayer        *player,
+                                                                  gint                     index_);
+
+gboolean                  clutter_gst_player_get_idle            (ClutterGstPlayer        *player);
+
+G_END_DECLS
+
+#endif /* __CLUTTER_GST_PLAYER_H__ */
diff --git a/clutter-gst/clutter-gst-video-texture.c b/clutter-gst/clutter-gst-video-texture.c
index 6f56cf7..f03d57d 100644
--- a/clutter-gst/clutter-gst-video-texture.c
+++ b/clutter-gst/clutter-gst-video-texture.c
@@ -43,68 +43,19 @@
 
 #include <glib.h>
 #include <gio/gio.h>
-#include <gst/gst.h>
 #include <gst/video/video.h>
-#include <gst/tag/tag.h>
-#include <gst/interfaces/streamvolume.h>
 
 #include "clutter-gst-debug.h"
 #include "clutter-gst-enum-types.h"
 #include "clutter-gst-marshal.h"
+#include "clutter-gst-player.h"
 #include "clutter-gst-private.h"
 #include "clutter-gst-video-sink.h"
 #include "clutter-gst-video-texture.h"
 
-/* Elements don't expose header files */
-typedef enum {
-  GST_PLAY_FLAG_VIDEO         = (1 << 0),
-  GST_PLAY_FLAG_AUDIO         = (1 << 1),
-  GST_PLAY_FLAG_TEXT          = (1 << 2),
-  GST_PLAY_FLAG_VIS           = (1 << 3),
-  GST_PLAY_FLAG_SOFT_VOLUME   = (1 << 4),
-  GST_PLAY_FLAG_NATIVE_AUDIO  = (1 << 5),
-  GST_PLAY_FLAG_NATIVE_VIDEO  = (1 << 6),
-  GST_PLAY_FLAG_DOWNLOAD      = (1 << 7),
-  GST_PLAY_FLAG_BUFFERING     = (1 << 8),
-  GST_PLAY_FLAG_DEINTERLACE   = (1 << 9)
-} GstPlayFlags;
-
-enum
-{
-  DOWNLOAD_BUFFERING,
-
-  LAST_SIGNAL
-};
-
-static guint signals[LAST_SIGNAL] = { 0, };
-
 struct _ClutterGstVideoTexturePrivate
 {
-  GstElement *pipeline;
-  GstBus *bus;
-
-  gchar *uri;
-
-  guint can_seek : 1;
-  guint in_seek : 1;
   guint is_idle : 1;
-  guint is_changing_uri : 1;
-  guint in_error : 1;
-  guint in_eos : 1;
-  guint in_download_buffering : 1;
-
-  /* when in progressive download, we use the buffer-fill property to signal
-   * that we have enough data to play the stream. This flag allows to send
-   * the notify that buffer-fill is 1.0 only once */
-  guint virtual_stream_buffer_signalled : 1;
-
-  gdouble stacked_progress;
-
-  gdouble target_progress;
-  GstState target_state;
-
-  guint tick_timeout_id;
-  guint buffering_timeout_id;
 
   /* width / height (in pixels) of the frame data before applying the pixel
    * aspect ratio */
@@ -118,223 +69,26 @@ struct _ClutterGstVideoTexturePrivate
   guint texture_width;
   guint texture_height;
 
-  /* This is a cubic volume, suitable for use in a UI cf. StreamVolume doc */
-  gdouble volume;
-
-  gdouble buffer_fill;
-  gdouble duration;
-  gchar *font_name;
-  gchar *user_agent;
-
   CoglHandle idle_material;
   CoglColor idle_color_unpre;
-
-  GstSeekFlags seek_flags;    /* flags for the seek in set_progress(); */
-
-  GstElement *download_buffering_element;
-
-  GList *audio_streams;
 };
 
 enum {
   PROP_0,
 
-  /* ClutterMedia properties */
-  PROP_URI,
-  PROP_PLAYING,
-  PROP_PROGRESS,
-  PROP_SUBTITLE_URI,
-  PROP_SUBTITLE_FONT_NAME,
-  PROP_AUDIO_VOLUME,
-  PROP_CAN_SEEK,
-  PROP_BUFFER_FILL,
-  PROP_DURATION,
-
-  PROP_IDLE_MATERIAL,
-  PROP_USER_AGENT,
-  PROP_SEEK_FLAGS,
-  PROP_AUDIO_STREAMS,
-  PROP_AUDIO_STREAM
+  PROP_IDLE_MATERIAL
 };
 
-/* idle timeouts (in ms) */
-#define TICK_TIMEOUT        500
-#define BUFFERING_TIMEOUT   250
-
-static void clutter_media_init (ClutterMediaIface *iface);
+static void clutter_gst_video_texture_media_init (ClutterMediaIface *iface);
+static void clutter_gst_video_texture_player_init (ClutterGstPlayerIface *iface);
 
 G_DEFINE_TYPE_WITH_CODE (ClutterGstVideoTexture,
                          clutter_gst_video_texture,
                          CLUTTER_TYPE_TEXTURE,
                          G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_MEDIA,
-                                                clutter_media_init));
-
-static const gchar *
-gst_state_to_string (GstState state)
-{
-  switch (state)
-    {
-    case GST_STATE_VOID_PENDING:
-      return "pending";
-    case GST_STATE_NULL:
-      return "null";
-    case GST_STATE_READY:
-      return "ready";
-    case GST_STATE_PAUSED:
-      return "paused";
-    case GST_STATE_PLAYING:
-      return "playing";
-    }
-
-  return "Unknown state";
-}
-
-static void set_subtitle_uri (ClutterGstVideoTexture *video_texture,
-                              const gchar            *uri);
-static void configure_buffering_timeout (ClutterGstVideoTexture *video_texture,
-                                         guint                   ms);
-
-static void
-free_string_list (GList **listp)
-{
-  GList *l;
-
-  l = *listp;
-  while (l)
-    {
-      g_free (l->data);
-      l = g_list_delete_link (l, l);
-    }
-
-  *listp = NULL;
-}
-
-static void
-clear_download_buffering (ClutterGstVideoTexture *video_texture)
-{
-  ClutterGstVideoTexturePrivate *priv = video_texture->priv;
-
-  if (priv->download_buffering_element)
-    {
-      g_object_unref (priv->download_buffering_element);
-      priv->download_buffering_element = NULL;
-    }
-  configure_buffering_timeout (video_texture, 0);
-  priv->in_download_buffering = FALSE;
-  priv->virtual_stream_buffer_signalled = 0;
-}
-
-static gboolean
-buffering_timeout (gpointer data)
-{
-  ClutterGstVideoTexture *video_texture = CLUTTER_GST_VIDEO_TEXTURE (data);
-  ClutterGstVideoTexturePrivate *priv = video_texture->priv;
-  gdouble start_d, stop_d, seconds_buffered;
-  gint64 start, stop, left;
-  GstState current_state;
-  GstElement *element;
-  GstQuery *query;
-  gboolean res;
-
-  element = priv->download_buffering_element;
-  if (element == NULL)
-    element = priv->pipeline;
-
-  /* queue2 only knows about _PERCENT and _BYTES */
-  query = gst_query_new_buffering (GST_FORMAT_PERCENT);
-  res = gst_element_query (element, query);
-
-  if (res == FALSE)
-    {
-      priv->buffering_timeout_id = 0;
-      clear_download_buffering (video_texture);
-      return FALSE;
-    }
-
-  /* signal the current range */
-  gst_query_parse_buffering_stats (query, NULL, NULL, NULL, &left);
-  gst_query_parse_buffering_range (query, NULL, &start, &stop, NULL);
-
-  CLUTTER_GST_NOTE (BUFFERING,
-                    "start %" G_GINT64_FORMAT ", stop %" G_GINT64_FORMAT
-                    ", buffering left %" G_GINT64_FORMAT, start, stop, left);
-
-  start_d = (gdouble) start / GST_FORMAT_PERCENT_MAX;
-  stop_d = (gdouble) stop / GST_FORMAT_PERCENT_MAX;
-
-  g_signal_emit (video_texture,
-                 signals[DOWNLOAD_BUFFERING], 0, start_d, stop_d);
-
-  /* handle the "virtual stream buffer" and the associated pipeline state.
-   * We pause the pipeline until 2s of content is buffered. With the current
-   * implementation of queue2, start is always 0, so even when we seek in
-   * the stream the start position of the download-buffering signal is
-   * always 0.0. FIXME: look at gst_query_parse_nth_buffering_range () */
-  seconds_buffered = priv->duration * (stop_d - start_d);
-  priv->buffer_fill = seconds_buffered / 2.0;
-  priv->buffer_fill = CLAMP (priv->buffer_fill, 0.0, 1.0);
-
-  if (priv->buffer_fill != 1.0 || !priv->virtual_stream_buffer_signalled)
-    {
-      CLUTTER_GST_NOTE (BUFFERING, "buffer holds %0.2fs of data, buffer-fill "
-                        "is %.02f", seconds_buffered, priv->buffer_fill);
-
-      g_object_notify (G_OBJECT (video_texture), "buffer-fill");
-
-      if (priv->buffer_fill == 1.0)
-        priv->virtual_stream_buffer_signalled = 1;
-    }
-
-  gst_element_get_state (priv->pipeline, &current_state, NULL, 0);
-  if (priv->buffer_fill < 1.0)
-    {
-      if (current_state != GST_STATE_PAUSED)
-        {
-          CLUTTER_GST_NOTE (BUFFERING, "pausing the pipeline");
-          gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
-        }
-    }
-  else
-    {
-      if (current_state != priv->target_state)
-        {
-          CLUTTER_GST_NOTE (BUFFERING, "restoring the pipeline");
-          gst_element_set_state (priv->pipeline, priv->target_state);
-        }
-    }
-
-  /* the file has finished downloading */
-  if (left == G_GINT64_CONSTANT (0))
-    {
-      priv->buffering_timeout_id = 0;
-
-      clear_download_buffering (video_texture);
-      gst_query_unref (query);
-      return FALSE;
-    }
-
-  gst_query_unref (query);
-  return TRUE;
-}
-
-static void
-configure_buffering_timeout (ClutterGstVideoTexture *video_texture,
-                             guint                   ms)
-{
-  ClutterGstVideoTexturePrivate *priv = video_texture->priv;
-
-  if (priv->buffering_timeout_id)
-    {
-      g_source_remove (priv->buffering_timeout_id);
-      priv->buffering_timeout_id = 0;
-    }
-
-  if (ms)
-    {
-      priv->buffering_timeout_id =
-        g_timeout_add (ms, buffering_timeout, video_texture);
-    }
-}
+                                                clutter_gst_video_texture_media_init)
+                         G_IMPLEMENT_INTERFACE (CLUTTER_GST_TYPE_PLAYER,
+                                                clutter_gst_video_texture_player_init));
 
 /* Clutter 1.4 has this symbol, we don't want to depend on 1.4 just for that
  * just yet */
@@ -400,1496 +154,384 @@ create_black_idle_material (ClutterGstVideoTexture *video_texture)
 }
 
 static void
-set_user_agent (ClutterGstVideoTexture *video_texture,
-                const gchar            *user_agent)
+clutter_gst_video_texture_media_init (ClutterMediaIface *iface)
 {
-  ClutterGstVideoTexturePrivate *priv = video_texture->priv;
-  GstElement *source;
-  GParamSpec *pspec;
-
-  if (user_agent == NULL)
-    return;
-
-  g_object_get (priv->pipeline, "source", &source, NULL);
-  if (source == NULL)
-    return;
-
-  pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (source),
-                                        "user-agent");
-  if (pspec == NULL)
-    return;
-
-  CLUTTER_GST_NOTE (MEDIA, "setting user agent: %s", user_agent);
-
-  g_object_set (source, "user-agent", user_agent, NULL);
 }
 
-/*
- * ClutterMedia implementation
- */
-
-static gboolean
-tick_timeout (gpointer data)
+static void
+clutter_gst_video_texture_player_init (ClutterGstPlayerIface *iface)
 {
-  GObject *video_texture = data;
-
-  g_object_notify (video_texture, "progress");
-
-  return TRUE;
 }
 
 static void
-autoload_subtitle (ClutterGstVideoTexture *video_texture,
-                   const gchar            *uri)
+clutter_gst_video_texture_size_change (ClutterTexture *texture,
+                                       gint            width,
+                                       gint            height)
 {
+  ClutterGstVideoTexture *video_texture = CLUTTER_GST_VIDEO_TEXTURE (texture);
   ClutterGstVideoTexturePrivate *priv = video_texture->priv;
-  gchar *path, *dot, *subtitle_path;
-  GFile *video;
-  guint i;
-
-  static const char subtitles_extensions[][4] =
-    {
-      "sub", "SUB",
-      "srt", "SRT",
-      "smi", "SMI",
-      "ssa", "SSA",
-      "ass", "ASS",
-      "asc", "ASC"
-    };
-
-  /* do not try to look for subtitle files if the video file is not mounted
-   * locally */
-  if (!g_str_has_prefix (uri, "file://"))
-    return;
-
-  /* Retrieve the absolute path of the video file */
-  video = g_file_new_for_uri (uri);
-  path = g_file_get_path (video);
-  g_object_unref (video);
-  if (path == NULL)
-    return;
-
-  /* Put a '\0' after the dot of the extension */
-  dot = strrchr (path, '.');
-  if (dot == NULL) {
-    g_free (path);
-    return;
-  }
-  *++dot = '\0';
-
-  /* we can't use path as the temporary buffer for the paths of the potential
-   * subtitle files as we may not have enough room there */
-  subtitle_path = g_malloc (strlen (path) + 1 + 4);
-  strcpy (subtitle_path, path);
-
-  /* reuse dot to point to the first byte of the extension of subtitle_path */
-  dot = subtitle_path + (dot - path);
-
-  for (i = 0; i < G_N_ELEMENTS (subtitles_extensions); i++)
-    {
-      GFile *candidate;
-
-      memcpy (dot, subtitles_extensions[i], 4);
-      candidate = g_file_new_for_path (subtitle_path);
-      if (g_file_query_exists (candidate, NULL))
-        {
-          gchar *suburi;
-
-          suburi = g_file_get_uri (candidate);
+  gboolean changed;
 
-          CLUTTER_GST_NOTE (MEDIA, "found subtitle: %s", suburi);
+  /* we are being told the actual (as in number of pixels in the buffers)
+   * frame size. Store the values to be used in preferred_width/height() */
+  changed = (priv->buffer_width != width) || (priv->buffer_height != height);
+  priv->buffer_width = width;
+  priv->buffer_height = height;
 
-          g_object_set (priv->pipeline, "suburi", suburi, NULL);
-          g_free (suburi);
+  if (changed)
+    {
+      /* reset the computed texture dimensions if the underlying frames have
+       * changed size */
+      CLUTTER_GST_NOTE (ASPECT_RATIO, "frame size has been updated to %dx%d",
+                        width, height);
 
-          g_object_unref (candidate);
-          break;
-        }
+      priv->texture_width = priv->texture_height = 0;
 
-      g_object_unref (candidate);
+      /* queue a relayout to ask containers/layout manager to ask for
+       * the preferred size again */
+      clutter_actor_queue_relayout (CLUTTER_ACTOR (texture));
     }
-
-  g_free (path);
-  g_free (subtitle_path);
 }
 
-static void
-query_duration (ClutterGstVideoTexture *video_texture)
-{
-  ClutterGstVideoTexturePrivate *priv = video_texture->priv;
-  gboolean success;
-  GstFormat format = GST_FORMAT_TIME;
-  gint64 duration;
-  gdouble new_duration, difference;
-
-  success = gst_element_query_duration (priv->pipeline, &format, &duration);
-  if (G_UNLIKELY (success != TRUE))
-    return;
-
-  new_duration = (gdouble) duration / GST_SECOND;
-
-  /* while we store the new duration if it sligthly changes, the duration
-   * signal is sent only if the new duration is at least one second different
-   * from the old one (as the duration signal is mainly used to update the
-   * time displayed in a UI */
-  difference = ABS (priv->duration - new_duration);
-  if (difference > 1e-3)
-    {
-      CLUTTER_GST_NOTE (MEDIA, "duration: %.02f", new_duration);
-      priv->duration = new_duration;
-
-      if (difference > 1.0)
-        g_object_notify (G_OBJECT (video_texture), "duration");
-    }
-}
+/*
+ * Clutter actor implementation
+ */
 
 static void
-set_uri (ClutterGstVideoTexture *video_texture,
-         const gchar            *uri)
+clutter_gst_video_texture_get_natural_size (ClutterGstVideoTexture *texture,
+                                            gfloat                 *width,
+                                            gfloat                 *height)
 {
-  ClutterGstVideoTexturePrivate *priv = video_texture->priv;
-  GObject *self = G_OBJECT (video_texture);
-  GstState state, pending;
-
-  CLUTTER_GST_NOTE (MEDIA, "setting uri %s", uri);
-
-  if (!priv->pipeline)
-    return;
-
-  g_free (priv->uri);
+  ClutterGstVideoTexturePrivate *priv = texture->priv;
+  guint dar_n, dar_d;
+  gboolean ret;
 
-  priv->in_eos = FALSE;
-  priv->in_error = FALSE;
+  /* we cache texture_width and texture_height */
 
-  if (uri)
+  if (G_UNLIKELY (priv->buffer_width == 0 || priv->buffer_height == 0))
     {
-      priv->uri = g_strdup (uri);
-
-      /* Ensure the tick timeout is installed.
-       *
-       * We also have it installed in PAUSED state, because
-       * seeks etc may have a delayed effect on the position.
-       */
-      if (priv->tick_timeout_id == 0)
-        {
-          priv->tick_timeout_id =
-            g_timeout_add (TICK_TIMEOUT, tick_timeout, self);
-        }
-
-      /* try to load subtitles based on the uri of the file */
-      set_subtitle_uri (video_texture, NULL);
-      autoload_subtitle (video_texture, uri);
-
-      /* reset the states of download buffering */
-      clear_download_buffering (video_texture);
+      /* we don't know the size of the frames yet default to 0,0 */
+      priv->texture_width = 0;
+      priv->texture_height = 0;
     }
-  else
+  else if (G_UNLIKELY (priv->texture_width == 0 || priv->texture_height == 0))
     {
-      priv->uri = NULL;
-
-      set_subtitle_uri (video_texture, NULL);
+      CLUTTER_GST_NOTE (ASPECT_RATIO, "frame is %dx%d with par %d/%d",
+                        priv->buffer_width, priv->buffer_height,
+                        priv->par_n, priv->par_d);
 
-      if (priv->tick_timeout_id)
-	{
-	  g_source_remove (priv->tick_timeout_id);
-	  priv->tick_timeout_id = 0;
-	}
+      ret = gst_video_calculate_display_ratio (&dar_n, &dar_d,
+                                               priv->buffer_width,
+                                               priv->buffer_height,
+                                               priv->par_n, priv->par_d,
+                                               1, 1);
+      if (ret == FALSE)
+        dar_n = dar_d = 1;
 
-      if (priv->buffering_timeout_id)
+      if (priv->buffer_height % dar_d == 0)
         {
-          g_source_remove (priv->buffering_timeout_id);
-          priv->buffering_timeout_id = 0;
+          priv->texture_width = gst_util_uint64_scale (priv->buffer_height,
+                                                       dar_n, dar_d);
+          priv->texture_height = priv->buffer_height;
         }
+      else if (priv->buffer_width % dar_n == 0)
+        {
+          priv->texture_width = priv->buffer_width;
+          priv->texture_height = gst_util_uint64_scale (priv->buffer_width,
+                                                        dar_d, dar_n);
 
-      if (priv->download_buffering_element)
+        }
+      else
         {
-          g_object_unref (priv->download_buffering_element);
-          priv->download_buffering_element = NULL;
+          priv->texture_width = gst_util_uint64_scale (priv->buffer_height,
+                                                       dar_n, dar_d);
+          priv->texture_height = priv->buffer_height;
         }
 
+      CLUTTER_GST_NOTE (ASPECT_RATIO,
+                        "final size is %dx%d (calculated par is %d/%d)",
+                        priv->texture_width, priv->texture_height,
+                        dar_n, dar_d);
     }
 
-  priv->can_seek = FALSE;
-  priv->duration = 0.0;
-  priv->stacked_progress = 0.0;
-  priv->target_progress = 0.0;
-
-  CLUTTER_GST_NOTE (MEDIA, "setting URI: %s", uri);
-
-  if (uri)
-    {
-      gst_element_get_state (priv->pipeline, &state, &pending, 0);
-      if (pending)
-        state = pending;
-
-      gst_element_set_state (priv->pipeline, GST_STATE_NULL);
-
-      g_object_set (priv->pipeline, "uri", uri, NULL);
-
-      gst_element_set_state (priv->pipeline, state);
-
-      priv->is_changing_uri = TRUE;
-    }
-  else
-    {
-      priv->is_idle = TRUE;
-      gst_element_set_state (priv->pipeline, GST_STATE_NULL);
-      clutter_actor_queue_redraw (CLUTTER_ACTOR (video_texture));
-    }
+  if (width)
+    *width = (gfloat)priv->texture_width;
 
-  /*
-   * Emit notifications for all these to make sure UI is not showing
-   * any properties of the old URI.
-   */
-  g_object_notify (self, "uri");
-  g_object_notify (self, "can-seek");
-  g_object_notify (self, "duration");
-  g_object_notify (self, "progress");
-
-  free_string_list (&priv->audio_streams);
-  CLUTTER_GST_NOTE (AUDIO_STREAM, "audio-streams changed");
-  g_object_notify (self, "audio-streams");
+  if (height)
+    *height = (gfloat)priv->texture_height;
 }
 
 static void
-set_playing (ClutterGstVideoTexture *video_texture,
-             gboolean                playing)
+clutter_gst_video_texture_get_preferred_width (ClutterActor *self,
+                                               gfloat        for_height,
+                                               gfloat       *min_width_p,
+                                               gfloat       *natural_width_p)
 {
-  ClutterGstVideoTexturePrivate *priv = video_texture->priv;
-
-  if (!priv->pipeline)
-    return;
+  ClutterGstVideoTexture *texture = CLUTTER_GST_VIDEO_TEXTURE (self);
+  ClutterGstVideoTexturePrivate *priv = texture->priv;
+  gboolean sync_size, keep_aspect_ratio;
+  gfloat natural_width, natural_height;
 
-  CLUTTER_GST_NOTE (MEDIA, "set playing: %d", playing);
+  /* Min request is always 0 since we can scale down or clip */
+  if (min_width_p)
+    *min_width_p = 0;
 
-  priv->in_error = FALSE;
-  priv->in_eos = FALSE;
+  sync_size = clutter_texture_get_sync_size (CLUTTER_TEXTURE (self));
+  keep_aspect_ratio =
+    clutter_texture_get_keep_aspect_ratio (CLUTTER_TEXTURE (self));
 
-  priv->target_state = playing ? GST_STATE_PLAYING : GST_STATE_PAUSED;
+  clutter_gst_video_texture_get_natural_size (texture,
+                                              &natural_width,
+                                              &natural_height);
 
-  if (priv->uri)
+  if (sync_size)
     {
-      priv->in_seek = FALSE;
+      if (natural_width_p)
+        {
+          if (!keep_aspect_ratio ||
+              for_height < 0 ||
+              priv->buffer_height <= 0)
+            {
+              *natural_width_p = natural_width;
+            }
+          else
+            {
+              /* Set the natural width so as to preserve the aspect ratio */
+              gfloat ratio =  natural_width /  natural_height;
 
-      gst_element_set_state (priv->pipeline, priv->target_state);
+              *natural_width_p = ratio * for_height;
+            }
+        }
     }
   else
     {
-      if (playing)
-       g_warning ("Unable to start playing: no URI is set");
+      if (natural_width_p)
+        *natural_width_p = 0;
     }
-
-  g_object_notify (G_OBJECT (video_texture), "playing");
-  g_object_notify (G_OBJECT (video_texture), "progress");
 }
 
-static gboolean
-get_playing (ClutterGstVideoTexture *video_texture)
+static void
+clutter_gst_video_texture_get_preferred_height (ClutterActor *self,
+                                                gfloat        for_width,
+                                                gfloat       *min_height_p,
+                                                gfloat       *natural_height_p)
 {
-  ClutterGstVideoTexturePrivate *priv = video_texture->priv;
-  GstState state, pending;
-  gboolean playing;
+  ClutterGstVideoTexture *texture = CLUTTER_GST_VIDEO_TEXTURE (self);
+  ClutterGstVideoTexturePrivate *priv = texture->priv;
+  gboolean sync_size, keep_aspect_ratio;
+  gfloat natural_width, natural_height;
 
-  if (!priv->pipeline)
-    return FALSE;
-
-  gst_element_get_state (priv->pipeline, &state, &pending, 0);
-
-  if (pending)
-    playing = (pending == GST_STATE_PLAYING);
-  else
-    playing = (state == GST_STATE_PLAYING);
-
-  CLUTTER_GST_NOTE (MEDIA, "get playing: %d", playing);
-
-  return playing;
-}
-
-static void
-set_progress (ClutterGstVideoTexture *video_texture,
-              gdouble                 progress)
-{
-  ClutterGstVideoTexturePrivate *priv = video_texture->priv;
-  GstQuery *duration_q;
-  gint64 position;
-
-  if (!priv->pipeline)
-    return;
-
-  CLUTTER_GST_NOTE (MEDIA, "set progress: %.02f", progress);
-
-  priv->in_eos = FALSE;
-  priv->target_progress = progress;
-
-  if (priv->in_download_buffering)
-    {
-      /* we clear the virtual_stream_buffer_signalled flag as it's likely we
-       * need to buffer again */
-      priv->virtual_stream_buffer_signalled = 0;
-    }
-
-  if (priv->in_seek || priv->is_idle || priv->is_changing_uri)
-    {
-      /* We can't seek right now, let's save the position where we
-         want to seek and do that later. */
-      CLUTTER_GST_NOTE (MEDIA,
-                        "already seeking/idleing. stacking progress point.");
-      priv->stacked_progress = progress;
-      return;
-    }
-
-  duration_q = gst_query_new_duration (GST_FORMAT_TIME);
-
-  if (gst_element_query (priv->pipeline, duration_q))
-    {
-      gint64 duration = 0;
-
-      gst_query_parse_duration (duration_q, NULL, &duration);
-
-      position = progress * duration;
-    }
-  else
-    position = 0;
-
-  gst_query_unref (duration_q);
-
-  gst_element_seek (priv->pipeline,
-		    1.0,
-		    GST_FORMAT_TIME,
-		    GST_SEEK_FLAG_FLUSH | priv->seek_flags,
-		    GST_SEEK_TYPE_SET,
-		    position,
-		    GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
-
-  priv->in_seek = TRUE;
-  priv->stacked_progress = 0.0;
-
-  CLUTTER_GST_NOTE (MEDIA, "set progress (seeked): %.02f", progress);
-}
-
-static gdouble
-get_progress (ClutterGstVideoTexture *video_texture)
-{
-  ClutterGstVideoTexturePrivate *priv = video_texture->priv;
-  GstQuery *position_q, *duration_q;
-  gdouble progress;
-
-  if (!priv->pipeline)
-    return 0.0;
-
-  /* when hitting an error or after an EOS, playbin2 has some weird values when
-   * querying the duration and progress. We default to 0.0 on error and 1.0 on
-   * EOS */
-  if (priv->in_error)
-    {
-      CLUTTER_GST_NOTE (MEDIA, "get progress (error): 0.0");
-      return 0.0;
-    }
-
-  if (priv->in_eos)
-    {
-      CLUTTER_GST_NOTE (MEDIA, "get progress (eos): 1.0");
-      return 1.0;
-    }
-
-  /* When seeking, the progress returned by playbin2 is 0.0. We want that to be
-   * the last known position instead as returning 0.0 will have some ugly
-   * effects, say on a progress bar getting updated from the progress tick. */
-  if (priv->in_seek || priv->is_changing_uri)
-    {
-      CLUTTER_GST_NOTE (MEDIA, "get progress (target): %.02f",
-                        priv->target_progress);
-      return priv->target_progress;
-    }
-
-  position_q = gst_query_new_position (GST_FORMAT_TIME);
-  duration_q = gst_query_new_duration (GST_FORMAT_TIME);
-
-  if (gst_element_query (priv->pipeline, position_q) &&
-      gst_element_query (priv->pipeline, duration_q))
-    {
-      gint64 position, duration;
-
-      position = duration = 0;
-
-      gst_query_parse_position (position_q, NULL, &position);
-      gst_query_parse_duration (duration_q, NULL, &duration);
-
-      progress = CLAMP ((gdouble) position / (gdouble) duration, 0.0, 1.0);
-    }
-  else
-    progress = 0.0;
-
-  gst_query_unref (position_q);
-  gst_query_unref (duration_q);
-
-  CLUTTER_GST_NOTE (MEDIA, "get progress (pipeline): %.02f", progress);
-
-  return progress;
-}
-
-static void
-set_subtitle_uri (ClutterGstVideoTexture *video_texture,
-                  const gchar            *uri)
-{
-  ClutterGstVideoTexturePrivate *priv = video_texture->priv;
-
-  if (!priv->pipeline)
-    return;
-
-  CLUTTER_GST_NOTE (MEDIA, "setting subtitle URI: %s", uri);
-
-  g_object_set (priv->pipeline, "suburi", uri, NULL);
-}
-
-static void
-set_subtitle_font_name (ClutterGstVideoTexture *video_texture,
-                        const gchar            *font_name)
-{
-  ClutterGstVideoTexturePrivate *priv = video_texture->priv;
-
-  if (!priv->pipeline)
-    return;
-
-  CLUTTER_GST_NOTE (MEDIA, "setting subtitle font to %s", font_name);
-
-  g_free (priv->font_name);
-  priv->font_name = g_strdup (font_name);
-  g_object_set (priv->pipeline, "subtitle-font-desc", font_name, NULL);
-}
-
-static void
-set_audio_volume (ClutterGstVideoTexture *video_texture,
-                  gdouble                 volume)
-{
-  ClutterGstVideoTexturePrivate *priv = video_texture->priv;
-
-  if (!priv->pipeline)
-    return;
-
-  CLUTTER_GST_NOTE (MEDIA, "set volume: %.02f", volume);
-
-  volume = CLAMP (volume, 0.0, 1.0);
-  gst_stream_volume_set_volume (GST_STREAM_VOLUME (priv->pipeline),
-				GST_STREAM_VOLUME_FORMAT_CUBIC,
-				volume);
-  g_object_notify (G_OBJECT (video_texture), "audio-volume");
-}
-
-static gdouble
-get_audio_volume (ClutterGstVideoTexture *video_texture)
-{
-  ClutterGstVideoTexturePrivate *priv = video_texture->priv;
-
-  if (!priv->pipeline)
-    return 0.0;
-
-  CLUTTER_GST_NOTE (MEDIA, "get volume: %.02f", priv->volume);
-
-  return priv->volume;
-}
-
-static void
-clutter_media_init (ClutterMediaIface *iface)
-{
-}
-
-/*
- * ClutterTexture implementation
- */
-
-static void
-clutter_gst_video_texture_size_change (ClutterTexture *texture,
-                                       gint            width,
-                                       gint            height)
-{
-  ClutterGstVideoTexture *video_texture = CLUTTER_GST_VIDEO_TEXTURE (texture);
-  ClutterGstVideoTexturePrivate *priv = video_texture->priv;
-  gboolean changed;
-
-  /* we are being told the actual (as in number of pixels in the buffers)
-   * frame size. Store the values to be used in preferred_width/height() */
-  changed = (priv->buffer_width != width) || (priv->buffer_height != height);
-  priv->buffer_width = width;
-  priv->buffer_height = height;
-
-  if (changed)
-    {
-      /* reset the computed texture dimensions if the underlying frames have
-       * changed size */
-      CLUTTER_GST_NOTE (ASPECT_RATIO, "frame size has been updated to %dx%d",
-                        width, height);
-
-      priv->texture_width = priv->texture_height = 0;
-
-      /* queue a relayout to ask containers/layout manager to ask for
-       * the preferred size again */
-      clutter_actor_queue_relayout (CLUTTER_ACTOR (texture));
-    }
-}
-
-/*
- * Clutter actor implementation
- */
-
-static void
-clutter_gst_video_texture_get_natural_size (ClutterGstVideoTexture *texture,
-                                            gfloat                 *width,
-                                            gfloat                 *height)
-{
-  ClutterGstVideoTexturePrivate *priv = texture->priv;
-  guint dar_n, dar_d;
-  gboolean ret;
-
-  /* we cache texture_width and texture_height */
-
-  if (G_UNLIKELY (priv->buffer_width == 0 || priv->buffer_height == 0))
-    {
-      /* we don't know the size of the frames yet default to 0,0 */
-      priv->texture_width = 0;
-      priv->texture_height = 0;
-    }
-  else if (G_UNLIKELY (priv->texture_width == 0 || priv->texture_height == 0))
-    {
-      CLUTTER_GST_NOTE (ASPECT_RATIO, "frame is %dx%d with par %d/%d",
-                        priv->buffer_width, priv->buffer_height,
-                        priv->par_n, priv->par_d);
-
-      ret = gst_video_calculate_display_ratio (&dar_n, &dar_d,
-                                               priv->buffer_width,
-                                               priv->buffer_height,
-                                               priv->par_n, priv->par_d,
-                                               1, 1);
-      if (ret == FALSE)
-        dar_n = dar_d = 1;
-
-      if (priv->buffer_height % dar_d == 0)
-        {
-          priv->texture_width = gst_util_uint64_scale (priv->buffer_height,
-                                                       dar_n, dar_d);
-          priv->texture_height = priv->buffer_height;
-        }
-      else if (priv->buffer_width % dar_n == 0)
-        {
-          priv->texture_width = priv->buffer_width;
-          priv->texture_height = gst_util_uint64_scale (priv->buffer_width,
-                                                        dar_d, dar_n);
-
-        }
-      else
-        {
-          priv->texture_width = gst_util_uint64_scale (priv->buffer_height,
-                                                       dar_n, dar_d);
-          priv->texture_height = priv->buffer_height;
-        }
-
-      CLUTTER_GST_NOTE (ASPECT_RATIO,
-                        "final size is %dx%d (calculated par is %d/%d)",
-                        priv->texture_width, priv->texture_height,
-                        dar_n, dar_d);
-    }
-
-  if (width)
-    *width = (gfloat)priv->texture_width;
-
-  if (height)
-    *height = (gfloat)priv->texture_height;
-}
-
-static void
-clutter_gst_video_texture_get_preferred_width (ClutterActor *self,
-                                               gfloat        for_height,
-                                               gfloat       *min_width_p,
-                                               gfloat       *natural_width_p)
-{
-  ClutterGstVideoTexture *texture = CLUTTER_GST_VIDEO_TEXTURE (self);
-  ClutterGstVideoTexturePrivate *priv = texture->priv;
-  gboolean sync_size, keep_aspect_ratio;
-  gfloat natural_width, natural_height;
-
-  /* Min request is always 0 since we can scale down or clip */
-  if (min_width_p)
-    *min_width_p = 0;
+  /* Min request is always 0 since we can scale down or clip */
+  if (min_height_p)
+    *min_height_p = 0;
 
   sync_size = clutter_texture_get_sync_size (CLUTTER_TEXTURE (self));
-  keep_aspect_ratio =
-    clutter_texture_get_keep_aspect_ratio (CLUTTER_TEXTURE (self));
-
-  clutter_gst_video_texture_get_natural_size (texture,
-                                              &natural_width,
-                                              &natural_height);
-
-  if (sync_size)
-    {
-      if (natural_width_p)
-        {
-          if (!keep_aspect_ratio ||
-              for_height < 0 ||
-              priv->buffer_height <= 0)
-            {
-              *natural_width_p = natural_width;
-            }
-          else
-            {
-              /* Set the natural width so as to preserve the aspect ratio */
-              gfloat ratio =  natural_width /  natural_height;
-
-              *natural_width_p = ratio * for_height;
-            }
-        }
-    }
-  else
-    {
-      if (natural_width_p)
-        *natural_width_p = 0;
-    }
-}
-
-static void
-clutter_gst_video_texture_get_preferred_height (ClutterActor *self,
-                                                gfloat        for_width,
-                                                gfloat       *min_height_p,
-                                                gfloat       *natural_height_p)
-{
-  ClutterGstVideoTexture *texture = CLUTTER_GST_VIDEO_TEXTURE (self);
-  ClutterGstVideoTexturePrivate *priv = texture->priv;
-  gboolean sync_size, keep_aspect_ratio;
-  gfloat natural_width, natural_height;
-
-  /* Min request is always 0 since we can scale down or clip */
-  if (min_height_p)
-    *min_height_p = 0;
-
-  sync_size = clutter_texture_get_sync_size (CLUTTER_TEXTURE (self));
-  keep_aspect_ratio =
-    clutter_texture_get_keep_aspect_ratio (CLUTTER_TEXTURE (self));
-
-  clutter_gst_video_texture_get_natural_size (texture,
-                                              &natural_width,
-                                              &natural_height);
-
-  if (sync_size)
-    {
-      if (natural_height_p)
-        {
-          if (!keep_aspect_ratio ||
-              for_width < 0 ||
-              priv->buffer_width <= 0)
-            {
-              *natural_height_p = natural_height;
-            }
-          else
-            {
-              /* Set the natural height so as to preserve the aspect ratio */
-              gfloat ratio = natural_height / natural_width;
-
-              *natural_height_p = ratio * for_width;
-            }
-        }
-    }
-  else
-    {
-      if (natural_height_p)
-        *natural_height_p = 0;
-    }
-}
-
-/*
- * ClutterTexture unconditionnaly sets the material color to:
- *    (opacity,opacity,opacity,opacity)
- * so we can't set a black material to the texture. Let's override paint()
- * for now.
- */
-static void
-clutter_gst_video_texture_paint (ClutterActor *actor)
-{
-  ClutterGstVideoTexture *video_texture = (ClutterGstVideoTexture *) actor;
-  ClutterGstVideoTexturePrivate *priv = video_texture->priv;
-  ClutterActorClass *actor_class;
-
-  if (G_UNLIKELY (priv->is_idle))
-    {
-      CoglColor *color;
-      gfloat alpha;
-
-      /* blend the alpha of the idle material with the actor's opacity */
-      color = cogl_color_copy (&priv->idle_color_unpre);
-      alpha = clutter_actor_get_paint_opacity (actor) *
-              cogl_color_get_alpha_byte (color) / 0xff;
-      _cogl_color_set_alpha_byte (color, alpha);
-      cogl_color_premultiply (color);
-      cogl_material_set_color (priv->idle_material, color);
-
-      cogl_set_source (priv->idle_material);
-
-      /* draw */
-      gen_texcoords_and_draw_cogl_rectangle (actor);
-    }
-  else
-    {
-      /* when not idle, just chain up to ClutterTexture::paint() */
-      actor_class =
-        CLUTTER_ACTOR_CLASS (clutter_gst_video_texture_parent_class);
-      actor_class->paint (actor);
-    }
-
-}
-
-/*
- * GObject implementation
- */
-
-static void
-clutter_gst_video_texture_dispose (GObject *object)
-{
-  ClutterGstVideoTexture        *self;
-  ClutterGstVideoTexturePrivate *priv;
-
-  self = CLUTTER_GST_VIDEO_TEXTURE(object);
-  priv = self->priv;
-
-  /* start by doing the usual clean up when not wanting to play an URI */
-  set_uri (self, NULL);
-
-  if (priv->bus)
-    {
-      gst_bus_remove_signal_watch (priv->bus);
-      priv->bus = NULL;
-    }
-
-  if (priv->pipeline)
-    {
-      gst_object_unref (GST_OBJECT (priv->pipeline));
-      priv->pipeline = NULL;
-    }
-
-  G_OBJECT_CLASS (clutter_gst_video_texture_parent_class)->dispose (object);
-}
-
-static void
-clutter_gst_video_texture_finalize (GObject *object)
-{
-  ClutterGstVideoTexture        *self;
-  ClutterGstVideoTexturePrivate *priv;
-
-  self = CLUTTER_GST_VIDEO_TEXTURE (object);
-  priv = self->priv;
-
-  g_free (priv->uri);
-  g_free (priv->font_name);
-  if (priv->idle_material != COGL_INVALID_HANDLE)
-    cogl_handle_unref (priv->idle_material);
-
-  free_string_list (&priv->audio_streams);
-
-  G_OBJECT_CLASS (clutter_gst_video_texture_parent_class)->finalize (object);
-}
-
-static void
-clutter_gst_video_texture_set_property (GObject      *object,
-				        guint         property_id,
-				        const GValue *value,
-				        GParamSpec   *pspec)
-{
-  ClutterGstVideoTexture *video_texture = CLUTTER_GST_VIDEO_TEXTURE (object);
-
-  switch (property_id)
-    {
-    case PROP_URI:
-      set_uri (video_texture, g_value_get_string (value));
-      break;
-
-    case PROP_PLAYING:
-      set_playing (video_texture, g_value_get_boolean (value));
-      break;
-
-    case PROP_PROGRESS:
-      set_progress (video_texture, g_value_get_double (value));
-      break;
-
-    case PROP_SUBTITLE_URI:
-      set_subtitle_uri (video_texture, g_value_get_string (value));
-      break;
-
-    case PROP_SUBTITLE_FONT_NAME:
-      set_subtitle_font_name (video_texture, g_value_get_string (value));
-      break;
-
-    case PROP_AUDIO_VOLUME:
-      set_audio_volume (video_texture, g_value_get_double (value));
-      break;
-
-    case PROP_IDLE_MATERIAL:
-      clutter_gst_video_texture_set_idle_material (video_texture,
-                                                   g_value_get_boxed (value));
-      break;
-
-    case PROP_USER_AGENT:
-      clutter_gst_video_texture_set_user_agent (video_texture,
-                                                g_value_get_string (value));
-      break;
-
-    case PROP_SEEK_FLAGS:
-      clutter_gst_video_texture_set_seek_flags (video_texture,
-                                                g_value_get_flags (value));
-      break;
-
-    case PROP_AUDIO_STREAM:
-      clutter_gst_video_texture_set_audio_stream (video_texture,
-                                                  g_value_get_int (value));
-      break;
-
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-    }
-}
-
-static void
-clutter_gst_video_texture_get_property (GObject    *object,
-				        guint       property_id,
-				        GValue     *value,
-				        GParamSpec *pspec)
-{
-  ClutterGstVideoTexture *video_texture;
-  ClutterGstVideoTexturePrivate *priv;
-  char *str;
-
-  video_texture = CLUTTER_GST_VIDEO_TEXTURE (object);
-  priv = video_texture->priv;
-
-  switch (property_id)
-    {
-    case PROP_URI:
-      g_value_set_string (value, priv->uri);
-      break;
-
-    case PROP_PLAYING:
-      g_value_set_boolean (value, get_playing (video_texture));
-      break;
-
-    case PROP_PROGRESS:
-      g_value_set_double (value, get_progress (video_texture));
-      break;
-
-    case PROP_SUBTITLE_URI:
-      g_object_get (priv->pipeline, "suburi", &str, NULL);
-      g_value_take_string (value, str);
-      break;
-
-    case PROP_SUBTITLE_FONT_NAME:
-      g_value_set_string (value, priv->font_name);
-      break;
-
-    case PROP_AUDIO_VOLUME:
-      g_value_set_double (value, get_audio_volume (video_texture));
-      break;
-
-    case PROP_CAN_SEEK:
-      g_value_set_boolean (value, priv->can_seek);
-      break;
-
-    case PROP_BUFFER_FILL:
-      g_value_set_double (value, priv->buffer_fill);
-      break;
-
-    case PROP_DURATION:
-      g_value_set_double (value, priv->duration);
-      break;
-
-    case PROP_IDLE_MATERIAL:
-      g_value_set_boxed (value, priv->idle_material);
-      break;
-
-    case PROP_USER_AGENT:
-      {
-        gchar *user_agent;
-
-        user_agent = clutter_gst_video_texture_get_user_agent (video_texture);
-        g_value_take_string (value, user_agent);
-      }
-      break;
-
-    case PROP_SEEK_FLAGS:
-      {
-        ClutterGstSeekFlags seek_flags;
-
-        seek_flags = clutter_gst_video_texture_get_seek_flags (video_texture);
-        g_value_set_flags (value, seek_flags);
-      }
-      break;
-
-    case PROP_AUDIO_STREAMS:
-      g_value_set_pointer (value, priv->audio_streams);
-      break;
-
-    case PROP_AUDIO_STREAM:
-      {
-        gint index_;
-
-        index_ = clutter_gst_video_texture_get_audio_stream (video_texture);
-        g_value_set_int (value, index_);
-      }
-      break;
-
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-    }
-}
-
-static void
-clutter_gst_video_texture_class_init (ClutterGstVideoTextureClass *klass)
-{
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
-  ClutterTextureClass *texture_class = CLUTTER_TEXTURE_CLASS (klass);
-  GParamSpec *pspec;
-
-  g_type_class_add_private (klass, sizeof (ClutterGstVideoTexturePrivate));
-
-  object_class->dispose      = clutter_gst_video_texture_dispose;
-  object_class->finalize     = clutter_gst_video_texture_finalize;
-  object_class->set_property = clutter_gst_video_texture_set_property;
-  object_class->get_property = clutter_gst_video_texture_get_property;
-
-  actor_class->paint = clutter_gst_video_texture_paint;
-  actor_class->get_preferred_width =
-    clutter_gst_video_texture_get_preferred_width;
-  actor_class->get_preferred_height =
-    clutter_gst_video_texture_get_preferred_height;
-
-  texture_class->size_change = clutter_gst_video_texture_size_change;
-
-  g_object_class_override_property (object_class,
-                                    PROP_URI, "uri");
-  g_object_class_override_property (object_class,
-                                    PROP_PLAYING, "playing");
-  g_object_class_override_property (object_class,
-                                    PROP_PROGRESS, "progress");
-  g_object_class_override_property (object_class,
-                                    PROP_SUBTITLE_URI, "subtitle-uri");
-  g_object_class_override_property (object_class,
-                                    PROP_SUBTITLE_FONT_NAME,
-                                    "subtitle-font-name");
-  g_object_class_override_property (object_class,
-                                    PROP_AUDIO_VOLUME, "audio-volume");
-  g_object_class_override_property (object_class,
-                                    PROP_CAN_SEEK, "can-seek");
-  g_object_class_override_property (object_class,
-                                    PROP_DURATION, "duration");
-  g_object_class_override_property (object_class,
-                                    PROP_BUFFER_FILL, "buffer-fill");
-
-  pspec = g_param_spec_boxed ("idle-material",
-                              "Idle material",
-                              "Material to use for drawing when not playing",
-                              COGL_TYPE_HANDLE,
-                              CLUTTER_GST_PARAM_READWRITE);
-  g_object_class_install_property (object_class, PROP_IDLE_MATERIAL, pspec);
-
-  pspec = g_param_spec_string ("user-agent",
-                               "User Agent",
-                               "User Agent used with network protocols",
-                               NULL,
-                               CLUTTER_GST_PARAM_READWRITE);
-  g_object_class_install_property (object_class, PROP_USER_AGENT, pspec);
-
-  pspec = g_param_spec_flags ("seek-flags",
-                              "Seek Flags",
-                              "Flags to use when seeking",
-                              CLUTTER_GST_TYPE_SEEK_FLAGS,
-                              CLUTTER_GST_SEEK_FLAG_NONE,
-                              CLUTTER_GST_PARAM_READWRITE);
-  g_object_class_install_property (object_class, PROP_SEEK_FLAGS, pspec);
-
-  pspec = g_param_spec_pointer ("audio-streams",
-                                "Audio Streams",
-                                "List of the audio streams of the media",
-                                CLUTTER_GST_PARAM_READABLE);
-  g_object_class_install_property (object_class, PROP_AUDIO_STREAMS, pspec);
-
-  pspec = g_param_spec_int ("audio-stream",
-                            "Audio Stream",
-                            "Index of the current audio stream",
-                            -1, G_MAXINT, -1,
-                            CLUTTER_GST_PARAM_READWRITE);
-  g_object_class_install_property (object_class, PROP_AUDIO_STREAM, pspec);
-
-  /**
-   * ClutterGstVideoTexture::download-buffering:
-   * @start: Start of the buffer (between 0.0 and 1.0)
-   * @stop: End of the buffer (between 0.0 and 1.0)
-   *
-   * When streaming, GStreamer can cache the data in a buffer on the disk,
-   * something called progressive download or download buffering. This signal
-   * is fired when this streaming mode.
-   */
-  signals[DOWNLOAD_BUFFERING] =
-    g_signal_new ("download-buffering",
-                  G_TYPE_FROM_CLASS (object_class),
-                  G_SIGNAL_RUN_LAST,
-                  G_STRUCT_OFFSET (ClutterGstVideoTextureClass,
-                                   download_buffering),
-                  NULL, NULL,
-                  _clutter_gst_marshal_VOID__DOUBLE_DOUBLE,
-                  G_TYPE_NONE, 2, G_TYPE_DOUBLE, G_TYPE_DOUBLE);
-}
-
-static void
-bus_message_error_cb (GstBus                 *bus,
-                      GstMessage             *message,
-                      ClutterGstVideoTexture *video_texture)
-{
-  GError *error = NULL;
-  ClutterGstVideoTexturePrivate *priv = video_texture->priv;
-
-  gst_element_set_state(priv->pipeline, GST_STATE_NULL);
-
-  gst_message_parse_error (message, &error, NULL);
-
-  /* restore the idle material so we don't just display the last frame */
-  priv->is_idle = TRUE;
-  clutter_actor_queue_redraw (CLUTTER_ACTOR (video_texture));
-
-  g_signal_emit_by_name (video_texture, "error", error);
-
-  g_error_free (error);
-}
-
-static void
-bus_message_eos_cb (GstBus                 *bus,
-                    GstMessage             *message,
-                    ClutterGstVideoTexture *video_texture)
-{
-  ClutterGstVideoTexturePrivate *priv = video_texture->priv;
-
-  CLUTTER_GST_NOTE (MEDIA, "EOS");
-
-  priv->in_eos = TRUE;
-
-  gst_element_set_state(priv->pipeline, GST_STATE_READY);
-
-  /* restore the idle material so we don't just display the last frame */
-  priv->is_idle = TRUE;
-  clutter_actor_queue_redraw (CLUTTER_ACTOR (video_texture));
-
-  g_signal_emit_by_name (video_texture, "eos");
-  g_object_notify (G_OBJECT (video_texture), "progress");
-}
-
-static void
-bus_message_buffering_cb (GstBus                 *bus,
-                          GstMessage             *message,
-                          ClutterGstVideoTexture *video_texture)
-{
-  ClutterGstVideoTexturePrivate *priv = video_texture->priv;
-  GstBufferingMode mode;
-  GstState current_state;
-  gint buffer_percent;
-
-  gst_message_parse_buffering_stats (message, &mode, NULL, NULL, NULL);
-
-  if (mode != GST_BUFFERING_DOWNLOAD)
-    priv->in_download_buffering = FALSE;
-
-  switch (mode)
-    {
-    case GST_BUFFERING_STREAM:
-      gst_message_parse_buffering (message, &buffer_percent);
-      priv->buffer_fill = CLAMP ((gdouble) buffer_percent / 100.0, 0.0, 1.0);
-
-      CLUTTER_GST_NOTE (BUFFERING, "buffer-fill: %.02f", priv->buffer_fill);
-
-      /* The playbin2 documentation says that we need to pause the pipeline
-       * when there's not enough data yet. We try to limit the calls to
-       * gst_element_set_state() */
-      gst_element_get_state (priv->pipeline, &current_state, NULL, 0);
-
-      if (priv->buffer_fill < 1.0)
-        {
-          if (current_state != GST_STATE_PAUSED)
-            {
-              CLUTTER_GST_NOTE (BUFFERING, "pausing the pipeline");
-              gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
-            }
-        }
-      else
-        {
-          if (current_state != priv->target_state)
-            {
-              CLUTTER_GST_NOTE (BUFFERING, "restoring the pipeline");
-              gst_element_set_state (priv->pipeline, priv->target_state);
-            }
-        }
-
-      g_object_notify (G_OBJECT (video_texture), "buffer-fill");
-      break;
-
-    case GST_BUFFERING_DOWNLOAD:
-      /* we rate limit the messages from GStreamer for a usage in a UI (we
-       * don't want *that* many updates). This is done by installing an idle
-       * handler querying the buffer range and sending a signal from there */
-
-      if (priv->in_download_buffering)
-        break;
-
-      /* install the querying idle handler the first time we receive a download
-       * buffering message */
-      configure_buffering_timeout (video_texture, BUFFERING_TIMEOUT);
-
-      /* pause the stream. the idle timeout will set the target state when
-       * having received enough data. We'll use buffer_fill as a "virtual
-       * stream buffer" to signal the application we're buffering until we
-       * can play back from the downloaded stream. */
-      gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
-      priv->buffer_fill = 0.0;
-      g_object_notify (G_OBJECT (video_texture), "buffer-fill");
-
-      priv->download_buffering_element = g_object_ref (message->src);
-      priv->in_download_buffering = TRUE;
-      priv->virtual_stream_buffer_signalled = 0;
-      break;
-
-    case GST_BUFFERING_TIMESHIFT:
-    case GST_BUFFERING_LIVE:
-    default:
-      g_warning ("Buffering mode %d not handled", mode);
-      break;
-    }
-}
-
-static void
-bus_message_duration_cb (GstBus                 *bus,
-                         GstMessage             *message,
-                         ClutterGstVideoTexture *video_texture)
-{
-  gint64 duration;
-
-  /* GstElements send a duration message on the bus with GST_CLOCK_TIME_NONE
-   * as duration to signal a new duration */
-  gst_message_parse_duration (message, NULL, &duration);
-  if (G_UNLIKELY (duration != GST_CLOCK_TIME_NONE))
-    return;
-
-  query_duration (video_texture);
-}
-
-static void
-bus_message_state_change_cb (GstBus                 *bus,
-                             GstMessage             *message,
-                             ClutterGstVideoTexture *video_texture)
-{
-  ClutterGstVideoTexturePrivate *priv = video_texture->priv;
-  GstState old_state, new_state;
-  gpointer src;
-
-  src = GST_MESSAGE_SRC (message);
-  if (src != priv->pipeline)
-    return;
-
-  gst_message_parse_state_changed (message, &old_state, &new_state, NULL);
-
-  CLUTTER_GST_NOTE (MEDIA, "state change:  %s -> %s",
-                    gst_state_to_string (old_state),
-                    gst_state_to_string (new_state));
-
-  if (old_state == new_state)
-    return;
-
-  if (old_state == GST_STATE_READY &&
-      new_state == GST_STATE_PAUSED)
-    {
-      GstQuery *query;
-
-      /* Determine whether we can seek */
-      query = gst_query_new_seeking (GST_FORMAT_TIME);
-
-      if (gst_element_query (priv->pipeline, query))
-        {
-          gboolean can_seek = FALSE;
-
-          gst_query_parse_seeking (query, NULL, &can_seek,
-                                   NULL,
-                                   NULL);
-
-          priv->can_seek = (can_seek == TRUE) ? TRUE : FALSE;
-        }
-      else
-        {
-	  /* could not query for ability to seek by querying the
-           * pipeline; let's crudely try by using the URI
-	   */
-	  if (priv->uri && g_str_has_prefix (priv->uri, "http://";))
-            priv->can_seek = FALSE;
-          else
-            priv->can_seek = TRUE;
-	}
-
-      gst_query_unref (query);
-
-      CLUTTER_GST_NOTE (MEDIA, "can-seek: %d", priv->can_seek);
-
-      g_object_notify (G_OBJECT (video_texture), "can-seek");
-
-      query_duration (video_texture);
-    }
+  keep_aspect_ratio =
+    clutter_texture_get_keep_aspect_ratio (CLUTTER_TEXTURE (self));
 
-  /* is_idle controls the drawing with the idle material */
-  if (new_state == GST_STATE_NULL)
-    priv->is_idle = TRUE;
-  else if (new_state == GST_STATE_PLAYING)
-    {
-      priv->is_idle = FALSE;
-      priv->is_changing_uri = FALSE;
-    }
+  clutter_gst_video_texture_get_natural_size (texture,
+                                              &natural_width,
+                                              &natural_height);
 
-  if (!priv->is_idle)
+  if (sync_size)
     {
-      if (priv->stacked_progress)
+      if (natural_height_p)
         {
-          set_progress (video_texture, priv->stacked_progress);
+          if (!keep_aspect_ratio ||
+              for_width < 0 ||
+              priv->buffer_width <= 0)
+            {
+              *natural_height_p = natural_height;
+            }
+          else
+            {
+              /* Set the natural height so as to preserve the aspect ratio */
+              gfloat ratio = natural_height / natural_width;
+
+              *natural_height_p = ratio * for_width;
+            }
         }
     }
+  else
+    {
+      if (natural_height_p)
+        *natural_height_p = 0;
+    }
 }
 
+/*
+ * ClutterTexture unconditionnaly sets the material color to:
+ *    (opacity,opacity,opacity,opacity)
+ * so we can't set a black material to the texture. Let's override paint()
+ * for now.
+ */
 static void
-bus_message_async_done_cb (GstBus                 *bus,
-                           GstMessage             *message,
-                           ClutterGstVideoTexture *video_texture)
+clutter_gst_video_texture_paint (ClutterActor *actor)
 {
+  ClutterGstVideoTexture *video_texture = (ClutterGstVideoTexture *) actor;
   ClutterGstVideoTexturePrivate *priv = video_texture->priv;
+  ClutterActorClass *actor_class;
 
-  if (priv->in_seek)
+  if (G_UNLIKELY (priv->is_idle))
     {
-      g_object_notify (G_OBJECT (video_texture), "progress");
+      CoglColor *color;
+      gfloat alpha;
 
-      priv->in_seek = FALSE;
+      /* blend the alpha of the idle material with the actor's opacity */
+      color = cogl_color_copy (&priv->idle_color_unpre);
+      alpha = clutter_actor_get_paint_opacity (actor) *
+              cogl_color_get_alpha_byte (color) / 0xff;
+      _cogl_color_set_alpha_byte (color, alpha);
+      cogl_color_premultiply (color);
+      cogl_material_set_color (priv->idle_material, color);
 
-      if (priv->stacked_progress)
-        {
-          set_progress (video_texture, priv->stacked_progress);
-        }
+      cogl_set_source (priv->idle_material);
+
+      /* draw */
+      gen_texcoords_and_draw_cogl_rectangle (actor);
+    }
+  else
+    {
+      /* when not idle, just chain up to ClutterTexture::paint() */
+      actor_class =
+        CLUTTER_ACTOR_CLASS (clutter_gst_video_texture_parent_class);
+      actor_class->paint (actor);
     }
+
 }
 
+/*
+ * GObject implementation
+ */
+
 static void
-on_source_changed (GstElement             *pipeline,
-                   GParamSpec             *pspec,
-                   ClutterGstVideoTexture *video_texture)
+clutter_gst_video_texture_dispose (GObject *object)
 {
-  set_user_agent (video_texture, video_texture->priv->user_agent);
+  G_OBJECT_CLASS (clutter_gst_video_texture_parent_class)->dispose (object);
 }
 
-static gboolean
-on_volume_changed_main_context (gpointer data)
+static void
+clutter_gst_video_texture_finalize (GObject *object)
 {
-  ClutterGstVideoTexture *video_texture = CLUTTER_GST_VIDEO_TEXTURE (data);
-  ClutterGstVideoTexturePrivate *priv = video_texture->priv;
-  gdouble volume;
+  ClutterGstVideoTexture        *self;
+  ClutterGstVideoTexturePrivate *priv;
 
-  volume = gst_stream_volume_get_volume (GST_STREAM_VOLUME (priv->pipeline),
-					 GST_STREAM_VOLUME_FORMAT_CUBIC);
-  priv->volume = volume;
+  self = CLUTTER_GST_VIDEO_TEXTURE (object);
+  priv = self->priv;
 
-  g_object_notify (G_OBJECT (video_texture), "audio-volume");
+  if (priv->idle_material != COGL_INVALID_HANDLE)
+    cogl_handle_unref (priv->idle_material);
 
-  return FALSE;
+  G_OBJECT_CLASS (clutter_gst_video_texture_parent_class)->finalize (object);
 }
 
-/* playbin2 proxies the volume property change notification directly from
- * the element having the "volume" property. This means this callback is
- * called from the thread that runs the element, potentially different from
- * the main thread */
 static void
-on_volume_changed (GstElement             *pipeline,
-		   GParamSpec             *pspec,
-		   ClutterGstVideoTexture *video_texture)
-{
-  g_idle_add (on_volume_changed_main_context, video_texture);
-}
-
-static GList *
-get_tags (GstElement  *pipeline,
-          const gchar *property_name,
-          const gchar *action_signal)
+clutter_gst_video_texture_set_property (GObject      *object,
+				        guint         property_id,
+				        const GValue *value,
+				        GParamSpec   *pspec)
 {
-  GList *ret = NULL;
-  gint num = 1, i, n;
-
-  g_object_get (G_OBJECT (pipeline), property_name, &n, NULL);
-  if (n == 0)
-    return NULL;
+  ClutterGstVideoTexture *video_texture = CLUTTER_GST_VIDEO_TEXTURE (object);
 
-  for (i = 0; i < n; i++)
+  switch (property_id)
     {
-      GstTagList *tags = NULL;
-      gchar *description = NULL;
-
-      g_signal_emit_by_name (G_OBJECT (pipeline), action_signal, i, &tags);
-
-      if (tags)
-        {
-
-          gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &description);
-
-          if (description)
-            {
-              const gchar *language = gst_tag_get_language_name (description);
-
-              if (language)
-                {
-                  g_free (description);
-                  description = g_strdup (language);
-                }
-            }
-
-          if (!description)
-            gst_tag_list_get_string (tags, GST_TAG_CODEC, &description);
-
-          gst_tag_list_free (tags);
-        }
-
-      if (!description)
-        description = g_strdup_printf ("Audio Track #%d", num++);
-
-      ret = g_list_prepend (ret, description);
+    case PROP_IDLE_MATERIAL:
+      clutter_gst_video_texture_set_idle_material (video_texture,
+                                                   g_value_get_boxed (value));
+      break;
 
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
     }
-
-  return g_list_reverse (ret);
 }
 
-static gboolean
-are_lists_equal (GList *list1,
-                 GList *list2)
+static void
+clutter_gst_video_texture_get_property (GObject    *object,
+				        guint       property_id,
+				        GValue     *value,
+				        GParamSpec *pspec)
 {
-  GList *l1, *l2;
+  ClutterGstVideoTexture *video_texture;
+  ClutterGstVideoTexturePrivate *priv;
 
-  l1 = list1;
-  l2 = list2;
+  video_texture = CLUTTER_GST_VIDEO_TEXTURE (object);
+  priv = video_texture->priv;
 
-  while (l1)
+  switch (property_id)
     {
-      const gchar *str1, *str2;
-
-      if (l2 == NULL)
-        return FALSE;
-
-      str1 = l1->data;
-      str2 = l2->data;
-
-      if (g_strcmp0 (str1, str2) != 0)
-        return FALSE;
+    case PROP_IDLE_MATERIAL:
+      g_value_set_boxed (value, priv->idle_material);
+      break;
 
-      l1 = g_list_next (l1);
-      l2 = g_list_next (l2);
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
     }
-
-  return l2 == NULL;
 }
 
-static gboolean
-on_audio_changed_main_context (gpointer data)
+static void
+clutter_gst_video_texture_class_init (ClutterGstVideoTextureClass *klass)
 {
-  ClutterGstVideoTexture *video_texture = CLUTTER_GST_VIDEO_TEXTURE (data);
-  ClutterGstVideoTexturePrivate *priv = video_texture->priv;
-  GList *audio_streams;
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+  ClutterTextureClass *texture_class = CLUTTER_TEXTURE_CLASS (klass);
+  GParamSpec *pspec;
 
-  audio_streams = get_tags (priv->pipeline, "n-audio", "get-audio-tags");
+  g_type_class_add_private (klass, sizeof (ClutterGstVideoTexturePrivate));
 
-  if (!are_lists_equal (priv->audio_streams, audio_streams))
-    {
-      free_string_list (&priv->audio_streams);
-      priv->audio_streams = audio_streams;
+  object_class->dispose      = clutter_gst_video_texture_dispose;
+  object_class->finalize     = clutter_gst_video_texture_finalize;
+  object_class->set_property = clutter_gst_video_texture_set_property;
+  object_class->get_property = clutter_gst_video_texture_get_property;
 
-      CLUTTER_GST_NOTE (AUDIO_STREAM, "audio-streams changed");
+  actor_class->paint = clutter_gst_video_texture_paint;
+  actor_class->get_preferred_width =
+    clutter_gst_video_texture_get_preferred_width;
+  actor_class->get_preferred_height =
+    clutter_gst_video_texture_get_preferred_height;
 
-      g_object_notify (G_OBJECT (video_texture), "audio-streams");
-    }
-  else
-    {
-      free_string_list (&audio_streams);
-    }
+  texture_class->size_change = clutter_gst_video_texture_size_change;
 
-  return FALSE;
-}
+  pspec = g_param_spec_boxed ("idle-material",
+                              "Idle material",
+                              "Material to use for drawing when not playing",
+                              COGL_TYPE_HANDLE,
+                              CLUTTER_GST_PARAM_READWRITE);
+  g_object_class_install_property (object_class, PROP_IDLE_MATERIAL, pspec);
 
-/* same explanation as for notify::volume's usage of g_idle_add() */
-static void
-on_audio_changed (GstElement             *pipeline,
-                  ClutterGstVideoTexture *video_texture)
-{
-  g_idle_add (on_audio_changed_main_context, video_texture);
+  clutter_gst_player_class_init (object_class);
 }
 
 static void
-on_audio_tags_changed (GstElement             *pipeline,
-                       gint                    stream,
-                       ClutterGstVideoTexture *video_texture)
+idle_cb (ClutterGstVideoTexture *video_texture,
+         GParamSpec             *pspec,
+         gpointer                data)
 {
-  g_idle_add (on_audio_changed_main_context, video_texture);
-}
-
-static gboolean
-on_current_audio_changed_main_context (gpointer data)
-{
-  ClutterGstVideoTexture *video_texture = CLUTTER_GST_VIDEO_TEXTURE (data);
-
-  CLUTTER_GST_NOTE (AUDIO_STREAM, "audio stream changed");
-  g_object_notify (G_OBJECT (video_texture), "audio-stream");
+  ClutterGstVideoTexturePrivate *priv = video_texture->priv;
 
-  return FALSE;
-}
+  priv->is_idle =
+    clutter_gst_player_get_idle (CLUTTER_GST_PLAYER (video_texture));
 
-static void
-on_current_audio_changed (GstElement             *pipeline,
-                          GParamSpec             *pspec,
-                          ClutterGstVideoTexture *video_texture)
-{
-  g_idle_add (on_current_audio_changed_main_context, video_texture);
+  /* restore the idle material so we don't just display the last frame */
+  clutter_actor_queue_redraw (CLUTTER_ACTOR (video_texture));
 }
 
 static gboolean
-lay_pipeline (ClutterGstVideoTexture *video_texture)
+setup_pipeline (ClutterGstVideoTexture *video_texture)
 {
-  ClutterGstVideoTexturePrivate *priv = video_texture->priv;
-  GstElement *audio_sink = NULL;
-  GstElement *video_sink = NULL;
+  GstElement *pipeline, *video_sink;
 
-  priv->pipeline = gst_element_factory_make ("playbin2", "pipeline");
-  if (!priv->pipeline)
+  pipeline =
+    clutter_gst_player_get_pipeline (CLUTTER_GST_PLAYER (video_texture));
+  if (!pipeline)
     {
-      g_critical ("Unable to create playbin2 element");
+      g_critical ("Unable to get playbin2 element");
       return FALSE;
     }
 
-  g_signal_connect (priv->pipeline, "notify::source",
-                    G_CALLBACK (on_source_changed), video_texture);
-
-  /* ugh - let's go through the audio sinks
-   *
-   * FIXME - there must be a way to ask gstreamer to do this for us
-   */
-  audio_sink = gst_element_factory_make ("gconfaudiosink", "audio-sink");
-  if (!audio_sink)
-    {
-      audio_sink = gst_element_factory_make ("autoaudiosink", "audio-sink");
-      if (!audio_sink)
-	{
-	  audio_sink = gst_element_factory_make ("alsasink", "audio-sink");
-	  g_warning ("Could not create a GST audio_sink. "
-		     "Audio unavailable.");
-
-          /* do we even need to bother? */
-	  if (!audio_sink)
-	    audio_sink = gst_element_factory_make ("fakesink", "audio-sink");
-	}
-    }
-
   video_sink = clutter_gst_video_sink_new (CLUTTER_TEXTURE (video_texture));
   g_object_set (G_OBJECT (video_sink), "qos", TRUE, "sync", TRUE, NULL);
-  g_object_set (G_OBJECT (priv->pipeline),
+  g_object_set (G_OBJECT (pipeline),
                 "video-sink", video_sink,
-                "audio-sink", audio_sink,
                 "subtitle-font-desc", "Sans 16",
                 NULL);
 
@@ -1906,61 +548,27 @@ clutter_gst_video_texture_init (ClutterGstVideoTexture *video_texture)
                                  CLUTTER_GST_TYPE_VIDEO_TEXTURE,
                                  ClutterGstVideoTexturePrivate);
 
-  if (!lay_pipeline (video_texture))
+  priv->is_idle = TRUE;
+
+  if (!clutter_gst_player_init (CLUTTER_GST_PLAYER (video_texture)))
     {
       g_warning ("Failed to initiate suitable playback pipeline.");
       return;
     }
 
-  create_black_idle_material (video_texture);
+  if (!setup_pipeline (video_texture))
+    {
+      g_warning ("Failed to initiate suitable sinks for pipeline.");
+      return;
+    }
 
-  priv->is_idle = TRUE;
-  priv->in_seek = FALSE;
-  priv->is_changing_uri = FALSE;
-  priv->in_download_buffering = FALSE;
+  create_black_idle_material (video_texture);
 
   priv->par_n = priv->par_d = 1;
 
-  /* We default to not playing until someone calls set_playing(TRUE) */
-  priv->target_state = GST_STATE_PAUSED;
-
-  /* Default to a fast seek, ie. same effect than set_seek_flags (NONE); */
-  priv->seek_flags = GST_SEEK_FLAG_KEY_UNIT;
-
-  priv->bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
-
-  gst_bus_add_signal_watch (priv->bus);
-
-  g_signal_connect_object (priv->bus, "message::error",
-			   G_CALLBACK (bus_message_error_cb),
-			   video_texture, 0);
-  g_signal_connect_object (priv->bus, "message::eos",
-			   G_CALLBACK (bus_message_eos_cb),
-			   video_texture, 0);
-  g_signal_connect_object (priv->bus, "message::buffering",
-			   G_CALLBACK (bus_message_buffering_cb),
-			   video_texture, 0);
-  g_signal_connect_object (priv->bus, "message::duration",
-			   G_CALLBACK (bus_message_duration_cb),
-			   video_texture, 0);
-  g_signal_connect_object (priv->bus, "message::state-changed",
-			   G_CALLBACK (bus_message_state_change_cb),
-			   video_texture, 0);
-  g_signal_connect_object (priv->bus, "message::async-done",
-                           G_CALLBACK (bus_message_async_done_cb),
-                           video_texture, 0);
-
-  g_signal_connect (priv->pipeline, "notify::volume",
-		    G_CALLBACK (on_volume_changed), video_texture);
-
-  g_signal_connect (priv->pipeline, "audio-changed",
-                    G_CALLBACK (on_audio_changed), video_texture);
-  g_signal_connect (priv->pipeline, "audio-tags-changed",
-                    G_CALLBACK (on_audio_tags_changed), video_texture);
-  g_signal_connect (priv->pipeline, "notify::current-audio",
-                    G_CALLBACK (on_current_audio_changed), video_texture);
-
-  gst_object_unref (GST_OBJECT (priv->bus));
+  g_signal_connect (video_texture, "notify::idle",
+                    G_CALLBACK (idle_cb),
+                    NULL);
 }
 
 /*
@@ -2013,9 +621,7 @@ clutter_gst_video_texture_new (void)
 GstElement *
 clutter_gst_video_texture_get_pipeline (ClutterGstVideoTexture *texture)
 {
-  g_return_val_if_fail (CLUTTER_GST_IS_VIDEO_TEXTURE (texture), NULL);
-
-  return texture->priv->pipeline;
+  return clutter_gst_player_get_pipeline (CLUTTER_GST_PLAYER (texture));
 }
 
 /**
@@ -2091,33 +697,7 @@ clutter_gst_video_texture_set_idle_material (ClutterGstVideoTexture *texture,
 gchar *
 clutter_gst_video_texture_get_user_agent (ClutterGstVideoTexture *texture)
 {
-  ClutterGstVideoTexturePrivate *priv;
-  GstElement *source;
-  GParamSpec *pspec;
-  gchar *user_agent;
-
-  g_return_val_if_fail (CLUTTER_GST_IS_VIDEO_TEXTURE (texture), NULL);
-
-  priv = texture->priv;
-
-  /* If the user has set a custom user agent, we just return it even if it is
-   * not used by the current source element of the pipeline */
-  if (priv->user_agent)
-    return g_strdup (priv->user_agent);
-
-  /* If not, we try to retrieve the user agent used by the current source */
-  g_object_get (priv->pipeline, "source", &source, NULL);
-  if (source == NULL)
-    return NULL;
-
-  pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (source),
-                                        "user-agent");
-  if (pspec == NULL)
-    return NULL;
-
-  g_object_get (source, "user-agent", &user_agent, NULL);
-
-  return user_agent;
+  return clutter_gst_player_get_user_agent (CLUTTER_GST_PLAYER (texture));
 }
 
 /**
@@ -2137,18 +717,8 @@ void
 clutter_gst_video_texture_set_user_agent (ClutterGstVideoTexture *texture,
                                           const gchar *           user_agent)
 {
-  ClutterGstVideoTexturePrivate *priv;
-
-  g_return_if_fail (CLUTTER_GST_IS_VIDEO_TEXTURE (texture));
-
-  priv = texture->priv;
-  g_free (priv->user_agent);
-  if (user_agent)
-    priv->user_agent = g_strdup (user_agent);
-  else
-    priv->user_agent = NULL;
-
-  set_user_agent (texture, user_agent);
+  clutter_gst_player_set_user_agent (CLUTTER_GST_PLAYER (texture),
+                                     user_agent);
 }
 
 /**
@@ -2164,13 +734,7 @@ clutter_gst_video_texture_set_user_agent (ClutterGstVideoTexture *texture,
 ClutterGstSeekFlags
 clutter_gst_video_texture_get_seek_flags (ClutterGstVideoTexture *texture)
 {
-  g_return_val_if_fail (CLUTTER_GST_IS_VIDEO_TEXTURE (texture),
-                        CLUTTER_GST_SEEK_FLAG_NONE);
-
-  if (texture->priv->seek_flags == GST_SEEK_FLAG_ACCURATE)
-    return CLUTTER_GST_SEEK_FLAG_ACCURATE;
-  else
-    return CLUTTER_GST_SEEK_FLAG_NONE;
+  return clutter_gst_player_get_seek_flags (CLUTTER_GST_PLAYER (texture));
 }
 
 /**
@@ -2187,15 +751,7 @@ void
 clutter_gst_video_texture_set_seek_flags (ClutterGstVideoTexture *texture,
                                           ClutterGstSeekFlags     flags)
 {
-  ClutterGstVideoTexturePrivate *priv;
-
-  g_return_if_fail (CLUTTER_GST_IS_VIDEO_TEXTURE (texture));
-  priv = texture->priv;
-
-  if (flags == CLUTTER_GST_SEEK_FLAG_NONE)
-    priv->seek_flags = GST_SEEK_FLAG_KEY_UNIT;
-  else if (flags & CLUTTER_GST_SEEK_FLAG_ACCURATE)
-    priv->seek_flags = GST_SEEK_FLAG_ACCURATE;
+  clutter_gst_player_set_seek_flags (CLUTTER_GST_PLAYER (texture), flags);
 }
 
 /**
@@ -2209,18 +765,7 @@ clutter_gst_video_texture_set_seek_flags (ClutterGstVideoTexture *texture,
 ClutterGstBufferingMode
 clutter_gst_video_texture_get_buffering_mode (ClutterGstVideoTexture *texture)
 {
-  ClutterGstVideoTexturePrivate *priv;
-  GstPlayFlags flags;
-
-  g_return_val_if_fail (CLUTTER_GST_IS_VIDEO_TEXTURE (texture),
-                        CLUTTER_GST_BUFFERING_MODE_STREAM);
-  priv = texture->priv;
-
-  g_object_get (G_OBJECT (priv->pipeline), "flags", &flags, NULL);
-  if (flags & GST_PLAY_FLAG_DOWNLOAD)
-    return CLUTTER_GST_BUFFERING_MODE_DOWNLOAD;
-
-  return CLUTTER_GST_BUFFERING_MODE_STREAM;
+  return clutter_gst_player_get_buffering_mode (CLUTTER_GST_PLAYER (texture));
 }
 
 /**
@@ -2234,52 +779,8 @@ void
 clutter_gst_video_texture_set_buffering_mode (ClutterGstVideoTexture *texture,
                                               ClutterGstBufferingMode mode)
 {
-  ClutterGstVideoTexturePrivate *priv;
-  GstPlayFlags flags;
-
-  g_return_if_fail (CLUTTER_GST_IS_VIDEO_TEXTURE (texture));
-  priv = texture->priv;
-
-  g_object_get (G_OBJECT (priv->pipeline), "flags", &flags, NULL);
-
-  switch (mode)
-    {
-    case CLUTTER_GST_BUFFERING_MODE_STREAM:
-      flags &= ~GST_PLAY_FLAG_DOWNLOAD;
-      break;
-    case CLUTTER_GST_BUFFERING_MODE_DOWNLOAD:
-      flags |= GST_PLAY_FLAG_DOWNLOAD;
-      break;
-    default:
-      g_warning ("Unexpected buffering mode %d", mode);
-      break;
-    }
-
-  g_object_set (G_OBJECT (priv->pipeline), "flags", flags, NULL);
-}
-
-#ifdef CLUTTER_GST_ENABLE_DEBUG
-gchar *
-list_to_string (GList *list)
-{
-  GString *string;
-  GList *l;
-  gint n, i;
-
-  if (!list)
-    return g_strdup ("<empty list>");
-
-  string = g_string_new (NULL);
-  n = g_list_length (list);
-  for (i = 0, l = list; i < n - 1; i++, l = g_list_next (l))
-    g_string_append_printf (string, "%s, ", (gchar *) l->data);
-
-  g_string_append_printf (string, "%s", (gchar *) l->data);
-
-
-  return g_string_free (string, FALSE);
+  clutter_gst_player_set_buffering_mode (CLUTTER_GST_PLAYER (texture), mode);
 }
-#endif
 
 /**
  * clutter_gst_video_texture_get_audio_streams:
@@ -2294,20 +795,7 @@ list_to_string (GList *list)
 GList *
 clutter_gst_video_texture_get_audio_streams (ClutterGstVideoTexture *texture)
 {
-  ClutterGstVideoTexturePrivate *priv = texture->priv;
-
-  g_return_val_if_fail (CLUTTER_GST_IS_VIDEO_TEXTURE (texture), NULL);
-
-  if (CLUTTER_GST_DEBUG_ENABLED (AUDIO_STREAM))
-    {
-      gchar *streams;
-
-      streams = list_to_string (priv->audio_streams);
-      CLUTTER_GST_NOTE (AUDIO_STREAM, "audio streams: %s", streams);
-      g_free (streams);
-    }
-
-  return priv->audio_streams;
+  return clutter_gst_player_get_audio_streams (CLUTTER_GST_PLAYER (texture));
 }
 
 /**
@@ -2326,17 +814,7 @@ clutter_gst_video_texture_get_audio_streams (ClutterGstVideoTexture *texture)
 gint
 clutter_gst_video_texture_get_audio_stream (ClutterGstVideoTexture *texture)
 {
-  gint index_ = -1;
-
-  g_return_val_if_fail (CLUTTER_GST_IS_VIDEO_TEXTURE (texture), -1);
-
-  g_object_get (G_OBJECT (texture->priv->pipeline),
-                "current-audio", &index_,
-                NULL);
-
-  CLUTTER_GST_NOTE (AUDIO_STREAM, "audio stream is #%d", index_);
-
-  return index_;
+  return clutter_gst_player_get_audio_stream (CLUTTER_GST_PLAYER (texture));
 }
 
 /**
@@ -2353,13 +831,5 @@ void
 clutter_gst_video_texture_set_audio_stream (ClutterGstVideoTexture *texture,
                                             gint                    index_)
 {
-  ClutterGstVideoTexturePrivate *priv = texture->priv;
-
-  g_return_if_fail (CLUTTER_GST_IS_VIDEO_TEXTURE (texture));
-  g_return_if_fail (index_ >= 0 &&
-                    index_ < g_list_length (priv->audio_streams));
-
-  CLUTTER_GST_NOTE (AUDIO_STREAM, "set audio audio stream to #%d", index_);
-
-  g_object_set (G_OBJECT (priv->pipeline), "current-audio", index_, NULL);
+  clutter_gst_player_set_audio_stream (CLUTTER_GST_PLAYER (texture), index_);
 }



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