play: Introducing the new playback library
authorStephan Hesse <stephan@emliri.com>
Sat, 2 Nov 2019 15:14:13 +0000 (16:14 +0100)
committerPhilippe Normand <philn@igalia.com>
Tue, 9 Mar 2021 18:03:48 +0000 (18:03 +0000)
This aims to be a replacement for the GstPlayer library. In GstPlay, notifications are
sent as application messages through a dedicated GstBus. The GMainContext-based
signal dispatcher was replaced by a GObject signal adapter, now relying on the
bus to emit its signals. The signal dispatcher is now optional and fully
decoupled from the GstPlay object.

Co-authored with: Philippe Normand <philn@igalia.com>

Fixes #394

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/2061>

23 files changed:
docs/libs/play/index.md [new file with mode: 0644]
docs/libs/play/sitemap.txt [new file with mode: 0644]
docs/meson.build
gst-libs/gst/meson.build
gst-libs/gst/play/gstplay-media-info-private.h [new file with mode: 0644]
gst-libs/gst/play/gstplay-media-info.c [new file with mode: 0644]
gst-libs/gst/play/gstplay-media-info.h [new file with mode: 0644]
gst-libs/gst/play/gstplay-message-private.h [new file with mode: 0644]
gst-libs/gst/play/gstplay-signal-adapter.c [new file with mode: 0644]
gst-libs/gst/play/gstplay-signal-adapter.h [new file with mode: 0644]
gst-libs/gst/play/gstplay-types.h [new file with mode: 0644]
gst-libs/gst/play/gstplay-video-overlay-video-renderer.c [new file with mode: 0644]
gst-libs/gst/play/gstplay-video-overlay-video-renderer.h [new file with mode: 0644]
gst-libs/gst/play/gstplay-video-renderer-private.h [new file with mode: 0644]
gst-libs/gst/play/gstplay-video-renderer.c [new file with mode: 0644]
gst-libs/gst/play/gstplay-video-renderer.h [new file with mode: 0644]
gst-libs/gst/play/gstplay-visualization.c [new file with mode: 0644]
gst-libs/gst/play/gstplay-visualization.h [new file with mode: 0644]
gst-libs/gst/play/gstplay.c [new file with mode: 0644]
gst-libs/gst/play/gstplay.h [new file with mode: 0644]
gst-libs/gst/play/meson.build [new file with mode: 0644]
gst-libs/gst/play/play-prelude.h [new file with mode: 0644]
gst-libs/gst/play/play.h [new file with mode: 0644]

diff --git a/docs/libs/play/index.md b/docs/libs/play/index.md
new file mode 100644 (file)
index 0000000..5b06b36
--- /dev/null
@@ -0,0 +1,3 @@
+# Play Library
+
+> NOTE: This library API is considered *unstable*
diff --git a/docs/libs/play/sitemap.txt b/docs/libs/play/sitemap.txt
new file mode 100644 (file)
index 0000000..4f91fcd
--- /dev/null
@@ -0,0 +1 @@
+gi-index
index 25d5743..d3489fb 100644 (file)
@@ -107,6 +107,7 @@ libs = []
 if build_gir
     libs = [
         {'name': 'mpegts', 'gir': mpegts_gir, 'lib': gstmpegts_dep},
+        {'name': 'play', 'gir': play_gir, 'lib': gstplay_dep},
         {'name': 'player', 'gir': player_gir, 'lib': gstplayer_dep},
         {'name': 'insertbin', 'gir': insertbin_gir, 'lib': gstinsertbin_dep},
         {'name': 'codecparsers', 'lib': gstcodecparsers_dep},
index 575d1b8..809ab4a 100644 (file)
@@ -11,6 +11,7 @@ subdir('interfaces')
 subdir('isoff')
 subdir('mpegts')
 subdir('opencv')
+subdir('play')
 subdir('player')
 subdir('sctp')
 subdir('transcoder')
diff --git a/gst-libs/gst/play/gstplay-media-info-private.h b/gst-libs/gst/play/gstplay-media-info-private.h
new file mode 100644 (file)
index 0000000..a6ec7ee
--- /dev/null
@@ -0,0 +1,126 @@
+/* GStreamer
+ *
+ * Copyright (C) 2015 Brijesh Singh <brijesh.ksingh@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "gstplay-media-info.h"
+
+#ifndef __GST_PLAY_MEDIA_INFO_PRIVATE_H__
+#define __GST_PLAY_MEDIA_INFO_PRIVATE_H__
+
+struct _GstPlayStreamInfo
+{
+  GObject parent;
+
+  gchar *codec;
+
+  GstCaps *caps;
+  gint stream_index;
+  GstTagList  *tags;
+  gchar *stream_id;
+};
+
+struct _GstPlayStreamInfoClass
+{
+  GObjectClass parent_class;
+};
+
+struct _GstPlaySubtitleInfo
+{
+  GstPlayStreamInfo  parent;
+
+  gchar *language;
+};
+
+struct _GstPlaySubtitleInfoClass
+{
+  GstPlayStreamInfoClass parent_class;
+};
+
+struct _GstPlayAudioInfo
+{
+  GstPlayStreamInfo  parent;
+
+  gint channels;
+  gint sample_rate;
+
+  guint bitrate;
+  guint max_bitrate;
+
+  gchar *language;
+};
+
+struct _GstPlayAudioInfoClass
+{
+  GstPlayStreamInfoClass parent_class;
+};
+
+struct _GstPlayVideoInfo
+{
+  GstPlayStreamInfo  parent;
+
+  gint width;
+  gint height;
+  gint framerate_num;
+  gint framerate_denom;
+  gint par_num;
+  gint par_denom;
+
+  guint bitrate;
+  guint max_bitrate;
+};
+
+struct _GstPlayVideoInfoClass
+{
+  GstPlayStreamInfoClass parent_class;
+};
+
+struct _GstPlayMediaInfo
+{
+  GObject parent;
+
+  gchar *uri;
+  gchar *title;
+  gchar *container;
+  gboolean seekable, is_live;
+  GstTagList *tags;
+  GstSample *image_sample;
+
+  GList *stream_list;
+  GList *audio_stream_list;
+  GList *video_stream_list;
+  GList *subtitle_stream_list;
+
+  GstClockTime  duration;
+};
+
+struct _GstPlayMediaInfoClass
+{
+  GObjectClass parent_class;
+};
+
+G_GNUC_INTERNAL GstPlayMediaInfo*   gst_play_media_info_new
+                                      (const gchar *uri);
+G_GNUC_INTERNAL GstPlayMediaInfo*   gst_play_media_info_copy
+                                      (GstPlayMediaInfo *ref);
+G_GNUC_INTERNAL GstPlayStreamInfo*  gst_play_stream_info_new
+                                      (gint stream_index, GType type);
+G_GNUC_INTERNAL GstPlayStreamInfo*  gst_play_stream_info_copy
+                                      (GstPlayStreamInfo *ref);
+
+#endif /* __GST_PLAY_MEDIA_INFO_PRIVATE_H__ */
diff --git a/gst-libs/gst/play/gstplay-media-info.c b/gst-libs/gst/play/gstplay-media-info.c
new file mode 100644 (file)
index 0000000..c1f357e
--- /dev/null
@@ -0,0 +1,933 @@
+/* GStreamer
+ *
+ * Copyright (C) 2015 Brijesh Singh <brijesh.ksingh@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:gstplay-mediainfo
+ * @title: GstPlayMediaInfo
+ * @short_description: Play Media Information
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstplay-media-info.h"
+#include "gstplay-media-info-private.h"
+
+/* Per-stream information */
+G_DEFINE_ABSTRACT_TYPE (GstPlayStreamInfo, gst_play_stream_info, G_TYPE_OBJECT);
+
+static void
+gst_play_stream_info_init (GstPlayStreamInfo * sinfo)
+{
+  sinfo->stream_index = -1;
+}
+
+static void
+gst_play_stream_info_finalize (GObject * object)
+{
+  GstPlayStreamInfo *sinfo = GST_PLAY_STREAM_INFO (object);
+
+  g_free (sinfo->codec);
+  g_free (sinfo->stream_id);
+
+  if (sinfo->caps)
+    gst_caps_unref (sinfo->caps);
+
+  if (sinfo->tags)
+    gst_tag_list_unref (sinfo->tags);
+
+  G_OBJECT_CLASS (gst_play_stream_info_parent_class)->finalize (object);
+}
+
+static void
+gst_play_stream_info_class_init (GstPlayStreamInfoClass * klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+
+  gobject_class->finalize = gst_play_stream_info_finalize;
+}
+
+/**
+ * gst_play_stream_info_get_index:
+ * @info: a #GstPlayStreamInfo
+ *
+ * Function to get stream index from #GstPlayStreamInfo instance.
+ *
+ * Returns: the stream index of this stream.
+ * Since: 1.20
+ */
+gint
+gst_play_stream_info_get_index (const GstPlayStreamInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_STREAM_INFO (info), -1);
+
+  return info->stream_index;
+}
+
+/**
+ * gst_play_stream_info_get_stream_type:
+ * @info: a #GstPlayStreamInfo
+ *
+ * Function to return human readable name for the stream type
+ * of the given @info (ex: "audio", "video", "subtitle")
+ *
+ * Returns: a human readable name
+ * Since: 1.20
+ */
+const gchar *
+gst_play_stream_info_get_stream_type (const GstPlayStreamInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_STREAM_INFO (info), NULL);
+
+  if (GST_IS_PLAY_VIDEO_INFO (info))
+    return "video";
+  else if (GST_IS_PLAY_AUDIO_INFO (info))
+    return "audio";
+  else
+    return "subtitle";
+}
+
+/**
+ * gst_play_stream_info_get_tags:
+ * @info: a #GstPlayStreamInfo
+ *
+ * Returns: (transfer none): the tags contained in this stream.
+ * Since: 1.20
+ */
+GstTagList *
+gst_play_stream_info_get_tags (const GstPlayStreamInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_STREAM_INFO (info), NULL);
+
+  return info->tags;
+}
+
+/**
+ * gst_play_stream_info_get_codec:
+ * @info: a #GstPlayStreamInfo
+ *
+ * A string describing codec used in #GstPlayStreamInfo.
+ *
+ * Returns: codec string or NULL on unknown.
+ * Since: 1.20
+ */
+const gchar *
+gst_play_stream_info_get_codec (const GstPlayStreamInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_STREAM_INFO (info), NULL);
+
+  return info->codec;
+}
+
+/**
+ * gst_play_stream_info_get_caps:
+ * @info: a #GstPlayStreamInfo
+ *
+ * Returns: (transfer none): the #GstCaps of the stream.
+ * Since: 1.20
+ */
+GstCaps *
+gst_play_stream_info_get_caps (const GstPlayStreamInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_STREAM_INFO (info), NULL);
+
+  return info->caps;
+}
+
+/* Video information */
+G_DEFINE_TYPE (GstPlayVideoInfo, gst_play_video_info,
+    GST_TYPE_PLAY_STREAM_INFO);
+
+static void
+gst_play_video_info_init (GstPlayVideoInfo * info)
+{
+  info->width = -1;
+  info->height = -1;
+  info->framerate_num = 0;
+  info->framerate_denom = 1;
+  info->par_num = 1;
+  info->par_denom = 1;
+}
+
+static void
+gst_play_video_info_class_init (G_GNUC_UNUSED GstPlayVideoInfoClass * klass)
+{
+  /* nothing to do here */
+}
+
+/**
+ * gst_play_video_info_get_width:
+ * @info: a #GstPlayVideoInfo
+ *
+ * Returns: the width of video in #GstPlayVideoInfo.
+ * Since: 1.20
+ */
+gint
+gst_play_video_info_get_width (const GstPlayVideoInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_VIDEO_INFO (info), -1);
+
+  return info->width;
+}
+
+/**
+ * gst_play_video_info_get_height:
+ * @info: a #GstPlayVideoInfo
+ *
+ * Returns: the height of video in #GstPlayVideoInfo.
+ * Since: 1.20
+ */
+gint
+gst_play_video_info_get_height (const GstPlayVideoInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_VIDEO_INFO (info), -1);
+
+  return info->height;
+}
+
+/**
+ * gst_play_video_info_get_framerate:
+ * @info: a #GstPlayVideoInfo
+ * @fps_n: (out): Numerator of frame rate
+ * @fps_d: (out): Denominator of frame rate
+ *
+ * Since: 1.20
+ */
+void
+gst_play_video_info_get_framerate (const GstPlayVideoInfo * info,
+    gint * fps_n, gint * fps_d)
+{
+  g_return_if_fail (GST_IS_PLAY_VIDEO_INFO (info));
+
+  *fps_n = info->framerate_num;
+  *fps_d = info->framerate_denom;
+}
+
+/**
+ * gst_play_video_info_get_pixel_aspect_ratio:
+ * @info: a #GstPlayVideoInfo
+ * @par_n: (out): numerator
+ * @par_d: (out): denominator
+ *
+ * Returns the pixel aspect ratio in @par_n and @par_d
+ *
+ * Since: 1.20
+ */
+void
+gst_play_video_info_get_pixel_aspect_ratio (const GstPlayVideoInfo * info,
+    guint * par_n, guint * par_d)
+{
+  g_return_if_fail (GST_IS_PLAY_VIDEO_INFO (info));
+
+  *par_n = info->par_num;
+  *par_d = info->par_denom;
+}
+
+/**
+ * gst_play_video_info_get_bitrate:
+ * @info: a #GstPlayVideoInfo
+ *
+ * Returns: the current bitrate of video in #GstPlayVideoInfo.
+ * Since: 1.20
+ */
+gint
+gst_play_video_info_get_bitrate (const GstPlayVideoInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_VIDEO_INFO (info), -1);
+
+  return info->bitrate;
+}
+
+/**
+ * gst_play_video_info_get_max_bitrate:
+ * @info: a #GstPlayVideoInfo
+ *
+ * Returns: the maximum bitrate of video in #GstPlayVideoInfo.
+ * Since: 1.20
+ */
+gint
+gst_play_video_info_get_max_bitrate (const GstPlayVideoInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_VIDEO_INFO (info), -1);
+
+  return info->max_bitrate;
+}
+
+/* Audio information */
+G_DEFINE_TYPE (GstPlayAudioInfo, gst_play_audio_info,
+    GST_TYPE_PLAY_STREAM_INFO);
+
+static void
+gst_play_audio_info_init (GstPlayAudioInfo * info)
+{
+  info->channels = 0;
+  info->sample_rate = 0;
+  info->bitrate = -1;
+  info->max_bitrate = -1;
+}
+
+static void
+gst_play_audio_info_finalize (GObject * object)
+{
+  GstPlayAudioInfo *info = GST_PLAY_AUDIO_INFO (object);
+
+  g_free (info->language);
+
+  G_OBJECT_CLASS (gst_play_audio_info_parent_class)->finalize (object);
+}
+
+static void
+gst_play_audio_info_class_init (GstPlayAudioInfoClass * klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+
+  gobject_class->finalize = gst_play_audio_info_finalize;
+}
+
+/**
+ * gst_play_audio_info_get_language:
+ * @info: a #GstPlayAudioInfo
+ *
+ * Returns: the language of the stream, or NULL if unknown.
+ * Since: 1.20
+ */
+const gchar *
+gst_play_audio_info_get_language (const GstPlayAudioInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_AUDIO_INFO (info), NULL);
+
+  return info->language;
+}
+
+/**
+ * gst_play_audio_info_get_channels:
+ * @info: a #GstPlayAudioInfo
+ *
+ * Returns: the number of audio channels in #GstPlayAudioInfo.
+ * Since: 1.20
+ */
+gint
+gst_play_audio_info_get_channels (const GstPlayAudioInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_AUDIO_INFO (info), 0);
+
+  return info->channels;
+}
+
+/**
+ * gst_play_audio_info_get_sample_rate:
+ * @info: a #GstPlayAudioInfo
+ *
+ * Returns: the audio sample rate in #GstPlayAudioInfo.
+ * Since: 1.20
+ */
+gint
+gst_play_audio_info_get_sample_rate (const GstPlayAudioInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_AUDIO_INFO (info), 0);
+
+  return info->sample_rate;
+}
+
+/**
+ * gst_play_audio_info_get_bitrate:
+ * @info: a #GstPlayAudioInfo
+ *
+ * Returns: the audio bitrate in #GstPlayAudioInfo.
+ * Since: 1.20
+ */
+gint
+gst_play_audio_info_get_bitrate (const GstPlayAudioInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_AUDIO_INFO (info), -1);
+
+  return info->bitrate;
+}
+
+/**
+ * gst_play_audio_info_get_max_bitrate:
+ * @info: a #GstPlayAudioInfo
+ *
+ * Returns: the audio maximum bitrate in #GstPlayAudioInfo.
+ * Since: 1.20
+ */
+gint
+gst_play_audio_info_get_max_bitrate (const GstPlayAudioInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_AUDIO_INFO (info), -1);
+
+  return info->max_bitrate;
+}
+
+/* Subtitle information */
+G_DEFINE_TYPE (GstPlaySubtitleInfo, gst_play_subtitle_info,
+    GST_TYPE_PLAY_STREAM_INFO);
+
+static void
+gst_play_subtitle_info_init (G_GNUC_UNUSED GstPlaySubtitleInfo * info)
+{
+  /* nothing to do */
+}
+
+static void
+gst_play_subtitle_info_finalize (GObject * object)
+{
+  GstPlaySubtitleInfo *info = GST_PLAY_SUBTITLE_INFO (object);
+
+  g_free (info->language);
+
+  G_OBJECT_CLASS (gst_play_subtitle_info_parent_class)->finalize (object);
+}
+
+static void
+gst_play_subtitle_info_class_init (GstPlaySubtitleInfoClass * klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+
+  gobject_class->finalize = gst_play_subtitle_info_finalize;
+}
+
+/**
+ * gst_play_subtitle_info_get_language:
+ * @info: a #GstPlaySubtitleInfo
+ *
+ * Returns: the language of the stream, or NULL if unknown.
+ * Since: 1.20
+ */
+const gchar *
+gst_play_subtitle_info_get_language (const GstPlaySubtitleInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_SUBTITLE_INFO (info), NULL);
+
+  return info->language;
+}
+
+/* Global media information */
+G_DEFINE_TYPE (GstPlayMediaInfo, gst_play_media_info, G_TYPE_OBJECT);
+
+static void
+gst_play_media_info_init (GstPlayMediaInfo * info)
+{
+  info->duration = -1;
+  info->is_live = FALSE;
+  info->seekable = FALSE;
+}
+
+static void
+gst_play_media_info_finalize (GObject * object)
+{
+  GstPlayMediaInfo *info = GST_PLAY_MEDIA_INFO (object);
+
+  g_free (info->uri);
+
+  if (info->tags)
+    gst_tag_list_unref (info->tags);
+
+  g_free (info->title);
+
+  g_free (info->container);
+
+  if (info->image_sample)
+    gst_sample_unref (info->image_sample);
+
+  if (info->audio_stream_list)
+    g_list_free (info->audio_stream_list);
+
+  if (info->video_stream_list)
+    g_list_free (info->video_stream_list);
+
+  if (info->subtitle_stream_list)
+    g_list_free (info->subtitle_stream_list);
+
+  if (info->stream_list)
+    g_list_free_full (info->stream_list, g_object_unref);
+
+  G_OBJECT_CLASS (gst_play_media_info_parent_class)->finalize (object);
+}
+
+static void
+gst_play_media_info_class_init (GstPlayMediaInfoClass * klass)
+{
+  GObjectClass *oclass = (GObjectClass *) klass;
+
+  oclass->finalize = gst_play_media_info_finalize;
+}
+
+static GstPlayVideoInfo *
+gst_play_video_info_new (void)
+{
+  return g_object_new (GST_TYPE_PLAY_VIDEO_INFO, NULL);
+}
+
+static GstPlayAudioInfo *
+gst_play_audio_info_new (void)
+{
+  return g_object_new (GST_TYPE_PLAY_AUDIO_INFO, NULL);
+}
+
+static GstPlaySubtitleInfo *
+gst_play_subtitle_info_new (void)
+{
+  return g_object_new (GST_TYPE_PLAY_SUBTITLE_INFO, NULL);
+}
+
+static GstPlayStreamInfo *
+gst_play_video_info_copy (GstPlayVideoInfo * ref)
+{
+  GstPlayVideoInfo *ret;
+
+  ret = gst_play_video_info_new ();
+
+  ret->width = ref->width;
+  ret->height = ref->height;
+  ret->framerate_num = ref->framerate_num;
+  ret->framerate_denom = ref->framerate_denom;
+  ret->par_num = ref->par_num;
+  ret->par_denom = ref->par_denom;
+  ret->bitrate = ref->bitrate;
+  ret->max_bitrate = ref->max_bitrate;
+
+  return (GstPlayStreamInfo *) ret;
+}
+
+static GstPlayStreamInfo *
+gst_play_audio_info_copy (GstPlayAudioInfo * ref)
+{
+  GstPlayAudioInfo *ret;
+
+  ret = gst_play_audio_info_new ();
+
+  ret->sample_rate = ref->sample_rate;
+  ret->channels = ref->channels;
+  ret->bitrate = ref->bitrate;
+  ret->max_bitrate = ref->max_bitrate;
+
+  if (ref->language)
+    ret->language = g_strdup (ref->language);
+
+  return (GstPlayStreamInfo *) ret;
+}
+
+static GstPlayStreamInfo *
+gst_play_subtitle_info_copy (GstPlaySubtitleInfo * ref)
+{
+  GstPlaySubtitleInfo *ret;
+
+  ret = gst_play_subtitle_info_new ();
+  if (ref->language)
+    ret->language = g_strdup (ref->language);
+
+  return (GstPlayStreamInfo *) ret;
+}
+
+GstPlayStreamInfo *
+gst_play_stream_info_copy (GstPlayStreamInfo * ref)
+{
+  GstPlayStreamInfo *info = NULL;
+
+  if (!ref)
+    return NULL;
+
+  if (GST_IS_PLAY_VIDEO_INFO (ref))
+    info = gst_play_video_info_copy ((GstPlayVideoInfo *) ref);
+  else if (GST_IS_PLAY_AUDIO_INFO (ref))
+    info = gst_play_audio_info_copy ((GstPlayAudioInfo *) ref);
+  else
+    info = gst_play_subtitle_info_copy ((GstPlaySubtitleInfo *) ref);
+
+  info->stream_index = ref->stream_index;
+  if (ref->tags)
+    info->tags = gst_tag_list_ref (ref->tags);
+  if (ref->caps)
+    info->caps = gst_caps_copy (ref->caps);
+  if (ref->codec)
+    info->codec = g_strdup (ref->codec);
+  if (ref->stream_id)
+    info->stream_id = g_strdup (ref->stream_id);
+
+  return info;
+}
+
+GstPlayMediaInfo *
+gst_play_media_info_copy (GstPlayMediaInfo * ref)
+{
+  GList *l;
+  GstPlayMediaInfo *info;
+
+  if (!ref)
+    return NULL;
+
+  info = gst_play_media_info_new (ref->uri);
+  info->duration = ref->duration;
+  info->seekable = ref->seekable;
+  info->is_live = ref->is_live;
+  if (ref->tags)
+    info->tags = gst_tag_list_ref (ref->tags);
+  if (ref->title)
+    info->title = g_strdup (ref->title);
+  if (ref->container)
+    info->container = g_strdup (ref->container);
+  if (ref->image_sample)
+    info->image_sample = gst_sample_ref (ref->image_sample);
+
+  for (l = ref->stream_list; l != NULL; l = l->next) {
+    GstPlayStreamInfo *s;
+
+    s = gst_play_stream_info_copy ((GstPlayStreamInfo *) l->data);
+    info->stream_list = g_list_append (info->stream_list, s);
+
+    if (GST_IS_PLAY_AUDIO_INFO (s))
+      info->audio_stream_list = g_list_append (info->audio_stream_list, s);
+    else if (GST_IS_PLAY_VIDEO_INFO (s))
+      info->video_stream_list = g_list_append (info->video_stream_list, s);
+    else
+      info->subtitle_stream_list =
+          g_list_append (info->subtitle_stream_list, s);
+  }
+
+  return info;
+}
+
+GstPlayStreamInfo *
+gst_play_stream_info_new (gint stream_index, GType type)
+{
+  GstPlayStreamInfo *info = NULL;
+
+  if (type == GST_TYPE_PLAY_AUDIO_INFO)
+    info = (GstPlayStreamInfo *) gst_play_audio_info_new ();
+  else if (type == GST_TYPE_PLAY_VIDEO_INFO)
+    info = (GstPlayStreamInfo *) gst_play_video_info_new ();
+  else
+    info = (GstPlayStreamInfo *) gst_play_subtitle_info_new ();
+
+  info->stream_index = stream_index;
+
+  return info;
+}
+
+GstPlayMediaInfo *
+gst_play_media_info_new (const gchar * uri)
+{
+  GstPlayMediaInfo *info;
+
+  g_return_val_if_fail (uri != NULL, NULL);
+
+  info = g_object_new (GST_TYPE_PLAY_MEDIA_INFO, NULL);
+  info->uri = g_strdup (uri);
+
+  return info;
+}
+
+/**
+ * gst_play_media_info_get_uri:
+ * @info: a #GstPlayMediaInfo
+ *
+ * Returns: the URI associated with #GstPlayMediaInfo.
+ * Since: 1.20
+ */
+const gchar *
+gst_play_media_info_get_uri (const GstPlayMediaInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_MEDIA_INFO (info), NULL);
+
+  return info->uri;
+}
+
+/**
+ * gst_play_media_info_is_seekable:
+ * @info: a #GstPlayMediaInfo
+ *
+ * Returns: %TRUE if the media is seekable.
+ * Since: 1.20
+ */
+gboolean
+gst_play_media_info_is_seekable (const GstPlayMediaInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_MEDIA_INFO (info), FALSE);
+
+  return info->seekable;
+}
+
+/**
+ * gst_play_media_info_is_live:
+ * @info: a #GstPlayMediaInfo
+ *
+ * Returns: %TRUE if the media is live.
+ * Since: 1.20
+ */
+gboolean
+gst_play_media_info_is_live (const GstPlayMediaInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_MEDIA_INFO (info), FALSE);
+
+  return info->is_live;
+}
+
+/**
+ * gst_play_media_info_get_stream_list:
+ * @info: a #GstPlayMediaInfo
+ *
+ * Returns: (transfer none) (element-type GstPlayStreamInfo): A #GList of
+ * matching #GstPlayStreamInfo.
+ * Since: 1.20
+ */
+GList *
+gst_play_media_info_get_stream_list (const GstPlayMediaInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_MEDIA_INFO (info), NULL);
+
+  return info->stream_list;
+}
+
+/**
+ * gst_play_media_info_get_video_streams:
+ * @info: a #GstPlayMediaInfo
+ *
+ * Returns: (transfer none) (element-type GstPlayVideoInfo): A #GList of
+ * matching #GstPlayVideoInfo.
+ * Since: 1.20
+ */
+GList *
+gst_play_media_info_get_video_streams (const GstPlayMediaInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_MEDIA_INFO (info), NULL);
+
+  return info->video_stream_list;
+}
+
+/**
+ * gst_play_media_info_get_subtitle_streams:
+ * @info: a #GstPlayMediaInfo
+ *
+ * Returns: (transfer none) (element-type GstPlaySubtitleInfo): A #GList of
+ * matching #GstPlaySubtitleInfo.
+ * Since: 1.20
+ */
+GList *
+gst_play_media_info_get_subtitle_streams (const GstPlayMediaInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_MEDIA_INFO (info), NULL);
+
+  return info->subtitle_stream_list;
+}
+
+/**
+ * gst_play_media_info_get_audio_streams:
+ * @info: a #GstPlayMediaInfo
+ *
+ * Returns: (transfer none) (element-type GstPlayAudioInfo): A #GList of
+ * matching #GstPlayAudioInfo.
+ * Since: 1.20
+ */
+GList *
+gst_play_media_info_get_audio_streams (const GstPlayMediaInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_MEDIA_INFO (info), NULL);
+
+  return info->audio_stream_list;
+}
+
+/**
+ * gst_play_media_info_get_duration:
+ * @info: a #GstPlayMediaInfo
+ *
+ * Returns: duration of the media.
+ * Since: 1.20
+ */
+GstClockTime
+gst_play_media_info_get_duration (const GstPlayMediaInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_MEDIA_INFO (info), -1);
+
+  return info->duration;
+}
+
+/**
+ * gst_play_media_info_get_tags:
+ * @info: a #GstPlayMediaInfo
+ *
+ * Returns: (transfer none): the tags contained in media info.
+ * Since: 1.20
+ */
+GstTagList *
+gst_play_media_info_get_tags (const GstPlayMediaInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_MEDIA_INFO (info), NULL);
+
+  return info->tags;
+}
+
+/**
+ * gst_play_media_info_get_title:
+ * @info: a #GstPlayMediaInfo
+ *
+ * Returns: the media title.
+ * Since: 1.20
+ */
+const gchar *
+gst_play_media_info_get_title (const GstPlayMediaInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_MEDIA_INFO (info), NULL);
+
+  return info->title;
+}
+
+/**
+ * gst_play_media_info_get_container_format:
+ * @info: a #GstPlayMediaInfo
+ *
+ * Returns: the container format.
+ * Since: 1.20
+ */
+const gchar *
+gst_play_media_info_get_container_format (const GstPlayMediaInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_MEDIA_INFO (info), NULL);
+
+  return info->container;
+}
+
+/**
+ * gst_play_media_info_get_image_sample:
+ * @info: a #GstPlayMediaInfo
+ *
+ * Function to get the image (or preview-image) stored in taglist.
+ * Application can use `gst_sample_*_()` API's to get caps, buffer etc.
+ *
+ * Returns: (transfer none): GstSample or NULL.
+ * Since: 1.20
+ */
+GstSample *
+gst_play_media_info_get_image_sample (const GstPlayMediaInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_MEDIA_INFO (info), NULL);
+
+  return info->image_sample;
+}
+
+/**
+ * gst_play_media_info_get_number_of_streams:
+ * @info: a #GstPlayMediaInfo
+ *
+ * Returns: number of total streams.
+ * Since: 1.20
+ */
+guint
+gst_play_media_info_get_number_of_streams (const GstPlayMediaInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_MEDIA_INFO (info), 0);
+
+  return g_list_length (info->stream_list);
+}
+
+/**
+ * gst_play_media_info_get_number_of_video_streams:
+ * @info: a #GstPlayMediaInfo
+ *
+ * Returns: number of video streams.
+ * Since: 1.20
+ */
+guint
+gst_play_media_info_get_number_of_video_streams (const GstPlayMediaInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_MEDIA_INFO (info), 0);
+
+  return g_list_length (info->video_stream_list);
+}
+
+/**
+ * gst_play_media_info_get_number_of_audio_streams:
+ * @info: a #GstPlayMediaInfo
+ *
+ * Returns: number of audio streams.
+ * Since: 1.20
+ */
+guint
+gst_play_media_info_get_number_of_audio_streams (const GstPlayMediaInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_MEDIA_INFO (info), 0);
+
+  return g_list_length (info->audio_stream_list);
+}
+
+/**
+ * gst_play_media_info_get_number_of_subtitle_streams:
+ * @info: a #GstPlayMediaInfo
+ *
+ * Returns: number of subtitle streams.
+ * Since: 1.20
+ */
+guint gst_play_media_info_get_number_of_subtitle_streams
+    (const GstPlayMediaInfo * info)
+{
+  g_return_val_if_fail (GST_IS_PLAY_MEDIA_INFO (info), 0);
+
+  return g_list_length (info->subtitle_stream_list);
+}
+
+/**
+ * gst_play_get_video_streams:
+ * @info: a #GstPlayMediaInfo
+ *
+ * Returns: (transfer none) (element-type GstPlayVideoInfo): A #GList of
+ * matching #GstPlayVideoInfo.
+ * Since: 1.20
+ */
+#ifndef GST_REMOVE_DEPRECATED
+GList *
+gst_play_get_video_streams (const GstPlayMediaInfo * info)
+{
+  return gst_play_media_info_get_video_streams (info);
+}
+#endif
+
+/**
+ * gst_play_get_audio_streams:
+ * @info: a #GstPlayMediaInfo
+ *
+ * Returns: (transfer none) (element-type GstPlayAudioInfo): A #GList of
+ * matching #GstPlayAudioInfo.
+ * Since: 1.20
+ */
+#ifndef GST_REMOVE_DEPRECATED
+GList *
+gst_play_get_audio_streams (const GstPlayMediaInfo * info)
+{
+  return gst_play_media_info_get_audio_streams (info);
+}
+#endif
+
+/**
+ * gst_play_get_subtitle_streams:
+ * @info: a #GstPlayMediaInfo
+ *
+ * Returns: (transfer none) (element-type GstPlaySubtitleInfo): A #GList of
+ * matching #GstPlaySubtitleInfo.
+ * Since: 1.20
+ */
+#ifndef GST_REMOVE_DEPRECATED
+GList *
+gst_play_get_subtitle_streams (const GstPlayMediaInfo * info)
+{
+  return gst_play_media_info_get_subtitle_streams (info);
+}
+#endif
diff --git a/gst-libs/gst/play/gstplay-media-info.h b/gst-libs/gst/play/gstplay-media-info.h
new file mode 100644 (file)
index 0000000..6796a38
--- /dev/null
@@ -0,0 +1,280 @@
+/* GStreamer
+ *
+ * Copyright (C) 2015 Brijesh Singh <brijesh.ksingh@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_PLAY_MEDIA_INFO_H__
+#define __GST_PLAY_MEDIA_INFO_H__
+
+#include <gst/gst.h>
+#include <gst/play/play-prelude.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GST_TYPE_PLAY_STREAM_INFO:
+ * Since: 1.20
+ */
+#define GST_TYPE_PLAY_STREAM_INFO \
+  (gst_play_stream_info_get_type ())
+#define GST_PLAY_STREAM_INFO(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PLAY_STREAM_INFO,GstPlayStreamInfo))
+#define GST_PLAY_STREAM_INFO_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PLAY_STREAM_INFO,GstPlayStreamInfo))
+#define GST_IS_PLAY_STREAM_INFO(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PLAY_STREAM_INFO))
+#define GST_IS_PLAY_STREAM_INFO_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PLAY_STREAM_INFO))
+
+/**
+ * GstPlayStreamInfo:
+ *
+ * Base structure for information concerning a media stream. Depending on
+ * the stream type, one can find more media-specific information in
+ * #GstPlayVideoInfo, #GstPlayAudioInfo, #GstPlaySubtitleInfo.
+ * Since: 1.20
+ */
+typedef struct _GstPlayStreamInfo GstPlayStreamInfo;
+typedef struct _GstPlayStreamInfoClass GstPlayStreamInfoClass;
+
+GST_PLAY_API
+GType         gst_play_stream_info_get_type (void);
+
+GST_PLAY_API
+gint          gst_play_stream_info_get_index (const GstPlayStreamInfo *info);
+
+GST_PLAY_API
+const gchar*  gst_play_stream_info_get_stream_type (const GstPlayStreamInfo *info);
+
+GST_PLAY_API
+GstTagList*   gst_play_stream_info_get_tags  (const GstPlayStreamInfo *info);
+
+GST_PLAY_API
+GstCaps*      gst_play_stream_info_get_caps  (const GstPlayStreamInfo *info);
+
+GST_PLAY_API
+const gchar*  gst_play_stream_info_get_codec (const GstPlayStreamInfo *info);
+
+/**
+ * GST_TYPE_PLAY_VIDEO_INFO:
+ * Since: 1.20
+ */
+#define GST_TYPE_PLAY_VIDEO_INFO \
+  (gst_play_video_info_get_type ())
+#define GST_PLAY_VIDEO_INFO(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PLAY_VIDEO_INFO, GstPlayVideoInfo))
+#define GST_PLAY_VIDEO_INFO_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((obj),GST_TYPE_PLAY_VIDEO_INFO, GstPlayVideoInfoClass))
+#define GST_IS_PLAY_VIDEO_INFO(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PLAY_VIDEO_INFO))
+#define GST_IS_PLAY_VIDEO_INFO_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((obj),GST_TYPE_PLAY_VIDEO_INFO))
+
+/**
+ * GstPlayVideoInfo:
+ *
+ * #GstPlayStreamInfo specific to video streams.
+ * Since: 1.20
+ */
+typedef struct _GstPlayVideoInfo GstPlayVideoInfo;
+typedef struct _GstPlayVideoInfoClass GstPlayVideoInfoClass;
+
+GST_PLAY_API
+GType         gst_play_video_info_get_type (void);
+
+GST_PLAY_API
+gint          gst_play_video_info_get_bitrate     (const GstPlayVideoInfo * info);
+
+GST_PLAY_API
+gint          gst_play_video_info_get_max_bitrate (const GstPlayVideoInfo * info);
+
+GST_PLAY_API
+gint          gst_play_video_info_get_width       (const GstPlayVideoInfo * info);
+
+GST_PLAY_API
+gint          gst_play_video_info_get_height      (const GstPlayVideoInfo * info);
+
+GST_PLAY_API
+void          gst_play_video_info_get_framerate   (const GstPlayVideoInfo * info,
+                                                     gint * fps_n,
+                                                     gint * fps_d);
+
+GST_PLAY_API
+void          gst_play_video_info_get_pixel_aspect_ratio (const GstPlayVideoInfo * info,
+                                                            guint * par_n,
+                                                            guint * par_d);
+
+/**
+ * GST_TYPE_PLAY_AUDIO_INFO:
+ * Since: 1.20
+ */
+#define GST_TYPE_PLAY_AUDIO_INFO \
+  (gst_play_audio_info_get_type ())
+#define GST_PLAY_AUDIO_INFO(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PLAY_AUDIO_INFO, GstPlayAudioInfo))
+#define GST_PLAY_AUDIO_INFO_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PLAY_AUDIO_INFO, GstPlayAudioInfoClass))
+#define GST_IS_PLAY_AUDIO_INFO(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PLAY_AUDIO_INFO))
+#define GST_IS_PLAY_AUDIO_INFO_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PLAY_AUDIO_INFO))
+
+/**
+ * GstPlayAudioInfo:
+ *
+ * #GstPlayStreamInfo specific to audio streams.
+ * Since: 1.20
+ */
+typedef struct _GstPlayAudioInfo GstPlayAudioInfo;
+typedef struct _GstPlayAudioInfoClass GstPlayAudioInfoClass;
+
+GST_PLAY_API
+GType         gst_play_audio_info_get_type (void);
+
+GST_PLAY_API
+gint          gst_play_audio_info_get_channels    (const GstPlayAudioInfo* info);
+
+GST_PLAY_API
+gint          gst_play_audio_info_get_sample_rate (const GstPlayAudioInfo* info);
+
+GST_PLAY_API
+gint          gst_play_audio_info_get_bitrate     (const GstPlayAudioInfo* info);
+
+GST_PLAY_API
+gint          gst_play_audio_info_get_max_bitrate (const GstPlayAudioInfo* info);
+
+GST_PLAY_API
+const gchar*  gst_play_audio_info_get_language    (const GstPlayAudioInfo* info);
+
+/**
+ * GST_TYPE_PLAY_SUBTITLE_INFO:
+ * Since: 1.20
+ */
+#define GST_TYPE_PLAY_SUBTITLE_INFO \
+  (gst_play_subtitle_info_get_type ())
+#define GST_PLAY_SUBTITLE_INFO(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PLAY_SUBTITLE_INFO, GstPlaySubtitleInfo))
+#define GST_PLAY_SUBTITLE_INFO_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PLAY_SUBTITLE_INFO,GstPlaySubtitleInfoClass))
+#define GST_IS_PLAY_SUBTITLE_INFO(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PLAY_SUBTITLE_INFO))
+#define GST_IS_PLAY_SUBTITLE_INFO_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PLAY_SUBTITLE_INFO))
+
+/**
+ * GstPlaySubtitleInfo:
+ *
+ * #GstPlayStreamInfo specific to subtitle streams.
+ * Since: 1.20
+ */
+typedef struct _GstPlaySubtitleInfo GstPlaySubtitleInfo;
+typedef struct _GstPlaySubtitleInfoClass GstPlaySubtitleInfoClass;
+
+GST_PLAY_API
+GType         gst_play_subtitle_info_get_type (void);
+
+GST_PLAY_API
+const gchar * gst_play_subtitle_info_get_language (const GstPlaySubtitleInfo* info);
+
+/**
+ * GST_TYPE_PLAY_MEDIA_INFO:
+ * Since: 1.20
+ */
+#define GST_TYPE_PLAY_MEDIA_INFO \
+  (gst_play_media_info_get_type())
+#define GST_PLAY_MEDIA_INFO(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PLAY_MEDIA_INFO,GstPlayMediaInfo))
+#define GST_PLAY_MEDIA_INFO_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PLAY_MEDIA_INFO,GstPlayMediaInfoClass))
+#define GST_IS_PLAY_MEDIA_INFO(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PLAY_MEDIA_INFO))
+#define GST_IS_PLAY_MEDIA_INFO_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PLAY_MEDIA_INFO))
+
+/**
+ * GstPlayMediaInfo:
+ *
+ * Structure containing the media information of a URI.
+ * Since: 1.20
+ */
+typedef struct _GstPlayMediaInfo GstPlayMediaInfo;
+typedef struct _GstPlayMediaInfoClass GstPlayMediaInfoClass;
+
+GST_PLAY_API
+GType         gst_play_media_info_get_type (void);
+
+GST_PLAY_API
+const gchar * gst_play_media_info_get_uri (const GstPlayMediaInfo *info);
+
+GST_PLAY_API
+gboolean      gst_play_media_info_is_seekable (const GstPlayMediaInfo *info);
+
+GST_PLAY_API
+gboolean      gst_play_media_info_is_live (const GstPlayMediaInfo *info);
+
+GST_PLAY_API
+GstClockTime  gst_play_media_info_get_duration (const GstPlayMediaInfo *info);
+
+GST_PLAY_API
+GList*        gst_play_media_info_get_stream_list (const GstPlayMediaInfo *info);
+
+GST_PLAY_API
+guint         gst_play_media_info_get_number_of_streams (const GstPlayMediaInfo *info);
+
+GST_PLAY_API
+GList*        gst_play_media_info_get_video_streams (const GstPlayMediaInfo *info);
+
+GST_PLAY_API
+guint         gst_play_media_info_get_number_of_video_streams (const GstPlayMediaInfo *info);
+
+GST_PLAY_API
+GList*        gst_play_media_info_get_audio_streams (const GstPlayMediaInfo *info);
+
+GST_PLAY_API
+guint         gst_play_media_info_get_number_of_audio_streams (const GstPlayMediaInfo *info);
+
+GST_PLAY_API
+GList*        gst_play_media_info_get_subtitle_streams (const GstPlayMediaInfo *info);
+
+GST_PLAY_API
+guint         gst_play_media_info_get_number_of_subtitle_streams (const GstPlayMediaInfo *info);
+
+GST_PLAY_API
+GstTagList*   gst_play_media_info_get_tags (const GstPlayMediaInfo *info);
+
+GST_PLAY_API
+const gchar*  gst_play_media_info_get_title (const GstPlayMediaInfo *info);
+
+GST_PLAY_API
+const gchar*  gst_play_media_info_get_container_format (const GstPlayMediaInfo *info);
+
+GST_PLAY_API
+GstSample*    gst_play_media_info_get_image_sample (const GstPlayMediaInfo *info);
+
+GST_PLAY_DEPRECATED_FOR(gst_play_media_info_get_video_streams)
+GList*        gst_play_get_video_streams    (const GstPlayMediaInfo *info);
+
+GST_PLAY_DEPRECATED_FOR(gst_play_media_info_get_audio_streams)
+GList*        gst_play_get_audio_streams    (const GstPlayMediaInfo *info);
+
+GST_PLAY_DEPRECATED_FOR(gst_play_media_info_get_subtitle_streams)
+GList*        gst_play_get_subtitle_streams (const GstPlayMediaInfo *info);
+
+G_END_DECLS
+
+#endif /* __GST_PLAY_MEDIA_INFO_H */
diff --git a/gst-libs/gst/play/gstplay-message-private.h b/gst-libs/gst/play/gstplay-message-private.h
new file mode 100644 (file)
index 0000000..3925e70
--- /dev/null
@@ -0,0 +1,42 @@
+/* GStreamer
+ *
+ * Copyright (C) 2020 Stephan Hesse <stephan@emliri.com>
+ * Copyright (C) 2020 Philippe Normand <philn@igalia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_PLAY_MESSAGE_PRIVATE_H__
+#define __GST_PLAY_MESSAGE_PRIVATE_H__
+
+#define GST_PLAY_MESSAGE_DATA "gst-play-message-data"
+#define GST_PLAY_MESSAGE_DATA_TYPE "play-message-type"
+#define GST_PLAY_MESSAGE_DATA_URI "uri"
+#define GST_PLAY_MESSAGE_DATA_POSITION "position"
+#define GST_PLAY_MESSAGE_DATA_DURATION "duration"
+#define GST_PLAY_MESSAGE_DATA_PLAY_STATE "play-state"
+#define GST_PLAY_MESSAGE_DATA_BUFFERING_PERCENT "bufferring-percent"
+#define GST_PLAY_MESSAGE_DATA_ERROR "error"
+#define GST_PLAY_MESSAGE_DATA_ERROR_DETAILS "error-details"
+#define GST_PLAY_MESSAGE_DATA_WARNING "warning"
+#define GST_PLAY_MESSAGE_DATA_WARNING_DETAILS "warning-details"
+#define GST_PLAY_MESSAGE_DATA_VIDEO_WIDTH "video-width"
+#define GST_PLAY_MESSAGE_DATA_VIDEO_HEIGHT "video-height"
+#define GST_PLAY_MESSAGE_DATA_MEDIA_INFO "media-info"
+#define GST_PLAY_MESSAGE_DATA_VOLUME "volume"
+#define GST_PLAY_MESSAGE_DATA_IS_MUTED "is-muted"
+
+#endif
diff --git a/gst-libs/gst/play/gstplay-signal-adapter.c b/gst-libs/gst/play/gstplay-signal-adapter.c
new file mode 100644 (file)
index 0000000..24b7e6b
--- /dev/null
@@ -0,0 +1,460 @@
+/* GStreamer
+ *
+ * Copyright (C) 2019-2020 Stephan Hesse <stephan@emliri.com>
+ * Copyright (C) 2020 Philippe Normand <philn@igalia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstplay.h"
+#include "gstplay-signal-adapter.h"
+#include "gstplay-message-private.h"
+
+GST_DEBUG_CATEGORY_STATIC (gst_play_signal_adapter_debug);
+#define GST_CAT_DEFAULT gst_play_signal_adapter_debug
+
+enum
+{
+  SIGNAL_URI_LOADED,
+  SIGNAL_POSITION_UPDATED,
+  SIGNAL_DURATION_CHANGED,
+  SIGNAL_STATE_CHANGED,
+  SIGNAL_BUFFERING,
+  SIGNAL_END_OF_STREAM,
+  SIGNAL_ERROR,
+  SIGNAL_WARNING,
+  SIGNAL_VIDEO_DIMENSIONS_CHANGED,
+  SIGNAL_MEDIA_INFO_UPDATED,
+  SIGNAL_VOLUME_CHANGED,
+  SIGNAL_MUTE_CHANGED,
+  SIGNAL_SEEK_DONE,
+  SIGNAL_LAST
+};
+
+enum
+{
+  PROP_0,
+  PROP_PLAY,
+  PROP_LAST
+};
+
+static GParamSpec *param_specs[PROP_LAST] = { NULL, };
+
+struct _GstPlaySignalAdapter
+{
+  GObject parent;
+  GstBus *bus;
+  GstPlay *play;
+  GSource *source;
+};
+
+struct _GstPlaySignalAdapterClass
+{
+  GObjectClass parent_class;
+};
+
+#define _do_init \
+  GST_DEBUG_CATEGORY_INIT (gst_play_signal_adapter_debug, "gst-play-signal-adapter", \
+      0, "GstPlay signal adapter")
+
+#define parent_class gst_play_signal_adapter_parent_class
+G_DEFINE_TYPE_WITH_CODE (GstPlaySignalAdapter, gst_play_signal_adapter,
+    G_TYPE_OBJECT, _do_init);
+
+static guint signals[SIGNAL_LAST] = { 0, };
+
+static void
+gst_play_signal_adapter_emit (GstPlaySignalAdapter * self,
+    const GstStructure * message_data)
+{
+  GstPlayMessage play_message_type;
+  g_return_if_fail (g_str_equal (gst_structure_get_name (message_data),
+          GST_PLAY_MESSAGE_DATA));
+
+  GST_LOG ("Emitting message %" GST_PTR_FORMAT, message_data);
+  gst_structure_get (message_data, GST_PLAY_MESSAGE_DATA_TYPE,
+      GST_TYPE_PLAY_MESSAGE, &play_message_type, NULL);
+
+  switch (play_message_type) {
+    case GST_PLAY_MESSAGE_URI_LOADED:{
+      const gchar *uri =
+          gst_structure_get_string (message_data, GST_PLAY_MESSAGE_DATA_URI);
+      g_signal_emit (self, signals[SIGNAL_URI_LOADED], 0, uri);
+      break;
+    }
+    case GST_PLAY_MESSAGE_POSITION_UPDATED:{
+      GstClockTime pos = GST_CLOCK_TIME_NONE;
+      gst_structure_get (message_data, GST_PLAY_MESSAGE_DATA_POSITION,
+          GST_TYPE_CLOCK_TIME, &pos, NULL);
+      g_signal_emit (self, signals[SIGNAL_POSITION_UPDATED], 0, pos);
+      break;
+    }
+    case GST_PLAY_MESSAGE_DURATION_CHANGED:{
+      GstClockTime duration = GST_CLOCK_TIME_NONE;
+      gst_structure_get (message_data, GST_PLAY_MESSAGE_DATA_DURATION,
+          GST_TYPE_CLOCK_TIME, &duration, NULL);
+      g_signal_emit (self, signals[SIGNAL_DURATION_CHANGED], 0, duration);
+      break;
+    }
+    case GST_PLAY_MESSAGE_STATE_CHANGED:{
+      GstPlayState state = 0;
+      gst_structure_get (message_data, GST_PLAY_MESSAGE_DATA_PLAY_STATE,
+          GST_TYPE_PLAY_STATE, &state, NULL);
+      g_signal_emit (self, signals[SIGNAL_STATE_CHANGED], 0, state);
+      break;
+    }
+    case GST_PLAY_MESSAGE_BUFFERING:{
+      guint percent = 0;
+      gst_structure_get (message_data,
+          GST_PLAY_MESSAGE_DATA_BUFFERING_PERCENT, G_TYPE_UINT, &percent, NULL);
+      g_signal_emit (self, signals[SIGNAL_BUFFERING], 0, percent);
+      break;
+    }
+    case GST_PLAY_MESSAGE_END_OF_STREAM:
+      g_signal_emit (self, signals[SIGNAL_END_OF_STREAM], 0);
+      break;
+    case GST_PLAY_MESSAGE_ERROR:{
+      GError *error = NULL;
+      GstStructure *details = NULL;
+      gst_structure_get (message_data, GST_PLAY_MESSAGE_DATA_ERROR,
+          G_TYPE_ERROR, &error, GST_PLAY_MESSAGE_DATA_ERROR_DETAILS,
+          GST_TYPE_STRUCTURE, &details, NULL);
+      g_signal_emit (self, signals[SIGNAL_ERROR], 0, error, details);
+      g_error_free (error);
+      if (details)
+        gst_structure_free (details);
+      break;
+    }
+    case GST_PLAY_MESSAGE_WARNING:{
+      GError *error = NULL;
+      GstStructure *details = NULL;
+      gst_structure_get (message_data, GST_PLAY_MESSAGE_DATA_WARNING,
+          G_TYPE_ERROR, &error, GST_PLAY_MESSAGE_DATA_WARNING_DETAILS,
+          GST_TYPE_STRUCTURE, &details, NULL);
+      g_signal_emit (self, signals[SIGNAL_WARNING], 0, error, details);
+      g_error_free (error);
+      if (details)
+        gst_structure_free (details);
+      break;
+    }
+    case GST_PLAY_MESSAGE_VIDEO_DIMENSIONS_CHANGED:{
+      guint width = 0;
+      guint height = 0;
+      gst_structure_get (message_data,
+          GST_PLAY_MESSAGE_DATA_VIDEO_WIDTH, G_TYPE_UINT, &width,
+          GST_PLAY_MESSAGE_DATA_VIDEO_HEIGHT, G_TYPE_UINT, &height, NULL);
+      g_signal_emit (self, signals[SIGNAL_VIDEO_DIMENSIONS_CHANGED], 0,
+          width, height);
+      break;
+    }
+    case GST_PLAY_MESSAGE_MEDIA_INFO_UPDATED:{
+      GstPlayMediaInfo *media_info;
+      gst_structure_get (message_data, GST_PLAY_MESSAGE_DATA_MEDIA_INFO,
+          GST_TYPE_PLAY_MEDIA_INFO, &media_info, NULL);
+      g_signal_emit (self, signals[SIGNAL_VIDEO_DIMENSIONS_CHANGED], 0,
+          media_info);
+      g_object_unref (media_info);
+      break;
+    }
+    case GST_PLAY_MESSAGE_VOLUME_CHANGED:{
+      gdouble volume;
+      gst_structure_get (message_data, GST_PLAY_MESSAGE_DATA_VOLUME,
+          G_TYPE_DOUBLE, &volume, NULL);
+      g_signal_emit (self, signals[SIGNAL_VOLUME_CHANGED], 0, volume);
+      break;
+    }
+    case GST_PLAY_MESSAGE_MUTE_CHANGED:{
+      gboolean is_muted;
+      gst_structure_get (message_data, GST_PLAY_MESSAGE_DATA_IS_MUTED,
+          G_TYPE_BOOLEAN, &is_muted, NULL);
+      g_signal_emit (self, signals[SIGNAL_MUTE_CHANGED], 0, is_muted);
+      break;
+    }
+    case GST_PLAY_MESSAGE_SEEK_DONE:{
+      GstClockTime pos;
+      gst_structure_get (message_data, GST_PLAY_MESSAGE_DATA_POSITION,
+          GST_TYPE_CLOCK_TIME, &pos, NULL);
+      g_signal_emit (self, signals[SIGNAL_SEEK_DONE], 0, pos);
+      break;
+    }
+    default:
+      g_assert_not_reached ();
+      break;
+  }
+}
+
+/*
+ * callback for the bus-message in-sync handling
+ */
+static GstBusSyncReply
+    gst_play_signal_adapter_bus_sync_handler
+    (GstBus * bus, GstMessage * message, gpointer user_data)
+{
+  GstPlaySignalAdapter *self = GST_PLAY_SIGNAL_ADAPTER (user_data);
+  const GstStructure *message_data = gst_message_get_structure (message);
+  gst_play_signal_adapter_emit (self, message_data);
+  gst_message_unref (message);
+  return GST_BUS_DROP;
+}
+
+/*
+ * callback for the bus-watch
+ * pre: there is a message on the bus
+ */
+static gboolean
+gst_play_signal_adapter_on_message (GstBus * bus,
+    GstMessage * message, gpointer user_data)
+{
+  GstPlaySignalAdapter *self = GST_PLAY_SIGNAL_ADAPTER (user_data);
+  const GstStructure *message_data = gst_message_get_structure (message);
+  gst_play_signal_adapter_emit (self, message_data);
+  return TRUE;
+}
+
+/**
+ * gst_play_signal_adapter_new:
+ * @play: (transfer none): #GstPlay instance to emit signals for.
+ *
+ * A bus-watching #GSource will be created and attached to the the
+ * thread-default #GMainContext. The attached callback will emit the
+ * corresponding signal for the message received. Matching signals for play
+ * messages from the bus will be emitted by it on the created adapter object.
+ *
+ * Returns: (transfer full): A new #GstPlaySignalAdapter to connect signal handlers to.
+ *
+ * Since: 1.20
+ */
+GstPlaySignalAdapter *
+gst_play_signal_adapter_new (GstPlay * play)
+{
+  GstPlaySignalAdapter *self = NULL;
+  GMainContext *context = NULL;
+
+  g_return_val_if_fail (GST_IS_PLAY (play), NULL);
+
+  self = g_object_new (GST_TYPE_PLAY_SIGNAL_ADAPTER, NULL);
+  self->play = play;
+  self->bus = gst_play_get_message_bus (play);
+  self->source = gst_bus_create_watch (self->bus);
+
+  context = g_main_context_get_thread_default ();
+  g_source_attach (self->source, context);
+  g_source_set_callback (self->source,
+      (GSourceFunc) gst_play_signal_adapter_on_message, self, NULL);
+  return self;
+}
+
+/**
+ * gst_play_signal_adapter_new_with_main_context:
+ * @play: (transfer none): #GstPlay instance to emit signals for.
+ * @context: A #GMainContext on which the main-loop will process play bus messages on.
+ *
+ * A bus-watching #GSource will be created and attached to the @context. The
+ * attached callback will emit the corresponding signal for the message
+ * received. Matching signals for play messages from the bus will be emitted by
+ * it on the created adapter object.
+ *
+ * Returns: (transfer full): A new #GstPlaySignalAdapter to connect signal handlers to.
+ *
+ * Since: 1.20
+ */
+GstPlaySignalAdapter *
+gst_play_signal_adapter_new_with_main_context (GstPlay * play,
+    GMainContext * context)
+{
+  GstPlaySignalAdapter *self = NULL;
+
+  g_return_val_if_fail (GST_IS_PLAY (play), NULL);
+  g_return_val_if_fail (context != NULL, NULL);
+
+  self = g_object_new (GST_TYPE_PLAY_SIGNAL_ADAPTER, NULL);
+  self->play = play;
+  self->bus = gst_play_get_message_bus (play);
+  self->source = gst_bus_create_watch (self->bus);
+
+  g_source_attach (self->source, context);
+  g_source_set_callback (self->source,
+      (GSourceFunc) gst_play_signal_adapter_on_message, self, NULL);
+  return self;
+}
+
+/**
+ * gst_play_signal_adapter_new_sync_emit:
+ * @play: (transfer none): #GstPlay instance to emit signals for.
+ *
+ * Create an adapter that synchronously emits its signals, from the thread in
+ * which the messages have been posted.
+ *
+ * Returns: (transfer full): A new #GstPlaySignalAdapter to connect signal handlers to.
+ *
+ * Since: 1.20
+ */
+GstPlaySignalAdapter *
+gst_play_signal_adapter_new_sync_emit (GstPlay * play)
+{
+  GstBus *bus = NULL;
+  GstPlaySignalAdapter *self = NULL;
+
+  g_return_val_if_fail (GST_IS_PLAY (play), NULL);
+
+  bus = gst_play_get_message_bus (play);
+
+  self = g_object_new (GST_TYPE_PLAY_SIGNAL_ADAPTER, NULL);
+  self->play = play;
+  self->bus = bus;
+  gst_bus_set_sync_handler (self->bus,
+      gst_play_signal_adapter_bus_sync_handler, self, NULL);
+  return self;
+}
+
+
+/**
+ * gst_play_signal_adapter_get_play:
+ * @adapter: #GstPlaySignalAdapter instance
+ *
+ * Returns: (transfer none): The #GstPlay owning this signal adapter.
+ *
+ * Since: 1.20
+ */
+GstPlay *
+gst_play_signal_adapter_get_play (GstPlaySignalAdapter * adapter)
+{
+  g_return_val_if_fail (GST_IS_PLAY_SIGNAL_ADAPTER (adapter), NULL);
+  return adapter->play;
+}
+
+static void
+gst_play_signal_adapter_init (GstPlaySignalAdapter * self)
+{
+  self->source = NULL;
+}
+
+static void
+gst_play_signal_adapter_dispose (GObject * object)
+{
+  GstPlaySignalAdapter *self = GST_PLAY_SIGNAL_ADAPTER (object);
+
+  if (self->source) {
+    g_source_destroy (self->source);
+    g_source_unref (self->source);
+    self->source = NULL;
+  }
+
+  gst_clear_object (&self->bus);
+
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gst_play_signal_adapter_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstPlaySignalAdapter *self = GST_PLAY_SIGNAL_ADAPTER (object);
+
+  switch (prop_id) {
+    case PROP_PLAY:
+      g_value_set_object (value, self->play);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gst_play_signal_adapter_class_init (GstPlaySignalAdapterClass * klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+
+  gobject_class->dispose = gst_play_signal_adapter_dispose;
+  gobject_class->get_property = gst_play_signal_adapter_get_property;
+
+  param_specs[PROP_PLAY] =
+      g_param_spec_object ("play", "Play",
+      "GstPlay owning this adapter",
+      GST_TYPE_PLAY, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  signals[SIGNAL_URI_LOADED] =
+      g_signal_new ("uri-loaded", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
+      NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING);
+
+  signals[SIGNAL_POSITION_UPDATED] =
+      g_signal_new ("position-updated", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
+      NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CLOCK_TIME);
+
+  signals[SIGNAL_DURATION_CHANGED] =
+      g_signal_new ("duration-changed", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
+      NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CLOCK_TIME);
+
+  signals[SIGNAL_STATE_CHANGED] =
+      g_signal_new ("state-changed", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
+      NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_PLAY_STATE);
+
+  signals[SIGNAL_BUFFERING] =
+      g_signal_new ("buffering", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
+      NULL, NULL, G_TYPE_NONE, 1, G_TYPE_INT);
+
+  signals[SIGNAL_END_OF_STREAM] =
+      g_signal_new ("end-of-stream", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
+      NULL, NULL, G_TYPE_NONE, 0, G_TYPE_INVALID);
+
+  signals[SIGNAL_ERROR] =
+      g_signal_new ("error", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
+      NULL, NULL, G_TYPE_NONE, 2, G_TYPE_ERROR, GST_TYPE_STRUCTURE);
+
+  signals[SIGNAL_VIDEO_DIMENSIONS_CHANGED] =
+      g_signal_new ("video-dimensions-changed", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
+      NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
+
+  signals[SIGNAL_MEDIA_INFO_UPDATED] =
+      g_signal_new ("media-info-updated", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
+      NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_PLAY_MEDIA_INFO);
+
+  signals[SIGNAL_VOLUME_CHANGED] =
+      g_signal_new ("volume-changed", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
+      NULL, NULL, G_TYPE_NONE, 0, G_TYPE_INVALID);
+
+  signals[SIGNAL_MUTE_CHANGED] =
+      g_signal_new ("mute-changed", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
+      NULL, NULL, G_TYPE_NONE, 0, G_TYPE_INVALID);
+
+  signals[SIGNAL_WARNING] =
+      g_signal_new ("warning", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
+      NULL, NULL, G_TYPE_NONE, 2, G_TYPE_ERROR, GST_TYPE_STRUCTURE);
+
+  signals[SIGNAL_SEEK_DONE] =
+      g_signal_new ("seek-done", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
+      NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CLOCK_TIME);
+
+  g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
+}
diff --git a/gst-libs/gst/play/gstplay-signal-adapter.h b/gst-libs/gst/play/gstplay-signal-adapter.h
new file mode 100644 (file)
index 0000000..4159ce2
--- /dev/null
@@ -0,0 +1,59 @@
+/* GStreamer
+ *
+ * Copyright (C) 2019-2020 Stephan Hesse <stephan@emliri.com>
+ * Copyright (C) 2020 Philippe Normand <philn@igalia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_PLAY_SIGNAL_ADAPTER_H__
+#define __GST_PLAY_SIGNAL_ADAPTER_H__
+
+#include <gst/play/gstplay-types.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_PLAY_SIGNAL_ADAPTER             (gst_play_signal_adapter_get_type ())
+#define GST_IS_PLAY_SIGNAL_ADAPTER(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PLAY_SIGNAL_ADAPTER))
+#define GST_IS_PLAY_SIGNAL_ADAPTER_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PLAY_SIGNAL_ADAPTER))
+#define GST_PLAY_SIGNAL_ADAPTER_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_PLAY_SIGNAL_ADAPTER, GstPlaySignalAdapterClass))
+#define GST_PLAY_SIGNAL_ADAPTER(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PLAY_SIGNAL_ADAPTER, GstPlaySignalAdapter))
+#define GST_PLAY_SIGNAL_ADAPTER_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_PLAY_SIGNAL_ADAPTER, GstPlaySignalAdapterClass))
+
+/**
+ * GST_PLAY_SIGNAL_ADAPTER_CAST:
+ * Since: 1.20
+ */
+#define GST_PLAY_SIGNAL_ADAPTER_CAST(obj)        ((GstPlaySignalAdapter*)(obj))
+
+GST_PLAY_API
+GType                  gst_play_signal_adapter_get_type               (void);
+
+GST_PLAY_API
+GstPlaySignalAdapter * gst_play_signal_adapter_new                    (GstPlay * play);
+
+GST_PLAY_API
+GstPlaySignalAdapter * gst_play_signal_adapter_new_with_main_context  (GstPlay * play, GMainContext * context);
+
+GST_PLAY_API
+GstPlaySignalAdapter * gst_play_signal_adapter_new_sync_emit          (GstPlay * play);
+
+GST_PLAY_API
+GstPlay              * gst_play_signal_adapter_get_play               (GstPlaySignalAdapter * adapter);
+
+G_END_DECLS
+
+#endif /* __GST_PLAY_SIGNAL_ADAPTER_H__ */
diff --git a/gst-libs/gst/play/gstplay-types.h b/gst-libs/gst/play/gstplay-types.h
new file mode 100644 (file)
index 0000000..da6c19f
--- /dev/null
@@ -0,0 +1,47 @@
+/* GStreamer
+ *
+ * Copyright (C) 2015 Sebastian Dröge <sebastian@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_PLAY_TYPES_H__
+#define __GST_PLAY_TYPES_H__
+
+#include <gst/gst.h>
+#include <gst/play/play-prelude.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GstPlay:
+ * Since: 1.20
+ */
+typedef struct _GstPlay GstPlay;
+typedef struct _GstPlayClass GstPlayClass;
+
+/**
+ * GstPlaySignalAdapter:
+ * Since: 1.20
+ */
+typedef struct _GstPlaySignalAdapter GstPlaySignalAdapter;
+typedef struct _GstPlaySignalAdapterClass GstPlaySignalAdapterClass;
+
+G_END_DECLS
+
+#endif /* __GST_PLAY_TYPES_H__ */
+
+
diff --git a/gst-libs/gst/play/gstplay-video-overlay-video-renderer.c b/gst-libs/gst/play/gstplay-video-overlay-video-renderer.c
new file mode 100644 (file)
index 0000000..f1005bd
--- /dev/null
@@ -0,0 +1,351 @@
+/* GStreamer
+ *
+ * Copyright (C) 2014-2015 Sebastian Dröge <sebastian@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:gstplay-videooverlayvideorenderer
+ * @title: GstPlayVideoOverlayVideoRenderer
+ * @short_description: Play Video Overlay Video Renderer
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstplay-video-overlay-video-renderer.h"
+#include "gstplay.h"
+
+#include <gst/video/video.h>
+
+struct _GstPlayVideoOverlayVideoRenderer
+{
+  GObject parent;
+
+  GstVideoOverlay *video_overlay;
+  gpointer window_handle;
+  gint x, y, width, height;
+
+  GstElement *video_sink;       /* configured video sink, or NULL      */
+};
+
+struct _GstPlayVideoOverlayVideoRendererClass
+{
+  GObjectClass parent_class;
+};
+
+static void
+    gst_play_video_overlay_video_renderer_interface_init
+    (GstPlayVideoRendererInterface * iface);
+
+enum
+{
+  VIDEO_OVERLAY_VIDEO_RENDERER_PROP_0,
+  VIDEO_OVERLAY_VIDEO_RENDERER_PROP_WINDOW_HANDLE,
+  VIDEO_OVERLAY_VIDEO_RENDERER_PROP_VIDEO_SINK,
+  VIDEO_OVERLAY_VIDEO_RENDERER_PROP_LAST
+};
+
+G_DEFINE_TYPE_WITH_CODE (GstPlayVideoOverlayVideoRenderer,
+    gst_play_video_overlay_video_renderer, G_TYPE_OBJECT,
+    G_IMPLEMENT_INTERFACE (GST_TYPE_PLAY_VIDEO_RENDERER,
+        gst_play_video_overlay_video_renderer_interface_init));
+
+static GParamSpec
+    * video_overlay_video_renderer_param_specs
+    [VIDEO_OVERLAY_VIDEO_RENDERER_PROP_LAST] = { NULL, };
+
+static void
+gst_play_video_overlay_video_renderer_set_property (GObject * object,
+    guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+  GstPlayVideoOverlayVideoRenderer *self =
+      GST_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER (object);
+
+  switch (prop_id) {
+    case VIDEO_OVERLAY_VIDEO_RENDERER_PROP_WINDOW_HANDLE:
+      self->window_handle = g_value_get_pointer (value);
+      if (self->video_overlay)
+        gst_video_overlay_set_window_handle (self->video_overlay,
+            (guintptr) self->window_handle);
+      break;
+    case VIDEO_OVERLAY_VIDEO_RENDERER_PROP_VIDEO_SINK:
+      self->video_sink = gst_object_ref_sink (g_value_get_object (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gst_play_video_overlay_video_renderer_get_property (GObject * object,
+    guint prop_id, GValue * value, GParamSpec * pspec)
+{
+  GstPlayVideoOverlayVideoRenderer *self =
+      GST_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER (object);
+
+  switch (prop_id) {
+    case VIDEO_OVERLAY_VIDEO_RENDERER_PROP_WINDOW_HANDLE:
+      g_value_set_pointer (value, self->window_handle);
+      break;
+    case VIDEO_OVERLAY_VIDEO_RENDERER_PROP_VIDEO_SINK:
+      g_value_set_object (value, self->video_sink);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gst_play_video_overlay_video_renderer_finalize (GObject * object)
+{
+  GstPlayVideoOverlayVideoRenderer *self =
+      GST_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER (object);
+
+  if (self->video_overlay)
+    gst_object_unref (self->video_overlay);
+
+  if (self->video_sink)
+    gst_object_unref (self->video_sink);
+
+  G_OBJECT_CLASS
+      (gst_play_video_overlay_video_renderer_parent_class)->finalize (object);
+}
+
+static void
+    gst_play_video_overlay_video_renderer_class_init
+    (GstPlayVideoOverlayVideoRendererClass * klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->set_property =
+      gst_play_video_overlay_video_renderer_set_property;
+  gobject_class->get_property =
+      gst_play_video_overlay_video_renderer_get_property;
+  gobject_class->finalize = gst_play_video_overlay_video_renderer_finalize;
+
+  video_overlay_video_renderer_param_specs
+      [VIDEO_OVERLAY_VIDEO_RENDERER_PROP_WINDOW_HANDLE] =
+      g_param_spec_pointer ("window-handle", "Window Handle",
+      "Window handle to embed the video into",
+      G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+  video_overlay_video_renderer_param_specs
+      [VIDEO_OVERLAY_VIDEO_RENDERER_PROP_VIDEO_SINK] =
+      g_param_spec_object ("video-sink", "Video Sink",
+      "the video output element to use (NULL = default sink)",
+      GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (gobject_class,
+      VIDEO_OVERLAY_VIDEO_RENDERER_PROP_LAST,
+      video_overlay_video_renderer_param_specs);
+}
+
+static void
+    gst_play_video_overlay_video_renderer_init
+    (GstPlayVideoOverlayVideoRenderer * self)
+{
+  self->x = self->y = self->width = self->height = -1;
+  self->video_sink = NULL;
+}
+
+static GstElement *gst_play_video_overlay_video_renderer_create_video_sink
+    (GstPlayVideoRenderer * iface, GstPlay * play)
+{
+  GstElement *video_overlay;
+  GstPlayVideoOverlayVideoRenderer *self =
+      GST_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER (iface);
+
+  if (self->video_overlay)
+    gst_object_unref (self->video_overlay);
+
+  video_overlay = gst_play_get_pipeline (play);
+  g_return_val_if_fail (GST_IS_VIDEO_OVERLAY (video_overlay), NULL);
+
+  self->video_overlay = GST_VIDEO_OVERLAY (video_overlay);
+
+  gst_video_overlay_set_window_handle (self->video_overlay,
+      (guintptr) self->window_handle);
+  if (self->width != -1 || self->height != -1)
+    gst_video_overlay_set_render_rectangle (self->video_overlay, self->x,
+        self->y, self->width, self->height);
+
+  return self->video_sink;
+}
+
+static void
+    gst_play_video_overlay_video_renderer_interface_init
+    (GstPlayVideoRendererInterface * iface)
+{
+  iface->create_video_sink =
+      gst_play_video_overlay_video_renderer_create_video_sink;
+}
+
+/**
+ * gst_play_video_overlay_video_renderer_new:
+ * @window_handle: (allow-none): Window handle to use or %NULL
+ *
+ * Returns: (transfer full):
+ * Since: 1.20
+ */
+GstPlayVideoRenderer *
+gst_play_video_overlay_video_renderer_new (gpointer window_handle)
+{
+  return g_object_new (GST_TYPE_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER,
+      "window-handle", window_handle, NULL);
+}
+
+/**
+ * gst_play_video_overlay_video_renderer_new_with_sink:
+ * @window_handle: (allow-none): Window handle to use or %NULL
+ * @video_sink: (transfer floating): the custom video_sink element to be set for the video renderer
+ *
+ * Returns: (transfer full):
+ *
+ * Since: 1.20
+ */
+GstPlayVideoRenderer *
+gst_play_video_overlay_video_renderer_new_with_sink (gpointer window_handle,
+    GstElement * video_sink)
+{
+  return g_object_new (GST_TYPE_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER,
+      "window-handle", window_handle, "video-sink", video_sink, NULL);
+}
+
+/**
+ * gst_play_video_overlay_video_renderer_set_window_handle:
+ * @self: #GstPlayVideoRenderer instance
+ * @window_handle: handle referencing to the platform specific window
+ *
+ * Sets the platform specific window handle into which the video
+ * should be rendered
+ * Since: 1.20
+ **/
+void gst_play_video_overlay_video_renderer_set_window_handle
+    (GstPlayVideoOverlayVideoRenderer * self, gpointer window_handle)
+{
+  g_return_if_fail (GST_IS_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER (self));
+
+  g_object_set (self, "window-handle", window_handle, NULL);
+}
+
+/**
+ * gst_play_video_overlay_video_renderer_get_window_handle:
+ * @self: #GstPlayVideoRenderer instance
+ *
+ * Returns: (transfer none): The currently set, platform specific window
+ * handle
+ * Since: 1.20
+ */
+gpointer
+    gst_play_video_overlay_video_renderer_get_window_handle
+    (GstPlayVideoOverlayVideoRenderer * self) {
+  gpointer window_handle;
+
+  g_return_val_if_fail (GST_IS_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER (self), NULL);
+
+  g_object_get (self, "window-handle", &window_handle, NULL);
+
+  return window_handle;
+}
+
+/**
+ * gst_play_video_overlay_video_renderer_expose:
+ * @self: a #GstPlayVideoOverlayVideoRenderer instance.
+ *
+ * Tell an overlay that it has been exposed. This will redraw the current frame
+ * in the drawable even if the pipeline is PAUSED.
+ * Since: 1.20
+ */
+void gst_play_video_overlay_video_renderer_expose
+    (GstPlayVideoOverlayVideoRenderer * self)
+{
+  g_return_if_fail (GST_IS_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER (self));
+
+  if (self->video_overlay)
+    gst_video_overlay_expose (self->video_overlay);
+}
+
+/**
+ * gst_play_video_overlay_video_renderer_set_render_rectangle:
+ * @self: a #GstPlayVideoOverlayVideoRenderer instance
+ * @x: the horizontal offset of the render area inside the window
+ * @y: the vertical offset of the render area inside the window
+ * @width: the width of the render area inside the window
+ * @height: the height of the render area inside the window
+ *
+ * Configure a subregion as a video target within the window set by
+ * gst_play_video_overlay_video_renderer_set_window_handle(). If this is not
+ * used or not supported the video will fill the area of the window set as the
+ * overlay to 100%. By specifying the rectangle, the video can be overlaid to
+ * a specific region of that window only. After setting the new rectangle one
+ * should call gst_play_video_overlay_video_renderer_expose() to force a
+ * redraw. To unset the region pass -1 for the @width and @height parameters.
+ *
+ * This method is needed for non fullscreen video overlay in UI toolkits that
+ * do not support subwindows.
+ *
+ * Since: 1.20
+ */
+void gst_play_video_overlay_video_renderer_set_render_rectangle
+    (GstPlayVideoOverlayVideoRenderer * self, gint x, gint y, gint width,
+    gint height)
+{
+  g_return_if_fail (GST_IS_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER (self));
+
+  self->x = x;
+  self->y = y;
+  self->width = width;
+  self->height = height;
+
+  if (self->video_overlay)
+    gst_video_overlay_set_render_rectangle (self->video_overlay,
+        x, y, width, height);
+}
+
+/**
+ * gst_play_video_overlay_video_renderer_get_render_rectangle:
+ * @self: a #GstPlayVideoOverlayVideoRenderer instance
+ * @x: (out) (allow-none): the horizontal offset of the render area inside the window
+ * @y: (out) (allow-none): the vertical offset of the render area inside the window
+ * @width: (out) (allow-none): the width of the render area inside the window
+ * @height: (out) (allow-none): the height of the render area inside the window
+ *
+ * Return the currently configured render rectangle. See gst_play_video_overlay_video_renderer_set_render_rectangle()
+ * for details.
+ *
+ * Since: 1.20
+ */
+void gst_play_video_overlay_video_renderer_get_render_rectangle
+    (GstPlayVideoOverlayVideoRenderer * self, gint * x, gint * y,
+    gint * width, gint * height)
+{
+  g_return_if_fail (GST_IS_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER (self));
+
+  if (x)
+    *x = self->x;
+  if (y)
+    *y = self->y;
+  if (width)
+    *width = self->width;
+  if (height)
+    *height = self->height;
+}
diff --git a/gst-libs/gst/play/gstplay-video-overlay-video-renderer.h b/gst-libs/gst/play/gstplay-video-overlay-video-renderer.h
new file mode 100644 (file)
index 0000000..1390f9a
--- /dev/null
@@ -0,0 +1,77 @@
+/* GStreamer
+ *
+ * Copyright (C) 2014-2015 Sebastian Dröge <sebastian@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER_H__
+#define __GST_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER_H__
+
+#include <gst/play/gstplay-types.h>
+#include <gst/play/gstplay-video-renderer.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GstPlayVideoOverlayVideoRenderer:
+ * Since: 1.20
+ */
+typedef struct _GstPlayVideoOverlayVideoRenderer
+    GstPlayVideoOverlayVideoRenderer;
+typedef struct _GstPlayVideoOverlayVideoRendererClass
+    GstPlayVideoOverlayVideoRendererClass;
+
+#define GST_TYPE_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER             (gst_play_video_overlay_video_renderer_get_type ())
+#define GST_IS_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER))
+#define GST_IS_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER))
+#define GST_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER, GstPlayVideoOverlayVideoRendererClass))
+#define GST_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER, GstPlayVideoOverlayVideoRenderer))
+#define GST_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER, GstPlayVideoOverlayVideoRendererClass))
+
+/**
+ * GST_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER_CAST:
+ * Since: 1.20
+ */
+#define GST_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER_CAST(obj)        ((GstPlayVideoOverlayVideoRenderer*)(obj))
+
+GST_PLAY_API
+GType gst_play_video_overlay_video_renderer_get_type (void);
+
+GST_PLAY_API
+GstPlayVideoRenderer * gst_play_video_overlay_video_renderer_new (gpointer window_handle);
+
+GST_PLAY_API
+GstPlayVideoRenderer * gst_play_video_overlay_video_renderer_new_with_sink (gpointer window_handle, GstElement * video_sink);
+
+GST_PLAY_API
+void gst_play_video_overlay_video_renderer_set_window_handle (GstPlayVideoOverlayVideoRenderer * self, gpointer window_handle);
+
+GST_PLAY_API
+gpointer gst_play_video_overlay_video_renderer_get_window_handle (GstPlayVideoOverlayVideoRenderer * self);
+
+GST_PLAY_API
+void gst_play_video_overlay_video_renderer_expose (GstPlayVideoOverlayVideoRenderer * self);
+
+GST_PLAY_API
+void gst_play_video_overlay_video_renderer_set_render_rectangle (GstPlayVideoOverlayVideoRenderer * self, gint x, gint y, gint width, gint height);
+
+GST_PLAY_API
+void gst_play_video_overlay_video_renderer_get_render_rectangle (GstPlayVideoOverlayVideoRenderer * self, gint *x, gint *y, gint *width, gint *height);
+
+G_END_DECLS
+
+#endif /* __GST_PLAY_VIDEO_OVERLAY_VIDEO_RENDERER_H__ */
diff --git a/gst-libs/gst/play/gstplay-video-renderer-private.h b/gst-libs/gst/play/gstplay-video-renderer-private.h
new file mode 100644 (file)
index 0000000..2131134
--- /dev/null
@@ -0,0 +1,33 @@
+/* GStreamer
+ *
+ * Copyright (C) 2014-2015 Sebastian Dröge <sebastian@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_PLAY_VIDEO_RENDERER_PRIVATE_H__
+#define __GST_PLAY_VIDEO_RENDERER_PRIVATE_H__
+
+#include <gst/play/gstplay-video-renderer.h>
+
+G_BEGIN_DECLS
+
+G_GNUC_INTERNAL GstElement * gst_play_video_renderer_create_video_sink (GstPlayVideoRenderer *
+    self, GstPlay * play);
+
+G_END_DECLS
+
+#endif /* __GST_PLAY_VIDEO_RENDERER_PRIVATE_H__ */
diff --git a/gst-libs/gst/play/gstplay-video-renderer.c b/gst-libs/gst/play/gstplay-video-renderer.c
new file mode 100644 (file)
index 0000000..ba48f85
--- /dev/null
@@ -0,0 +1,49 @@
+/* GStreamer
+ *
+ * Copyright (C) 2014-2015 Sebastian Dröge <sebastian@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstplay-video-renderer.h"
+#include "gstplay-video-renderer-private.h"
+
+G_DEFINE_INTERFACE (GstPlayVideoRenderer, gst_play_video_renderer,
+    G_TYPE_OBJECT);
+
+static void
+gst_play_video_renderer_default_init (G_GNUC_UNUSED
+    GstPlayVideoRendererInterface * iface)
+{
+
+}
+
+GstElement *
+gst_play_video_renderer_create_video_sink (GstPlayVideoRenderer * self,
+    GstPlay * play)
+{
+  GstPlayVideoRendererInterface *iface;
+
+  g_return_val_if_fail (GST_IS_PLAY_VIDEO_RENDERER (self), NULL);
+  iface = GST_PLAY_VIDEO_RENDERER_GET_INTERFACE (self);
+  g_return_val_if_fail (iface->create_video_sink != NULL, NULL);
+
+  return iface->create_video_sink (self, play);
+}
diff --git a/gst-libs/gst/play/gstplay-video-renderer.h b/gst-libs/gst/play/gstplay-video-renderer.h
new file mode 100644 (file)
index 0000000..d140ba6
--- /dev/null
@@ -0,0 +1,57 @@
+/* GStreamer
+ *
+ * Copyright (C) 2014-2015 Sebastian Dröge <sebastian@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_PLAY_VIDEO_RENDERER_H__
+#define __GST_PLAY_VIDEO_RENDERER_H__
+
+#include <gst/gst.h>
+#include <gst/play/gstplay-types.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GstPlayVideoRenderer:
+ * Since: 1.20
+ */
+typedef struct _GstPlayVideoRenderer GstPlayVideoRenderer;
+typedef struct _GstPlayVideoRendererInterface GstPlayVideoRendererInterface;
+
+#define GST_TYPE_PLAY_VIDEO_RENDERER                (gst_play_video_renderer_get_type ())
+#define GST_PLAY_VIDEO_RENDERER(obj)                (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PLAY_VIDEO_RENDERER, GstPlayVideoRenderer))
+#define GST_IS_PLAY_VIDEO_RENDERER(obj)             (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PLAY_VIDEO_RENDERER))
+
+/**
+ * GST_PLAY_VIDEO_RENDERER_GET_INTERFACE:
+ * Since: 1.20
+ */
+#define GST_PLAY_VIDEO_RENDERER_GET_INTERFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GST_TYPE_PLAY_VIDEO_RENDERER, GstPlayVideoRendererInterface))
+
+struct _GstPlayVideoRendererInterface {
+  GTypeInterface parent_iface;
+
+  GstElement * (*create_video_sink) (GstPlayVideoRenderer * self, GstPlay * play);
+};
+
+GST_PLAY_API
+GType        gst_play_video_renderer_get_type       (void);
+
+G_END_DECLS
+
+#endif /* __GST_PLAY_VIDEO_RENDERER_H__ */
diff --git a/gst-libs/gst/play/gstplay-visualization.c b/gst-libs/gst/play/gstplay-visualization.c
new file mode 100644 (file)
index 0000000..c00d223
--- /dev/null
@@ -0,0 +1,183 @@
+/* GStreamer
+ *
+ * Copyright (C) 2014-2015 Sebastian Dröge <sebastian@centricular.com>
+ * Copyright (C) 2015 Brijesh Singh <brijesh.ksingh@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:gstplay-visualization
+ * @title: GstPlayVisualization
+ * @short_description: Play Visualization
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstplay-visualization.h"
+
+#include <string.h>
+
+static GMutex vis_lock;
+static GQueue vis_list = G_QUEUE_INIT;
+static guint32 vis_cookie;
+
+G_DEFINE_BOXED_TYPE (GstPlayVisualization, gst_play_visualization,
+    (GBoxedCopyFunc) gst_play_visualization_copy,
+    (GBoxedFreeFunc) gst_play_visualization_free);
+
+/**
+ * gst_play_visualization_free:
+ * @vis: #GstPlayVisualization instance
+ *
+ * Frees a #GstPlayVisualization.
+ * Since: 1.20
+ */
+void
+gst_play_visualization_free (GstPlayVisualization * vis)
+{
+  g_return_if_fail (vis != NULL);
+
+  g_free (vis->name);
+  g_free (vis->description);
+  g_free (vis);
+}
+
+/**
+ * gst_play_visualization_copy:
+ * @vis: #GstPlayVisualization instance
+ *
+ * Makes a copy of the #GstPlayVisualization. The result must be
+ * freed using gst_play_visualization_free().
+ *
+ * Returns: (transfer full): an allocated copy of @vis.
+ * Since: 1.20
+ */
+GstPlayVisualization *
+gst_play_visualization_copy (const GstPlayVisualization * vis)
+{
+  GstPlayVisualization *ret;
+
+  g_return_val_if_fail (vis != NULL, NULL);
+
+  ret = g_new0 (GstPlayVisualization, 1);
+  ret->name = vis->name ? g_strdup (vis->name) : NULL;
+  ret->description = vis->description ? g_strdup (vis->description) : NULL;
+
+  return ret;
+}
+
+/**
+ * gst_play_visualizations_free:
+ * @viss: a %NULL terminated array of #GstPlayVisualization to free
+ *
+ * Frees a %NULL terminated array of #GstPlayVisualization.
+ * Since: 1.20
+ */
+void
+gst_play_visualizations_free (GstPlayVisualization ** viss)
+{
+  GstPlayVisualization **p;
+
+  g_return_if_fail (viss != NULL);
+
+  p = viss;
+  while (*p) {
+    g_free ((*p)->name);
+    g_free ((*p)->description);
+    g_free (*p);
+    p++;
+  }
+  g_free (viss);
+}
+
+static void
+gst_play_update_visualization_list (void)
+{
+  GList *features;
+  GList *l;
+  guint32 cookie;
+  GstPlayVisualization *vis;
+
+  g_mutex_lock (&vis_lock);
+
+  /* check if we need to update the list */
+  cookie = gst_registry_get_feature_list_cookie (gst_registry_get ());
+  if (vis_cookie == cookie) {
+    g_mutex_unlock (&vis_lock);
+    return;
+  }
+
+  /* if update is needed then first free the existing list */
+  while ((vis = g_queue_pop_head (&vis_list)))
+    gst_play_visualization_free (vis);
+
+  features = gst_registry_get_feature_list (gst_registry_get (),
+      GST_TYPE_ELEMENT_FACTORY);
+
+  for (l = features; l; l = l->next) {
+    GstPluginFeature *feature = l->data;
+    const gchar *klass;
+
+    klass = gst_element_factory_get_metadata (GST_ELEMENT_FACTORY (feature),
+        GST_ELEMENT_METADATA_KLASS);
+
+    if (strstr (klass, "Visualization")) {
+      vis = g_new0 (GstPlayVisualization, 1);
+
+      vis->name = g_strdup (gst_plugin_feature_get_name (feature));
+      vis->description =
+          g_strdup (gst_element_factory_get_metadata (GST_ELEMENT_FACTORY
+              (feature), GST_ELEMENT_METADATA_DESCRIPTION));
+      g_queue_push_tail (&vis_list, vis);
+    }
+  }
+  gst_plugin_feature_list_free (features);
+
+  vis_cookie = cookie;
+
+  g_mutex_unlock (&vis_lock);
+}
+
+/**
+ * gst_play_visualizations_get:
+ *
+ * Returns: (transfer full) (array zero-terminated=1) (element-type GstPlayVisualization):
+ *  a %NULL terminated array containing all available
+ *  visualizations. Use gst_play_visualizations_free() after
+ *  usage.
+ * Since: 1.20
+ */
+GstPlayVisualization **
+gst_play_visualizations_get (void)
+{
+  gint i = 0;
+  GList *l;
+  GstPlayVisualization **ret;
+
+  gst_play_update_visualization_list ();
+
+  g_mutex_lock (&vis_lock);
+  ret = g_new0 (GstPlayVisualization *, g_queue_get_length (&vis_list) + 1);
+  for (l = vis_list.head; l; l = l->next)
+    ret[i++] = gst_play_visualization_copy (l->data);
+  g_mutex_unlock (&vis_lock);
+
+  return ret;
+}
diff --git a/gst-libs/gst/play/gstplay-visualization.h b/gst-libs/gst/play/gstplay-visualization.h
new file mode 100644 (file)
index 0000000..6f08bf5
--- /dev/null
@@ -0,0 +1,61 @@
+/* GStreamer
+ *
+ * Copyright (C) 2014-2015 Sebastian Dröge <sebastian@centricular.com>
+ * Copyright (C) 2015 Brijesh Singh <brijesh.ksingh@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_PLAY_VISUALIZATION_H__
+#define __GST_PLAY_VISUALIZATION_H__
+
+#include <gst/gst.h>
+#include <gst/play/play-prelude.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GstPlayVisualization GstPlayVisualization;
+/**
+ * GstPlayVisualization:
+ * @name: name of the visualization.
+ * @description: description of the visualization.
+ *
+ * A #GstPlayVisualization descriptor.
+ * Since: 1.20
+ */
+struct _GstPlayVisualization {
+  gchar *name;
+  gchar *description;
+};
+
+GST_PLAY_API
+GType                     gst_play_visualization_get_type (void);
+
+GST_PLAY_API
+GstPlayVisualization *  gst_play_visualization_copy  (const GstPlayVisualization *vis);
+
+GST_PLAY_API
+void                      gst_play_visualization_free  (GstPlayVisualization *vis);
+
+GST_PLAY_API
+GstPlayVisualization ** gst_play_visualizations_get  (void);
+
+GST_PLAY_API
+void                      gst_play_visualizations_free (GstPlayVisualization **viss);
+
+G_END_DECLS
+
+#endif /* __GST_PLAY_VISUALIZATION_H__ */
diff --git a/gst-libs/gst/play/gstplay.c b/gst-libs/gst/play/gstplay.c
new file mode 100644 (file)
index 0000000..0286b80
--- /dev/null
@@ -0,0 +1,4738 @@
+/* GStreamer
+ *
+ * Copyright (C) 2014-2015 Sebastian Dröge <sebastian@centricular.com>
+ * Copyright (C) 2015 Brijesh Singh <brijesh.ksingh@gmail.com>
+ * Copyright (C) 2019-2020 Stephan Hesse <stephan@emliri.com>
+ * Copyright (C) 2020 Philippe Normand <philn@igalia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:gstplay
+ * @title: GstPlay
+ * @short_description: Player
+ * @symbols:
+ * - GstPlay
+ *
+ * Since: 1.20
+ */
+
+/* TODO:
+ *
+ * - Equalizer
+ * - Gapless playback
+ * - Frame stepping
+ * - Subtitle font, connection speed
+ * - Deinterlacing
+ * - Buffering control (-> progressive downloading)
+ * - Playlist/queue object
+ * - Custom video sink (e.g. embed in GL scene)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstplay.h"
+#include "gstplay-video-renderer-private.h"
+#include "gstplay-media-info-private.h"
+#include "gstplay-message-private.h"
+
+#include <gst/gst.h>
+#include <gst/video/video.h>
+#include <gst/video/colorbalance.h>
+#include <gst/tag/tag.h>
+#include <gst/pbutils/descriptions.h>
+
+#include <string.h>
+
+GST_DEBUG_CATEGORY_STATIC (gst_play_debug);
+#define GST_CAT_DEFAULT gst_play_debug
+
+#define DEFAULT_URI NULL
+#define DEFAULT_POSITION GST_CLOCK_TIME_NONE
+#define DEFAULT_DURATION GST_CLOCK_TIME_NONE
+#define DEFAULT_VOLUME 1.0
+#define DEFAULT_MUTE FALSE
+#define DEFAULT_RATE 1.0
+#define DEFAULT_POSITION_UPDATE_INTERVAL_MS 100
+#define DEFAULT_AUDIO_VIDEO_OFFSET 0
+#define DEFAULT_SUBTITLE_VIDEO_OFFSET 0
+
+/**
+ * gst_play_error_quark:
+ * Since: 1.20
+ */
+GQuark
+gst_play_error_quark (void)
+{
+  return g_quark_from_static_string ("gst-play-error-quark");
+}
+
+static GQuark QUARK_CONFIG;
+
+/* Keep ConfigQuarkId and _config_quark_strings ordered and synced */
+typedef enum
+{
+  CONFIG_QUARK_USER_AGENT = 0,
+  CONFIG_QUARK_POSITION_INTERVAL_UPDATE,
+  CONFIG_QUARK_ACCURATE_SEEK,
+
+  CONFIG_QUARK_MAX
+} ConfigQuarkId;
+
+static const gchar *_config_quark_strings[] = {
+  "user-agent",
+  "position-interval-update",
+  "accurate-seek",
+};
+
+GQuark _config_quark_table[CONFIG_QUARK_MAX];
+
+#define CONFIG_QUARK(q) _config_quark_table[CONFIG_QUARK_##q]
+
+enum
+{
+  PROP_0,
+  PROP_VIDEO_RENDERER,
+  PROP_URI,
+  PROP_SUBURI,
+  PROP_POSITION,
+  PROP_DURATION,
+  PROP_MEDIA_INFO,
+  PROP_CURRENT_AUDIO_TRACK,
+  PROP_CURRENT_VIDEO_TRACK,
+  PROP_CURRENT_SUBTITLE_TRACK,
+  PROP_VOLUME,
+  PROP_MUTE,
+  PROP_RATE,
+  PROP_PIPELINE,
+  PROP_VIDEO_MULTIVIEW_MODE,
+  PROP_VIDEO_MULTIVIEW_FLAGS,
+  PROP_AUDIO_VIDEO_OFFSET,
+  PROP_SUBTITLE_VIDEO_OFFSET,
+  PROP_LAST
+};
+
+enum
+{
+  GST_PLAY_FLAG_VIDEO = (1 << 0),
+  GST_PLAY_FLAG_AUDIO = (1 << 1),
+  GST_PLAY_FLAG_SUBTITLE = (1 << 2),
+  GST_PLAY_FLAG_VIS = (1 << 3)
+};
+
+struct _GstPlay
+{
+  GstObject parent;
+
+  GstPlayVideoRenderer *video_renderer;
+
+  gchar *uri;
+  gchar *redirect_uri;
+  gchar *suburi;
+
+  GThread *thread;
+  GMutex lock;
+  GCond cond;
+  GMainContext *context;
+  GMainLoop *loop;
+
+  GstBus *api_bus;
+
+  GstElement *playbin;
+  GstBus *bus;
+  GstState target_state, current_state;
+  gboolean is_live, is_eos;
+  GSource *tick_source, *ready_timeout_source;
+
+  GstClockTime cached_duration;
+  gint64 cached_position;
+
+  gdouble rate;
+
+  GstPlayState app_state;
+
+  gint buffering;
+
+  GstTagList *global_tags;
+  GstPlayMediaInfo *media_info;
+
+  GstElement *current_vis_element;
+
+  GstStructure *config;
+
+  /* Protected by lock */
+  gboolean seek_pending;        /* Only set from main context */
+  GstClockTime last_seek_time;  /* Only set from main context */
+  GSource *seek_source;
+  GstClockTime seek_position;
+
+  /* For playbin3 */
+  gboolean use_playbin3;
+  GstStreamCollection *collection;
+  gchar *video_sid;
+  gchar *audio_sid;
+  gchar *subtitle_sid;
+  gulong stream_notify_id;
+};
+
+struct _GstPlayClass
+{
+  GstObjectClass parent_class;
+};
+
+#define parent_class gst_play_parent_class
+G_DEFINE_TYPE (GstPlay, gst_play, GST_TYPE_OBJECT);
+
+static GParamSpec *param_specs[PROP_LAST] = { NULL, };
+
+static void gst_play_dispose (GObject * object);
+static void gst_play_finalize (GObject * object);
+static void gst_play_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec);
+static void gst_play_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec);
+static void gst_play_constructed (GObject * object);
+
+static gpointer gst_play_main (gpointer data);
+
+static void gst_play_seek_internal_locked (GstPlay * self);
+static void gst_play_stop_internal (GstPlay * self, gboolean transient);
+static gboolean gst_play_pause_internal (gpointer user_data);
+static gboolean gst_play_play_internal (gpointer user_data);
+static gboolean gst_play_seek_internal (gpointer user_data);
+static void gst_play_set_rate_internal (GstPlay * self);
+static void change_state (GstPlay * self, GstPlayState state);
+
+static GstPlayMediaInfo *gst_play_media_info_create (GstPlay * self);
+
+static void gst_play_streams_info_create (GstPlay * self,
+    GstPlayMediaInfo * media_info, const gchar * prop, GType type);
+static void gst_play_stream_info_update (GstPlay * self, GstPlayStreamInfo * s);
+static void gst_play_stream_info_update_tags_and_caps (GstPlay * self,
+    GstPlayStreamInfo * s);
+static GstPlayStreamInfo *gst_play_stream_info_find (GstPlayMediaInfo *
+    media_info, GType type, gint stream_index);
+static GstPlayStreamInfo *gst_play_stream_info_get_current (GstPlay *
+    self, const gchar * prop, GType type);
+
+static void gst_play_video_info_update (GstPlay * self,
+    GstPlayStreamInfo * stream_info);
+static void gst_play_audio_info_update (GstPlay * self,
+    GstPlayStreamInfo * stream_info);
+static void gst_play_subtitle_info_update (GstPlay * self,
+    GstPlayStreamInfo * stream_info);
+
+/* For playbin3 */
+static void gst_play_streams_info_create_from_collection (GstPlay * self,
+    GstPlayMediaInfo * media_info, GstStreamCollection * collection);
+static void gst_play_stream_info_update_from_stream (GstPlay * self,
+    GstPlayStreamInfo * s, GstStream * stream);
+static GstPlayStreamInfo *gst_play_stream_info_find_from_stream_id
+    (GstPlayMediaInfo * media_info, const gchar * stream_id);
+static GstPlayStreamInfo *gst_play_stream_info_get_current_from_stream_id
+    (GstPlay * self, const gchar * stream_id, GType type);
+static void stream_notify_cb (GstStreamCollection * collection,
+    GstStream * stream, GParamSpec * pspec, GstPlay * self);
+
+static void on_media_info_updated (GstPlay * self);
+
+static void *get_title (GstTagList * tags);
+static void *get_container_format (GstTagList * tags);
+static void *get_from_tags (GstPlay * self, GstPlayMediaInfo * media_info,
+    void *(*func) (GstTagList *));
+static void *get_cover_sample (GstTagList * tags);
+
+static void remove_seek_source (GstPlay * self);
+
+static gboolean query_position (GstPlay * self, GstClockTime * position);
+
+static void
+gst_play_init (GstPlay * self)
+{
+  GST_TRACE_OBJECT (self, "Initializing");
+
+  self = gst_play_get_instance_private (self);
+
+  g_mutex_init (&self->lock);
+  g_cond_init (&self->cond);
+
+  self->context = g_main_context_new ();
+  self->loop = g_main_loop_new (self->context, FALSE);
+  self->api_bus = gst_bus_new ();
+
+  /* *INDENT-OFF* */
+  self->config = gst_structure_new_id (QUARK_CONFIG,
+      CONFIG_QUARK (POSITION_INTERVAL_UPDATE), G_TYPE_UINT, DEFAULT_POSITION_UPDATE_INTERVAL_MS,
+      CONFIG_QUARK (ACCURATE_SEEK), G_TYPE_BOOLEAN, FALSE,
+      NULL);
+  /* *INDENT-ON* */
+
+  self->seek_pending = FALSE;
+  self->seek_position = GST_CLOCK_TIME_NONE;
+  self->last_seek_time = GST_CLOCK_TIME_NONE;
+
+  self->cached_position = 0;
+  self->cached_duration = GST_CLOCK_TIME_NONE;
+
+  GST_TRACE_OBJECT (self, "Initialized");
+}
+
+/*
+ * Works same as gst_structure_set to set field/type/value triplets on message data
+ */
+static void
+api_bus_post_message (GstPlay * self, GstPlayMessage message_type,
+    const gchar * firstfield, ...)
+{
+  GstStructure *message_data = NULL;
+  GstMessage *msg = NULL;
+  va_list varargs;
+
+  GST_INFO ("Posting API-bus message-type: %s",
+      gst_play_message_get_name (message_type));
+  message_data = gst_structure_new (GST_PLAY_MESSAGE_DATA,
+      GST_PLAY_MESSAGE_DATA_TYPE, GST_TYPE_PLAY_MESSAGE, message_type, NULL);
+
+  va_start (varargs, firstfield);
+  gst_structure_set_valist (message_data, firstfield, varargs);
+  va_end (varargs);
+
+  msg = gst_message_new_custom (GST_MESSAGE_APPLICATION,
+      GST_OBJECT (self), message_data);
+  GST_DEBUG ("Created message with payload: [ %" GST_PTR_FORMAT " ]",
+      message_data);
+  gst_bus_post (self->api_bus, msg);
+}
+
+static void
+config_quark_initialize (void)
+{
+  gint i;
+
+  QUARK_CONFIG = g_quark_from_static_string ("play-config");
+
+  if (G_N_ELEMENTS (_config_quark_strings) != CONFIG_QUARK_MAX)
+    g_warning ("the quark table is not consistent! %d != %d",
+        (int) G_N_ELEMENTS (_config_quark_strings), CONFIG_QUARK_MAX);
+
+  for (i = 0; i < CONFIG_QUARK_MAX; i++) {
+    _config_quark_table[i] =
+        g_quark_from_static_string (_config_quark_strings[i]);
+  }
+}
+
+static void
+gst_play_class_init (GstPlayClass * klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+
+  gobject_class->set_property = gst_play_set_property;
+  gobject_class->get_property = gst_play_get_property;
+  gobject_class->dispose = gst_play_dispose;
+  gobject_class->finalize = gst_play_finalize;
+  gobject_class->constructed = gst_play_constructed;
+
+  param_specs[PROP_VIDEO_RENDERER] =
+      g_param_spec_object ("video-renderer",
+      "Video Renderer", "Video renderer to use for rendering videos",
+      GST_TYPE_PLAY_VIDEO_RENDERER,
+      G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+  param_specs[PROP_URI] = g_param_spec_string ("uri", "URI", "Current URI",
+      DEFAULT_URI, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+  param_specs[PROP_SUBURI] = g_param_spec_string ("suburi", "Subtitle URI",
+      "Current Subtitle URI", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+  param_specs[PROP_POSITION] =
+      g_param_spec_uint64 ("position", "Position", "Current Position",
+      0, G_MAXUINT64, DEFAULT_POSITION,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  param_specs[PROP_MEDIA_INFO] =
+      g_param_spec_object ("media-info", "Media Info",
+      "Current media information", GST_TYPE_PLAY_MEDIA_INFO,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  param_specs[PROP_CURRENT_AUDIO_TRACK] =
+      g_param_spec_object ("current-audio-track", "Current Audio Track",
+      "Current audio track information", GST_TYPE_PLAY_AUDIO_INFO,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  param_specs[PROP_CURRENT_VIDEO_TRACK] =
+      g_param_spec_object ("current-video-track", "Current Video Track",
+      "Current video track information", GST_TYPE_PLAY_VIDEO_INFO,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  param_specs[PROP_CURRENT_SUBTITLE_TRACK] =
+      g_param_spec_object ("current-subtitle-track", "Current Subtitle Track",
+      "Current audio subtitle information", GST_TYPE_PLAY_SUBTITLE_INFO,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  param_specs[PROP_DURATION] =
+      g_param_spec_uint64 ("duration", "Duration", "Duration",
+      0, G_MAXUINT64, DEFAULT_DURATION,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  param_specs[PROP_VOLUME] =
+      g_param_spec_double ("volume", "Volume", "Volume",
+      0, 10.0, DEFAULT_VOLUME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+  param_specs[PROP_MUTE] =
+      g_param_spec_boolean ("mute", "Mute", "Mute",
+      DEFAULT_MUTE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+  param_specs[PROP_PIPELINE] =
+      g_param_spec_object ("pipeline", "Pipeline",
+      "GStreamer pipeline that is used",
+      GST_TYPE_ELEMENT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  param_specs[PROP_RATE] =
+      g_param_spec_double ("rate", "rate", "Playback rate",
+      -64.0, 64.0, DEFAULT_RATE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+  param_specs[PROP_VIDEO_MULTIVIEW_MODE] =
+      g_param_spec_enum ("video-multiview-mode",
+      "Multiview Mode Override",
+      "Re-interpret a video stream as one of several frame-packed stereoscopic modes.",
+      GST_TYPE_VIDEO_MULTIVIEW_FRAME_PACKING,
+      GST_VIDEO_MULTIVIEW_FRAME_PACKING_NONE,
+      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+  param_specs[PROP_VIDEO_MULTIVIEW_FLAGS] =
+      g_param_spec_flags ("video-multiview-flags",
+      "Multiview Flags Override",
+      "Override details of the multiview frame layout",
+      GST_TYPE_VIDEO_MULTIVIEW_FLAGS, GST_VIDEO_MULTIVIEW_FLAGS_NONE,
+      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+  param_specs[PROP_AUDIO_VIDEO_OFFSET] =
+      g_param_spec_int64 ("audio-video-offset", "Audio Video Offset",
+      "The synchronisation offset between audio and video in nanoseconds",
+      G_MININT64, G_MAXINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+  param_specs[PROP_SUBTITLE_VIDEO_OFFSET] =
+      g_param_spec_int64 ("subtitle-video-offset", "Text Video Offset",
+      "The synchronisation offset between text and video in nanoseconds",
+      G_MININT64, G_MAXINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
+
+  config_quark_initialize ();
+}
+
+static void
+gst_play_dispose (GObject * object)
+{
+  GstPlay *self = GST_PLAY (object);
+
+  GST_TRACE_OBJECT (self, "Stopping main thread");
+
+  if (self->loop) {
+    g_main_loop_quit (self->loop);
+
+    if (self->thread != g_thread_self ())
+      g_thread_join (self->thread);
+    else
+      g_thread_unref (self->thread);
+    self->thread = NULL;
+
+    g_main_loop_unref (self->loop);
+    self->loop = NULL;
+
+    g_main_context_unref (self->context);
+    self->context = NULL;
+  }
+
+  gst_clear_object (&self->api_bus);
+
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gst_play_finalize (GObject * object)
+{
+  GstPlay *self = GST_PLAY (object);
+
+  GST_TRACE_OBJECT (self, "Finalizing");
+
+  g_free (self->uri);
+  g_free (self->redirect_uri);
+  g_free (self->suburi);
+  g_free (self->video_sid);
+  g_free (self->audio_sid);
+  g_free (self->subtitle_sid);
+  if (self->global_tags)
+    gst_tag_list_unref (self->global_tags);
+  if (self->video_renderer)
+    g_object_unref (self->video_renderer);
+  if (self->current_vis_element)
+    gst_object_unref (self->current_vis_element);
+  if (self->config)
+    gst_structure_free (self->config);
+  if (self->collection)
+    gst_object_unref (self->collection);
+  g_mutex_clear (&self->lock);
+  g_cond_clear (&self->cond);
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_play_constructed (GObject * object)
+{
+  GstPlay *self = GST_PLAY (object);
+
+  GST_TRACE_OBJECT (self, "Constructed");
+
+  g_mutex_lock (&self->lock);
+  self->thread = g_thread_new ("GstPlay", gst_play_main, self);
+  while (!self->loop || !g_main_loop_is_running (self->loop))
+    g_cond_wait (&self->cond, &self->lock);
+  g_mutex_unlock (&self->lock);
+
+  G_OBJECT_CLASS (parent_class)->constructed (object);
+}
+
+static gboolean
+gst_play_set_uri_internal (gpointer user_data)
+{
+  GstPlay *self = user_data;
+
+  gst_play_stop_internal (self, FALSE);
+
+  g_mutex_lock (&self->lock);
+
+  GST_DEBUG_OBJECT (self, "Changing URI to '%s'", GST_STR_NULL (self->uri));
+
+  g_object_set (self->playbin, "uri", self->uri, NULL);
+
+  api_bus_post_message (self, GST_PLAY_MESSAGE_URI_LOADED,
+      GST_PLAY_MESSAGE_DATA_URI, G_TYPE_STRING, self->uri, NULL);
+
+  g_object_set (self->playbin, "suburi", NULL, NULL);
+
+  g_mutex_unlock (&self->lock);
+
+  return G_SOURCE_REMOVE;
+}
+
+static gboolean
+gst_play_set_suburi_internal (gpointer user_data)
+{
+  GstPlay *self = user_data;
+  GstClockTime position;
+  GstState target_state;
+
+  /* save the state and position */
+  target_state = self->target_state;
+  position = gst_play_get_position (self);
+
+  gst_play_stop_internal (self, TRUE);
+  g_mutex_lock (&self->lock);
+
+  GST_DEBUG_OBJECT (self, "Changing SUBURI to '%s'",
+      GST_STR_NULL (self->suburi));
+
+  g_object_set (self->playbin, "suburi", self->suburi, NULL);
+
+  g_mutex_unlock (&self->lock);
+
+  /* restore state and position */
+  if (position != GST_CLOCK_TIME_NONE)
+    gst_play_seek (self, position);
+  if (target_state == GST_STATE_PAUSED)
+    gst_play_pause_internal (self);
+  else if (target_state == GST_STATE_PLAYING)
+    gst_play_play_internal (self);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+gst_play_set_rate_internal (GstPlay * self)
+{
+  self->seek_position = gst_play_get_position (self);
+
+  /* If there is no seek being dispatch to the main context currently do that,
+   * otherwise we just updated the rate so that it will be taken by
+   * the seek handler from the main context instead of the old one.
+   */
+  if (!self->seek_source) {
+    /* If no seek is pending then create new seek source */
+    if (!self->seek_pending) {
+      self->seek_source = g_idle_source_new ();
+      g_source_set_callback (self->seek_source,
+          (GSourceFunc) gst_play_seek_internal, self, NULL);
+      g_source_attach (self->seek_source, self->context);
+    }
+  }
+}
+
+static void
+gst_play_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstPlay *self = GST_PLAY (object);
+
+  switch (prop_id) {
+    case PROP_VIDEO_RENDERER:
+      self->video_renderer = g_value_dup_object (value);
+      break;
+    case PROP_URI:{
+      g_mutex_lock (&self->lock);
+      g_free (self->uri);
+      g_free (self->redirect_uri);
+      self->redirect_uri = NULL;
+
+      g_free (self->suburi);
+      self->suburi = NULL;
+
+      self->uri = g_value_dup_string (value);
+      GST_DEBUG_OBJECT (self, "Set uri=%s", self->uri);
+      g_mutex_unlock (&self->lock);
+
+      g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT,
+          gst_play_set_uri_internal, self, NULL);
+      break;
+    }
+    case PROP_SUBURI:{
+      g_mutex_lock (&self->lock);
+      g_free (self->suburi);
+
+      self->suburi = g_value_dup_string (value);
+      GST_DEBUG_OBJECT (self, "Set suburi=%s", self->suburi);
+      g_mutex_unlock (&self->lock);
+
+      g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT,
+          gst_play_set_suburi_internal, self, NULL);
+      break;
+    }
+    case PROP_VOLUME:
+      GST_DEBUG_OBJECT (self, "Set volume=%lf", g_value_get_double (value));
+      g_object_set_property (G_OBJECT (self->playbin), "volume", value);
+      break;
+    case PROP_RATE:
+      g_mutex_lock (&self->lock);
+      self->rate = g_value_get_double (value);
+      GST_DEBUG_OBJECT (self, "Set rate=%lf", g_value_get_double (value));
+      gst_play_set_rate_internal (self);
+      g_mutex_unlock (&self->lock);
+      break;
+    case PROP_MUTE:
+      GST_DEBUG_OBJECT (self, "Set mute=%d", g_value_get_boolean (value));
+      g_object_set_property (G_OBJECT (self->playbin), "mute", value);
+      break;
+    case PROP_VIDEO_MULTIVIEW_MODE:
+      GST_DEBUG_OBJECT (self, "Set multiview mode=%u",
+          g_value_get_enum (value));
+      g_object_set_property (G_OBJECT (self->playbin), "video-multiview-mode",
+          value);
+      break;
+    case PROP_VIDEO_MULTIVIEW_FLAGS:
+      GST_DEBUG_OBJECT (self, "Set multiview flags=%x",
+          g_value_get_flags (value));
+      g_object_set_property (G_OBJECT (self->playbin), "video-multiview-flags",
+          value);
+      break;
+    case PROP_AUDIO_VIDEO_OFFSET:
+      g_object_set_property (G_OBJECT (self->playbin), "av-offset", value);
+      break;
+    case PROP_SUBTITLE_VIDEO_OFFSET:
+      g_object_set_property (G_OBJECT (self->playbin), "text-offset", value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gst_play_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstPlay *self = GST_PLAY (object);
+
+  switch (prop_id) {
+    case PROP_URI:
+      g_mutex_lock (&self->lock);
+      g_value_set_string (value, self->uri);
+      g_mutex_unlock (&self->lock);
+      break;
+    case PROP_SUBURI:
+      g_mutex_lock (&self->lock);
+      g_value_set_string (value, self->suburi);
+      g_mutex_unlock (&self->lock);
+      GST_DEBUG_OBJECT (self, "Returning suburi=%s",
+          g_value_get_string (value));
+      break;
+    case PROP_POSITION:{
+      GstClockTime position = GST_CLOCK_TIME_NONE;
+      query_position (self, &position);
+      g_value_set_uint64 (value, position);
+      GST_TRACE_OBJECT (self, "Returning position=%" GST_TIME_FORMAT,
+          GST_TIME_ARGS (g_value_get_uint64 (value)));
+      break;
+    }
+    case PROP_DURATION:{
+      g_value_set_uint64 (value, self->cached_duration);
+      GST_TRACE_OBJECT (self, "Returning duration=%" GST_TIME_FORMAT,
+          GST_TIME_ARGS (g_value_get_uint64 (value)));
+      break;
+    }
+    case PROP_MEDIA_INFO:{
+      GstPlayMediaInfo *media_info = gst_play_get_media_info (self);
+      g_value_take_object (value, media_info);
+      break;
+    }
+    case PROP_CURRENT_AUDIO_TRACK:{
+      GstPlayAudioInfo *audio_info = gst_play_get_current_audio_track (self);
+      g_value_take_object (value, audio_info);
+      break;
+    }
+    case PROP_CURRENT_VIDEO_TRACK:{
+      GstPlayVideoInfo *video_info = gst_play_get_current_video_track (self);
+      g_value_take_object (value, video_info);
+      break;
+    }
+    case PROP_CURRENT_SUBTITLE_TRACK:{
+      GstPlaySubtitleInfo *subtitle_info =
+          gst_play_get_current_subtitle_track (self);
+      g_value_take_object (value, subtitle_info);
+      break;
+    }
+    case PROP_VOLUME:
+      g_object_get_property (G_OBJECT (self->playbin), "volume", value);
+      GST_TRACE_OBJECT (self, "Returning volume=%lf",
+          g_value_get_double (value));
+      break;
+    case PROP_RATE:
+      g_mutex_lock (&self->lock);
+      g_value_set_double (value, self->rate);
+      g_mutex_unlock (&self->lock);
+      break;
+    case PROP_MUTE:
+      g_object_get_property (G_OBJECT (self->playbin), "mute", value);
+      GST_TRACE_OBJECT (self, "Returning mute=%d", g_value_get_boolean (value));
+      break;
+    case PROP_PIPELINE:
+      g_value_set_object (value, self->playbin);
+      break;
+    case PROP_VIDEO_MULTIVIEW_MODE:{
+      g_object_get_property (G_OBJECT (self->playbin), "video-multiview-mode",
+          value);
+      GST_TRACE_OBJECT (self, "Return multiview mode=%d",
+          g_value_get_enum (value));
+      break;
+    }
+    case PROP_VIDEO_MULTIVIEW_FLAGS:{
+      g_object_get_property (G_OBJECT (self->playbin), "video-multiview-flags",
+          value);
+      GST_TRACE_OBJECT (self, "Return multiview flags=%x",
+          g_value_get_flags (value));
+      break;
+    }
+    case PROP_AUDIO_VIDEO_OFFSET:
+      g_object_get_property (G_OBJECT (self->playbin), "av-offset", value);
+      break;
+    case PROP_SUBTITLE_VIDEO_OFFSET:
+      g_object_get_property (G_OBJECT (self->playbin), "text-offset", value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static gboolean
+main_loop_running_cb (gpointer user_data)
+{
+  GstPlay *self = GST_PLAY (user_data);
+
+  GST_TRACE_OBJECT (self, "Main loop running now");
+
+  g_mutex_lock (&self->lock);
+  g_cond_signal (&self->cond);
+  g_mutex_unlock (&self->lock);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+change_state (GstPlay * self, GstPlayState state)
+{
+  if (state == self->app_state)
+    return;
+
+  GST_DEBUG_OBJECT (self, "Changing app state from %s to %s",
+      gst_play_state_get_name (self->app_state),
+      gst_play_state_get_name (state));
+
+  self->app_state = state;
+
+  api_bus_post_message (self, GST_PLAY_MESSAGE_STATE_CHANGED,
+      GST_PLAY_MESSAGE_DATA_PLAY_STATE, GST_TYPE_PLAY_STATE,
+      self->app_state, NULL);
+}
+
+static gboolean
+tick_cb (gpointer user_data)
+{
+  GstPlay *self = GST_PLAY (user_data);
+  GstClockTime position;
+  if (query_position (self, &position)) {
+    api_bus_post_message (self, GST_PLAY_MESSAGE_POSITION_UPDATED,
+        GST_PLAY_MESSAGE_DATA_POSITION, GST_TYPE_CLOCK_TIME, position, NULL);
+  }
+
+  return G_SOURCE_CONTINUE;
+}
+
+/*
+ * Returns true when position is queried and differed from cached position.
+ * Sets position to cached value, and to queried value if position can be queried
+ * and different.
+ */
+static gboolean
+query_position (GstPlay * self, GstClockTime * position)
+{
+  gint64 current_position;
+  *position = self->cached_position;
+  if (self->target_state >= GST_STATE_PAUSED
+      && gst_element_query_position (self->playbin, GST_FORMAT_TIME,
+          &current_position)) {
+    GST_LOG_OBJECT (self, "Queried position %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (current_position));
+    if (self->cached_position != current_position) {
+      self->cached_position = current_position;
+      *position = (GstClockTime) current_position;
+      return TRUE;
+    }
+  }
+  return FALSE;
+}
+
+static void
+add_tick_source (GstPlay * self)
+{
+  guint position_update_interval_ms;
+
+  if (self->tick_source)
+    return;
+
+  position_update_interval_ms =
+      gst_play_config_get_position_update_interval (self->config);
+  if (!position_update_interval_ms)
+    return;
+
+  self->tick_source = g_timeout_source_new (position_update_interval_ms);
+  g_source_set_callback (self->tick_source, (GSourceFunc) tick_cb, self, NULL);
+  g_source_attach (self->tick_source, self->context);
+}
+
+static void
+remove_tick_source (GstPlay * self)
+{
+  if (!self->tick_source)
+    return;
+
+  g_source_destroy (self->tick_source);
+  g_source_unref (self->tick_source);
+  self->tick_source = NULL;
+}
+
+static gboolean
+ready_timeout_cb (gpointer user_data)
+{
+  GstPlay *self = user_data;
+
+  if (self->target_state <= GST_STATE_READY) {
+    GST_DEBUG_OBJECT (self, "Setting pipeline to NULL state");
+    self->target_state = GST_STATE_NULL;
+    self->current_state = GST_STATE_NULL;
+    gst_element_set_state (self->playbin, GST_STATE_NULL);
+  }
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+add_ready_timeout_source (GstPlay * self)
+{
+  if (self->ready_timeout_source)
+    return;
+
+  self->ready_timeout_source = g_timeout_source_new_seconds (60);
+  g_source_set_callback (self->ready_timeout_source,
+      (GSourceFunc) ready_timeout_cb, self, NULL);
+  g_source_attach (self->ready_timeout_source, self->context);
+}
+
+static void
+remove_ready_timeout_source (GstPlay * self)
+{
+  if (!self->ready_timeout_source)
+    return;
+
+  g_source_destroy (self->ready_timeout_source);
+  g_source_unref (self->ready_timeout_source);
+  self->ready_timeout_source = NULL;
+}
+
+
+static void
+on_error (GstPlay * self, GError * err, const GstStructure * details)
+{
+  GST_ERROR_OBJECT (self, "Error: %s (%s, %d)", err->message,
+      g_quark_to_string (err->domain), err->code);
+
+  api_bus_post_message (self, GST_PLAY_MESSAGE_ERROR,
+      GST_PLAY_MESSAGE_DATA_ERROR, G_TYPE_ERROR, err,
+      GST_PLAY_MESSAGE_DATA_ERROR_DETAILS, GST_TYPE_STRUCTURE, details, NULL);
+
+  g_error_free (err);
+
+  remove_tick_source (self);
+  remove_ready_timeout_source (self);
+
+  self->target_state = GST_STATE_NULL;
+  self->current_state = GST_STATE_NULL;
+  self->is_live = FALSE;
+  self->is_eos = FALSE;
+  gst_element_set_state (self->playbin, GST_STATE_NULL);
+  change_state (self, GST_PLAY_STATE_STOPPED);
+  self->buffering = 100;
+
+  g_mutex_lock (&self->lock);
+  if (self->media_info) {
+    g_object_unref (self->media_info);
+    self->media_info = NULL;
+  }
+
+  if (self->global_tags) {
+    gst_tag_list_unref (self->global_tags);
+    self->global_tags = NULL;
+  }
+
+  self->seek_pending = FALSE;
+  remove_seek_source (self);
+  self->seek_position = GST_CLOCK_TIME_NONE;
+  self->last_seek_time = GST_CLOCK_TIME_NONE;
+  g_mutex_unlock (&self->lock);
+}
+
+static void
+dump_dot_file (GstPlay * self, const gchar * name)
+{
+  gchar *full_name;
+
+  full_name = g_strdup_printf ("gst-play.%p.%s", self, name);
+
+  GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (self->playbin),
+      GST_DEBUG_GRAPH_SHOW_ALL, full_name);
+
+  g_free (full_name);
+}
+
+static void
+error_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
+{
+  GstPlay *self = GST_PLAY (user_data);
+  GError *err, *play_err;
+  gchar *name, *debug, *message, *full_message;
+  const GstStructure *details = NULL;
+
+  dump_dot_file (self, "error");
+
+  gst_message_parse_error (msg, &err, &debug);
+  gst_message_parse_error_details (msg, &details);
+
+  name = gst_object_get_path_string (msg->src);
+  message = gst_error_get_message (err->domain, err->code);
+
+  if (debug)
+    full_message =
+        g_strdup_printf ("Error from element %s: %s\n%s\n%s", name, message,
+        err->message, debug);
+  else
+    full_message =
+        g_strdup_printf ("Error from element %s: %s\n%s", name, message,
+        err->message);
+
+  GST_ERROR_OBJECT (self, "ERROR: from element %s: %s", name, err->message);
+  if (debug != NULL)
+    GST_ERROR_OBJECT (self, "Additional debug info: %s", debug);
+
+  play_err =
+      g_error_new_literal (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED, full_message);
+  on_error (self, play_err, details);
+
+  g_clear_error (&err);
+  g_free (debug);
+  g_free (name);
+  g_free (full_message);
+  g_free (message);
+}
+
+static void
+warning_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
+{
+  GstPlay *self = GST_PLAY (user_data);
+  GError *err, *play_err;
+  gchar *name, *debug, *message, *full_message;
+  const GstStructure *details = NULL;
+
+  dump_dot_file (self, "warning");
+
+  gst_message_parse_warning (msg, &err, &debug);
+  gst_message_parse_warning_details (msg, &details);
+
+  name = gst_object_get_path_string (msg->src);
+  message = gst_error_get_message (err->domain, err->code);
+
+  if (debug)
+    full_message =
+        g_strdup_printf ("Warning from element %s: %s\n%s\n%s", name, message,
+        err->message, debug);
+  else
+    full_message =
+        g_strdup_printf ("Warning from element %s: %s\n%s", name, message,
+        err->message);
+
+  GST_WARNING_OBJECT (self, "WARNING: from element %s: %s", name, err->message);
+  if (debug != NULL)
+    GST_WARNING_OBJECT (self, "Additional debug info: %s", debug);
+
+  play_err =
+      g_error_new_literal (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED, full_message);
+
+  GST_ERROR_OBJECT (self, "Warning: %s (%s, %d)", err->message,
+      g_quark_to_string (err->domain), err->code);
+
+  api_bus_post_message (self, GST_PLAY_MESSAGE_WARNING,
+      GST_PLAY_MESSAGE_DATA_WARNING, G_TYPE_ERROR, play_err,
+      GST_PLAY_MESSAGE_DATA_WARNING_DETAILS, GST_TYPE_STRUCTURE, details, NULL);
+
+  g_clear_error (&play_err);
+  g_clear_error (&err);
+  g_free (debug);
+  g_free (name);
+  g_free (full_message);
+  g_free (message);
+}
+
+static void
+eos_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg,
+    gpointer user_data)
+{
+  GstPlay *self = GST_PLAY (user_data);
+
+  GST_DEBUG_OBJECT (self, "End of stream");
+
+  tick_cb (self);
+  remove_tick_source (self);
+
+  api_bus_post_message (self, GST_PLAY_MESSAGE_END_OF_STREAM, NULL);
+
+  change_state (self, GST_PLAY_STATE_STOPPED);
+  self->buffering = 100;
+  self->is_eos = TRUE;
+}
+
+static void
+buffering_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
+{
+  GstPlay *self = GST_PLAY (user_data);
+  gint percent;
+
+  if (self->target_state < GST_STATE_PAUSED)
+    return;
+  if (self->is_live)
+    return;
+
+  gst_message_parse_buffering (msg, &percent);
+  GST_LOG_OBJECT (self, "Buffering %d%%", percent);
+
+  if (percent < 100 && self->target_state >= GST_STATE_PAUSED) {
+    GstStateChangeReturn state_ret;
+
+    GST_DEBUG_OBJECT (self, "Waiting for buffering to finish");
+    state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED);
+
+    if (state_ret == GST_STATE_CHANGE_FAILURE) {
+      on_error (self, g_error_new (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED,
+              "Failed to handle buffering_"), NULL);
+      return;
+    }
+
+    change_state (self, GST_PLAY_STATE_BUFFERING);
+  }
+
+  if (self->buffering != percent) {
+    self->buffering = percent;
+
+    api_bus_post_message (self, GST_PLAY_MESSAGE_BUFFERING,
+        GST_PLAY_MESSAGE_DATA_BUFFERING_PERCENT, G_TYPE_UINT, percent, NULL);
+  }
+
+  g_mutex_lock (&self->lock);
+  if (percent == 100 && (self->seek_position != GST_CLOCK_TIME_NONE ||
+          self->seek_pending)) {
+    g_mutex_unlock (&self->lock);
+
+    GST_DEBUG_OBJECT (self, "Buffering finished - seek pending");
+  } else if (percent == 100 && self->target_state >= GST_STATE_PLAYING
+      && self->current_state >= GST_STATE_PAUSED) {
+    GstStateChangeReturn state_ret;
+
+    g_mutex_unlock (&self->lock);
+
+    GST_DEBUG_OBJECT (self, "Buffering finished - going to PLAYING");
+    state_ret = gst_element_set_state (self->playbin, GST_STATE_PLAYING);
+    /* Application state change is happening when the state change happened */
+    if (state_ret == GST_STATE_CHANGE_FAILURE)
+      on_error (self, g_error_new (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED,
+              "Failed to handle buffering"), NULL);
+  } else if (percent == 100 && self->target_state >= GST_STATE_PAUSED) {
+    g_mutex_unlock (&self->lock);
+
+    GST_DEBUG_OBJECT (self, "Buffering finished - staying PAUSED");
+    change_state (self, GST_PLAY_STATE_PAUSED);
+  } else {
+    g_mutex_unlock (&self->lock);
+  }
+}
+
+static void
+clock_lost_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg,
+    gpointer user_data)
+{
+  GstPlay *self = GST_PLAY (user_data);
+  GstStateChangeReturn state_ret;
+
+  GST_DEBUG_OBJECT (self, "Clock lost");
+  if (self->target_state >= GST_STATE_PLAYING) {
+    state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED);
+    if (state_ret != GST_STATE_CHANGE_FAILURE)
+      state_ret = gst_element_set_state (self->playbin, GST_STATE_PLAYING);
+
+    if (state_ret == GST_STATE_CHANGE_FAILURE)
+      on_error (self, g_error_new (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED,
+              "Failed to handle clock loss"), NULL);
+  }
+}
+
+
+static void
+check_video_dimensions_changed (GstPlay * self)
+{
+  GstElement *video_sink;
+  GstPad *video_sink_pad;
+  GstCaps *caps;
+  GstVideoInfo info;
+  guint width = 0, height = 0;
+
+  g_object_get (self->playbin, "video-sink", &video_sink, NULL);
+  if (!video_sink)
+    goto out;
+
+  video_sink_pad = gst_element_get_static_pad (video_sink, "sink");
+  if (!video_sink_pad) {
+    gst_object_unref (video_sink);
+    goto out;
+  }
+
+  caps = gst_pad_get_current_caps (video_sink_pad);
+
+  if (caps) {
+    if (gst_video_info_from_caps (&info, caps)) {
+      info.width = info.width * info.par_n / info.par_d;
+
+      GST_DEBUG_OBJECT (self, "Video dimensions changed: %dx%d", info.width,
+          info.height);
+      width = info.width;
+      height = info.height;
+    }
+
+    gst_caps_unref (caps);
+  }
+  gst_object_unref (video_sink_pad);
+  gst_object_unref (video_sink);
+
+out:
+  api_bus_post_message (self, GST_PLAY_MESSAGE_VIDEO_DIMENSIONS_CHANGED,
+      GST_PLAY_MESSAGE_DATA_VIDEO_WIDTH, G_TYPE_UINT, width,
+      GST_PLAY_MESSAGE_DATA_VIDEO_HEIGHT, G_TYPE_UINT, height, NULL);
+}
+
+static void
+notify_caps_cb (G_GNUC_UNUSED GObject * object,
+    G_GNUC_UNUSED GParamSpec * pspec, gpointer user_data)
+{
+  GstPlay *self = GST_PLAY (user_data);
+
+  check_video_dimensions_changed (self);
+}
+
+static void
+on_duration_changed (GstPlay * self, GstClockTime duration)
+{
+  gboolean updated = FALSE;
+
+  if (self->cached_duration == duration)
+    return;
+
+  GST_DEBUG_OBJECT (self, "Duration changed %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (duration));
+
+  g_mutex_lock (&self->lock);
+  self->cached_duration = duration;
+  if (self->media_info) {
+    self->media_info->duration = duration;
+    updated = TRUE;
+  }
+  g_mutex_unlock (&self->lock);
+
+  api_bus_post_message (self, GST_PLAY_MESSAGE_DURATION_CHANGED,
+      GST_PLAY_MESSAGE_DATA_DURATION, GST_TYPE_CLOCK_TIME,
+      gst_play_get_duration (self), NULL);
+
+  if (updated) {
+    on_media_info_updated (self);
+  }
+}
+
+static void
+on_seek_done (GstPlay * self)
+{
+  api_bus_post_message (self, GST_PLAY_MESSAGE_SEEK_DONE,
+      GST_PLAY_MESSAGE_DATA_POSITION, GST_TYPE_CLOCK_TIME,
+      gst_play_get_position (self), NULL);
+}
+
+static void
+state_changed_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
+    gpointer user_data)
+{
+  GstPlay *self = GST_PLAY (user_data);
+  GstState old_state, new_state, pending_state;
+
+  gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
+
+  if (GST_MESSAGE_SRC (msg) == GST_OBJECT (self->playbin)) {
+    gchar *transition_name;
+
+    GST_DEBUG_OBJECT (self, "Changed state old: %s new: %s pending: %s",
+        gst_element_state_get_name (old_state),
+        gst_element_state_get_name (new_state),
+        gst_element_state_get_name (pending_state));
+
+    transition_name = g_strdup_printf ("%s_%s",
+        gst_element_state_get_name (old_state),
+        gst_element_state_get_name (new_state));
+    dump_dot_file (self, transition_name);
+    g_free (transition_name);
+
+    self->current_state = new_state;
+
+    if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED
+        && pending_state == GST_STATE_VOID_PENDING) {
+      GstElement *video_sink;
+      GstPad *video_sink_pad;
+      gint64 duration = -1;
+
+      GST_DEBUG_OBJECT (self, "Initial PAUSED - pre-rolled");
+
+      g_mutex_lock (&self->lock);
+      if (self->media_info)
+        g_object_unref (self->media_info);
+      self->media_info = gst_play_media_info_create (self);
+      g_mutex_unlock (&self->lock);
+      on_media_info_updated (self);
+
+      g_object_get (self->playbin, "video-sink", &video_sink, NULL);
+
+      if (video_sink) {
+        video_sink_pad = gst_element_get_static_pad (video_sink, "sink");
+
+        if (video_sink_pad) {
+          g_signal_connect (video_sink_pad, "notify::caps",
+              (GCallback) notify_caps_cb, self);
+          gst_object_unref (video_sink_pad);
+        }
+        gst_object_unref (video_sink);
+      }
+
+      check_video_dimensions_changed (self);
+      if (gst_element_query_duration (self->playbin, GST_FORMAT_TIME,
+              &duration)) {
+        on_duration_changed (self, duration);
+      } else {
+        self->cached_duration = GST_CLOCK_TIME_NONE;
+      }
+    }
+
+    if (new_state == GST_STATE_PAUSED
+        && pending_state == GST_STATE_VOID_PENDING) {
+      remove_tick_source (self);
+
+      g_mutex_lock (&self->lock);
+      if (self->seek_pending) {
+        self->seek_pending = FALSE;
+
+        if (!self->media_info->seekable) {
+          GST_DEBUG_OBJECT (self, "Media is not seekable");
+          remove_seek_source (self);
+          self->seek_position = GST_CLOCK_TIME_NONE;
+          self->last_seek_time = GST_CLOCK_TIME_NONE;
+        } else if (self->seek_source) {
+          GST_DEBUG_OBJECT (self, "Seek finished but new seek is pending");
+          gst_play_seek_internal_locked (self);
+        } else {
+          GST_DEBUG_OBJECT (self, "Seek finished");
+          on_seek_done (self);
+        }
+      }
+
+      if (self->seek_position != GST_CLOCK_TIME_NONE) {
+        GST_DEBUG_OBJECT (self, "Seeking now that we reached PAUSED state");
+        gst_play_seek_internal_locked (self);
+        g_mutex_unlock (&self->lock);
+      } else if (!self->seek_pending) {
+        g_mutex_unlock (&self->lock);
+
+        tick_cb (self);
+
+        if (self->target_state >= GST_STATE_PLAYING && self->buffering == 100) {
+          GstStateChangeReturn state_ret;
+
+          state_ret = gst_element_set_state (self->playbin, GST_STATE_PLAYING);
+          if (state_ret == GST_STATE_CHANGE_FAILURE)
+            on_error (self, g_error_new (GST_PLAY_ERROR,
+                    GST_PLAY_ERROR_FAILED, "Failed to play"), NULL);
+        } else if (self->buffering == 100) {
+          change_state (self, GST_PLAY_STATE_PAUSED);
+        }
+      } else {
+        g_mutex_unlock (&self->lock);
+      }
+    } else if (new_state == GST_STATE_PLAYING
+        && pending_state == GST_STATE_VOID_PENDING) {
+      /* api_bus_post_message (self, GST_PLAY_MESSAGE_POSITION_UPDATED, */
+      /*     GST_PLAY_MESSAGE_DATA_POSITION, GST_TYPE_CLOCK_TIME, 0, NULL); */
+
+      /* If no seek is currently pending, add the tick source. This can happen
+       * if we seeked already but the state-change message was still queued up */
+      if (!self->seek_pending) {
+        add_tick_source (self);
+        change_state (self, GST_PLAY_STATE_PLAYING);
+      }
+    } else if (new_state == GST_STATE_READY && old_state > GST_STATE_READY) {
+      change_state (self, GST_PLAY_STATE_STOPPED);
+    } else {
+      /* Otherwise we neither reached PLAYING nor PAUSED, so must
+       * wait for something to happen... i.e. are BUFFERING now */
+      change_state (self, GST_PLAY_STATE_BUFFERING);
+    }
+  }
+}
+
+static void
+duration_changed_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg,
+    gpointer user_data)
+{
+  GstPlay *self = GST_PLAY (user_data);
+  gint64 duration = GST_CLOCK_TIME_NONE;
+
+  if (gst_element_query_duration (self->playbin, GST_FORMAT_TIME, &duration)) {
+    on_duration_changed (self, duration);
+  }
+}
+
+static void
+latency_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg,
+    gpointer user_data)
+{
+  GstPlay *self = GST_PLAY (user_data);
+
+  GST_DEBUG_OBJECT (self, "Latency changed");
+
+  gst_bin_recalculate_latency (GST_BIN (self->playbin));
+}
+
+static void
+request_state_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
+    gpointer user_data)
+{
+  GstPlay *self = GST_PLAY (user_data);
+  GstState state;
+  GstStateChangeReturn state_ret;
+
+  gst_message_parse_request_state (msg, &state);
+
+  GST_DEBUG_OBJECT (self, "State %s requested",
+      gst_element_state_get_name (state));
+
+  self->target_state = state;
+  state_ret = gst_element_set_state (self->playbin, state);
+  if (state_ret == GST_STATE_CHANGE_FAILURE)
+    on_error (self, g_error_new (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED,
+            "Failed to change to requested state %s",
+            gst_element_state_get_name (state)), NULL);
+}
+
+static void
+media_info_update (GstPlay * self, GstPlayMediaInfo * info)
+{
+  g_free (info->title);
+  info->title = get_from_tags (self, info, get_title);
+
+  g_free (info->container);
+  info->container = get_from_tags (self, info, get_container_format);
+
+  if (info->image_sample)
+    gst_sample_unref (info->image_sample);
+  info->image_sample = get_from_tags (self, info, get_cover_sample);
+
+  GST_DEBUG_OBJECT (self, "title: %s, container: %s "
+      "image_sample: %p", info->title, info->container, info->image_sample);
+}
+
+static void
+tags_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
+{
+  GstPlay *self = GST_PLAY (user_data);
+  GstTagList *tags = NULL;
+
+  gst_message_parse_tag (msg, &tags);
+
+  GST_DEBUG_OBJECT (self, "received %s tags",
+      gst_tag_list_get_scope (tags) ==
+      GST_TAG_SCOPE_GLOBAL ? "global" : "stream");
+
+  if (gst_tag_list_get_scope (tags) == GST_TAG_SCOPE_GLOBAL) {
+    g_mutex_lock (&self->lock);
+    if (self->media_info) {
+      if (self->media_info->tags)
+        gst_tag_list_unref (self->media_info->tags);
+      self->media_info->tags = gst_tag_list_ref (tags);
+      media_info_update (self, self->media_info);
+      g_mutex_unlock (&self->lock);
+      on_media_info_updated (self);
+    } else {
+      if (self->global_tags)
+        gst_tag_list_unref (self->global_tags);
+      self->global_tags = gst_tag_list_ref (tags);
+      g_mutex_unlock (&self->lock);
+    }
+  }
+
+  gst_tag_list_unref (tags);
+}
+
+static void
+element_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
+{
+  GstPlay *self = GST_PLAY (user_data);
+  const GstStructure *s;
+
+  s = gst_message_get_structure (msg);
+  if (gst_structure_has_name (s, "redirect")) {
+    const gchar *new_location;
+
+    new_location = gst_structure_get_string (s, "new-location");
+    if (!new_location) {
+      const GValue *locations_list, *location_val;
+      guint i, size;
+
+      locations_list = gst_structure_get_value (s, "locations");
+      size = gst_value_list_get_size (locations_list);
+      for (i = 0; i < size; ++i) {
+        const GstStructure *location_s;
+
+        location_val = gst_value_list_get_value (locations_list, i);
+        if (!GST_VALUE_HOLDS_STRUCTURE (location_val))
+          continue;
+
+        location_s = (const GstStructure *) g_value_get_boxed (location_val);
+        if (!gst_structure_has_name (location_s, "redirect"))
+          continue;
+
+        new_location = gst_structure_get_string (location_s, "new-location");
+        if (new_location)
+          break;
+      }
+    }
+
+    if (new_location) {
+      GstState target_state;
+
+      GST_DEBUG_OBJECT (self, "Redirect to '%s'", new_location);
+
+      /* Remember target state and restore after setting the URI */
+      target_state = self->target_state;
+
+      gst_play_stop_internal (self, TRUE);
+
+      g_mutex_lock (&self->lock);
+      g_free (self->redirect_uri);
+      self->redirect_uri = g_strdup (new_location);
+      g_object_set (self->playbin, "uri", self->redirect_uri, NULL);
+      g_mutex_unlock (&self->lock);
+
+      if (target_state == GST_STATE_PAUSED)
+        gst_play_pause_internal (self);
+      else if (target_state == GST_STATE_PLAYING)
+        gst_play_play_internal (self);
+    }
+  }
+}
+
+/* Must be called with lock */
+static gboolean
+update_stream_collection (GstPlay * self, GstStreamCollection * collection)
+{
+  if (self->collection && self->collection == collection)
+    return FALSE;
+
+  if (self->collection && self->stream_notify_id)
+    g_signal_handler_disconnect (self->collection, self->stream_notify_id);
+
+  gst_object_replace ((GstObject **) & self->collection,
+      (GstObject *) collection);
+  if (self->media_info) {
+    gst_object_unref (self->media_info);
+    self->media_info = gst_play_media_info_create (self);
+  }
+
+  self->stream_notify_id =
+      g_signal_connect (self->collection, "stream-notify",
+      G_CALLBACK (stream_notify_cb), self);
+
+  return TRUE;
+}
+
+static void
+stream_collection_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
+    gpointer user_data)
+{
+  GstPlay *self = GST_PLAY (user_data);
+  GstStreamCollection *collection = NULL;
+  gboolean updated = FALSE;
+
+  gst_message_parse_stream_collection (msg, &collection);
+
+  if (!collection)
+    return;
+
+  g_mutex_lock (&self->lock);
+  updated = update_stream_collection (self, collection);
+  gst_object_unref (collection);
+  g_mutex_unlock (&self->lock);
+
+  if (self->media_info && updated)
+    on_media_info_updated (self);
+}
+
+static void
+streams_selected_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
+    gpointer user_data)
+{
+  GstPlay *self = GST_PLAY (user_data);
+  GstStreamCollection *collection = NULL;
+  gboolean updated = FALSE;
+  guint i, len;
+
+  gst_message_parse_streams_selected (msg, &collection);
+
+  if (!collection)
+    return;
+
+  g_mutex_lock (&self->lock);
+  updated = update_stream_collection (self, collection);
+  gst_object_unref (collection);
+
+  g_free (self->video_sid);
+  g_free (self->audio_sid);
+  g_free (self->subtitle_sid);
+  self->video_sid = NULL;
+  self->audio_sid = NULL;
+  self->subtitle_sid = NULL;
+
+  len = gst_message_streams_selected_get_size (msg);
+  for (i = 0; i < len; i++) {
+    GstStream *stream;
+    GstStreamType stream_type;
+    const gchar *stream_id;
+    gchar **current_sid;
+    stream = gst_message_streams_selected_get_stream (msg, i);
+    stream_type = gst_stream_get_stream_type (stream);
+    stream_id = gst_stream_get_stream_id (stream);
+    if (stream_type & GST_STREAM_TYPE_AUDIO)
+      current_sid = &self->audio_sid;
+    else if (stream_type & GST_STREAM_TYPE_VIDEO)
+      current_sid = &self->video_sid;
+    else if (stream_type & GST_STREAM_TYPE_TEXT)
+      current_sid = &self->subtitle_sid;
+    else {
+      GST_WARNING_OBJECT (self,
+          "Unknown stream-id %s with type 0x%x", stream_id, stream_type);
+      continue;
+    }
+
+    if (G_UNLIKELY (*current_sid)) {
+      GST_FIXME_OBJECT (self,
+          "Multiple streams are selected for type %s, choose the first one",
+          gst_stream_type_get_name (stream_type));
+      continue;
+    }
+
+    *current_sid = g_strdup (stream_id);
+  }
+  g_mutex_unlock (&self->lock);
+
+  if (self->media_info && updated)
+    on_media_info_updated (self);
+}
+
+static void
+play_set_flag (GstPlay * self, gint pos)
+{
+  gint flags;
+
+  g_object_get (self->playbin, "flags", &flags, NULL);
+  flags |= pos;
+  g_object_set (self->playbin, "flags", flags, NULL);
+
+  GST_DEBUG_OBJECT (self, "setting flags=%#x", flags);
+}
+
+static void
+play_clear_flag (GstPlay * self, gint pos)
+{
+  gint flags;
+
+  g_object_get (self->playbin, "flags", &flags, NULL);
+  flags &= ~pos;
+  g_object_set (self->playbin, "flags", flags, NULL);
+
+  GST_DEBUG_OBJECT (self, "setting flags=%#x", flags);
+}
+
+/*
+ * on_media_info_updated:
+ *
+ * create a new copy of self->media_info object and post it to the user
+ * application.
+ */
+static void
+on_media_info_updated (GstPlay * self)
+{
+  GstPlayMediaInfo *media_info_copy;
+
+  g_mutex_lock (&self->lock);
+  media_info_copy = gst_play_media_info_copy (self->media_info);
+  g_mutex_unlock (&self->lock);
+
+  api_bus_post_message (self, GST_PLAY_MESSAGE_MEDIA_INFO_UPDATED,
+      GST_PLAY_MESSAGE_DATA_MEDIA_INFO, GST_TYPE_PLAY_MEDIA_INFO,
+      media_info_copy, NULL);
+  g_object_unref (media_info_copy);
+}
+
+static GstCaps *
+get_caps (GstPlay * self, gint stream_index, GType type)
+{
+  GstPad *pad = NULL;
+  GstCaps *caps = NULL;
+
+  if (type == GST_TYPE_PLAY_VIDEO_INFO)
+    g_signal_emit_by_name (G_OBJECT (self->playbin),
+        "get-video-pad", stream_index, &pad);
+  else if (type == GST_TYPE_PLAY_AUDIO_INFO)
+    g_signal_emit_by_name (G_OBJECT (self->playbin),
+        "get-audio-pad", stream_index, &pad);
+  else
+    g_signal_emit_by_name (G_OBJECT (self->playbin),
+        "get-text-pad", stream_index, &pad);
+
+  if (pad) {
+    caps = gst_pad_get_current_caps (pad);
+    gst_object_unref (pad);
+  }
+
+  return caps;
+}
+
+static void
+gst_play_subtitle_info_update (GstPlay * self, GstPlayStreamInfo * stream_info)
+{
+  GstPlaySubtitleInfo *info = (GstPlaySubtitleInfo *) stream_info;
+
+  if (stream_info->tags) {
+
+    /* free the old language info */
+    g_free (info->language);
+    info->language = NULL;
+
+    /* First try to get the language full name from tag, if name is not
+     * available then try language code. If we find the language code
+     * then use gstreamer api to translate code to full name.
+     */
+    gst_tag_list_get_string (stream_info->tags, GST_TAG_LANGUAGE_NAME,
+        &info->language);
+    if (!info->language) {
+      gchar *lang_code = NULL;
+
+      gst_tag_list_get_string (stream_info->tags, GST_TAG_LANGUAGE_CODE,
+          &lang_code);
+      if (lang_code) {
+        info->language = g_strdup (gst_tag_get_language_name (lang_code));
+        g_free (lang_code);
+      }
+    }
+
+    /* If we are still failed to find language name then check if external
+     * subtitle is loaded and compare the stream index between current sub
+     * stream index with our stream index and if matches then declare it as
+     * external subtitle and use the filename.
+     */
+    if (!info->language) {
+      gint text_index = -1;
+      gchar *suburi = NULL;
+
+      g_object_get (G_OBJECT (self->playbin), "current-suburi", &suburi, NULL);
+      if (suburi) {
+        if (self->use_playbin3) {
+          if (g_str_equal (self->subtitle_sid, stream_info->stream_id))
+            info->language = g_path_get_basename (suburi);
+        } else {
+          g_object_get (G_OBJECT (self->playbin), "current-text", &text_index,
+              NULL);
+          if (text_index == gst_play_stream_info_get_index (stream_info))
+            info->language = g_path_get_basename (suburi);
+        }
+        g_free (suburi);
+      }
+    }
+
+  } else {
+    g_free (info->language);
+    info->language = NULL;
+  }
+
+  GST_DEBUG_OBJECT (self, "language=%s", info->language);
+}
+
+static void
+gst_play_video_info_update (GstPlay * self, GstPlayStreamInfo * stream_info)
+{
+  GstPlayVideoInfo *info = (GstPlayVideoInfo *) stream_info;
+
+  if (stream_info->caps) {
+    GstStructure *s;
+
+    s = gst_caps_get_structure (stream_info->caps, 0);
+    if (s) {
+      gint width, height;
+      gint fps_n, fps_d;
+      gint par_n, par_d;
+
+      if (gst_structure_get_int (s, "width", &width))
+        info->width = width;
+      else
+        info->width = -1;
+
+      if (gst_structure_get_int (s, "height", &height))
+        info->height = height;
+      else
+        info->height = -1;
+
+      if (gst_structure_get_fraction (s, "framerate", &fps_n, &fps_d)) {
+        info->framerate_num = fps_n;
+        info->framerate_denom = fps_d;
+      } else {
+        info->framerate_num = 0;
+        info->framerate_denom = 1;
+      }
+
+
+      if (gst_structure_get_fraction (s, "pixel-aspect-ratio", &par_n, &par_d)) {
+        info->par_num = par_n;
+        info->par_denom = par_d;
+      } else {
+        info->par_num = 1;
+        info->par_denom = 1;
+      }
+    }
+  } else {
+    info->width = info->height = -1;
+    info->par_num = info->par_denom = 1;
+    info->framerate_num = 0;
+    info->framerate_denom = 1;
+  }
+
+  if (stream_info->tags) {
+    guint bitrate, max_bitrate;
+
+    if (gst_tag_list_get_uint (stream_info->tags, GST_TAG_BITRATE, &bitrate))
+      info->bitrate = bitrate;
+    else
+      info->bitrate = -1;
+
+    if (gst_tag_list_get_uint (stream_info->tags, GST_TAG_MAXIMUM_BITRATE,
+            &max_bitrate) || gst_tag_list_get_uint (stream_info->tags,
+            GST_TAG_NOMINAL_BITRATE, &max_bitrate))
+      info->max_bitrate = max_bitrate;
+    else
+      info->max_bitrate = -1;
+  } else {
+    info->bitrate = info->max_bitrate = -1;
+  }
+
+  GST_DEBUG_OBJECT (self, "width=%d height=%d fps=%.2f par=%d:%d "
+      "bitrate=%d max_bitrate=%d", info->width, info->height,
+      (gdouble) info->framerate_num / info->framerate_denom,
+      info->par_num, info->par_denom, info->bitrate, info->max_bitrate);
+}
+
+static void
+gst_play_audio_info_update (GstPlay * self, GstPlayStreamInfo * stream_info)
+{
+  GstPlayAudioInfo *info = (GstPlayAudioInfo *) stream_info;
+
+  if (stream_info->caps) {
+    GstStructure *s;
+
+    s = gst_caps_get_structure (stream_info->caps, 0);
+    if (s) {
+      gint rate, channels;
+
+      if (gst_structure_get_int (s, "rate", &rate))
+        info->sample_rate = rate;
+      else
+        info->sample_rate = -1;
+
+      if (gst_structure_get_int (s, "channels", &channels))
+        info->channels = channels;
+      else
+        info->channels = 0;
+    }
+  } else {
+    info->sample_rate = -1;
+    info->channels = 0;
+  }
+
+  if (stream_info->tags) {
+    guint bitrate, max_bitrate;
+
+    if (gst_tag_list_get_uint (stream_info->tags, GST_TAG_BITRATE, &bitrate))
+      info->bitrate = bitrate;
+    else
+      info->bitrate = -1;
+
+    if (gst_tag_list_get_uint (stream_info->tags, GST_TAG_MAXIMUM_BITRATE,
+            &max_bitrate) || gst_tag_list_get_uint (stream_info->tags,
+            GST_TAG_NOMINAL_BITRATE, &max_bitrate))
+      info->max_bitrate = max_bitrate;
+    else
+      info->max_bitrate = -1;
+
+    /* if we have old language the free it */
+    g_free (info->language);
+    info->language = NULL;
+
+    /* First try to get the language full name from tag, if name is not
+     * available then try language code. If we find the language code
+     * then use gstreamer api to translate code to full name.
+     */
+    gst_tag_list_get_string (stream_info->tags, GST_TAG_LANGUAGE_NAME,
+        &info->language);
+    if (!info->language) {
+      gchar *lang_code = NULL;
+
+      gst_tag_list_get_string (stream_info->tags, GST_TAG_LANGUAGE_CODE,
+          &lang_code);
+      if (lang_code) {
+        info->language = g_strdup (gst_tag_get_language_name (lang_code));
+        g_free (lang_code);
+      }
+    }
+  } else {
+    g_free (info->language);
+    info->language = NULL;
+    info->max_bitrate = info->bitrate = -1;
+  }
+
+  GST_DEBUG_OBJECT (self, "language=%s rate=%d channels=%d bitrate=%d "
+      "max_bitrate=%d", info->language, info->sample_rate, info->channels,
+      info->bitrate, info->max_bitrate);
+}
+
+static GstPlayStreamInfo *
+gst_play_stream_info_find (GstPlayMediaInfo * media_info,
+    GType type, gint stream_index)
+{
+  GList *list, *l;
+  GstPlayStreamInfo *info = NULL;
+
+  if (!media_info)
+    return NULL;
+
+  list = gst_play_media_info_get_stream_list (media_info);
+  for (l = list; l != NULL; l = l->next) {
+    info = (GstPlayStreamInfo *) l->data;
+    if ((G_OBJECT_TYPE (info) == type) && (info->stream_index == stream_index)) {
+      return info;
+    }
+  }
+
+  return NULL;
+}
+
+static GstPlayStreamInfo *
+gst_play_stream_info_find_from_stream_id (GstPlayMediaInfo * media_info,
+    const gchar * stream_id)
+{
+  GList *list, *l;
+  GstPlayStreamInfo *info = NULL;
+
+  if (!media_info)
+    return NULL;
+
+  list = gst_play_media_info_get_stream_list (media_info);
+  for (l = list; l != NULL; l = l->next) {
+    info = (GstPlayStreamInfo *) l->data;
+    if (g_str_equal (info->stream_id, stream_id)) {
+      return info;
+    }
+  }
+
+  return NULL;
+}
+
+static gboolean
+is_track_enabled (GstPlay * self, gint pos)
+{
+  gint flags;
+
+  g_object_get (G_OBJECT (self->playbin), "flags", &flags, NULL);
+
+  if ((flags & pos))
+    return TRUE;
+
+  return FALSE;
+}
+
+static GstPlayStreamInfo *
+gst_play_stream_info_get_current (GstPlay * self, const gchar * prop,
+    GType type)
+{
+  gint current;
+  GstPlayStreamInfo *info;
+
+  if (!self->media_info)
+    return NULL;
+
+  g_object_get (G_OBJECT (self->playbin), prop, &current, NULL);
+  g_mutex_lock (&self->lock);
+  info = gst_play_stream_info_find (self->media_info, type, current);
+  if (info)
+    info = gst_play_stream_info_copy (info);
+  g_mutex_unlock (&self->lock);
+
+  return info;
+}
+
+static GstPlayStreamInfo *
+gst_play_stream_info_get_current_from_stream_id (GstPlay * self,
+    const gchar * stream_id, GType type)
+{
+  GstPlayStreamInfo *info;
+
+  if (!self->media_info || !stream_id)
+    return NULL;
+
+  g_mutex_lock (&self->lock);
+  info = gst_play_stream_info_find_from_stream_id (self->media_info, stream_id);
+  if (info && G_OBJECT_TYPE (info) == type)
+    info = gst_play_stream_info_copy (info);
+  else
+    info = NULL;
+  g_mutex_unlock (&self->lock);
+
+  return info;
+}
+
+static void
+stream_notify_cb (GstStreamCollection * collection, GstStream * stream,
+    GParamSpec * pspec, GstPlay * self)
+{
+  GstPlayStreamInfo *info;
+  const gchar *stream_id;
+  gboolean emit_signal = FALSE;
+
+  if (!self->media_info)
+    return;
+
+  if (G_PARAM_SPEC_VALUE_TYPE (pspec) != GST_TYPE_CAPS &&
+      G_PARAM_SPEC_VALUE_TYPE (pspec) != GST_TYPE_TAG_LIST)
+    return;
+
+  stream_id = gst_stream_get_stream_id (stream);
+  g_mutex_lock (&self->lock);
+  info = gst_play_stream_info_find_from_stream_id (self->media_info, stream_id);
+  if (info) {
+    gst_play_stream_info_update_from_stream (self, info, stream);
+    emit_signal = TRUE;
+  }
+  g_mutex_unlock (&self->lock);
+
+  if (emit_signal)
+    on_media_info_updated (self);
+}
+
+static void
+gst_play_stream_info_update (GstPlay * self, GstPlayStreamInfo * s)
+{
+  if (GST_IS_PLAY_VIDEO_INFO (s))
+    gst_play_video_info_update (self, s);
+  else if (GST_IS_PLAY_AUDIO_INFO (s))
+    gst_play_audio_info_update (self, s);
+  else
+    gst_play_subtitle_info_update (self, s);
+}
+
+static gchar *
+stream_info_get_codec (GstPlayStreamInfo * s)
+{
+  const gchar *type;
+  GstTagList *tags;
+  gchar *codec = NULL;
+
+  if (GST_IS_PLAY_VIDEO_INFO (s))
+    type = GST_TAG_VIDEO_CODEC;
+  else if (GST_IS_PLAY_AUDIO_INFO (s))
+    type = GST_TAG_AUDIO_CODEC;
+  else
+    type = GST_TAG_SUBTITLE_CODEC;
+
+  tags = gst_play_stream_info_get_tags (s);
+  if (tags) {
+    gst_tag_list_get_string (tags, type, &codec);
+    if (!codec)
+      gst_tag_list_get_string (tags, GST_TAG_CODEC, &codec);
+  }
+
+  if (!codec) {
+    GstCaps *caps;
+    caps = gst_play_stream_info_get_caps (s);
+    if (caps) {
+      codec = gst_pb_utils_get_codec_description (caps);
+    }
+  }
+
+  return codec;
+}
+
+static void
+gst_play_stream_info_update_tags_and_caps (GstPlay * self,
+    GstPlayStreamInfo * s)
+{
+  GstTagList *tags;
+  gint stream_index;
+
+  stream_index = gst_play_stream_info_get_index (s);
+
+  if (GST_IS_PLAY_VIDEO_INFO (s))
+    g_signal_emit_by_name (self->playbin, "get-video-tags",
+        stream_index, &tags);
+  else if (GST_IS_PLAY_AUDIO_INFO (s))
+    g_signal_emit_by_name (self->playbin, "get-audio-tags",
+        stream_index, &tags);
+  else
+    g_signal_emit_by_name (self->playbin, "get-text-tags", stream_index, &tags);
+
+  if (s->tags)
+    gst_tag_list_unref (s->tags);
+  s->tags = tags;
+
+  if (s->caps)
+    gst_caps_unref (s->caps);
+  s->caps = get_caps (self, stream_index, G_OBJECT_TYPE (s));
+
+  g_free (s->codec);
+  s->codec = stream_info_get_codec (s);
+
+  GST_DEBUG_OBJECT (self, "%s index: %d tags: %p caps: %p",
+      gst_play_stream_info_get_stream_type (s), stream_index, s->tags, s->caps);
+
+  gst_play_stream_info_update (self, s);
+}
+
+static void
+gst_play_streams_info_create (GstPlay * self,
+    GstPlayMediaInfo * media_info, const gchar * prop, GType type)
+{
+  gint i;
+  gint total = -1;
+  GstPlayStreamInfo *s;
+
+  if (!media_info)
+    return;
+
+  g_object_get (G_OBJECT (self->playbin), prop, &total, NULL);
+
+  GST_DEBUG_OBJECT (self, "%s: %d", prop, total);
+
+  for (i = 0; i < total; i++) {
+    /* check if stream already exist in the list */
+    s = gst_play_stream_info_find (media_info, type, i);
+
+    if (!s) {
+      /* create a new stream info instance */
+      s = gst_play_stream_info_new (i, type);
+
+      /* add the object in stream list */
+      media_info->stream_list = g_list_append (media_info->stream_list, s);
+
+      /* based on type, add the object in its corresponding stream_ list */
+      if (GST_IS_PLAY_AUDIO_INFO (s))
+        media_info->audio_stream_list = g_list_append
+            (media_info->audio_stream_list, s);
+      else if (GST_IS_PLAY_VIDEO_INFO (s))
+        media_info->video_stream_list = g_list_append
+            (media_info->video_stream_list, s);
+      else
+        media_info->subtitle_stream_list = g_list_append
+            (media_info->subtitle_stream_list, s);
+
+      GST_DEBUG_OBJECT (self, "create %s stream stream_index: %d",
+          gst_play_stream_info_get_stream_type (s), i);
+    }
+
+    gst_play_stream_info_update_tags_and_caps (self, s);
+  }
+}
+
+static void
+gst_play_stream_info_update_from_stream (GstPlay * self,
+    GstPlayStreamInfo * s, GstStream * stream)
+{
+  if (s->tags)
+    gst_tag_list_unref (s->tags);
+  s->tags = gst_stream_get_tags (stream);
+
+  if (s->caps)
+    gst_caps_unref (s->caps);
+  s->caps = gst_stream_get_caps (stream);
+
+  g_free (s->codec);
+  s->codec = stream_info_get_codec (s);
+
+  GST_DEBUG_OBJECT (self, "%s index: %d tags: %p caps: %p",
+      gst_play_stream_info_get_stream_type (s), s->stream_index,
+      s->tags, s->caps);
+
+  gst_play_stream_info_update (self, s);
+}
+
+static void
+gst_play_streams_info_create_from_collection (GstPlay * self,
+    GstPlayMediaInfo * media_info, GstStreamCollection * collection)
+{
+  guint i;
+  guint total;
+  GstPlayStreamInfo *s;
+  guint n_audio = 0;
+  guint n_video = 0;
+  guint n_text = 0;
+
+  if (!media_info || !collection)
+    return;
+
+  total = gst_stream_collection_get_size (collection);
+
+  for (i = 0; i < total; i++) {
+    GstStream *stream = gst_stream_collection_get_stream (collection, i);
+    GstStreamType stream_type = gst_stream_get_stream_type (stream);
+    const gchar *stream_id = gst_stream_get_stream_id (stream);
+
+    if (stream_type & GST_STREAM_TYPE_AUDIO) {
+      s = gst_play_stream_info_new (n_audio, GST_TYPE_PLAY_AUDIO_INFO);
+      n_audio++;
+    } else if (stream_type & GST_STREAM_TYPE_VIDEO) {
+      s = gst_play_stream_info_new (n_video, GST_TYPE_PLAY_VIDEO_INFO);
+      n_video++;
+    } else if (stream_type & GST_STREAM_TYPE_TEXT) {
+      s = gst_play_stream_info_new (n_text, GST_TYPE_PLAY_SUBTITLE_INFO);
+      n_text++;
+    } else {
+      GST_DEBUG_OBJECT (self, "Unknown type stream %d", i);
+      continue;
+    }
+
+    s->stream_id = g_strdup (stream_id);
+
+    /* add the object in stream list */
+    media_info->stream_list = g_list_append (media_info->stream_list, s);
+
+    /* based on type, add the object in its corresponding stream_ list */
+    if (GST_IS_PLAY_AUDIO_INFO (s))
+      media_info->audio_stream_list = g_list_append
+          (media_info->audio_stream_list, s);
+    else if (GST_IS_PLAY_VIDEO_INFO (s))
+      media_info->video_stream_list = g_list_append
+          (media_info->video_stream_list, s);
+    else
+      media_info->subtitle_stream_list = g_list_append
+          (media_info->subtitle_stream_list, s);
+
+    GST_DEBUG_OBJECT (self, "create %s stream stream_index: %d",
+        gst_play_stream_info_get_stream_type (s), s->stream_index);
+
+    gst_play_stream_info_update_from_stream (self, s, stream);
+  }
+}
+
+static void
+video_changed_cb (G_GNUC_UNUSED GObject * object, gpointer user_data)
+{
+  GstPlay *self = GST_PLAY (user_data);
+
+  g_mutex_lock (&self->lock);
+  gst_play_streams_info_create (self, self->media_info,
+      "n-video", GST_TYPE_PLAY_VIDEO_INFO);
+  g_mutex_unlock (&self->lock);
+}
+
+static void
+audio_changed_cb (G_GNUC_UNUSED GObject * object, gpointer user_data)
+{
+  GstPlay *self = GST_PLAY (user_data);
+
+  g_mutex_lock (&self->lock);
+  gst_play_streams_info_create (self, self->media_info,
+      "n-audio", GST_TYPE_PLAY_AUDIO_INFO);
+  g_mutex_unlock (&self->lock);
+}
+
+static void
+subtitle_changed_cb (G_GNUC_UNUSED GObject * object, gpointer user_data)
+{
+  GstPlay *self = GST_PLAY (user_data);
+
+  g_mutex_lock (&self->lock);
+  gst_play_streams_info_create (self, self->media_info,
+      "n-text", GST_TYPE_PLAY_SUBTITLE_INFO);
+  g_mutex_unlock (&self->lock);
+}
+
+static void *
+get_title (GstTagList * tags)
+{
+  gchar *title = NULL;
+
+  gst_tag_list_get_string (tags, GST_TAG_TITLE, &title);
+  if (!title)
+    gst_tag_list_get_string (tags, GST_TAG_TITLE_SORTNAME, &title);
+
+  return title;
+}
+
+static void *
+get_container_format (GstTagList * tags)
+{
+  gchar *container = NULL;
+
+  gst_tag_list_get_string (tags, GST_TAG_CONTAINER_FORMAT, &container);
+
+  /* TODO: If container is not available then maybe consider
+   * parsing caps or file extension to guess the container format.
+   */
+
+  return container;
+}
+
+static void *
+get_from_tags (GstPlay * self, GstPlayMediaInfo * media_info,
+    void *(*func) (GstTagList *))
+{
+  GList *l;
+  void *ret = NULL;
+
+  if (media_info->tags) {
+    ret = func (media_info->tags);
+    if (ret)
+      return ret;
+  }
+
+  /* if global tag does not exit then try video and audio streams */
+  GST_DEBUG_OBJECT (self, "trying video tags");
+  for (l = gst_play_media_info_get_video_streams (media_info); l != NULL;
+      l = l->next) {
+    GstTagList *tags;
+
+    tags = gst_play_stream_info_get_tags ((GstPlayStreamInfo *) l->data);
+    if (tags)
+      ret = func (tags);
+
+    if (ret)
+      return ret;
+  }
+
+  GST_DEBUG_OBJECT (self, "trying audio tags");
+  for (l = gst_play_media_info_get_audio_streams (media_info); l != NULL;
+      l = l->next) {
+    GstTagList *tags;
+
+    tags = gst_play_stream_info_get_tags ((GstPlayStreamInfo *) l->data);
+    if (tags)
+      ret = func (tags);
+
+    if (ret)
+      return ret;
+  }
+
+  GST_DEBUG_OBJECT (self, "failed to get the information from tags");
+  return NULL;
+}
+
+static void *
+get_cover_sample (GstTagList * tags)
+{
+  GstSample *cover_sample = NULL;
+
+  gst_tag_list_get_sample (tags, GST_TAG_IMAGE, &cover_sample);
+  if (!cover_sample)
+    gst_tag_list_get_sample (tags, GST_TAG_PREVIEW_IMAGE, &cover_sample);
+
+  return cover_sample;
+}
+
+static GstPlayMediaInfo *
+gst_play_media_info_create (GstPlay * self)
+{
+  GstPlayMediaInfo *media_info;
+  GstQuery *query;
+
+  GST_DEBUG_OBJECT (self, "begin");
+  media_info = gst_play_media_info_new (self->uri);
+  media_info->duration = gst_play_get_duration (self);
+  media_info->tags = self->global_tags;
+  media_info->is_live = self->is_live;
+  self->global_tags = NULL;
+
+  query = gst_query_new_seeking (GST_FORMAT_TIME);
+  if (gst_element_query (self->playbin, query))
+    gst_query_parse_seeking (query, NULL, &media_info->seekable, NULL, NULL);
+  gst_query_unref (query);
+
+  if (self->use_playbin3 && self->collection) {
+    gst_play_streams_info_create_from_collection (self, media_info,
+        self->collection);
+  } else {
+    /* create audio/video/sub streams */
+    gst_play_streams_info_create (self, media_info, "n-video",
+        GST_TYPE_PLAY_VIDEO_INFO);
+    gst_play_streams_info_create (self, media_info, "n-audio",
+        GST_TYPE_PLAY_AUDIO_INFO);
+    gst_play_streams_info_create (self, media_info, "n-text",
+        GST_TYPE_PLAY_SUBTITLE_INFO);
+  }
+
+  media_info->title = get_from_tags (self, media_info, get_title);
+  media_info->container =
+      get_from_tags (self, media_info, get_container_format);
+  media_info->image_sample = get_from_tags (self, media_info, get_cover_sample);
+
+  GST_DEBUG_OBJECT (self, "uri: %s title: %s duration: %" GST_TIME_FORMAT
+      " seekable: %s live: %s container: %s image_sample %p",
+      media_info->uri, media_info->title, GST_TIME_ARGS (media_info->duration),
+      media_info->seekable ? "yes" : "no", media_info->is_live ? "yes" : "no",
+      media_info->container, media_info->image_sample);
+
+  GST_DEBUG_OBJECT (self, "end");
+  return media_info;
+}
+
+static void
+tags_changed_cb (GstPlay * self, gint stream_index, GType type)
+{
+  GstPlayStreamInfo *s;
+
+  if (!self->media_info)
+    return;
+
+  /* update the stream information */
+  g_mutex_lock (&self->lock);
+  s = gst_play_stream_info_find (self->media_info, type, stream_index);
+  gst_play_stream_info_update_tags_and_caps (self, s);
+  g_mutex_unlock (&self->lock);
+
+  on_media_info_updated (self);
+}
+
+static void
+video_tags_changed_cb (G_GNUC_UNUSED GstElement * playbin, gint stream_index,
+    gpointer user_data)
+{
+  tags_changed_cb (GST_PLAY (user_data), stream_index,
+      GST_TYPE_PLAY_VIDEO_INFO);
+}
+
+static void
+audio_tags_changed_cb (G_GNUC_UNUSED GstElement * playbin, gint stream_index,
+    gpointer user_data)
+{
+  tags_changed_cb (GST_PLAY (user_data), stream_index,
+      GST_TYPE_PLAY_AUDIO_INFO);
+}
+
+static void
+subtitle_tags_changed_cb (G_GNUC_UNUSED GstElement * playbin, gint stream_index,
+    gpointer user_data)
+{
+  tags_changed_cb (GST_PLAY (user_data), stream_index,
+      GST_TYPE_PLAY_SUBTITLE_INFO);
+}
+
+static void
+volume_notify_cb (G_GNUC_UNUSED GObject * obj, G_GNUC_UNUSED GParamSpec * pspec,
+    GstPlay * self)
+{
+  api_bus_post_message (self, GST_PLAY_MESSAGE_VOLUME_CHANGED,
+      GST_PLAY_MESSAGE_DATA_VOLUME, G_TYPE_DOUBLE,
+      gst_play_get_volume (self), NULL);
+}
+
+static void
+mute_notify_cb (G_GNUC_UNUSED GObject * obj, G_GNUC_UNUSED GParamSpec * pspec,
+    GstPlay * self)
+{
+
+  api_bus_post_message (self, GST_PLAY_MESSAGE_MUTE_CHANGED,
+      GST_PLAY_MESSAGE_DATA_IS_MUTED, G_TYPE_BOOLEAN,
+      gst_play_get_mute (self), NULL);
+}
+
+static void
+source_setup_cb (GstElement * playbin, GstElement * source, GstPlay * self)
+{
+  gchar *user_agent;
+
+  user_agent = gst_play_config_get_user_agent (self->config);
+  if (user_agent) {
+    GParamSpec *prop;
+
+    prop = g_object_class_find_property (G_OBJECT_GET_CLASS (source),
+        "user-agent");
+    if (prop && prop->value_type == G_TYPE_STRING) {
+      GST_INFO_OBJECT (self, "Setting source user-agent: %s", user_agent);
+      g_object_set (source, "user-agent", user_agent, NULL);
+    }
+
+    g_free (user_agent);
+  }
+}
+
+static gpointer
+gst_play_main (gpointer data)
+{
+  GstPlay *self = GST_PLAY (data);
+  GstBus *bus;
+  GSource *source;
+  GstElement *scaletempo;
+  const gchar *env;
+
+  GST_TRACE_OBJECT (self, "Starting main thread");
+
+  g_main_context_push_thread_default (self->context);
+
+  source = g_idle_source_new ();
+  g_source_set_callback (source, (GSourceFunc) main_loop_running_cb, self,
+      NULL);
+  g_source_attach (source, self->context);
+  g_source_unref (source);
+
+  env = g_getenv ("GST_PLAY_USE_PLAYBIN3");
+  if (env && g_str_has_prefix (env, "1"))
+    self->use_playbin3 = TRUE;
+
+  if (self->use_playbin3) {
+    GST_DEBUG_OBJECT (self, "playbin3 enabled");
+    self->playbin = gst_element_factory_make ("playbin3", "playbin3");
+  } else {
+    self->playbin = gst_element_factory_make ("playbin", "playbin");
+  }
+
+  if (!self->playbin) {
+    g_error ("GstPlay: 'playbin' element not found, please check your setup");
+    g_assert_not_reached ();
+  }
+
+  gst_object_ref_sink (self->playbin);
+
+  if (self->video_renderer) {
+    GstElement *video_sink =
+        gst_play_video_renderer_create_video_sink (self->video_renderer,
+        self);
+
+    if (video_sink)
+      g_object_set (self->playbin, "video-sink", video_sink, NULL);
+  }
+
+  scaletempo = gst_element_factory_make ("scaletempo", NULL);
+  if (scaletempo) {
+    g_object_set (self->playbin, "audio-filter", scaletempo, NULL);
+  } else {
+    g_warning ("GstPlay: scaletempo element not available. Audio pitch "
+        "will not be preserved during trick modes");
+  }
+
+  self->bus = bus = gst_element_get_bus (self->playbin);
+  gst_bus_add_signal_watch (bus);
+
+  g_signal_connect (G_OBJECT (bus), "message::error", G_CALLBACK (error_cb),
+      self);
+  g_signal_connect (G_OBJECT (bus), "message::warning", G_CALLBACK (warning_cb),
+      self);
+  g_signal_connect (G_OBJECT (bus), "message::eos", G_CALLBACK (eos_cb), self);
+  g_signal_connect (G_OBJECT (bus), "message::state-changed",
+      G_CALLBACK (state_changed_cb), self);
+  g_signal_connect (G_OBJECT (bus), "message::buffering",
+      G_CALLBACK (buffering_cb), self);
+  g_signal_connect (G_OBJECT (bus), "message::clock-lost",
+      G_CALLBACK (clock_lost_cb), self);
+  g_signal_connect (G_OBJECT (bus), "message::duration-changed",
+      G_CALLBACK (duration_changed_cb), self);
+  g_signal_connect (G_OBJECT (bus), "message::latency",
+      G_CALLBACK (latency_cb), self);
+  g_signal_connect (G_OBJECT (bus), "message::request-state",
+      G_CALLBACK (request_state_cb), self);
+  g_signal_connect (G_OBJECT (bus), "message::element",
+      G_CALLBACK (element_cb), self);
+  g_signal_connect (G_OBJECT (bus), "message::tag", G_CALLBACK (tags_cb), self);
+
+  if (self->use_playbin3) {
+    g_signal_connect (G_OBJECT (bus), "message::stream-collection",
+        G_CALLBACK (stream_collection_cb), self);
+    g_signal_connect (G_OBJECT (bus), "message::streams-selected",
+        G_CALLBACK (streams_selected_cb), self);
+  } else {
+    g_signal_connect (self->playbin, "video-changed",
+        G_CALLBACK (video_changed_cb), self);
+    g_signal_connect (self->playbin, "audio-changed",
+        G_CALLBACK (audio_changed_cb), self);
+    g_signal_connect (self->playbin, "text-changed",
+        G_CALLBACK (subtitle_changed_cb), self);
+
+    g_signal_connect (self->playbin, "video-tags-changed",
+        G_CALLBACK (video_tags_changed_cb), self);
+    g_signal_connect (self->playbin, "audio-tags-changed",
+        G_CALLBACK (audio_tags_changed_cb), self);
+    g_signal_connect (self->playbin, "text-tags-changed",
+        G_CALLBACK (subtitle_tags_changed_cb), self);
+  }
+
+  g_signal_connect (self->playbin, "notify::volume",
+      G_CALLBACK (volume_notify_cb), self);
+  g_signal_connect (self->playbin, "notify::mute",
+      G_CALLBACK (mute_notify_cb), self);
+  g_signal_connect (self->playbin, "source-setup",
+      G_CALLBACK (source_setup_cb), self);
+
+  self->target_state = GST_STATE_NULL;
+  self->current_state = GST_STATE_NULL;
+  change_state (self, GST_PLAY_STATE_STOPPED);
+  self->buffering = 100;
+  self->is_eos = FALSE;
+  self->is_live = FALSE;
+  self->rate = 1.0;
+
+  GST_TRACE_OBJECT (self, "Starting main loop");
+  g_main_loop_run (self->loop);
+  GST_TRACE_OBJECT (self, "Stopped main loop");
+
+  gst_bus_remove_signal_watch (bus);
+  gst_object_unref (bus);
+
+  remove_tick_source (self);
+  remove_ready_timeout_source (self);
+
+  g_mutex_lock (&self->lock);
+  if (self->media_info) {
+    g_object_unref (self->media_info);
+    self->media_info = NULL;
+  }
+
+  remove_seek_source (self);
+  g_mutex_unlock (&self->lock);
+
+  g_main_context_pop_thread_default (self->context);
+
+  self->target_state = GST_STATE_NULL;
+  self->current_state = GST_STATE_NULL;
+  if (self->playbin) {
+    gst_element_set_state (self->playbin, GST_STATE_NULL);
+    gst_object_unref (self->playbin);
+    self->playbin = NULL;
+  }
+
+  GST_TRACE_OBJECT (self, "Stopped main thread");
+
+  return NULL;
+}
+
+static gpointer
+gst_play_init_once (G_GNUC_UNUSED gpointer user_data)
+{
+  gst_init (NULL, NULL);
+
+  GST_DEBUG_CATEGORY_INIT (gst_play_debug, "gst-play", 0, "GstPlay");
+  gst_play_error_quark ();
+
+  return NULL;
+}
+
+/**
+ * gst_play_new:
+ * @video_renderer: (transfer full) (allow-none): GstPlayVideoRenderer to use
+ *
+ * Creates a new #GstPlay instance.
+ *
+ * Video is going to be rendered by @video_renderer, or if %NULL is provided
+ * no special video set up will be done and some default handling will be
+ * performed.
+ *
+ * Returns: (transfer full): a new #GstPlay instance
+ * Since: 1.20
+ */
+GstPlay *
+gst_play_new (GstPlayVideoRenderer * video_renderer)
+{
+  static GOnce once = G_ONCE_INIT;
+  GstPlay *self;
+
+  g_once (&once, gst_play_init_once, NULL);
+
+  self = g_object_new (GST_TYPE_PLAY, "video-renderer", video_renderer, NULL);
+
+  gst_object_ref_sink (self);
+
+  if (video_renderer)
+    g_object_unref (video_renderer);
+
+  return self;
+}
+
+/**
+ * gst_play_get_message_bus:
+ * @play: #GstPlay instance
+ *
+ * GstPlay API exposes a #GstBus instance which purpose is to provide data
+ * structures representing play-internal events in form of #GstMessage<!-- -->s of
+ * type GST_MESSAGE_APPLICATION.
+ *
+ * Each message carries a "play-message" field of type #GstPlayMessage.
+ * Further fields of the message data are specific to each possible value of
+ * that enumeration.
+ *
+ * Applications can consume the messages asynchronously within their own
+ * event-loop / UI-thread etc. Note that in case the application does not
+ * consume the messages, the bus will accumulate these internally and eventually
+ * fill memory. To avoid that, the bus has to be set "flushing".
+ *
+ * Returns: (transfer full): The play message bus instance
+ *
+ * Since: 1.20
+ */
+GstBus *
+gst_play_get_message_bus (GstPlay * self)
+{
+  return g_object_ref (self->api_bus);
+}
+
+static gboolean
+gst_play_play_internal (gpointer user_data)
+{
+  GstPlay *self = GST_PLAY (user_data);
+  GstStateChangeReturn state_ret;
+
+  GST_DEBUG_OBJECT (self, "Play");
+
+  g_mutex_lock (&self->lock);
+  if (!self->uri) {
+    g_mutex_unlock (&self->lock);
+    return G_SOURCE_REMOVE;
+  }
+  g_mutex_unlock (&self->lock);
+
+  remove_ready_timeout_source (self);
+  self->target_state = GST_STATE_PLAYING;
+
+  if (self->current_state < GST_STATE_PAUSED)
+    change_state (self, GST_PLAY_STATE_BUFFERING);
+
+  if (self->current_state >= GST_STATE_PAUSED && !self->is_eos
+      && self->buffering >= 100 && !(self->seek_position != GST_CLOCK_TIME_NONE
+          || self->seek_pending)) {
+    state_ret = gst_element_set_state (self->playbin, GST_STATE_PLAYING);
+  } else {
+    state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED);
+  }
+
+  if (state_ret == GST_STATE_CHANGE_FAILURE) {
+    on_error (self, g_error_new (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED,
+            "Failed to play"), NULL);
+    return G_SOURCE_REMOVE;
+  } else if (state_ret == GST_STATE_CHANGE_NO_PREROLL) {
+    self->is_live = TRUE;
+    GST_DEBUG_OBJECT (self, "Pipeline is live");
+  }
+
+  if (self->is_eos) {
+    gboolean ret;
+
+    GST_DEBUG_OBJECT (self, "Was EOS, seeking to beginning");
+    self->is_eos = FALSE;
+    ret =
+        gst_element_seek_simple (self->playbin, GST_FORMAT_TIME,
+        GST_SEEK_FLAG_FLUSH, 0);
+    if (!ret) {
+      GST_ERROR_OBJECT (self, "Seek to beginning failed");
+      gst_play_stop_internal (self, TRUE);
+      gst_play_play_internal (self);
+    }
+  }
+
+  return G_SOURCE_REMOVE;
+}
+
+/**
+ * gst_play_play:
+ * @play: #GstPlay instance
+ *
+ * Request to play the loaded stream.
+ * Since: 1.20
+ */
+void
+gst_play_play (GstPlay * self)
+{
+  g_return_if_fail (GST_IS_PLAY (self));
+
+  g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT,
+      gst_play_play_internal, self, NULL);
+}
+
+static gboolean
+gst_play_pause_internal (gpointer user_data)
+{
+  GstPlay *self = GST_PLAY (user_data);
+  GstStateChangeReturn state_ret;
+
+  GST_DEBUG_OBJECT (self, "Pause");
+
+  g_mutex_lock (&self->lock);
+  if (!self->uri) {
+    g_mutex_unlock (&self->lock);
+    return G_SOURCE_REMOVE;
+  }
+  g_mutex_unlock (&self->lock);
+
+  tick_cb (self);
+  remove_tick_source (self);
+  remove_ready_timeout_source (self);
+
+  self->target_state = GST_STATE_PAUSED;
+
+  if (self->current_state < GST_STATE_PAUSED)
+    change_state (self, GST_PLAY_STATE_BUFFERING);
+
+  state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED);
+  if (state_ret == GST_STATE_CHANGE_FAILURE) {
+    on_error (self, g_error_new (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED,
+            "Failed to pause"), NULL);
+    return G_SOURCE_REMOVE;
+  } else if (state_ret == GST_STATE_CHANGE_NO_PREROLL) {
+    self->is_live = TRUE;
+    GST_DEBUG_OBJECT (self, "Pipeline is live");
+  }
+
+  if (self->is_eos) {
+    gboolean ret;
+
+    GST_DEBUG_OBJECT (self, "Was EOS, seeking to beginning");
+    self->is_eos = FALSE;
+    ret =
+        gst_element_seek_simple (self->playbin, GST_FORMAT_TIME,
+        GST_SEEK_FLAG_FLUSH, 0);
+    if (!ret) {
+      GST_ERROR_OBJECT (self, "Seek to beginning failed");
+      gst_play_stop_internal (self, TRUE);
+      gst_play_pause_internal (self);
+    }
+  }
+
+  return G_SOURCE_REMOVE;
+}
+
+/**
+ * gst_play_pause:
+ * @play: #GstPlay instance
+ *
+ * Pauses the current stream.
+ * Since: 1.20
+ */
+void
+gst_play_pause (GstPlay * self)
+{
+  g_return_if_fail (GST_IS_PLAY (self));
+
+  g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT,
+      gst_play_pause_internal, self, NULL);
+}
+
+static void
+gst_play_stop_internal (GstPlay * self, gboolean transient)
+{
+  /* directly return if we're already stopped */
+  if (self->current_state <= GST_STATE_READY &&
+      self->target_state <= GST_STATE_READY)
+    return;
+
+  GST_DEBUG_OBJECT (self, "Stop (transient %d)", transient);
+
+  tick_cb (self);
+  remove_tick_source (self);
+
+  add_ready_timeout_source (self);
+
+  self->target_state = GST_STATE_NULL;
+  self->current_state = GST_STATE_READY;
+  self->is_live = FALSE;
+  self->is_eos = FALSE;
+  gst_bus_set_flushing (self->bus, TRUE);
+  gst_element_set_state (self->playbin, GST_STATE_READY);
+  gst_bus_set_flushing (self->bus, FALSE);
+  change_state (self, transient
+      && self->app_state !=
+      GST_PLAY_STATE_STOPPED ? GST_PLAY_STATE_BUFFERING :
+      GST_PLAY_STATE_STOPPED);
+  self->buffering = 100;
+  self->cached_duration = GST_CLOCK_TIME_NONE;
+  g_mutex_lock (&self->lock);
+  if (self->media_info) {
+    g_object_unref (self->media_info);
+    self->media_info = NULL;
+  }
+  if (self->global_tags) {
+    gst_tag_list_unref (self->global_tags);
+    self->global_tags = NULL;
+  }
+  self->seek_pending = FALSE;
+  remove_seek_source (self);
+  self->seek_position = GST_CLOCK_TIME_NONE;
+  self->last_seek_time = GST_CLOCK_TIME_NONE;
+  self->rate = 1.0;
+  if (self->collection) {
+    if (self->stream_notify_id)
+      g_signal_handler_disconnect (self->collection, self->stream_notify_id);
+    self->stream_notify_id = 0;
+    gst_object_unref (self->collection);
+    self->collection = NULL;
+  }
+  g_free (self->video_sid);
+  g_free (self->audio_sid);
+  g_free (self->subtitle_sid);
+  self->video_sid = NULL;
+  self->audio_sid = NULL;
+  self->subtitle_sid = NULL;
+  g_mutex_unlock (&self->lock);
+}
+
+static gboolean
+gst_play_stop_internal_dispatch (gpointer user_data)
+{
+  GstPlay *self = GST_PLAY (user_data);
+
+  gst_play_stop_internal (self, FALSE);
+
+  return G_SOURCE_REMOVE;
+}
+
+
+/**
+ * gst_play_stop:
+ * @play: #GstPlay instance
+ *
+ * Stops playing the current stream and resets to the first position
+ * in the stream.
+ * Since: 1.20
+ */
+void
+gst_play_stop (GstPlay * self)
+{
+  g_return_if_fail (GST_IS_PLAY (self));
+
+  g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT,
+      gst_play_stop_internal_dispatch, self, NULL);
+}
+
+/* Must be called with lock from main context, releases lock! */
+static void
+gst_play_seek_internal_locked (GstPlay * self)
+{
+  gboolean ret;
+  GstClockTime position;
+  gdouble rate;
+  GstStateChangeReturn state_ret;
+  GstEvent *s_event;
+  GstSeekFlags flags = 0;
+  gboolean accurate = FALSE;
+
+  remove_seek_source (self);
+
+  /* Only seek in PAUSED */
+  if (self->current_state < GST_STATE_PAUSED) {
+    return;
+  } else if (self->current_state != GST_STATE_PAUSED) {
+    g_mutex_unlock (&self->lock);
+    state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED);
+    if (state_ret == GST_STATE_CHANGE_FAILURE) {
+      on_error (self, g_error_new (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED,
+              "Failed to seek"), NULL);
+      g_mutex_lock (&self->lock);
+      return;
+    }
+    g_mutex_lock (&self->lock);
+    return;
+  }
+
+  self->last_seek_time = gst_util_get_timestamp ();
+  position = self->seek_position;
+  self->seek_position = GST_CLOCK_TIME_NONE;
+  self->seek_pending = TRUE;
+  rate = self->rate;
+  g_mutex_unlock (&self->lock);
+
+  remove_tick_source (self);
+  self->is_eos = FALSE;
+
+  flags |= GST_SEEK_FLAG_FLUSH;
+
+  accurate = gst_play_config_get_seek_accurate (self->config);
+
+  if (accurate) {
+    flags |= GST_SEEK_FLAG_ACCURATE;
+  } else {
+    flags &= ~GST_SEEK_FLAG_ACCURATE;
+  }
+
+  if (rate != 1.0) {
+    flags |= GST_SEEK_FLAG_TRICKMODE;
+  }
+
+  if (rate >= 0.0) {
+    s_event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags,
+        GST_SEEK_TYPE_SET, position, GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE);
+  } else {
+    s_event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags,
+        GST_SEEK_TYPE_SET, G_GINT64_CONSTANT (0), GST_SEEK_TYPE_SET, position);
+  }
+
+  GST_DEBUG_OBJECT (self, "Seek with rate %.2lf to %" GST_TIME_FORMAT,
+      rate, GST_TIME_ARGS (position));
+
+  ret = gst_element_send_event (self->playbin, s_event);
+  if (!ret)
+    on_error (self, g_error_new (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED,
+            "Failed to seek to %" GST_TIME_FORMAT, GST_TIME_ARGS (position)),
+        NULL);
+
+  g_mutex_lock (&self->lock);
+}
+
+static gboolean
+gst_play_seek_internal (gpointer user_data)
+{
+  GstPlay *self = GST_PLAY (user_data);
+
+  g_mutex_lock (&self->lock);
+  gst_play_seek_internal_locked (self);
+  g_mutex_unlock (&self->lock);
+
+  return G_SOURCE_REMOVE;
+}
+
+/**
+ * gst_play_set_rate:
+ * @play: #GstPlay instance
+ * @rate: playback rate
+ *
+ * Playback at specified rate
+ * Since: 1.20
+ */
+void
+gst_play_set_rate (GstPlay * self, gdouble rate)
+{
+  g_return_if_fail (GST_IS_PLAY (self));
+  g_return_if_fail (rate != 0.0);
+
+  g_object_set (self, "rate", rate, NULL);
+}
+
+/**
+ * gst_play_get_rate:
+ * @play: #GstPlay instance
+ *
+ * Returns: current playback rate
+ * Since: 1.20
+ */
+gdouble
+gst_play_get_rate (GstPlay * self)
+{
+  gdouble val;
+
+  g_return_val_if_fail (GST_IS_PLAY (self), DEFAULT_RATE);
+
+  g_object_get (self, "rate", &val, NULL);
+
+  return val;
+}
+
+/**
+ * gst_play_seek:
+ * @play: #GstPlay instance
+ * @position: position to seek in nanoseconds
+ *
+ * Seeks the currently-playing stream to the absolute @position time
+ * in nanoseconds.
+ * Since: 1.20
+ */
+void
+gst_play_seek (GstPlay * self, GstClockTime position)
+{
+  g_return_if_fail (GST_IS_PLAY (self));
+  g_return_if_fail (GST_CLOCK_TIME_IS_VALID (position));
+
+  g_mutex_lock (&self->lock);
+  if (self->media_info && !self->media_info->seekable) {
+    GST_DEBUG_OBJECT (self, "Media is not seekable");
+    g_mutex_unlock (&self->lock);
+    return;
+  }
+
+  self->seek_position = position;
+
+  /* If there is no seek being dispatch to the main context currently do that,
+   * otherwise we just updated the seek position so that it will be taken by
+   * the seek handler from the main context instead of the old one.
+   */
+  if (!self->seek_source) {
+    GstClockTime now = gst_util_get_timestamp ();
+
+    /* If no seek is pending or it was started more than 250 mseconds ago seek
+     * immediately, otherwise wait until the 250 mseconds have passed */
+    if (!self->seek_pending || (now - self->last_seek_time > 250 * GST_MSECOND)) {
+      self->seek_source = g_idle_source_new ();
+      g_source_set_callback (self->seek_source,
+          (GSourceFunc) gst_play_seek_internal, self, NULL);
+      GST_TRACE_OBJECT (self, "Dispatching seek to position %" GST_TIME_FORMAT,
+          GST_TIME_ARGS (position));
+      g_source_attach (self->seek_source, self->context);
+    } else {
+      guint delay = 250000 - (now - self->last_seek_time) / 1000;
+
+      /* Note that last_seek_time must be set to something at this point and
+       * it must be smaller than 250 mseconds */
+      self->seek_source = g_timeout_source_new (delay);
+      g_source_set_callback (self->seek_source,
+          (GSourceFunc) gst_play_seek_internal, self, NULL);
+
+      GST_TRACE_OBJECT (self,
+          "Delaying seek to position %" GST_TIME_FORMAT " by %u us",
+          GST_TIME_ARGS (position), delay);
+      g_source_attach (self->seek_source, self->context);
+    }
+  }
+  g_mutex_unlock (&self->lock);
+}
+
+static void
+remove_seek_source (GstPlay * self)
+{
+  if (!self->seek_source)
+    return;
+
+  g_source_destroy (self->seek_source);
+  g_source_unref (self->seek_source);
+  self->seek_source = NULL;
+}
+
+/**
+ * gst_play_get_uri:
+ * @play: #GstPlay instance
+ *
+ * Gets the URI of the currently-playing stream.
+ *
+ * Returns: (transfer full): a string containing the URI of the
+ * currently-playing stream. g_free() after usage.
+ * Since: 1.20
+ */
+gchar *
+gst_play_get_uri (GstPlay * self)
+{
+  gchar *val;
+
+  g_return_val_if_fail (GST_IS_PLAY (self), DEFAULT_URI);
+
+  g_object_get (self, "uri", &val, NULL);
+
+  return val;
+}
+
+/**
+ * gst_play_set_uri:
+ * @play: #GstPlay instance
+ * @uri: next URI to play.
+ *
+ * Sets the next URI to play.
+ * Since: 1.20
+ */
+void
+gst_play_set_uri (GstPlay * self, const gchar * val)
+{
+  g_return_if_fail (GST_IS_PLAY (self));
+
+  g_object_set (self, "uri", val, NULL);
+}
+
+/**
+ * gst_play_set_subtitle_uri:
+ * @play: #GstPlay instance
+ * @uri: subtitle URI
+ *
+ * Sets the external subtitle URI. This should be combined with a call to
+ * gst_play_set_subtitle_track_enabled(@play, TRUE) so the subtitles are actually
+ * rendered.
+ * Since: 1.20
+ */
+void
+gst_play_set_subtitle_uri (GstPlay * self, const gchar * suburi)
+{
+  g_return_if_fail (GST_IS_PLAY (self));
+
+  g_object_set (self, "suburi", suburi, NULL);
+}
+
+/**
+ * gst_play_get_subtitle_uri:
+ * @play: #GstPlay instance
+ *
+ * current subtitle URI
+ *
+ * Returns: (transfer full): URI of the current external subtitle.
+ *   g_free() after usage.
+ * Since: 1.20
+ */
+gchar *
+gst_play_get_subtitle_uri (GstPlay * self)
+{
+  gchar *val = NULL;
+
+  g_return_val_if_fail (GST_IS_PLAY (self), NULL);
+
+  g_object_get (self, "suburi", &val, NULL);
+
+  return val;
+}
+
+/**
+ * gst_play_get_position:
+ * @play: #GstPlay instance
+ *
+ * Returns: the absolute position time, in nanoseconds, of the
+ * currently-playing stream.
+ * Since: 1.20
+ */
+GstClockTime
+gst_play_get_position (GstPlay * self)
+{
+  GstClockTime val;
+
+  g_return_val_if_fail (GST_IS_PLAY (self), DEFAULT_POSITION);
+
+  g_object_get (self, "position", &val, NULL);
+
+  return val;
+}
+
+/**
+ * gst_play_get_duration:
+ * @play: #GstPlay instance
+ *
+ * Retrieves the duration of the media stream that self represents.
+ *
+ * Returns: the duration of the currently-playing media stream, in
+ * nanoseconds.
+ * Since: 1.20
+ */
+GstClockTime
+gst_play_get_duration (GstPlay * self)
+{
+  GstClockTime val;
+
+  g_return_val_if_fail (GST_IS_PLAY (self), DEFAULT_DURATION);
+
+  g_object_get (self, "duration", &val, NULL);
+
+  return val;
+}
+
+/**
+ * gst_play_get_volume:
+ * @play: #GstPlay instance
+ *
+ * Returns the current volume level, as a percentage between 0 and 1.
+ *
+ * Returns: the volume as percentage between 0 and 1.
+ * Since: 1.20
+ */
+gdouble
+gst_play_get_volume (GstPlay * self)
+{
+  gdouble val;
+
+  g_return_val_if_fail (GST_IS_PLAY (self), DEFAULT_VOLUME);
+
+  g_object_get (self, "volume", &val, NULL);
+
+  return val;
+}
+
+/**
+ * gst_play_set_volume:
+ * @play: #GstPlay instance
+ * @val: the new volume level, as a percentage between 0 and 1
+ *
+ * Sets the volume level of the stream as a percentage between 0 and 1.
+ * Since: 1.20
+ */
+void
+gst_play_set_volume (GstPlay * self, gdouble val)
+{
+  g_return_if_fail (GST_IS_PLAY (self));
+
+  g_object_set (self, "volume", val, NULL);
+}
+
+/**
+ * gst_play_get_mute:
+ * @play: #GstPlay instance
+ *
+ * Returns: %TRUE if the currently-playing stream is muted.
+ * Since: 1.20
+ */
+gboolean
+gst_play_get_mute (GstPlay * self)
+{
+  gboolean val;
+
+  g_return_val_if_fail (GST_IS_PLAY (self), DEFAULT_MUTE);
+
+  g_object_get (self, "mute", &val, NULL);
+
+  return val;
+}
+
+/**
+ * gst_play_set_mute:
+ * @play: #GstPlay instance
+ * @val: Mute state the should be set
+ *
+ * %TRUE if the currently-playing stream should be muted.
+ * Since: 1.20
+ */
+void
+gst_play_set_mute (GstPlay * self, gboolean val)
+{
+  g_return_if_fail (GST_IS_PLAY (self));
+
+  g_object_set (self, "mute", val, NULL);
+}
+
+/**
+ * gst_play_get_pipeline:
+ * @play: #GstPlay instance
+ *
+ * Returns: (transfer full): The internal playbin instance.
+ *
+ * The caller should free it with g_object_unref()
+ * Since: 1.20
+ */
+GstElement *
+gst_play_get_pipeline (GstPlay * self)
+{
+  GstElement *val;
+
+  g_return_val_if_fail (GST_IS_PLAY (self), NULL);
+
+  g_object_get (self, "pipeline", &val, NULL);
+
+  return val;
+}
+
+/**
+ * gst_play_get_media_info:
+ * @play: #GstPlay instance
+ *
+ * A Function to get the current media info #GstPlayMediaInfo instance.
+ *
+ * Returns: (transfer full): media info instance.
+ *
+ * The caller should free it with g_object_unref()
+ * Since: 1.20
+ */
+GstPlayMediaInfo *
+gst_play_get_media_info (GstPlay * self)
+{
+  GstPlayMediaInfo *info;
+
+  g_return_val_if_fail (GST_IS_PLAY (self), NULL);
+
+  if (!self->media_info)
+    return NULL;
+
+  g_mutex_lock (&self->lock);
+  info = gst_play_media_info_copy (self->media_info);
+  g_mutex_unlock (&self->lock);
+
+  return info;
+}
+
+/**
+ * gst_play_get_current_audio_track:
+ * @play: #GstPlay instance
+ *
+ * A Function to get current audio #GstPlayAudioInfo instance.
+ *
+ * Returns: (transfer full): current audio track.
+ *
+ * The caller should free it with g_object_unref()
+ * Since: 1.20
+ */
+GstPlayAudioInfo *
+gst_play_get_current_audio_track (GstPlay * self)
+{
+  GstPlayAudioInfo *info;
+
+  g_return_val_if_fail (GST_IS_PLAY (self), NULL);
+
+  if (!is_track_enabled (self, GST_PLAY_FLAG_AUDIO))
+    return NULL;
+
+  if (self->use_playbin3) {
+    info = (GstPlayAudioInfo *)
+        gst_play_stream_info_get_current_from_stream_id (self,
+        self->audio_sid, GST_TYPE_PLAY_AUDIO_INFO);
+  } else {
+    info = (GstPlayAudioInfo *) gst_play_stream_info_get_current (self,
+        "current-audio", GST_TYPE_PLAY_AUDIO_INFO);
+  }
+
+  return info;
+}
+
+/**
+ * gst_play_get_current_video_track:
+ * @play: #GstPlay instance
+ *
+ * A Function to get current video #GstPlayVideoInfo instance.
+ *
+ * Returns: (transfer full): current video track.
+ *
+ * The caller should free it with g_object_unref()
+ * Since: 1.20
+ */
+GstPlayVideoInfo *
+gst_play_get_current_video_track (GstPlay * self)
+{
+  GstPlayVideoInfo *info;
+
+  g_return_val_if_fail (GST_IS_PLAY (self), NULL);
+
+  if (!is_track_enabled (self, GST_PLAY_FLAG_VIDEO))
+    return NULL;
+
+  if (self->use_playbin3) {
+    info = (GstPlayVideoInfo *)
+        gst_play_stream_info_get_current_from_stream_id (self,
+        self->video_sid, GST_TYPE_PLAY_VIDEO_INFO);
+  } else {
+    info = (GstPlayVideoInfo *) gst_play_stream_info_get_current (self,
+        "current-video", GST_TYPE_PLAY_VIDEO_INFO);
+  }
+
+  return info;
+}
+
+/**
+ * gst_play_get_current_subtitle_track:
+ * @play: #GstPlay instance
+ *
+ * A Function to get current subtitle #GstPlaySubtitleInfo instance.
+ *
+ * Returns: (transfer full): current subtitle track.
+ *
+ * The caller should free it with g_object_unref()
+ * Since: 1.20
+ */
+GstPlaySubtitleInfo *
+gst_play_get_current_subtitle_track (GstPlay * self)
+{
+  GstPlaySubtitleInfo *info;
+
+  g_return_val_if_fail (GST_IS_PLAY (self), NULL);
+
+  if (!is_track_enabled (self, GST_PLAY_FLAG_SUBTITLE))
+    return NULL;
+
+  if (self->use_playbin3) {
+    info = (GstPlaySubtitleInfo *)
+        gst_play_stream_info_get_current_from_stream_id (self,
+        self->subtitle_sid, GST_TYPE_PLAY_SUBTITLE_INFO);
+  } else {
+    info = (GstPlaySubtitleInfo *) gst_play_stream_info_get_current (self,
+        "current-text", GST_TYPE_PLAY_SUBTITLE_INFO);
+  }
+
+  return info;
+}
+
+/* Must be called with lock */
+static gboolean
+gst_play_select_streams (GstPlay * self)
+{
+  GList *stream_list = NULL;
+  gboolean ret = FALSE;
+
+  if (self->audio_sid)
+    stream_list = g_list_append (stream_list, g_strdup (self->audio_sid));
+  if (self->video_sid)
+    stream_list = g_list_append (stream_list, g_strdup (self->video_sid));
+  if (self->subtitle_sid)
+    stream_list = g_list_append (stream_list, g_strdup (self->subtitle_sid));
+
+  g_mutex_unlock (&self->lock);
+  if (stream_list) {
+    ret = gst_element_send_event (self->playbin,
+        gst_event_new_select_streams (stream_list));
+    g_list_free_full (stream_list, g_free);
+  } else {
+    GST_ERROR_OBJECT (self, "No available streams for select-streams");
+  }
+  g_mutex_lock (&self->lock);
+
+  return ret;
+}
+
+/**
+ * gst_play_set_audio_track:
+ * @play: #GstPlay instance
+ * @stream_index: stream index
+ *
+ * Returns: %TRUE or %FALSE
+ *
+ * Sets the audio track @stream_index.
+ * Since: 1.20
+ */
+gboolean
+gst_play_set_audio_track (GstPlay * self, gint stream_index)
+{
+  GstPlayStreamInfo *info;
+  gboolean ret = TRUE;
+
+  g_return_val_if_fail (GST_IS_PLAY (self), 0);
+
+  g_mutex_lock (&self->lock);
+  info = gst_play_stream_info_find (self->media_info,
+      GST_TYPE_PLAY_AUDIO_INFO, stream_index);
+  g_mutex_unlock (&self->lock);
+  if (!info) {
+    GST_ERROR_OBJECT (self, "invalid audio stream index %d", stream_index);
+    return FALSE;
+  }
+
+  if (self->use_playbin3) {
+    g_mutex_lock (&self->lock);
+    g_free (self->audio_sid);
+    self->audio_sid = g_strdup (info->stream_id);
+    ret = gst_play_select_streams (self);
+    g_mutex_unlock (&self->lock);
+  } else {
+    g_object_set (G_OBJECT (self->playbin), "current-audio", stream_index,
+        NULL);
+  }
+
+  GST_DEBUG_OBJECT (self, "set stream index '%d'", stream_index);
+  return ret;
+}
+
+/**
+ * gst_play_set_video_track:
+ * @play: #GstPlay instance
+ * @stream_index: stream index
+ *
+ * Returns: %TRUE or %FALSE
+ *
+ * Sets the video track @stream_index.
+ * Since: 1.20
+ */
+gboolean
+gst_play_set_video_track (GstPlay * self, gint stream_index)
+{
+  GstPlayStreamInfo *info;
+  gboolean ret = TRUE;
+
+  g_return_val_if_fail (GST_IS_PLAY (self), 0);
+
+  /* check if stream_index exist in our internal media_info list */
+  g_mutex_lock (&self->lock);
+  info = gst_play_stream_info_find (self->media_info,
+      GST_TYPE_PLAY_VIDEO_INFO, stream_index);
+  g_mutex_unlock (&self->lock);
+  if (!info) {
+    GST_ERROR_OBJECT (self, "invalid video stream index %d", stream_index);
+    return FALSE;
+  }
+
+  if (self->use_playbin3) {
+    g_mutex_lock (&self->lock);
+    g_free (self->video_sid);
+    self->video_sid = g_strdup (info->stream_id);
+    ret = gst_play_select_streams (self);
+    g_mutex_unlock (&self->lock);
+  } else {
+    g_object_set (G_OBJECT (self->playbin), "current-video", stream_index,
+        NULL);
+  }
+
+  GST_DEBUG_OBJECT (self, "set stream index '%d'", stream_index);
+  return ret;
+}
+
+/**
+ * gst_play_set_subtitle_track:
+ * @play: #GstPlay instance
+ * @stream_index: stream index
+ *
+ * Returns: %TRUE or %FALSE
+ *
+ * Sets the subtitle stack @stream_index.
+ * Since: 1.20
+ */
+gboolean
+gst_play_set_subtitle_track (GstPlay * self, gint stream_index)
+{
+  GstPlayStreamInfo *info;
+  gboolean ret = TRUE;
+
+  g_return_val_if_fail (GST_IS_PLAY (self), 0);
+
+  g_mutex_lock (&self->lock);
+  info = gst_play_stream_info_find (self->media_info,
+      GST_TYPE_PLAY_SUBTITLE_INFO, stream_index);
+  g_mutex_unlock (&self->lock);
+  if (!info) {
+    GST_ERROR_OBJECT (self, "invalid subtitle stream index %d", stream_index);
+    return FALSE;
+  }
+
+  if (self->use_playbin3) {
+    g_mutex_lock (&self->lock);
+    g_free (self->subtitle_sid);
+    self->subtitle_sid = g_strdup (info->stream_id);
+    ret = gst_play_select_streams (self);
+    g_mutex_unlock (&self->lock);
+  } else {
+    g_object_set (G_OBJECT (self->playbin), "current-text", stream_index, NULL);
+  }
+
+  GST_DEBUG_OBJECT (self, "set stream index '%d'", stream_index);
+  return ret;
+}
+
+/**
+ * gst_play_set_audio_track_enabled:
+ * @play: #GstPlay instance
+ * @enabled: TRUE or FALSE
+ *
+ * Enable or disable the current audio track.
+ * Since: 1.20
+ */
+void
+gst_play_set_audio_track_enabled (GstPlay * self, gboolean enabled)
+{
+  g_return_if_fail (GST_IS_PLAY (self));
+
+  if (enabled)
+    play_set_flag (self, GST_PLAY_FLAG_AUDIO);
+  else
+    play_clear_flag (self, GST_PLAY_FLAG_AUDIO);
+
+  GST_DEBUG_OBJECT (self, "track is '%s'", enabled ? "Enabled" : "Disabled");
+}
+
+/**
+ * gst_play_set_video_track_enabled:
+ * @play: #GstPlay instance
+ * @enabled: TRUE or FALSE
+ *
+ * Enable or disable the current video track.
+ * Since: 1.20
+ */
+void
+gst_play_set_video_track_enabled (GstPlay * self, gboolean enabled)
+{
+  g_return_if_fail (GST_IS_PLAY (self));
+
+  if (enabled)
+    play_set_flag (self, GST_PLAY_FLAG_VIDEO);
+  else
+    play_clear_flag (self, GST_PLAY_FLAG_VIDEO);
+
+  GST_DEBUG_OBJECT (self, "track is '%s'", enabled ? "Enabled" : "Disabled");
+}
+
+/**
+ * gst_play_set_subtitle_track_enabled:
+ * @play: #GstPlay instance
+ * @enabled: TRUE or FALSE
+ *
+ * Enable or disable the current subtitle track.
+ * Since: 1.20
+ */
+void
+gst_play_set_subtitle_track_enabled (GstPlay * self, gboolean enabled)
+{
+  g_return_if_fail (GST_IS_PLAY (self));
+
+  if (enabled)
+    play_set_flag (self, GST_PLAY_FLAG_SUBTITLE);
+  else
+    play_clear_flag (self, GST_PLAY_FLAG_SUBTITLE);
+
+  GST_DEBUG_OBJECT (self, "track is '%s'", enabled ? "Enabled" : "Disabled");
+}
+
+/**
+ * gst_play_set_visualization:
+ * @play: #GstPlay instance
+ * @name: visualization element obtained from
+ * #gst_play_visualizations_get()
+ *
+ * Returns: %TRUE if the visualizations was set correctly. Otherwise,
+ * %FALSE.
+ * Since: 1.20
+ */
+gboolean
+gst_play_set_visualization (GstPlay * self, const gchar * name)
+{
+  g_return_val_if_fail (GST_IS_PLAY (self), FALSE);
+
+  g_mutex_lock (&self->lock);
+  if (self->current_vis_element) {
+    gst_object_unref (self->current_vis_element);
+    self->current_vis_element = NULL;
+  }
+
+  if (name) {
+    self->current_vis_element = gst_element_factory_make (name, NULL);
+    if (!self->current_vis_element)
+      goto error_no_element;
+    gst_object_ref_sink (self->current_vis_element);
+  }
+  g_object_set (self->playbin, "vis-plugin", self->current_vis_element, NULL);
+
+  g_mutex_unlock (&self->lock);
+  GST_DEBUG_OBJECT (self, "set vis-plugin to '%s'", name);
+
+  return TRUE;
+
+error_no_element:
+  g_mutex_unlock (&self->lock);
+  GST_WARNING_OBJECT (self, "could not find visualization '%s'", name);
+  return FALSE;
+}
+
+/**
+ * gst_play_get_current_visualization:
+ * @play: #GstPlay instance
+ *
+ * Returns: (transfer full): Name of the currently enabled visualization.
+ *   g_free() after usage.
+ * Since: 1.20
+ */
+gchar *
+gst_play_get_current_visualization (GstPlay * self)
+{
+  gchar *name = NULL;
+  GstElement *vis_plugin = NULL;
+
+  g_return_val_if_fail (GST_IS_PLAY (self), NULL);
+
+  if (!is_track_enabled (self, GST_PLAY_FLAG_VIS))
+    return NULL;
+
+  g_object_get (self->playbin, "vis-plugin", &vis_plugin, NULL);
+
+  if (vis_plugin) {
+    GstElementFactory *factory = gst_element_get_factory (vis_plugin);
+    if (factory)
+      name = g_strdup (gst_plugin_feature_get_name (factory));
+    gst_object_unref (vis_plugin);
+  }
+
+  GST_DEBUG_OBJECT (self, "vis-plugin '%s' %p", name, vis_plugin);
+
+  return name;
+}
+
+/**
+ * gst_play_set_visualization_enabled:
+ * @play: #GstPlay instance
+ * @enabled: TRUE or FALSE
+ *
+ * Enable or disable the visualization.
+ * Since: 1.20
+ */
+void
+gst_play_set_visualization_enabled (GstPlay * self, gboolean enabled)
+{
+  g_return_if_fail (GST_IS_PLAY (self));
+
+  if (enabled)
+    play_set_flag (self, GST_PLAY_FLAG_VIS);
+  else
+    play_clear_flag (self, GST_PLAY_FLAG_VIS);
+
+  GST_DEBUG_OBJECT (self, "visualization is '%s'",
+      enabled ? "Enabled" : "Disabled");
+}
+
+struct CBChannelMap
+{
+  const gchar *label;           /* channel label name */
+  const gchar *name;            /* get_name () */
+};
+
+static const struct CBChannelMap cb_channel_map[] = {
+  /* GST_PLAY_COLOR_BALANCE_BRIGHTNESS */ {"BRIGHTNESS", "brightness"},
+  /* GST_PLAY_COLOR_BALANCE_CONTRAST   */ {"CONTRAST", "contrast"},
+  /* GST_PLAY_COLOR_BALANCE_SATURATION */ {"SATURATION", "saturation"},
+  /* GST_PLAY_COLOR_BALANCE_HUE        */ {"HUE", "hue"},
+};
+
+static GstColorBalanceChannel *
+gst_play_color_balance_find_channel (GstPlay * self,
+    GstPlayColorBalanceType type)
+{
+  GstColorBalanceChannel *channel;
+  const GList *l, *channels;
+
+  if (type < GST_PLAY_COLOR_BALANCE_BRIGHTNESS ||
+      type > GST_PLAY_COLOR_BALANCE_HUE)
+    return NULL;
+
+  channels =
+      gst_color_balance_list_channels (GST_COLOR_BALANCE (self->playbin));
+  for (l = channels; l; l = l->next) {
+    channel = l->data;
+    if (g_strrstr (channel->label, cb_channel_map[type].label))
+      return channel;
+  }
+
+  return NULL;
+}
+
+/**
+ * gst_play_has_color_balance:
+ * @play:#GstPlay instance
+ *
+ * Checks whether the @play has color balance support available.
+ *
+ * Returns: %TRUE if @play has color balance support. Otherwise,
+ *   %FALSE.
+ * Since: 1.20
+ */
+gboolean
+gst_play_has_color_balance (GstPlay * self)
+{
+  const GList *channels;
+
+  g_return_val_if_fail (GST_IS_PLAY (self), FALSE);
+
+  if (!GST_IS_COLOR_BALANCE (self->playbin))
+    return FALSE;
+
+  channels =
+      gst_color_balance_list_channels (GST_COLOR_BALANCE (self->playbin));
+  return (channels != NULL);
+}
+
+/**
+ * gst_play_set_color_balance:
+ * @play: #GstPlay instance
+ * @type: #GstPlayColorBalanceType
+ * @value: The new value for the @type, ranged [0,1]
+ *
+ * Sets the current value of the indicated channel @type to the passed
+ * value.
+ * Since: 1.20
+ */
+void
+gst_play_set_color_balance (GstPlay * self, GstPlayColorBalanceType type,
+    gdouble value)
+{
+  GstColorBalanceChannel *channel;
+  gdouble new_val;
+
+  g_return_if_fail (GST_IS_PLAY (self));
+  g_return_if_fail (value >= 0.0 && value <= 1.0);
+
+  if (!GST_IS_COLOR_BALANCE (self->playbin))
+    return;
+
+  channel = gst_play_color_balance_find_channel (self, type);
+  if (!channel)
+    return;
+
+  value = CLAMP (value, 0.0, 1.0);
+
+  /* Convert to channel range */
+  new_val = channel->min_value + value * ((gdouble) channel->max_value -
+      (gdouble) channel->min_value);
+
+  gst_color_balance_set_value (GST_COLOR_BALANCE (self->playbin), channel,
+      new_val);
+}
+
+/**
+ * gst_play_get_color_balance:
+ * @play: #GstPlay instance
+ * @type: #GstPlayColorBalanceType
+ *
+ * Retrieve the current value of the indicated @type.
+ *
+ * Returns: The current value of @type, between [0,1]. In case of
+ *   error -1 is returned.
+ * Since: 1.20
+ */
+gdouble
+gst_play_get_color_balance (GstPlay * self, GstPlayColorBalanceType type)
+{
+  GstColorBalanceChannel *channel;
+  gint value;
+
+  g_return_val_if_fail (GST_IS_PLAY (self), -1);
+
+  if (!GST_IS_COLOR_BALANCE (self->playbin))
+    return -1;
+
+  channel = gst_play_color_balance_find_channel (self, type);
+  if (!channel)
+    return -1;
+
+  value = gst_color_balance_get_value (GST_COLOR_BALANCE (self->playbin),
+      channel);
+
+  return ((gdouble) value -
+      (gdouble) channel->min_value) / ((gdouble) channel->max_value -
+      (gdouble) channel->min_value);
+}
+
+/**
+ * gst_play_get_multiview_mode:
+ * @play: #GstPlay instance
+ *
+ * Retrieve the current value of the indicated @type.
+ *
+ * Returns: The current value of @type, Default: -1 "none"
+ *
+ * Since: 1.20
+ */
+GstVideoMultiviewFramePacking
+gst_play_get_multiview_mode (GstPlay * self)
+{
+  GstVideoMultiviewFramePacking val = GST_VIDEO_MULTIVIEW_FRAME_PACKING_NONE;
+
+  g_return_val_if_fail (GST_IS_PLAY (self),
+      GST_VIDEO_MULTIVIEW_FRAME_PACKING_NONE);
+
+  g_object_get (self, "video-multiview-mode", &val, NULL);
+
+  return val;
+}
+
+/**
+ * gst_play_set_multiview_mode:
+ * @play: #GstPlay instance
+ * @mode: The new value for the @type
+ *
+ * Sets the current value of the indicated mode @type to the passed
+ * value.
+ *
+ * Since: 1.20
+ */
+void
+gst_play_set_multiview_mode (GstPlay * self, GstVideoMultiviewFramePacking mode)
+{
+  g_return_if_fail (GST_IS_PLAY (self));
+
+  g_object_set (self, "video-multiview-mode", mode, NULL);
+}
+
+/**
+ * gst_play_get_multiview_flags:
+ * @play: #GstPlay instance
+ *
+ * Retrieve the current value of the indicated @type.
+ *
+ * Returns: The current value of @type, Default: 0x00000000 "none
+ *
+ * Since: 1.20
+ */
+GstVideoMultiviewFlags
+gst_play_get_multiview_flags (GstPlay * self)
+{
+  GstVideoMultiviewFlags val = GST_VIDEO_MULTIVIEW_FLAGS_NONE;
+
+  g_return_val_if_fail (GST_IS_PLAY (self), val);
+
+  g_object_get (self, "video-multiview-flags", &val, NULL);
+
+  return val;
+}
+
+/**
+ * gst_play_set_multiview_flags:
+ * @play: #GstPlay instance
+ * @flags: The new value for the @type
+ *
+ * Sets the current value of the indicated mode @type to the passed
+ * value.
+ *
+ * Since: 1.20
+ */
+void
+gst_play_set_multiview_flags (GstPlay * self, GstVideoMultiviewFlags flags)
+{
+  g_return_if_fail (GST_IS_PLAY (self));
+
+  g_object_set (self, "video-multiview-flags", flags, NULL);
+}
+
+/**
+ * gst_play_get_audio_video_offset:
+ * @play: #GstPlay instance
+ *
+ * Retrieve the current value of audio-video-offset property
+ *
+ * Returns: The current value of audio-video-offset in nanoseconds
+ *
+ * Since: 1.20
+ */
+gint64
+gst_play_get_audio_video_offset (GstPlay * self)
+{
+  gint64 val = 0;
+
+  g_return_val_if_fail (GST_IS_PLAY (self), DEFAULT_AUDIO_VIDEO_OFFSET);
+
+  g_object_get (self, "audio-video-offset", &val, NULL);
+
+  return val;
+}
+
+/**
+ * gst_play_set_audio_video_offset:
+ * @play: #GstPlay instance
+ * @offset: #gint64 in nanoseconds
+ *
+ * Sets audio-video-offset property by value of @offset
+ *
+ * Since: 1.20
+ */
+void
+gst_play_set_audio_video_offset (GstPlay * self, gint64 offset)
+{
+  g_return_if_fail (GST_IS_PLAY (self));
+
+  g_object_set (self, "audio-video-offset", offset, NULL);
+}
+
+/**
+ * gst_play_get_subtitle_video_offset:
+ * @play: #GstPlay instance
+ *
+ * Retrieve the current value of subtitle-video-offset property
+ *
+ * Returns: The current value of subtitle-video-offset in nanoseconds
+ *
+ * Since: 1.20
+ */
+gint64
+gst_play_get_subtitle_video_offset (GstPlay * self)
+{
+  gint64 val = 0;
+
+  g_return_val_if_fail (GST_IS_PLAY (self), DEFAULT_SUBTITLE_VIDEO_OFFSET);
+
+  g_object_get (self, "subtitle-video-offset", &val, NULL);
+
+  return val;
+}
+
+/**
+ * gst_play_set_subtitle_video_offset:
+ * @play: #GstPlay instance
+ * @offset: #gint64 in nanoseconds
+ *
+ * Sets subtitle-video-offset property by value of @offset
+ *
+ * Since: 1.20
+ */
+void
+gst_play_set_subtitle_video_offset (GstPlay * self, gint64 offset)
+{
+  g_return_if_fail (GST_IS_PLAY (self));
+
+  g_object_set (self, "subtitle-video-offset", offset, NULL);
+}
+
+
+#define C_ENUM(v) ((gint) v)
+#define C_FLAGS(v) ((guint) v)
+
+GType
+gst_play_color_balance_type_get_type (void)
+{
+  static gsize id = 0;
+  static const GEnumValue values[] = {
+    {C_ENUM (GST_PLAY_COLOR_BALANCE_HUE), "GST_PLAY_COLOR_BALANCE_HUE",
+        "hue"},
+    {C_ENUM (GST_PLAY_COLOR_BALANCE_BRIGHTNESS),
+        "GST_PLAY_COLOR_BALANCE_BRIGHTNESS", "brightness"},
+    {C_ENUM (GST_PLAY_COLOR_BALANCE_SATURATION),
+        "GST_PLAY_COLOR_BALANCE_SATURATION", "saturation"},
+    {C_ENUM (GST_PLAY_COLOR_BALANCE_CONTRAST),
+        "GST_PLAY_COLOR_BALANCE_CONTRAST", "contrast"},
+    {0, NULL, NULL}
+  };
+
+  if (g_once_init_enter (&id)) {
+    GType tmp = g_enum_register_static ("GstPlayColorBalanceType", values);
+    g_once_init_leave (&id, tmp);
+  }
+
+  return (GType) id;
+}
+
+/**
+ * gst_play_color_balance_type_get_name:
+ * @type: a #GstPlayColorBalanceType
+ *
+ * Gets a string representing the given color balance type.
+ *
+ * Returns: (transfer none): a string with the name of the color
+ *   balance type.
+ * Since: 1.20
+ */
+const gchar *
+gst_play_color_balance_type_get_name (GstPlayColorBalanceType type)
+{
+  g_return_val_if_fail (type >= GST_PLAY_COLOR_BALANCE_BRIGHTNESS &&
+      type <= GST_PLAY_COLOR_BALANCE_HUE, NULL);
+
+  return cb_channel_map[type].name;
+}
+
+GType
+gst_play_state_get_type (void)
+{
+  static gsize id = 0;
+  static const GEnumValue values[] = {
+    {C_ENUM (GST_PLAY_STATE_STOPPED), "GST_PLAY_STATE_STOPPED", "stopped"},
+    {C_ENUM (GST_PLAY_STATE_BUFFERING), "GST_PLAY_STATE_BUFFERING",
+        "buffering"},
+    {C_ENUM (GST_PLAY_STATE_PAUSED), "GST_PLAY_STATE_PAUSED", "paused"},
+    {C_ENUM (GST_PLAY_STATE_PLAYING), "GST_PLAY_STATE_PLAYING", "playing"},
+    {0, NULL, NULL}
+  };
+
+  if (g_once_init_enter (&id)) {
+    GType tmp = g_enum_register_static ("GstPlayState", values);
+    g_once_init_leave (&id, tmp);
+  }
+
+  return (GType) id;
+}
+
+GType
+gst_play_message_get_type (void)
+{
+  static gsize id = 0;
+  static const GEnumValue values[] = {
+    {C_ENUM (GST_PLAY_MESSAGE_URI_LOADED), "GST_PLAY_MESSAGE_URI_LOADED",
+        "uri-loaded"},
+    {C_ENUM (GST_PLAY_MESSAGE_POSITION_UPDATED),
+        "GST_PLAY_MESSAGE_POSITION_UPDATED", "position-updated"},
+    {C_ENUM (GST_PLAY_MESSAGE_DURATION_CHANGED),
+        "GST_PLAY_MESSAGE_DURATION_CHANGED", "duration-changed"},
+    {C_ENUM (GST_PLAY_MESSAGE_STATE_CHANGED),
+        "GST_PLAY_MESSAGE_STATE_CHANGED", "state-changed"},
+    {C_ENUM (GST_PLAY_MESSAGE_BUFFERING), "GST_PLAY_MESSAGE_BUFFERING",
+        "buffering"},
+    {C_ENUM (GST_PLAY_MESSAGE_END_OF_STREAM),
+        "GST_PLAY_MESSAGE_END_OF_STREAM", "end-of-stream"},
+    {C_ENUM (GST_PLAY_MESSAGE_ERROR), "GST_PLAY_MESSAGE_ERROR", "error"},
+    {C_ENUM (GST_PLAY_MESSAGE_WARNING), "GST_PLAY_MESSAGE_WARNING",
+        "warning"},
+    {C_ENUM (GST_PLAY_MESSAGE_VIDEO_DIMENSIONS_CHANGED),
+          "GST_PLAY_MESSAGE_VIDEO_DIMENSIONS_CHANGED",
+        "video-dimensions-changed"},
+    {C_ENUM (GST_PLAY_MESSAGE_MEDIA_INFO_UPDATED),
+        "GST_PLAY_MESSAGE_MEDIA_INFO_UPDATED", "media-info-updated"},
+    {C_ENUM (GST_PLAY_MESSAGE_VOLUME_CHANGED),
+        "GST_PLAY_MESSAGE_VOLUME_CHANGED", "volume-changed"},
+    {C_ENUM (GST_PLAY_MESSAGE_MUTE_CHANGED),
+        "GST_PLAY_MESSAGE_MUTE_CHANGED", "mute-changed"},
+    {C_ENUM (GST_PLAY_MESSAGE_SEEK_DONE), "GST_PLAY_MESSAGE_SEEK_DONE",
+        "seek-done"},
+    {0, NULL, NULL}
+  };
+
+  if (g_once_init_enter (&id)) {
+    GType tmp = g_enum_register_static ("GstPlayMessage", values);
+    g_once_init_leave (&id, tmp);
+  }
+
+  return (GType) id;
+}
+
+/**
+ * gst_play_state_get_name:
+ * @state: a #GstPlayState
+ *
+ * Gets a string representing the given state.
+ *
+ * Returns: (transfer none): a string with the name of the state.
+ * Since: 1.20
+ */
+const gchar *
+gst_play_state_get_name (GstPlayState state)
+{
+  switch (state) {
+    case GST_PLAY_STATE_STOPPED:
+      return "stopped";
+    case GST_PLAY_STATE_BUFFERING:
+      return "buffering";
+    case GST_PLAY_STATE_PAUSED:
+      return "paused";
+    case GST_PLAY_STATE_PLAYING:
+      return "playing";
+  }
+
+  g_assert_not_reached ();
+  return NULL;
+}
+
+/**
+ * gst_play_message_get_name:
+ * @message_type: a #GstPlayMessage
+ *
+ * Returns: (transfer none): a string with the name of the message.
+ * Since: 1.20
+ */
+const gchar *
+gst_play_message_get_name (GstPlayMessage message_type)
+{
+  GEnumClass *enum_class;
+  GEnumValue *enum_value;
+  enum_class = g_type_class_ref (GST_TYPE_PLAY_MESSAGE);
+  enum_value = g_enum_get_value (enum_class, message_type);
+  g_assert (enum_value != NULL);
+  g_type_class_unref (enum_class);
+  return enum_value->value_name;
+}
+
+GType
+gst_play_error_get_type (void)
+{
+  static gsize id = 0;
+  static const GEnumValue values[] = {
+    {C_ENUM (GST_PLAY_ERROR_FAILED), "GST_PLAY_ERROR_FAILED", "failed"},
+    {0, NULL, NULL}
+  };
+
+  if (g_once_init_enter (&id)) {
+    GType tmp = g_enum_register_static ("GstPlayError", values);
+    g_once_init_leave (&id, tmp);
+  }
+
+  return (GType) id;
+}
+
+/**
+ * gst_play_error_get_name:
+ * @error: a #GstPlayError
+ *
+ * Gets a string representing the given error.
+ *
+ * Returns: (transfer none): a string with the given error.
+ * Since: 1.20
+ */
+const gchar *
+gst_play_error_get_name (GstPlayError error)
+{
+  switch (error) {
+    case GST_PLAY_ERROR_FAILED:
+      return "failed";
+  }
+
+  g_assert_not_reached ();
+  return NULL;
+}
+
+/**
+ * gst_play_set_config:
+ * @play: #GstPlay instance
+ * @config: (transfer full): a #GstStructure
+ *
+ * Set the configuration of the play. If the play is already configured, and
+ * the configuration haven't change, this function will return %TRUE. If the
+ * play is not in the GST_PLAY_STATE_STOPPED, this method will return %FALSE
+ * and active configuration will remain.
+ *
+ * @config is a #GstStructure that contains the configuration parameters for
+ * the play.
+ *
+ * This function takes ownership of @config.
+ *
+ * Returns: %TRUE when the configuration could be set.
+ * Since: 1.20
+ */
+gboolean
+gst_play_set_config (GstPlay * self, GstStructure * config)
+{
+  g_return_val_if_fail (GST_IS_PLAY (self), FALSE);
+  g_return_val_if_fail (config != NULL, FALSE);
+
+  g_mutex_lock (&self->lock);
+
+  if (self->app_state != GST_PLAY_STATE_STOPPED) {
+    GST_INFO_OBJECT (self, "can't change config while play is %s",
+        gst_play_state_get_name (self->app_state));
+    g_mutex_unlock (&self->lock);
+    return FALSE;
+  }
+
+  if (self->config)
+    gst_structure_free (self->config);
+  self->config = config;
+  g_mutex_unlock (&self->lock);
+
+  return TRUE;
+}
+
+/**
+ * gst_play_get_config:
+ * @play: #GstPlay instance
+ *
+ * Get a copy of the current configuration of the play. This configuration
+ * can either be modified and used for the gst_play_set_config() call
+ * or it must be freed after usage.
+ *
+ * Returns: (transfer full): a copy of the current configuration of @play. Use
+ * gst_structure_free() after usage or gst_play_set_config().
+ *
+ * Since: 1.20
+ */
+GstStructure *
+gst_play_get_config (GstPlay * self)
+{
+  GstStructure *ret;
+
+  g_return_val_if_fail (GST_IS_PLAY (self), NULL);
+
+  g_mutex_lock (&self->lock);
+  ret = gst_structure_copy (self->config);
+  g_mutex_unlock (&self->lock);
+
+  return ret;
+}
+
+/**
+ * gst_play_config_set_user_agent:
+ * @config: a #GstPlay configuration
+ * @agent: the string to use as user agent
+ *
+ * Set the user agent to pass to the server if @play needs to connect
+ * to a server during playback. This is typically used when playing HTTP
+ * or RTSP streams.
+ *
+ * Since: 1.20
+ */
+void
+gst_play_config_set_user_agent (GstStructure * config, const gchar * agent)
+{
+  g_return_if_fail (config != NULL);
+  g_return_if_fail (agent != NULL);
+
+  gst_structure_id_set (config,
+      CONFIG_QUARK (USER_AGENT), G_TYPE_STRING, agent, NULL);
+}
+
+/**
+ * gst_play_config_get_user_agent:
+ * @config: a #GstPlay configuration
+ *
+ * Return the user agent which has been configured using
+ * gst_play_config_set_user_agent() if any.
+ *
+ * Returns: (transfer full): the configured agent, or %NULL
+ * Since: 1.20
+ */
+gchar *
+gst_play_config_get_user_agent (const GstStructure * config)
+{
+  gchar *agent = NULL;
+
+  g_return_val_if_fail (config != NULL, NULL);
+
+  gst_structure_id_get (config,
+      CONFIG_QUARK (USER_AGENT), G_TYPE_STRING, &agent, NULL);
+
+  return agent;
+}
+
+/**
+ * gst_play_config_set_position_update_interval:
+ * @config: a #GstPlay configuration
+ * @interval: interval in ms
+ *
+ * set desired interval in milliseconds between two position-updated messages.
+ * pass 0 to stop updating the position.
+ * Since: 1.20
+ */
+void
+gst_play_config_set_position_update_interval (GstStructure * config,
+    guint interval)
+{
+  g_return_if_fail (config != NULL);
+  g_return_if_fail (interval <= 10000);
+
+  gst_structure_id_set (config,
+      CONFIG_QUARK (POSITION_INTERVAL_UPDATE), G_TYPE_UINT, interval, NULL);
+}
+
+/**
+ * gst_play_config_get_position_update_interval:
+ * @config: a #GstPlay configuration
+ *
+ * Returns: current position update interval in milliseconds
+ *
+ * Since: 1.20
+ */
+guint
+gst_play_config_get_position_update_interval (const GstStructure * config)
+{
+  guint interval = DEFAULT_POSITION_UPDATE_INTERVAL_MS;
+
+  g_return_val_if_fail (config != NULL, DEFAULT_POSITION_UPDATE_INTERVAL_MS);
+
+  gst_structure_id_get (config,
+      CONFIG_QUARK (POSITION_INTERVAL_UPDATE), G_TYPE_UINT, &interval, NULL);
+
+  return interval;
+}
+
+/**
+ * gst_play_config_set_seek_accurate:
+ * @config: a #GstPlay configuration
+ * @accurate: accurate seek or not
+ *
+ * Enable or disable accurate seeking. When enabled, elements will try harder
+ * to seek as accurately as possible to the requested seek position. Generally
+ * it will be slower especially for formats that don't have any indexes or
+ * timestamp markers in the stream.
+ *
+ * If accurate seeking is disabled, elements will seek as close as the request
+ * position without slowing down seeking too much.
+ *
+ * Accurate seeking is disabled by default.
+ *
+ * Since: 1.20
+ */
+void
+gst_play_config_set_seek_accurate (GstStructure * config, gboolean accurate)
+{
+  g_return_if_fail (config != NULL);
+
+  gst_structure_id_set (config,
+      CONFIG_QUARK (ACCURATE_SEEK), G_TYPE_BOOLEAN, accurate, NULL);
+}
+
+/**
+ * gst_play_config_get_seek_accurate:
+ * @config: a #GstPlay configuration
+ *
+ * Returns: %TRUE if accurate seeking is enabled
+ *
+ * Since: 1.20
+ */
+gboolean
+gst_play_config_get_seek_accurate (const GstStructure * config)
+{
+  gboolean accurate = FALSE;
+
+  g_return_val_if_fail (config != NULL, FALSE);
+
+  gst_structure_id_get (config,
+      CONFIG_QUARK (ACCURATE_SEEK), G_TYPE_BOOLEAN, &accurate, NULL);
+
+  return accurate;
+}
+
+/**
+ * gst_play_get_video_snapshot:
+ * @play: #GstPlay instance
+ * @format: output format of the video snapshot
+ * @config: (allow-none): Additional configuration
+ *
+ * Get a snapshot of the currently selected video stream, if any. The format can be
+ * selected with @format and optional configuration is possible with @config
+ * Currently supported settings are:
+ * - width, height of type G_TYPE_INT
+ * - pixel-aspect-ratio of type GST_TYPE_FRACTION
+ *  Except for GST_PLAY_THUMBNAIL_RAW_NATIVE format, if no config is set, pixel-aspect-ratio would be 1/1
+ *
+ * Returns: (transfer full):  Current video snapshot sample or %NULL on failure
+ *
+ * Since: 1.20
+ */
+GstSample *
+gst_play_get_video_snapshot (GstPlay * self,
+    GstPlaySnapshotFormat format, const GstStructure * config)
+{
+  gint video_tracks = 0;
+  GstSample *sample = NULL;
+  GstCaps *caps = NULL;
+  gint width = -1;
+  gint height = -1;
+  gint par_n = 1;
+  gint par_d = 1;
+  g_return_val_if_fail (GST_IS_PLAY (self), NULL);
+
+  g_object_get (self->playbin, "n-video", &video_tracks, NULL);
+  if (video_tracks == 0) {
+    GST_DEBUG_OBJECT (self, "total video track num is 0");
+    return NULL;
+  }
+
+  switch (format) {
+    case GST_PLAY_THUMBNAIL_RAW_xRGB:
+      caps = gst_caps_new_simple ("video/x-raw",
+          "format", G_TYPE_STRING, "xRGB", NULL);
+      break;
+    case GST_PLAY_THUMBNAIL_RAW_BGRx:
+      caps = gst_caps_new_simple ("video/x-raw",
+          "format", G_TYPE_STRING, "BGRx", NULL);
+      break;
+    case GST_PLAY_THUMBNAIL_JPG:
+      caps = gst_caps_new_empty_simple ("image/jpeg");
+      break;
+    case GST_PLAY_THUMBNAIL_PNG:
+      caps = gst_caps_new_empty_simple ("image/png");
+      break;
+    case GST_PLAY_THUMBNAIL_RAW_NATIVE:
+    default:
+      caps = gst_caps_new_empty_simple ("video/x-raw");
+      break;
+  }
+
+  if (NULL != config) {
+    if (!gst_structure_get_int (config, "width", &width))
+      width = -1;
+    if (!gst_structure_get_int (config, "height", &height))
+      height = -1;
+    if (!gst_structure_get_fraction (config, "pixel-aspect-ratio", &par_n,
+            &par_d)) {
+      if (format != GST_PLAY_THUMBNAIL_RAW_NATIVE) {
+        par_n = 1;
+        par_d = 1;
+      } else {
+        par_n = 0;
+        par_d = 0;
+      }
+    }
+  }
+
+  if (width > 0 && height > 0) {
+    gst_caps_set_simple (caps, "width", G_TYPE_INT, width,
+        "height", G_TYPE_INT, height, NULL);
+  }
+
+  if (format != GST_PLAY_THUMBNAIL_RAW_NATIVE) {
+    gst_caps_set_simple (caps, "pixel-aspect-ratio", GST_TYPE_FRACTION,
+        par_n, par_d, NULL);
+  } else if (NULL != config && par_n != 0 && par_d != 0) {
+    gst_caps_set_simple (caps, "pixel-aspect-ratio", GST_TYPE_FRACTION,
+        par_n, par_d, NULL);
+  }
+
+  g_signal_emit_by_name (self->playbin, "convert-sample", caps, &sample);
+  gst_caps_unref (caps);
+  if (!sample) {
+    GST_WARNING_OBJECT (self, "Failed to retrieve or convert video frame");
+    return NULL;
+  }
+
+  return sample;
+}
+
+/**
+ * gst_play_is_play_message:
+ * @msg: A #GstMessage
+ *
+ * Returns: A #gboolean indicating wheter the passes message represents a #GstPlay message or not.
+ *
+ * Since: 1.20
+ */
+gboolean
+gst_play_is_play_message (GstMessage * msg)
+{
+  const GstStructure *data = NULL;
+  g_return_val_if_fail (GST_IS_MESSAGE (msg), FALSE);
+
+  data = gst_message_get_structure (msg);
+  g_return_val_if_fail (data, FALSE);
+
+  return g_str_equal (gst_structure_get_name (data), GST_PLAY_MESSAGE_DATA);
+}
+
+#define PARSE_MESSAGE_FIELD(msg, field, value_type, value) G_STMT_START { \
+    const GstStructure *data = NULL;                                      \
+    g_return_if_fail (gst_play_is_play_message (msg));                    \
+    data = gst_message_get_structure (msg);                               \
+    if (!gst_structure_get (data, field, value_type, value, NULL)) {      \
+      g_error ("Could not parse field from structure: %s", field);        \
+    }                                                                     \
+} G_STMT_END
+
+/**
+ * gst_play_message_parse_type:
+ * @msg: A #GstMessage
+ * @type: (out): (optional): (transfer full): the resulting message type
+ *
+ * Parse the given @msg and extract its #GstPlayMessage type.
+ *
+ * Since: 1.20
+ */
+void
+gst_play_message_parse_type (GstMessage * msg, GstPlayMessage * type)
+{
+  PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_TYPE,
+      GST_TYPE_PLAY_MESSAGE, type);
+}
+
+/**
+ * gst_play_message_parse_duration_updated:
+ * @msg: A #GstMessage
+ * @duration: (out): (optional): (transfer full): the resulting duration
+ *
+ * Parse the given duration @msg and extract the corresponding #GstClockTime
+ *
+ * Since: 1.20
+ */
+void
+gst_play_message_parse_duration_updated (GstMessage * msg,
+    GstClockTime * duration)
+{
+  PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_DURATION,
+      GST_TYPE_CLOCK_TIME, duration);
+}
+
+/**
+ * gst_play_message_parse_position_updated:
+ * @msg: A #GstMessage
+ * @position: (out): (optional): (transfer full): the resulting position
+ *
+ * Parse the given position @msg and extract the corresponding #GstClockTime
+ *
+ * Since: 1.20
+ */
+void
+gst_play_message_parse_position_updated (GstMessage * msg,
+    GstClockTime * position)
+{
+  PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_POSITION,
+      GST_TYPE_CLOCK_TIME, position);
+}
+
+/**
+ * gst_play_message_parse_state_changed:
+ * @msg: A #GstMessage
+ * @state: (out): (optional): (transfer full): the resulting play state
+ *
+ * Parse the given state @msg and extract the corresponding #GstPlayState
+ *
+ * Since: 1.20
+ */
+void
+gst_play_message_parse_state_changed (GstMessage * msg, GstPlayState * state)
+{
+  PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_PLAY_STATE,
+      GST_TYPE_PLAY_STATE, state);
+}
+
+/**
+ * gst_play_message_parse_buffering_percent:
+ * @msg: A #GstMessage
+ * @percent: (out): (optional): the resulting buffering percent
+ *
+ * Parse the given buffering-percent @msg and extract the corresponding value
+ *
+ * Since: 1.20
+ */
+void
+gst_play_message_parse_buffering_percent (GstMessage * msg, guint * percent)
+{
+  PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_BUFFERING_PERCENT,
+      G_TYPE_UINT, percent);
+}
+
+/**
+ * gst_play_message_parse_error:
+ * @msg: A #GstMessage
+ * @error: (out): (optional): (transfer full): the resulting error
+ * @details: (out): (optional): (transfer none): A GstStructure containing extra details about the error
+ *
+ * Parse the given error @msg and extract the corresponding #GError
+ *
+ * Since: 1.20
+ */
+void
+gst_play_message_parse_error (GstMessage * msg, GError * error,
+    GstStructure ** details)
+{
+  PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_ERROR, G_TYPE_ERROR, error);
+  PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_ERROR, GST_TYPE_STRUCTURE,
+      details);
+}
+
+/**
+ * gst_play_message_parse_warning:
+ * @msg: A #GstMessage
+ * @error: (out): (optional): (transfer full): the resulting warning
+ * @details: (out): (optional): (transfer none): A GstStructure containing extra details about the error
+ *
+ * Parse the given error @msg and extract the corresponding #GError warning
+ *
+ * Since: 1.20
+ */
+void
+gst_play_message_parse_warning (GstMessage * msg, GError * error,
+    GstStructure ** details)
+{
+  PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_WARNING, G_TYPE_ERROR, error);
+  PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_WARNING, GST_TYPE_STRUCTURE,
+      details);
+}
+
+/**
+ * gst_play_message_parse_video_dimensions_changed:
+ * @msg: A #GstMessage
+ * @width: (out): (optional): the resulting video width
+ * @height: (out): (optional): the resulting video height
+ *
+ * Parse the given @msg and extract the corresponding video dimensions
+ *
+ * Since: 1.20
+ */
+void
+gst_play_message_parse_video_dimensions_changed (GstMessage * msg,
+    guint * width, guint * height)
+{
+  PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_VIDEO_WIDTH,
+      G_TYPE_UINT, width);
+  PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_VIDEO_HEIGHT,
+      G_TYPE_UINT, height);
+}
+
+/**
+ * gst_play_message_parse_media_info_updated:
+ * @msg: A #GstMessage
+ * @info: (out): (optional): (transfer full): the resulting media info
+ *
+ * Parse the given @msg and extract the corresponding media information
+ *
+ * Since: 1.20
+ */
+void
+gst_play_message_parse_media_info_updated (GstMessage * msg,
+    GstPlayMediaInfo ** info)
+{
+  PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_MEDIA_INFO,
+      GST_TYPE_PLAY_MEDIA_INFO, info);
+}
+
+/**
+ * gst_play_message_parse_volume_changed:
+ * @msg: A #GstMessage
+ * @volume: (out): (optional): the resulting audio volume
+ *
+ * Parse the given @msg and extract the corresponding audio volume
+ *
+ * Since: 1.20
+ */
+void
+gst_play_message_parse_volume_changed (GstMessage * msg, gdouble * volume)
+{
+  PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_VOLUME, G_TYPE_DOUBLE,
+      volume);
+}
+
+/**
+ * gst_play_message_parse_muted_changed:
+ * @msg: A #GstMessage
+ * @muted: (out): (optional): the resulting audio muted state
+ *
+ * Parse the given @msg and extract the corresponding audio muted state
+ *
+ * Since: 1.20
+ */
+void
+gst_play_message_parse_muted_changed (GstMessage * msg, gboolean * muted)
+{
+  PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_IS_MUTED, G_TYPE_BOOLEAN,
+      muted);
+}
diff --git a/gst-libs/gst/play/gstplay.h b/gst-libs/gst/play/gstplay.h
new file mode 100644 (file)
index 0000000..ce0d6c5
--- /dev/null
@@ -0,0 +1,442 @@
+/* GStreamer
+ *
+ * Copyright (C) 2014-2015 Sebastian Dröge <sebastian@centricular.com>
+ * Copyright (C) 2019-2020 Stephan Hesse <stephan@emliri.com>
+ * Copyright (C) 2020 Philippe Normand <philn@igalia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_PLAY_H__
+#define __GST_PLAY_H__
+
+#include <gst/gst.h>
+#include <gst/video/video.h>
+#include <gst/play/play-prelude.h>
+#include <gst/play/gstplay-types.h>
+#include <gst/play/gstplay-video-renderer.h>
+#include <gst/play/gstplay-media-info.h>
+
+G_BEGIN_DECLS
+
+GST_PLAY_API
+GType        gst_play_state_get_type                (void);
+
+/**
+ * GST_TYPE_PLAY_STATE:
+ * Since: 1.20
+ */
+#define      GST_TYPE_PLAY_STATE                    (gst_play_state_get_type ())
+
+GST_PLAY_API
+GType        gst_play_message_get_type              (void);
+
+/**
+ * GST_TYPE_PLAY_MESSAGE:
+ * Since: 1.20
+ */
+#define      GST_TYPE_PLAY_MESSAGE                  (gst_play_message_get_type ())
+
+/**
+ * GstPlayState:
+ * @GST_PLAY_STATE_STOPPED: the play is stopped.
+ * @GST_PLAY_STATE_BUFFERING: the play is buffering.
+ * @GST_PLAY_STATE_PAUSED: the play is paused.
+ * @GST_PLAY_STATE_PLAYING: the play is currently playing a
+ * stream.
+ *
+ * Since: 1.20
+ */
+typedef enum
+{
+  GST_PLAY_STATE_STOPPED,
+  GST_PLAY_STATE_BUFFERING,
+  GST_PLAY_STATE_PAUSED,
+  GST_PLAY_STATE_PLAYING
+} GstPlayState;
+
+/**
+ * GstPlayMessage:
+ * @GST_PLAY_MESSAGE_URI_LOADED: Source element was initalized for set URI
+ * @GST_PLAY_MESSAGE_POSITION_UPDATED: Sink position changed
+ * @GST_PLAY_MESSAGE_DURATION_CHANGED: Duration of stream changed
+ * @GST_PLAY_MESSAGE_STATE_CHANGED: State changed, see #GstPlayState
+ * @GST_PLAY_MESSAGE_BUFFERING: Pipeline is in buffering state, message contains the percentage value of the decoding buffer
+ * @GST_PLAY_MESSAGE_END_OF_STREAM: Sink has received EOS
+ * @GST_PLAY_MESSAGE_ERROR: Message contains an error
+ * @GST_PLAY_MESSAGE_WARNING: Message contains an error
+ * @GST_PLAY_MESSAGE_VIDEO_DIMENSIONS_CHANGED: Video sink received format in different dimensions than before
+ * @GST_PLAY_MESSAGE_MEDIA_INFO_UPDATED: A media-info property has changed, message contains current #GstPlayMediaInfo
+ * @GST_PLAY_MESSAGE_VOLUME_CHANGED: The volume of the audio ouput has changed
+ * @GST_PLAY_MESSAGE_MUTE_CHANGED: Audio muting flag has been toggled
+ * @GST_PLAY_MESSAGE_SEEK_DONE: Any pending seeking operation has been completed
+ *
+ * Since: 1.20
+ *
+ * Types of messages that will be posted on the play API bus.
+ *
+ * See also #gst_play_get_message_bus()
+ *
+ */
+typedef enum
+{
+  GST_PLAY_MESSAGE_URI_LOADED,
+  GST_PLAY_MESSAGE_POSITION_UPDATED,
+  GST_PLAY_MESSAGE_DURATION_CHANGED,
+  GST_PLAY_MESSAGE_STATE_CHANGED,
+  GST_PLAY_MESSAGE_BUFFERING,
+  GST_PLAY_MESSAGE_END_OF_STREAM,
+  GST_PLAY_MESSAGE_ERROR,
+  GST_PLAY_MESSAGE_WARNING,
+  GST_PLAY_MESSAGE_VIDEO_DIMENSIONS_CHANGED,
+  GST_PLAY_MESSAGE_MEDIA_INFO_UPDATED,
+  GST_PLAY_MESSAGE_VOLUME_CHANGED,
+  GST_PLAY_MESSAGE_MUTE_CHANGED,
+  GST_PLAY_MESSAGE_SEEK_DONE
+} GstPlayMessage;
+
+GST_PLAY_API
+const gchar *gst_play_state_get_name                (GstPlayState state);
+
+GST_PLAY_API
+const gchar *gst_play_message_get_name              (GstPlayMessage message_type);
+
+GST_PLAY_API
+GQuark       gst_play_error_quark                   (void);
+
+GST_PLAY_API
+GType        gst_play_error_get_type                (void);
+
+/**
+ * GST_PLAY_ERROR:
+ *
+ * Since: 1.20
+ */
+#define      GST_PLAY_ERROR                         (gst_play_error_quark ())
+
+/**
+ * GST_TYPE_PLAY_ERROR:
+ *
+ * Since: 1.20
+ */
+#define      GST_TYPE_PLAY_ERROR                    (gst_play_error_get_type ())
+
+/**
+ * GstPlayError:
+ * @GST_PLAY_ERROR_FAILED: generic error.
+ *
+ * Since: 1.20
+ */
+typedef enum {
+  GST_PLAY_ERROR_FAILED = 0
+} GstPlayError;
+
+GST_PLAY_API
+const gchar *gst_play_error_get_name                (GstPlayError error);
+
+GST_PLAY_API
+GType gst_play_color_balance_type_get_type          (void);
+
+/**
+ * GST_TYPE_PLAY_COLOR_BALANCE_TYPE:
+ *
+ * Since: 1.20
+ */
+#define GST_TYPE_PLAY_COLOR_BALANCE_TYPE            (gst_play_color_balance_type_get_type ())
+
+/**
+ * GstPlayColorBalanceType:
+ * @GST_PLAY_COLOR_BALANCE_BRIGHTNESS: brightness or black level.
+ * @GST_PLAY_COLOR_BALANCE_CONTRAST: contrast or luma gain.
+ * @GST_PLAY_COLOR_BALANCE_SATURATION: color saturation or chroma
+ * gain.
+ * @GST_PLAY_COLOR_BALANCE_HUE: hue or color balance.
+ *
+ * Since: 1.20
+ */
+typedef enum
+{
+  GST_PLAY_COLOR_BALANCE_BRIGHTNESS,
+  GST_PLAY_COLOR_BALANCE_CONTRAST,
+  GST_PLAY_COLOR_BALANCE_SATURATION,
+  GST_PLAY_COLOR_BALANCE_HUE,
+} GstPlayColorBalanceType;
+
+GST_PLAY_API
+const gchar *gst_play_color_balance_type_get_name   (GstPlayColorBalanceType type);
+
+#define GST_TYPE_PLAY             (gst_play_get_type ())
+#define GST_IS_PLAY(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PLAY))
+#define GST_IS_PLAY_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PLAY))
+#define GST_PLAY_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_PLAY, GstPlayClass))
+#define GST_PLAY(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PLAY, GstPlay))
+#define GST_PLAY_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_PLAY, GstPlayClass))
+
+/**
+ * GST_PLAY_CAST:
+ * Since: 1.20
+ */
+#define GST_PLAY_CAST(obj)        ((GstPlay*)(obj))
+
+#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstPlay, gst_object_unref)
+#endif
+
+GST_PLAY_API
+GType        gst_play_get_type                      (void);
+
+GST_PLAY_API
+GstPlay *    gst_play_new                           (GstPlayVideoRenderer * video_renderer);
+
+GST_PLAY_API
+GstBus *     gst_play_get_message_bus               (GstPlay    * play);
+
+GST_PLAY_API
+void         gst_play_play                          (GstPlay    * play);
+
+GST_PLAY_API
+void         gst_play_pause                         (GstPlay    * play);
+
+GST_PLAY_API
+void         gst_play_stop                          (GstPlay    * play);
+
+GST_PLAY_API
+void         gst_play_seek                          (GstPlay    * play,
+                                                     GstClockTime   position);
+
+GST_PLAY_API
+void         gst_play_set_rate                      (GstPlay    * play,
+                                                     gdouble        rate);
+
+GST_PLAY_API
+gdouble      gst_play_get_rate                      (GstPlay    * play);
+
+GST_PLAY_API
+gchar *      gst_play_get_uri                       (GstPlay    * play);
+
+GST_PLAY_API
+void         gst_play_set_uri                       (GstPlay    * play,
+                                                     const gchar  * uri);
+
+GST_PLAY_API
+gchar *      gst_play_get_subtitle_uri              (GstPlay    * play);
+
+GST_PLAY_API
+void         gst_play_set_subtitle_uri              (GstPlay    * play,
+                                                     const gchar *uri);
+
+GST_PLAY_API
+GstClockTime gst_play_get_position                  (GstPlay    * play);
+
+GST_PLAY_API
+GstClockTime gst_play_get_duration                  (GstPlay    * play);
+
+GST_PLAY_API
+gdouble      gst_play_get_volume                    (GstPlay    * play);
+
+GST_PLAY_API
+void         gst_play_set_volume                    (GstPlay    * play,
+                                                     gdouble        val);
+
+GST_PLAY_API
+gboolean     gst_play_get_mute                      (GstPlay    * play);
+
+GST_PLAY_API
+void         gst_play_set_mute                      (GstPlay    * play,
+                                                     gboolean       val);
+
+GST_PLAY_API
+GstElement * gst_play_get_pipeline                  (GstPlay    * play);
+
+GST_PLAY_API
+void         gst_play_set_video_track_enabled       (GstPlay    * play,
+                                                     gboolean enabled);
+
+GST_PLAY_API
+void         gst_play_set_audio_track_enabled       (GstPlay    * play,
+                                                     gboolean enabled);
+
+GST_PLAY_API
+void         gst_play_set_subtitle_track_enabled    (GstPlay    * play,
+                                                     gboolean enabled);
+
+GST_PLAY_API
+gboolean     gst_play_set_audio_track               (GstPlay    *play,
+                                                     gint stream_index);
+
+GST_PLAY_API
+gboolean     gst_play_set_video_track               (GstPlay    *play,
+                                                     gint stream_index);
+
+GST_PLAY_API
+gboolean     gst_play_set_subtitle_track            (GstPlay    *play,
+                                                     gint stream_index);
+
+GST_PLAY_API
+GstPlayMediaInfo *    gst_play_get_media_info     (GstPlay * play);
+
+GST_PLAY_API
+GstPlayAudioInfo *    gst_play_get_current_audio_track (GstPlay * play);
+
+GST_PLAY_API
+GstPlayVideoInfo *    gst_play_get_current_video_track (GstPlay * play);
+
+GST_PLAY_API
+GstPlaySubtitleInfo * gst_play_get_current_subtitle_track (GstPlay * play);
+
+GST_PLAY_API
+gboolean     gst_play_set_visualization             (GstPlay    * play,
+                                                     const gchar *name);
+
+GST_PLAY_API
+void         gst_play_set_visualization_enabled     (GstPlay    * play,
+                                                     gboolean enabled);
+
+GST_PLAY_API
+gchar *      gst_play_get_current_visualization     (GstPlay    * play);
+
+GST_PLAY_API
+gboolean     gst_play_has_color_balance             (GstPlay    * play);
+
+GST_PLAY_API
+void         gst_play_set_color_balance             (GstPlay    * play,
+                                                     GstPlayColorBalanceType type,
+                                                     gdouble value);
+
+GST_PLAY_API
+gdouble      gst_play_get_color_balance             (GstPlay    * play,
+                                                     GstPlayColorBalanceType type);
+
+
+GST_PLAY_API
+GstVideoMultiviewFramePacking gst_play_get_multiview_mode (GstPlay    * play);
+
+GST_PLAY_API
+void                     gst_play_set_multiview_mode (GstPlay    * play,
+                                                      GstVideoMultiviewFramePacking mode);
+
+GST_PLAY_API
+GstVideoMultiviewFlags  gst_play_get_multiview_flags  (GstPlay  * play);
+
+GST_PLAY_API
+void                    gst_play_set_multiview_flags  (GstPlay  * play,
+                                                       GstVideoMultiviewFlags flags);
+
+GST_PLAY_API
+gint64       gst_play_get_audio_video_offset        (GstPlay    * play);
+
+GST_PLAY_API
+void         gst_play_set_audio_video_offset        (GstPlay    * play,
+                                                     gint64 offset);
+
+GST_PLAY_API
+gint64       gst_play_get_subtitle_video_offset        (GstPlay    * play);
+
+GST_PLAY_API
+void         gst_play_set_subtitle_video_offset        (GstPlay    * play,
+                                                        gint64 offset);
+
+GST_PLAY_API
+gboolean       gst_play_set_config                  (GstPlay * play,
+                                                     GstStructure * config);
+
+GST_PLAY_API
+GstStructure * gst_play_get_config                  (GstPlay * play);
+
+/* helpers for configuring the config structure */
+
+GST_PLAY_API
+void           gst_play_config_set_user_agent       (GstStructure * config,
+                                                     const gchar * agent);
+
+GST_PLAY_API
+gchar *        gst_play_config_get_user_agent       (const GstStructure * config);
+
+GST_PLAY_API
+void           gst_play_config_set_position_update_interval  (GstStructure * config,
+                                                              guint          interval);
+
+GST_PLAY_API
+guint          gst_play_config_get_position_update_interval  (const GstStructure * config);
+
+GST_PLAY_API
+void           gst_play_config_set_seek_accurate (GstStructure * config, gboolean accurate);
+
+GST_PLAY_API
+gboolean       gst_play_config_get_seek_accurate (const GstStructure * config);
+
+/**
+ * GstPlaySnapshotFormat:
+ * @GST_PLAY_THUMBNAIL_RAW_NATIVE: raw native format.
+ * @GST_PLAY_THUMBNAIL_RAW_xRGB: raw xRGB format.
+ * @GST_PLAY_THUMBNAIL_RAW_BGRx: raw BGRx format.
+ * @GST_PLAY_THUMBNAIL_JPG: jpeg format.
+ * @GST_PLAY_THUMBNAIL_PNG: png format.
+ *
+ * Since: 1.20
+ */
+typedef enum
+{
+  GST_PLAY_THUMBNAIL_RAW_NATIVE = 0,
+  GST_PLAY_THUMBNAIL_RAW_xRGB,
+  GST_PLAY_THUMBNAIL_RAW_BGRx,
+  GST_PLAY_THUMBNAIL_JPG,
+  GST_PLAY_THUMBNAIL_PNG
+} GstPlaySnapshotFormat;
+
+GST_PLAY_API
+GstSample * gst_play_get_video_snapshot (GstPlay * play,
+    GstPlaySnapshotFormat format, const GstStructure * config);
+
+GST_PLAY_API
+gboolean       gst_play_is_play_message                        (GstMessage *msg);
+
+GST_PLAY_API
+void           gst_play_message_parse_type                       (GstMessage *msg, GstPlayMessage *type);
+
+GST_PLAY_API
+void           gst_play_message_parse_duration_updated           (GstMessage *msg, GstClockTime *duration);
+
+GST_PLAY_API
+void           gst_play_message_parse_position_updated           (GstMessage *msg, GstClockTime *position);
+
+GST_PLAY_API
+void           gst_play_message_parse_state_changed              (GstMessage *msg, GstPlayState *state);
+
+GST_PLAY_API
+void           gst_play_message_parse_buffering_percent          (GstMessage *msg, guint *percent);
+
+GST_PLAY_API
+void           gst_play_message_parse_error                      (GstMessage *msg, GError *error, GstStructure **details);
+
+GST_PLAY_API
+void           gst_play_message_parse_warning                    (GstMessage *msg, GError *error, GstStructure **details);
+
+GST_PLAY_API
+void           gst_play_message_parse_video_dimensions_changed   (GstMessage *msg, guint *width, guint *height);
+
+GST_PLAY_API
+void           gst_play_message_parse_media_info_updated         (GstMessage *msg, GstPlayMediaInfo **info);
+
+GST_PLAY_API
+void           gst_play_message_parse_volume_changed             (GstMessage *msg, gdouble *volume);
+
+GST_PLAY_API
+void           gst_play_message_parse_muted_changed              (GstMessage *msg, gboolean *muted);
+
+G_END_DECLS
+
+#endif /* __GST_PLAY_H__ */
diff --git a/gst-libs/gst/play/meson.build b/gst-libs/gst/play/meson.build
new file mode 100644 (file)
index 0000000..99535ba
--- /dev/null
@@ -0,0 +1,69 @@
+gstplay_sources = [
+  'gstplay.c',
+  'gstplay-signal-adapter.c',
+  'gstplay-video-renderer.c',
+  'gstplay-media-info.c',
+  'gstplay-video-overlay-video-renderer.c',
+  'gstplay-visualization.c',
+]
+
+gstplay_headers = [
+  'play.h',
+  'play-prelude.h',
+  'gstplay.h',
+  'gstplay-types.h',
+  'gstplay-signal-adapter.h',
+  'gstplay-video-renderer.h',
+  'gstplay-media-info.h',
+  'gstplay-video-overlay-video-renderer.h',
+  'gstplay-visualization.h',
+]
+
+install_headers(gstplay_headers, subdir : 'gstreamer-' + api_version + '/gst/play/')
+
+gstplay = library('gstplay-' + api_version,
+  gstplay_sources,
+  c_args : gst_plugins_bad_args + ['-DBUILDING_GST_PLAY'],
+  include_directories : [configinc, libsinc],
+  version : libversion,
+  soversion : soversion,
+  darwin_versions : osxversion,
+  install : true,
+  dependencies : [gstbase_dep, gstvideo_dep, gstaudio_dep,
+                  gsttag_dep, gstpbutils_dep],
+)
+
+pkgconfig.generate(gstplay,
+  libraries : [gst_dep, gstvideo_dep],
+  variables : pkgconfig_variables,
+  subdirs : pkgconfig_subdirs,
+  name : 'gstreamer-play-1.0',
+  description : 'GStreamer Player convenience library',
+)
+
+gen_sources = []
+if build_gir
+  play_gir = gnome.generate_gir(gstplay,
+    sources : gstplay_sources + gstplay_headers,
+    namespace : 'GstPlay',
+    nsversion : api_version,
+    identifier_prefix : 'Gst',
+    symbol_prefix : 'gst',
+    export_packages : 'gstreamer-play-1.0',
+    includes : ['Gst-1.0', 'GstPbutils-1.0', 'GstBase-1.0', 'GstVideo-1.0',
+      'GstAudio-1.0', 'GstTag-1.0'],
+    install : true,
+    extra_args : gir_init_section + ['-DGST_USE_UNSTABLE_API'] + ['--c-include=gst/play/play.h'],
+    dependencies : [gstbase_dep, gstvideo_dep, gstaudio_dep,
+                  gsttag_dep, gstpbutils_dep]
+  )
+  gen_sources += play_gir
+endif
+
+gstplay_dep = declare_dependency(link_with : gstplay,
+  include_directories : [libsinc],
+  sources: gen_sources,
+  dependencies : [gstbase_dep, gstvideo_dep, gstaudio_dep,
+                  gsttag_dep, gstpbutils_dep])
+
+meson.override_dependency('gstreamer-play-1.0', gstplay_dep)
diff --git a/gst-libs/gst/play/play-prelude.h b/gst-libs/gst/play/play-prelude.h
new file mode 100644 (file)
index 0000000..466903e
--- /dev/null
@@ -0,0 +1,43 @@
+/* GStreamer Play Library
+ * Copyright (C) 2018 GStreamer developers
+ *
+ * play-prelude.h: prelude include header for gst-play library
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_PLAY_PRELUDE_H__
+#define __GST_PLAY_PRELUDE_H__
+
+#include <gst/gst.h>
+
+#ifndef GST_PLAY_API
+# ifdef BUILDING_GST_PLAY
+#  define GST_PLAY_API GST_API_EXPORT         /* from config.h */
+# else
+#  define GST_PLAY_API GST_API_IMPORT
+# endif
+#endif
+
+#ifndef GST_DISABLE_DEPRECATED
+#define GST_PLAY_DEPRECATED GST_PLAY_API
+#define GST_PLAY_DEPRECATED_FOR(f) GST_PLAY_API
+#else
+#define GST_PLAY_DEPRECATED G_DEPRECATED GST_PLAY_API
+#define GST_PLAY_DEPRECATED_FOR(f) G_DEPRECATED_FOR(f) GST_PLAY_API
+#endif
+
+#endif /* __GST_PLAY_PRELUDE_H__ */
diff --git a/gst-libs/gst/play/play.h b/gst-libs/gst/play/play.h
new file mode 100644 (file)
index 0000000..b1044a5
--- /dev/null
@@ -0,0 +1,31 @@
+/* GStreamer
+ *
+ * Copyright (C) 2014 Sebastian Dröge <sebastian@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PLAY_H__
+#define __PLAY_H__
+
+#include <gst/play/play-prelude.h>
+#include <gst/play/gstplay.h>
+#include <gst/play/gstplay-media-info.h>
+#include <gst/play/gstplay-video-overlay-video-renderer.h>
+#include <gst/play/gstplay-visualization.h>
+#include <gst/play/gstplay-signal-adapter.h>
+
+#endif /* __PLAY_H__ */