From 0b2d698b1ebbe5bdba9812ec9fc0e0f3ba6c2d8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 22 Dec 2015 14:24:00 +0100 Subject: [PATCH] player: Import GstPlayer playback convenience API Based on https://github.com/sdroege/gst-player commit 9ce6ae0dbb8eeeefaf794cfae80e279a03cc598d --- configure.ac | 1 + gst-libs/gst/Makefile.am | 6 +- gst-libs/gst/player/Makefile.am | 101 + ...tplayer-g-main-context-signal-dispatcher.c | 212 + ...tplayer-g-main-context-signal-dispatcher.h | 48 + .../gst/player/gstplayer-media-info-private.h | 125 + gst-libs/gst/player/gstplayer-media-info.c | 775 ++++ gst-libs/gst/player/gstplayer-media-info.h | 192 + .../gstplayer-signal-dispatcher-private.h | 34 + .../gst/player/gstplayer-signal-dispatcher.c | 57 + .../gst/player/gstplayer-signal-dispatcher.h | 53 + gst-libs/gst/player/gstplayer-types.h | 35 + .../gstplayer-video-overlay-video-renderer.c | 224 + .../gstplayer-video-overlay-video-renderer.h | 49 + .../player/gstplayer-video-renderer-private.h | 33 + .../gst/player/gstplayer-video-renderer.c | 49 + .../gst/player/gstplayer-video-renderer.h | 47 + gst-libs/gst/player/gstplayer-visualization.c | 178 + gst-libs/gst/player/gstplayer-visualization.h | 52 + gst-libs/gst/player/gstplayer.c | 3801 +++++++++++++++++ gst-libs/gst/player/gstplayer.h | 184 + gst-libs/gst/player/player.h | 30 + win32/common/libgstplayer.def | 92 + 23 files changed, 6375 insertions(+), 3 deletions(-) create mode 100644 gst-libs/gst/player/Makefile.am create mode 100644 gst-libs/gst/player/gstplayer-g-main-context-signal-dispatcher.c create mode 100644 gst-libs/gst/player/gstplayer-g-main-context-signal-dispatcher.h create mode 100644 gst-libs/gst/player/gstplayer-media-info-private.h create mode 100644 gst-libs/gst/player/gstplayer-media-info.c create mode 100644 gst-libs/gst/player/gstplayer-media-info.h create mode 100644 gst-libs/gst/player/gstplayer-signal-dispatcher-private.h create mode 100644 gst-libs/gst/player/gstplayer-signal-dispatcher.c create mode 100644 gst-libs/gst/player/gstplayer-signal-dispatcher.h create mode 100644 gst-libs/gst/player/gstplayer-types.h create mode 100644 gst-libs/gst/player/gstplayer-video-overlay-video-renderer.c create mode 100644 gst-libs/gst/player/gstplayer-video-overlay-video-renderer.h create mode 100644 gst-libs/gst/player/gstplayer-video-renderer-private.h create mode 100644 gst-libs/gst/player/gstplayer-video-renderer.c create mode 100644 gst-libs/gst/player/gstplayer-video-renderer.h create mode 100644 gst-libs/gst/player/gstplayer-visualization.c create mode 100644 gst-libs/gst/player/gstplayer-visualization.h create mode 100644 gst-libs/gst/player/gstplayer.c create mode 100644 gst-libs/gst/player/gstplayer.h create mode 100644 gst-libs/gst/player/player.h create mode 100644 win32/common/libgstplayer.def diff --git a/configure.ac b/configure.ac index 95329714ea..0a29ec60bb 100644 --- a/configure.ac +++ b/configure.ac @@ -3550,6 +3550,7 @@ gst-libs/gst/mpegts/Makefile gst-libs/gst/uridownloader/Makefile gst-libs/gst/wayland/Makefile gst-libs/gst/base/Makefile +gst-libs/gst/player/Makefile gst-libs/gst/video/Makefile sys/Makefile sys/dshowdecwrapper/Makefile diff --git a/gst-libs/gst/Makefile.am b/gst-libs/gst/Makefile.am index a7380f84d9..732978d5a5 100644 --- a/gst-libs/gst/Makefile.am +++ b/gst-libs/gst/Makefile.am @@ -11,11 +11,11 @@ WAYLAND_DIR=wayland endif SUBDIRS = uridownloader adaptivedemux interfaces basecamerabinsrc codecparsers \ - insertbin mpegts base video $(GL_DIR) $(WAYLAND_DIR) + insertbin mpegts base video player $(GL_DIR) $(WAYLAND_DIR) noinst_HEADERS = gst-i18n-plugin.h gettext.h glib-compat-private.h DIST_SUBDIRS = uridownloader adaptivedemux interfaces gl basecamerabinsrc \ - codecparsers insertbin mpegts wayland base video + codecparsers insertbin mpegts wayland base video player #dependencies video: base @@ -24,7 +24,7 @@ adaptivedemux: uridownloader INDEPENDENT_SUBDIRS = \ interfaces basecamerabinsrc codecparsers insertbin uridownloader \ - mpegts base $(GL_DIR) $(WAYLAND_DIR) + mpegts base player $(GL_DIR) $(WAYLAND_DIR) .PHONY: independent-subdirs $(INDEPENDENT_SUBDIRS) diff --git a/gst-libs/gst/player/Makefile.am b/gst-libs/gst/player/Makefile.am new file mode 100644 index 0000000000..60e8ecfdd3 --- /dev/null +++ b/gst-libs/gst/player/Makefile.am @@ -0,0 +1,101 @@ +lib_LTLIBRARIES = libgstplayer-@GST_API_VERSION@.la + +libgstplayer_@GST_API_VERSION@_la_SOURCES = \ + gstplayer.c \ + gstplayer-signal-dispatcher.c \ + gstplayer-video-renderer.c \ + gstplayer-media-info.c \ + gstplayer-g-main-context-signal-dispatcher.c \ + gstplayer-video-overlay-video-renderer.c \ + gstplayer-visualization.c + +libgstplayer_@GST_API_VERSION@_la_CFLAGS = \ + -I$(top_srcdir)/gst-libs \ + -I$(top_builddir)/gst-libs \ + $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_BASE_CFLAGS) \ + $(GST_CFLAGS) + +libgstplayer_@GST_API_VERSION@_la_LDFLAGS = \ + $(GST_LIB_LDFLAGS) \ + $(GST_ALL_LDFLAGS) \ + $(GST_LT_LDFLAGS) + +libgstplayer_@GST_API_VERSION@_la_LIBADD = \ + $(GST_PLUGINS_BASE_LIBS) \ + -lgstvideo-$(GST_API_VERSION) \ + -lgstaudio-$(GST_API_VERSION) \ + -lgsttag-$(GST_API_VERSION) \ + -lgstpbutils-$(GST_API_VERSION) \ + $(GST_LIBS) \ + $(LIBM) + +libgstplayerdir = $(includedir)/gstreamer-@GST_API_VERSION@/gst/player + +noinst_HEADERS = \ + gstplayer-signal-dispatcher-private.h \ + gstplayer-video-renderer-private.h \ + gstplayer-media-info-private.h + +libgstplayer_HEADERS = \ + player.h \ + gstplayer.h \ + gstplayer-types.h \ + gstplayer-signal-dispatcher.h \ + gstplayer-video-renderer.h \ + gstplayer-media-info.h \ + gstplayer-g-main-context-signal-dispatcher.h \ + gstplayer-video-overlay-video-renderer.h \ + gstplayer-visualization.h + +CLEANFILES = + +if HAVE_INTROSPECTION +BUILT_GIRSOURCES = GstPlayer-@GST_API_VERSION@.gir + +gir_headers=$(patsubst %,$(srcdir)/%, $(libgstplayer_HEADERS)) +gir_sources=$(patsubst %,$(srcdir)/%, $(libgstplayer_@GST_API_VERSION@_la_SOURCES)) + +GstPlayer-@GST_API_VERSION@.gir: $(INTROSPECTION_SCANNER) libgstplayer-@GST_API_VERSION@.la + $(AM_V_GEN)$(INTROSPECTION_SCANNER) -v --namespace GstPlayer \ + --nsversion=@GST_API_VERSION@ \ + --warn-all \ + --strip-prefix=Gst \ + -I$(top_srcdir)/gst-libs \ + -I$(top_builddir)/gst-libs \ + --c-include "gst/player/player.h" \ + --add-include-path=$(top_builddir)/gst-libs \ + --add-include-path=`$(PKG_CONFIG) --variable=girdir gstreamer-1.0` \ + --library-path=$(top_builddir)/gst-libs \ + --library=libgstplayer-@GST_API_VERSION@.la \ + --include=Gst-@GST_API_VERSION@ \ + --libtool="${LIBTOOL}" \ + --pkg gstreamer-@GST_API_VERSION@ \ + --pkg gstreamer-audio-@GST_API_VERSION@ \ + --pkg gstreamer-video-@GST_API_VERSION@ \ + --pkg gstreamer-tag-@GST_API_VERSION@ \ + --pkg gstreamer-pbutils-@GST_API_VERSION@ \ + --pkg-export gstreamer-player-@GST_API_VERSION@ \ + --add-init-section="gst_init(NULL,NULL);" \ + --output $@ \ + $(gir_headers) \ + $(gir_sources) + +# INTROSPECTION_GIRDIR/INTROSPECTION_TYPELIBDIR aren't the right place to +# install anything - we need to install inside our prefix. +girdir = $(datadir)/gir-1.0 +gir_DATA = $(BUILT_GIRSOURCES) + +typelibsdir = $(libdir)/girepository-1.0/ + +typelibs_DATA = $(BUILT_GIRSOURCES:.gir=.typelib) + +%.typelib: %.gir $(INTROSPECTION_COMPILER) + $(AM_V_GEN)$(INTROSPECTION_COMPILER) \ + --includedir=$(srcdir)/gst-libs \ + --includedir=$(builddir)/gst-libs \ + --includedir=`$(PKG_CONFIG) --variable=girdir gstreamer-1.0` \ + $(INTROSPECTION_COMPILER_OPTS) $< -o $(@F) + +CLEANFILES += $(BUILT_GIRSOURCES) $(typelibs_DATA) +endif diff --git a/gst-libs/gst/player/gstplayer-g-main-context-signal-dispatcher.c b/gst-libs/gst/player/gstplayer-g-main-context-signal-dispatcher.c new file mode 100644 index 0000000000..c1f57d07a5 --- /dev/null +++ b/gst-libs/gst/player/gstplayer-g-main-context-signal-dispatcher.c @@ -0,0 +1,212 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * + * 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:gstplayer-gmaincontextsignaldispatcher + * @short_description: Player GLib MainContext dispatcher + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstplayer-g-main-context-signal-dispatcher.h" + +struct _GstPlayerGMainContextSignalDispatcher +{ + GObject parent; + GMainContext *application_context; +}; + +struct _GstPlayerGMainContextSignalDispatcherClass +{ + GObjectClass parent_class; +}; + +static void + gst_player_g_main_context_signal_dispatcher_interface_init + (GstPlayerSignalDispatcherInterface * iface); + +enum +{ + G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_0, + G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_APPLICATION_CONTEXT, + G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_LAST +}; + +G_DEFINE_TYPE_WITH_CODE (GstPlayerGMainContextSignalDispatcher, + gst_player_g_main_context_signal_dispatcher, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GST_TYPE_PLAYER_SIGNAL_DISPATCHER, + gst_player_g_main_context_signal_dispatcher_interface_init)); + +static GParamSpec + * g_main_context_signal_dispatcher_param_specs + [G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_LAST] = { NULL, }; + +static void +gst_player_g_main_context_signal_dispatcher_finalize (GObject * object) +{ + GstPlayerGMainContextSignalDispatcher *self = + GST_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER (object); + + if (self->application_context) + g_main_context_unref (self->application_context); + + G_OBJECT_CLASS + (gst_player_g_main_context_signal_dispatcher_parent_class)->finalize + (object); +} + +static void +gst_player_g_main_context_signal_dispatcher_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstPlayerGMainContextSignalDispatcher *self = + GST_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER (object); + + switch (prop_id) { + case G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_APPLICATION_CONTEXT: + self->application_context = g_value_dup_boxed (value); + if (!self->application_context) + self->application_context = g_main_context_ref_thread_default (); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_player_g_main_context_signal_dispatcher_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstPlayerGMainContextSignalDispatcher *self = + GST_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER (object); + + switch (prop_id) { + case G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_APPLICATION_CONTEXT: + g_value_set_boxed (value, self->application_context); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void + gst_player_g_main_context_signal_dispatcher_class_init + (GstPlayerGMainContextSignalDispatcherClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = + gst_player_g_main_context_signal_dispatcher_finalize; + gobject_class->set_property = + gst_player_g_main_context_signal_dispatcher_set_property; + gobject_class->get_property = + gst_player_g_main_context_signal_dispatcher_get_property; + + g_main_context_signal_dispatcher_param_specs + [G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_APPLICATION_CONTEXT] = + g_param_spec_boxed ("application-context", "Application Context", + "Application GMainContext to dispatch signals to", G_TYPE_MAIN_CONTEXT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, + G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_LAST, + g_main_context_signal_dispatcher_param_specs); +} + +static void + gst_player_g_main_context_signal_dispatcher_init + (G_GNUC_UNUSED GstPlayerGMainContextSignalDispatcher * self) +{ +} + +typedef struct +{ + void (*emitter) (gpointer data); + gpointer data; + GDestroyNotify destroy; +} GMainContextSignalDispatcherData; + +static gboolean +g_main_context_signal_dispatcher_dispatch_gsourcefunc (gpointer user_data) +{ + GMainContextSignalDispatcherData *data = user_data; + + data->emitter (data->data); + + return G_SOURCE_REMOVE; +} + +static void +g_main_context_signal_dispatcher_dispatch_destroy (gpointer user_data) +{ + GMainContextSignalDispatcherData *data = user_data; + + if (data->destroy) + data->destroy (data->data); + g_free (data); +} + +static void +gst_player_g_main_context_signal_dispatcher_dispatch (GstPlayerSignalDispatcher + * iface, G_GNUC_UNUSED GstPlayer * player, void (*emitter) (gpointer data), + gpointer data, GDestroyNotify destroy) +{ + GstPlayerGMainContextSignalDispatcher *self = + GST_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER (iface); + GMainContextSignalDispatcherData *gsourcefunc_data = + g_new (GMainContextSignalDispatcherData, 1); + + gsourcefunc_data->emitter = emitter; + gsourcefunc_data->data = data; + gsourcefunc_data->destroy = destroy; + + g_main_context_invoke_full (self->application_context, + G_PRIORITY_DEFAULT, g_main_context_signal_dispatcher_dispatch_gsourcefunc, + gsourcefunc_data, g_main_context_signal_dispatcher_dispatch_destroy); +} + +static void + gst_player_g_main_context_signal_dispatcher_interface_init + (GstPlayerSignalDispatcherInterface * iface) +{ + iface->dispatch = gst_player_g_main_context_signal_dispatcher_dispatch; +} + +/** + * gst_player_g_main_context_signal_dispatcher_new: + * @application_context: (allow-none): GMainContext to use or %NULL + * + * Creates a new GstPlayerSignalDispatcher that uses @application_context, + * or the thread default one if %NULL is used. See gst_player_new_full(). + * + * Returns: (transfer full): the new GstPlayerSignalDispatcher + */ +GstPlayerSignalDispatcher * +gst_player_g_main_context_signal_dispatcher_new (GMainContext * + application_context) +{ + return g_object_new (GST_TYPE_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER, + "application-context", application_context, NULL); +} diff --git a/gst-libs/gst/player/gstplayer-g-main-context-signal-dispatcher.h b/gst-libs/gst/player/gstplayer-g-main-context-signal-dispatcher.h new file mode 100644 index 0000000000..431032b6b4 --- /dev/null +++ b/gst-libs/gst/player/gstplayer-g-main-context-signal-dispatcher.h @@ -0,0 +1,48 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * + * 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_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_H__ +#define __GST_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef struct _GstPlayerGMainContextSignalDispatcher + GstPlayerGMainContextSignalDispatcher; +typedef struct _GstPlayerGMainContextSignalDispatcherClass + GstPlayerGMainContextSignalDispatcherClass; + +#define GST_TYPE_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER (gst_player_g_main_context_signal_dispatcher_get_type ()) +#define GST_IS_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER)) +#define GST_IS_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER)) +#define GST_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER, GstPlayerGMainContextSignalDispatcherClass)) +#define GST_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER, GstPlayerGMainContextSignalDispatcher)) +#define GST_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER, GstPlayerGMainContextSignalDispatcherClass)) +#define GST_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_CAST(obj) ((GstPlayerGMainContextSignalDispatcher*)(obj)) + +GType gst_player_g_main_context_signal_dispatcher_get_type (void); + +GstPlayerSignalDispatcher * gst_player_g_main_context_signal_dispatcher_new (GMainContext * application_context); + +G_END_DECLS + +#endif /* __GST_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_H__ */ diff --git a/gst-libs/gst/player/gstplayer-media-info-private.h b/gst-libs/gst/player/gstplayer-media-info-private.h new file mode 100644 index 0000000000..b8757be46a --- /dev/null +++ b/gst-libs/gst/player/gstplayer-media-info-private.h @@ -0,0 +1,125 @@ +/* GStreamer + * + * Copyright (C) 2015 Brijesh Singh + * + * 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 "gstplayer-media-info.h" + +#ifndef __GST_PLAYER_MEDIA_INFO_PRIVATE_H__ +#define __GST_PLAYER_MEDIA_INFO_PRIVATE_H__ + +struct _GstPlayerStreamInfo +{ + GObject parent; + + gchar *codec; + + GstCaps *caps; + gint stream_index; + GstTagList *tags; +}; + +struct _GstPlayerStreamInfoClass +{ + GObjectClass parent_class; +}; + +struct _GstPlayerSubtitleInfo +{ + GstPlayerStreamInfo parent; + + gchar *language; +}; + +struct _GstPlayerSubtitleInfoClass +{ + GstPlayerStreamInfoClass parent_class; +}; + +struct _GstPlayerAudioInfo +{ + GstPlayerStreamInfo parent; + + gint channels; + gint sample_rate; + + guint bitrate; + guint max_bitrate; + + gchar *language; +}; + +struct _GstPlayerAudioInfoClass +{ + GstPlayerStreamInfoClass parent_class; +}; + +struct _GstPlayerVideoInfo +{ + GstPlayerStreamInfo parent; + + gint width; + gint height; + gint framerate_num; + gint framerate_denom; + gint par_num; + gint par_denom; + + guint bitrate; + guint max_bitrate; +}; + +struct _GstPlayerVideoInfoClass +{ + GstPlayerStreamInfoClass parent_class; +}; + +struct _GstPlayerMediaInfo +{ + GObject parent; + + gchar *uri; + gchar *title; + gchar *container; + gboolean seekable; + GstTagList *tags; + GstSample *image_sample; + + GList *stream_list; + GList *audio_stream_list; + GList *video_stream_list; + GList *subtitle_stream_list; + + GstClockTime duration; +}; + +struct _GstPlayerMediaInfoClass +{ + GObjectClass parent_class; +}; + +G_GNUC_INTERNAL GstPlayerMediaInfo* gst_player_media_info_new + (const gchar *uri); +G_GNUC_INTERNAL GstPlayerMediaInfo* gst_player_media_info_copy + (GstPlayerMediaInfo *ref); +G_GNUC_INTERNAL GstPlayerStreamInfo* gst_player_stream_info_new + (gint stream_index, GType type); +G_GNUC_INTERNAL GstPlayerStreamInfo* gst_player_stream_info_copy + (GstPlayerStreamInfo *ref); + +#endif /* __GST_PLAYER_MEDIA_INFO_PRIVATE_H__ */ diff --git a/gst-libs/gst/player/gstplayer-media-info.c b/gst-libs/gst/player/gstplayer-media-info.c new file mode 100644 index 0000000000..a42b61151b --- /dev/null +++ b/gst-libs/gst/player/gstplayer-media-info.c @@ -0,0 +1,775 @@ +/* GStreamer + * + * Copyright (C) 2015 Brijesh Singh + * + * 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:gstplayer-mediainfo + * @short_description: Player Media Information + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstplayer-media-info.h" +#include "gstplayer-media-info-private.h" + +/* Per-stream information */ +G_DEFINE_ABSTRACT_TYPE (GstPlayerStreamInfo, gst_player_stream_info, + G_TYPE_OBJECT); + +static void +gst_player_stream_info_init (GstPlayerStreamInfo * sinfo) +{ + sinfo->stream_index = -1; +} + +static void +gst_player_stream_info_finalize (GObject * object) +{ + GstPlayerStreamInfo *sinfo = GST_PLAYER_STREAM_INFO (object); + + g_free (sinfo->codec); + + if (sinfo->caps) + gst_caps_unref (sinfo->caps); + + if (sinfo->tags) + gst_tag_list_unref (sinfo->tags); + + G_OBJECT_CLASS (gst_player_stream_info_parent_class)->finalize (object); +} + +static void +gst_player_stream_info_class_init (GstPlayerStreamInfoClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->finalize = gst_player_stream_info_finalize; +} + +/** + * gst_player_stream_info_get_index: + * @info: a #GstPlayerStreamInfo + * + * Function to get stream index from #GstPlayerStreamInfo instance. + * + * Returns: the stream index of this stream. + */ +gint +gst_player_stream_info_get_index (const GstPlayerStreamInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_STREAM_INFO (info), -1); + + return info->stream_index; +} + +/** + * gst_player_stream_info_get_stream_type: + * @info: a #GstPlayerStreamInfo + * + * Function to return human readable name for the stream type + * of the given @info (ex: "audio", "video", "subtitle") + * + * Returns: a human readable name + */ +const gchar * +gst_player_stream_info_get_stream_type (const GstPlayerStreamInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_STREAM_INFO (info), NULL); + + if (GST_IS_PLAYER_VIDEO_INFO (info)) + return "video"; + else if (GST_IS_PLAYER_AUDIO_INFO (info)) + return "audio"; + else + return "subtitle"; +} + +/** + * gst_player_stream_info_get_tags: + * @info: a #GstPlayerStreamInfo + * + * Returns: (transfer none): the tags contained in this stream. + */ +GstTagList * +gst_player_stream_info_get_tags (const GstPlayerStreamInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_STREAM_INFO (info), NULL); + + return info->tags; +} + +/** + * gst_player_stream_info_get_codec: + * @info: a #GstPlayerStreamInfo + * + * A string describing codec used in #GstPlayerStreamInfo. + * + * Returns: codec string or NULL on unknown. + */ +const gchar * +gst_player_stream_info_get_codec (const GstPlayerStreamInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_STREAM_INFO (info), NULL); + + return info->codec; +} + +/** + * gst_player_stream_info_get_caps: + * @info: a #GstPlayerStreamInfo + * + * Returns: (transfer none): the #GstCaps of the stream. + */ +GstCaps * +gst_player_stream_info_get_caps (const GstPlayerStreamInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_STREAM_INFO (info), NULL); + + return info->caps; +} + +/* Video information */ +G_DEFINE_TYPE (GstPlayerVideoInfo, gst_player_video_info, + GST_TYPE_PLAYER_STREAM_INFO); + +static void +gst_player_video_info_init (GstPlayerVideoInfo * 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_player_video_info_class_init (G_GNUC_UNUSED GstPlayerVideoInfoClass * klass) +{ + /* nothing to do here */ +} + +/** + * gst_player_video_info_get_width: + * @info: a #GstPlayerVideoInfo + * + * Returns: the width of video in #GstPlayerVideoInfo. + */ +gint +gst_player_video_info_get_width (const GstPlayerVideoInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_VIDEO_INFO (info), -1); + + return info->width; +} + +/** + * gst_player_video_info_get_height: + * @info: a #GstPlayerVideoInfo + * + * Returns: the height of video in #GstPlayerVideoInfo. + */ +gint +gst_player_video_info_get_height (const GstPlayerVideoInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_VIDEO_INFO (info), -1); + + return info->height; +} + +/** + * gst_player_video_info_get_framerate_num: + * @info: a #GstPlayerVideoInfo + * + */ +void +gst_player_video_info_get_framerate (const GstPlayerVideoInfo * info, + gint * fps_n, gint * fps_d) +{ + g_return_if_fail (GST_IS_PLAYER_VIDEO_INFO (info)); + + *fps_n = info->framerate_num; + *fps_d = info->framerate_denom; +} + +/** + * gst_player_video_info_get_pixel_aspect_ratio: + * @info: a #GstPlayerVideoInfo + * @par_n: (out): numerator + * @par_d: (out): denominator + * + * Returns the pixel aspect ratio in @par_n and @par_d + * + */ +void +gst_player_video_info_get_pixel_aspect_ratio (const GstPlayerVideoInfo * info, + guint * par_n, guint * par_d) +{ + g_return_if_fail (GST_IS_PLAYER_VIDEO_INFO (info)); + + *par_n = info->par_num; + *par_d = info->par_denom; +} + +/** + * gst_player_video_info_get_bitrate: + * @info: a #GstPlayerVideoInfo + * + * Returns: the current bitrate of video in #GstPlayerVideoInfo. + */ +gint +gst_player_video_info_get_bitrate (const GstPlayerVideoInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_VIDEO_INFO (info), -1); + + return info->bitrate; +} + +/** + * gst_player_video_info_get_max_bitrate: + * @info: a #GstPlayerVideoInfo + * + * Returns: the maximum bitrate of video in #GstPlayerVideoInfo. + */ +gint +gst_player_video_info_get_max_bitrate (const GstPlayerVideoInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_VIDEO_INFO (info), -1); + + return info->max_bitrate; +} + +/* Audio information */ +G_DEFINE_TYPE (GstPlayerAudioInfo, gst_player_audio_info, + GST_TYPE_PLAYER_STREAM_INFO); + +static void +gst_player_audio_info_init (GstPlayerAudioInfo * info) +{ + info->channels = 0; + info->sample_rate = 0; + info->bitrate = -1; + info->max_bitrate = -1; +} + +static void +gst_player_audio_info_finalize (GObject * object) +{ + GstPlayerAudioInfo *info = GST_PLAYER_AUDIO_INFO (object); + + g_free (info->language); + + G_OBJECT_CLASS (gst_player_audio_info_parent_class)->finalize (object); +} + +static void +gst_player_audio_info_class_init (GstPlayerAudioInfoClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->finalize = gst_player_audio_info_finalize; +} + +/** + * gst_player_audio_info_get_language: + * @info: a #GstPlayerAudioInfo + * + * Returns: the language of the stream, or NULL if unknown. + */ +const gchar * +gst_player_audio_info_get_language (const GstPlayerAudioInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_AUDIO_INFO (info), NULL); + + return info->language; +} + +/** + * gst_player_audio_info_get_channels: + * @info: a #GstPlayerAudioInfo + * + * Returns: the number of audio channels in #GstPlayerAudioInfo. + */ +gint +gst_player_audio_info_get_channels (const GstPlayerAudioInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_AUDIO_INFO (info), 0); + + return info->channels; +} + +/** + * gst_player_audio_info_get_sample_rate: + * @info: a #GstPlayerAudioInfo + * + * Returns: the audio sample rate in #GstPlayerAudioInfo. + */ +gint +gst_player_audio_info_get_sample_rate (const GstPlayerAudioInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_AUDIO_INFO (info), 0); + + return info->sample_rate; +} + +/** + * gst_player_audio_info_get_bitrate: + * @info: a #GstPlayerAudioInfo + * + * Returns: the audio bitrate in #GstPlayerAudioInfo. + */ +gint +gst_player_audio_info_get_bitrate (const GstPlayerAudioInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_AUDIO_INFO (info), -1); + + return info->bitrate; +} + +/** + * gst_player_audio_info_get_max_bitrate: + * @info: a #GstPlayerAudioInfo + * + * Returns: the audio maximum bitrate in #GstPlayerAudioInfo. + */ +gint +gst_player_audio_info_get_max_bitrate (const GstPlayerAudioInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_AUDIO_INFO (info), -1); + + return info->max_bitrate; +} + +/* Subtitle information */ +G_DEFINE_TYPE (GstPlayerSubtitleInfo, gst_player_subtitle_info, + GST_TYPE_PLAYER_STREAM_INFO); + +static void +gst_player_subtitle_info_init (G_GNUC_UNUSED GstPlayerSubtitleInfo * info) +{ + /* nothing to do */ +} + +static void +gst_player_subtitle_info_finalize (GObject * object) +{ + GstPlayerSubtitleInfo *info = GST_PLAYER_SUBTITLE_INFO (object); + + g_free (info->language); + + G_OBJECT_CLASS (gst_player_subtitle_info_parent_class)->finalize (object); +} + +static void +gst_player_subtitle_info_class_init (GstPlayerSubtitleInfoClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->finalize = gst_player_subtitle_info_finalize; +} + +/** + * gst_player_subtitle_info_get_language: + * @info: a #GstPlayerSubtitleInfo + * + * Returns: the language of the stream, or NULL if unknown. + */ +const gchar * +gst_player_subtitle_info_get_language (const GstPlayerSubtitleInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_SUBTITLE_INFO (info), NULL); + + return info->language; +} + +/* Global media information */ +G_DEFINE_TYPE (GstPlayerMediaInfo, gst_player_media_info, G_TYPE_OBJECT); + +static void +gst_player_media_info_init (GstPlayerMediaInfo * info) +{ + info->duration = -1; + info->seekable = FALSE; +} + +static void +gst_player_media_info_finalize (GObject * object) +{ + GstPlayerMediaInfo *info = GST_PLAYER_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_player_media_info_parent_class)->finalize (object); +} + +static void +gst_player_media_info_class_init (GstPlayerMediaInfoClass * klass) +{ + GObjectClass *oclass = (GObjectClass *) klass; + + oclass->finalize = gst_player_media_info_finalize; +} + +static GstPlayerVideoInfo * +gst_player_video_info_new (void) +{ + return g_object_new (GST_TYPE_PLAYER_VIDEO_INFO, NULL); +} + +static GstPlayerAudioInfo * +gst_player_audio_info_new (void) +{ + return g_object_new (GST_TYPE_PLAYER_AUDIO_INFO, NULL); +} + +static GstPlayerSubtitleInfo * +gst_player_subtitle_info_new (void) +{ + return g_object_new (GST_TYPE_PLAYER_SUBTITLE_INFO, NULL); +} + +static GstPlayerStreamInfo * +gst_player_video_info_copy (GstPlayerVideoInfo * ref) +{ + GstPlayerVideoInfo *ret; + + ret = gst_player_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 (GstPlayerStreamInfo *) ret; +} + +static GstPlayerStreamInfo * +gst_player_audio_info_copy (GstPlayerAudioInfo * ref) +{ + GstPlayerAudioInfo *ret; + + ret = gst_player_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 (GstPlayerStreamInfo *) ret; +} + +static GstPlayerStreamInfo * +gst_player_subtitle_info_copy (GstPlayerSubtitleInfo * ref) +{ + GstPlayerSubtitleInfo *ret; + + ret = gst_player_subtitle_info_new (); + if (ref->language) + ret->language = g_strdup (ref->language); + + return (GstPlayerStreamInfo *) ret; +} + +GstPlayerStreamInfo * +gst_player_stream_info_copy (GstPlayerStreamInfo * ref) +{ + GstPlayerStreamInfo *info = NULL; + + if (!ref) + return NULL; + + if (GST_IS_PLAYER_VIDEO_INFO (ref)) + info = gst_player_video_info_copy ((GstPlayerVideoInfo *) ref); + else if (GST_IS_PLAYER_AUDIO_INFO (ref)) + info = gst_player_audio_info_copy ((GstPlayerAudioInfo *) ref); + else + info = gst_player_subtitle_info_copy ((GstPlayerSubtitleInfo *) 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); + + return info; +} + +GstPlayerMediaInfo * +gst_player_media_info_copy (GstPlayerMediaInfo * ref) +{ + GList *l; + GstPlayerMediaInfo *info; + + if (!ref) + return NULL; + + info = gst_player_media_info_new (ref->uri); + info->duration = ref->duration; + info->seekable = ref->seekable; + 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) { + GstPlayerStreamInfo *s; + + s = gst_player_stream_info_copy ((GstPlayerStreamInfo *) l->data); + info->stream_list = g_list_append (info->stream_list, s); + + if (GST_IS_PLAYER_AUDIO_INFO (s)) + info->audio_stream_list = g_list_append (info->audio_stream_list, s); + else if (GST_IS_PLAYER_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; +} + +GstPlayerStreamInfo * +gst_player_stream_info_new (gint stream_index, GType type) +{ + GstPlayerStreamInfo *info = NULL; + + if (type == GST_TYPE_PLAYER_AUDIO_INFO) + info = (GstPlayerStreamInfo *) gst_player_audio_info_new (); + else if (type == GST_TYPE_PLAYER_VIDEO_INFO) + info = (GstPlayerStreamInfo *) gst_player_video_info_new (); + else + info = (GstPlayerStreamInfo *) gst_player_subtitle_info_new (); + + info->stream_index = stream_index; + + return info; +} + +GstPlayerMediaInfo * +gst_player_media_info_new (const gchar * uri) +{ + GstPlayerMediaInfo *info; + + g_return_val_if_fail (uri != NULL, NULL); + + info = g_object_new (GST_TYPE_PLAYER_MEDIA_INFO, NULL); + info->uri = g_strdup (uri); + + return info; +} + +/** + * gst_player_media_info_get_uri: + * @info: a #GstPlayerMediaInfo + * + * Returns: the URI associated with #GstPlayerMediaInfo. + */ +const gchar * +gst_player_media_info_get_uri (const GstPlayerMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_MEDIA_INFO (info), NULL); + + return info->uri; +} + +/** + * gst_player_media_info_is_seekable: + * @info: a #GstPlayerMediaInfo + * + * Returns: %TRUE if the media is seekable. + */ +gboolean +gst_player_media_info_is_seekable (const GstPlayerMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_MEDIA_INFO (info), FALSE); + + return info->seekable; +} + +/** + * gst_player_media_info_get_stream_list: + * @info: a #GstPlayerMediaInfo + * + * Returns: (transfer none) (element-type GstPlayerStreamInfo): A #GList of + * matching #GstPlayerStreamInfo. + */ +GList * +gst_player_media_info_get_stream_list (const GstPlayerMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_MEDIA_INFO (info), NULL); + + return info->stream_list; +} + +/** + * gst_player_get_video_streams: + * @info: a #GstPlayerMediaInfo + * + * Returns: (transfer none) (element-type GstPlayerVideoInfo): A #GList of + * matching #GstPlayerVideoInfo. + */ +GList * +gst_player_get_video_streams (const GstPlayerMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_MEDIA_INFO (info), NULL); + + return info->video_stream_list; +} + +/** + * gst_player_get_subtitle_streams: + * @info: a #GstPlayerMediaInfo + * + * Returns: (transfer none) (element-type GstPlayerSubtitleInfo): A #GList of + * matching #GstPlayerSubtitleInfo. + */ +GList * +gst_player_get_subtitle_streams (const GstPlayerMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_MEDIA_INFO (info), NULL); + + return info->subtitle_stream_list; +} + +/** + * gst_player_get_audio_streams: + * @info: a #GstPlayerMediaInfo + * + * Returns: (transfer none) (element-type GstPlayerAudioInfo): A #GList of + * matching #GstPlayerAudioInfo. + */ +GList * +gst_player_get_audio_streams (const GstPlayerMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_MEDIA_INFO (info), NULL); + + return info->audio_stream_list; +} + +/** + * gst_player_media_info_get_duration: + * @info: a #GstPlayerMediaInfo + * + * Returns: duration of the media. + */ +GstClockTime +gst_player_media_info_get_duration (const GstPlayerMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_MEDIA_INFO (info), -1); + + return info->duration; +} + +/** + * gst_player_media_info_get_tags: + * @info: a #GstPlayerMediaInfo + * + * Returns: (transfer none): the tags contained in media info. + */ +GstTagList * +gst_player_media_info_get_tags (const GstPlayerMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_MEDIA_INFO (info), NULL); + + return info->tags; +} + +/** + * gst_player_media_info_get_title: + * @info: a #GstPlayerMediaInfo + * + * Returns: the media title. + */ +const gchar * +gst_player_media_info_get_title (const GstPlayerMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_MEDIA_INFO (info), NULL); + + return info->title; +} + +/** + * gst_player_media_info_get_container_format: + * @info: a #GstPlayerMediaInfo + * + * Returns: the container format. + */ +const gchar * +gst_player_media_info_get_container_format (const GstPlayerMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_MEDIA_INFO (info), NULL); + + return info->container; +} + +/** + * gst_player_media_info_get_image_sample: + * @info: a #GstPlayerMediaInfo + * + * 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. + */ +GstSample * +gst_player_media_info_get_image_sample (const GstPlayerMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_PLAYER_MEDIA_INFO (info), NULL); + + return info->image_sample; +} diff --git a/gst-libs/gst/player/gstplayer-media-info.h b/gst-libs/gst/player/gstplayer-media-info.h new file mode 100644 index 0000000000..a458b75ec8 --- /dev/null +++ b/gst-libs/gst/player/gstplayer-media-info.h @@ -0,0 +1,192 @@ +/* GStreamer + * + * Copyright (C) 2015 Brijesh Singh + * + * 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_PLAYER_MEDIA_INFO_H__ +#define __GST_PLAYER_MEDIA_INFO_H__ + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_PLAYER_STREAM_INFO \ + (gst_player_stream_info_get_type ()) +#define GST_PLAYER_STREAM_INFO(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PLAYER_STREAM_INFO,GstPlayerStreamInfo)) +#define GST_PLAYER_STREAM_INFO_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PLAYER_STREAM_INFO,GstPlayerStreamInfo)) +#define GST_IS_PLAYER_STREAM_INFO(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PLAYER_STREAM_INFO)) +#define GST_IS_PLAYER_STREAM_INFO_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PLAYER_STREAM_INFO)) + +/** + * GstPlayerStreamInfo: + * + * Base structure for information concering a media stream. Depending on + * the stream type, one can find more media-specific information in + * #GstPlayerVideoInfo, #GstPlayerAudioInfo, #GstPlayerSubtitleInfo. + */ +typedef struct _GstPlayerStreamInfo GstPlayerStreamInfo; +typedef struct _GstPlayerStreamInfoClass GstPlayerStreamInfoClass; +GType gst_player_stream_info_get_type (void); + +gint gst_player_stream_info_get_index + (const GstPlayerStreamInfo *info); +const gchar* gst_player_stream_info_get_stream_type + (const GstPlayerStreamInfo *info); +GstTagList* gst_player_stream_info_get_tags + (const GstPlayerStreamInfo *info); +GstCaps* gst_player_stream_info_get_caps + (const GstPlayerStreamInfo *info); +const gchar* gst_player_stream_info_get_codec + (const GstPlayerStreamInfo *info); + +#define GST_TYPE_PLAYER_VIDEO_INFO \ + (gst_player_video_info_get_type ()) +#define GST_PLAYER_VIDEO_INFO(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PLAYER_VIDEO_INFO, GstPlayerVideoInfo)) +#define GST_PLAYER_VIDEO_INFO_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((obj),GST_TYPE_PLAYER_VIDEO_INFO, GstPlayerVideoInfoClass)) +#define GST_IS_PLAYER_VIDEO_INFO(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PLAYER_VIDEO_INFO)) +#define GST_IS_PLAYER_VIDEO_INFO_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((obj),GST_TYPE_PLAYER_VIDEO_INFO)) + +/** + * GstPlayerVideoInfo: + * + * #GstPlayerStreamInfo specific to video streams. + */ +typedef struct _GstPlayerVideoInfo GstPlayerVideoInfo; +typedef struct _GstPlayerVideoInfoClass GstPlayerVideoInfoClass; +GType gst_player_video_info_get_type (void); + +gint gst_player_video_info_get_bitrate + (const GstPlayerVideoInfo* info); +gint gst_player_video_info_get_max_bitrate + (const GstPlayerVideoInfo* info); +gint gst_player_video_info_get_width + (const GstPlayerVideoInfo* info); +gint gst_player_video_info_get_height + (const GstPlayerVideoInfo* info); +void gst_player_video_info_get_framerate + (const GstPlayerVideoInfo* info, gint *fps_n, gint *fps_d); +void gst_player_video_info_get_pixel_aspect_ratio + (const GstPlayerVideoInfo* info, guint *par_n, guint *par_d); + +#define GST_TYPE_PLAYER_AUDIO_INFO \ + (gst_player_audio_info_get_type ()) +#define GST_PLAYER_AUDIO_INFO(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PLAYER_AUDIO_INFO, GstPlayerAudioInfo)) +#define GST_PLAYER_AUDIO_INFO_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PLAYER_AUDIO_INFO, GstPlayerAudioInfoClass)) +#define GST_IS_PLAYER_AUDIO_INFO(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PLAYER_AUDIO_INFO)) +#define GST_IS_PLAYER_AUDIO_INFO_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PLAYER_AUDIO_INFO)) + +/** + * GstPlayerAudioInfo: + * + * #GstPlayerStreamInfo specific to audio streams. + */ +typedef struct _GstPlayerAudioInfo GstPlayerAudioInfo; +typedef struct _GstPlayerAudioInfoClass GstPlayerAudioInfoClass; +GType gst_player_audio_info_get_type (void); + +gint gst_player_audio_info_get_channels + (const GstPlayerAudioInfo* info); +gint gst_player_audio_info_get_sample_rate + (const GstPlayerAudioInfo* info); +gint gst_player_audio_info_get_bitrate + (const GstPlayerAudioInfo* info); +gint gst_player_audio_info_get_max_bitrate + (const GstPlayerAudioInfo* info); +const gchar* gst_player_audio_info_get_language + (const GstPlayerAudioInfo* info); + +#define GST_TYPE_PLAYER_SUBTITLE_INFO \ + (gst_player_subtitle_info_get_type ()) +#define GST_PLAYER_SUBTITLE_INFO(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PLAYER_SUBTITLE_INFO, GstPlayerSubtitleInfo)) +#define GST_PLAYER_SUBTITLE_INFO_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PLAYER_SUBTITLE_INFO,GstPlayerSubtitleInfoClass)) +#define GST_IS_PLAYER_SUBTITLE_INFO(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PLAYER_SUBTITLE_INFO)) +#define GST_IS_PLAYER_SUBTITLE_INFO_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PLAYER_SUBTITLE_INFO)) + +/** + * GstPlayerSubtitleInfo: + * + * #GstPlayerStreamInfo specific to subtitle streams. + */ +typedef struct _GstPlayerSubtitleInfo GstPlayerSubtitleInfo; +typedef struct _GstPlayerSubtitleInfoClass GstPlayerSubtitleInfoClass; +GType gst_player_subtitle_info_get_type (void); + +const gchar* gst_player_subtitle_info_get_language + (const GstPlayerSubtitleInfo* info); + +#define GST_TYPE_PLAYER_MEDIA_INFO \ + (gst_player_media_info_get_type()) +#define GST_PLAYER_MEDIA_INFO(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PLAYER_MEDIA_INFO,GstPlayerMediaInfo)) +#define GST_PLAYER_MEDIA_INFO_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PLAYER_MEDIA_INFO,GstPlayerMediaInfoClass)) +#define GST_IS_PLAYER_MEDIA_INFO(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PLAYER_MEDIA_INFO)) +#define GST_IS_PLAYER_MEDIA_INFO_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PLAYER_MEDIA_INFO)) + +/** + * GstPlayerMediaInfo: + * + * Structure containing the media information of a URI. + */ +typedef struct _GstPlayerMediaInfo GstPlayerMediaInfo; +typedef struct _GstPlayerMediaInfoClass GstPlayerMediaInfoClass; +GType gst_player_media_info_get_type (void); + +const gchar* gst_player_media_info_get_uri + (const GstPlayerMediaInfo *info); +gboolean gst_player_media_info_is_seekable + (const GstPlayerMediaInfo *info); +GstClockTime gst_player_media_info_get_duration + (const GstPlayerMediaInfo *info); +GList* gst_player_media_info_get_stream_list + (const GstPlayerMediaInfo *info); +GList* gst_player_get_video_streams + (const GstPlayerMediaInfo *info); +GList* gst_player_get_audio_streams + (const GstPlayerMediaInfo *info); +GList* gst_player_get_subtitle_streams + (const GstPlayerMediaInfo *info); +GstTagList* gst_player_media_info_get_tags + (const GstPlayerMediaInfo *info); +const gchar* gst_player_media_info_get_title + (const GstPlayerMediaInfo *info); +const gchar* gst_player_media_info_get_container_format + (const GstPlayerMediaInfo *info); +GstSample* gst_player_media_info_get_image_sample + (const GstPlayerMediaInfo *info); +G_END_DECLS + +#endif /* __GST_PLAYER_MEDIA_INFO_H */ diff --git a/gst-libs/gst/player/gstplayer-signal-dispatcher-private.h b/gst-libs/gst/player/gstplayer-signal-dispatcher-private.h new file mode 100644 index 0000000000..7399161756 --- /dev/null +++ b/gst-libs/gst/player/gstplayer-signal-dispatcher-private.h @@ -0,0 +1,34 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * + * 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_PLAYER_SIGNAL_DISPATCHER_PRIVATE_H__ +#define __GST_PLAYER_SIGNAL_DISPATCHER_PRIVATE_H__ + +#include + +G_BEGIN_DECLS + +G_GNUC_INTERNAL void gst_player_signal_dispatcher_dispatch (GstPlayerSignalDispatcher * self, + GstPlayer * player, GstPlayerSignalDispatcherFunc emitter, gpointer data, + GDestroyNotify destroy); + +G_END_DECLS + +#endif /* __GST_PLAYER_SIGNAL_DISPATCHER_PRIVATE_H__ */ diff --git a/gst-libs/gst/player/gstplayer-signal-dispatcher.c b/gst-libs/gst/player/gstplayer-signal-dispatcher.c new file mode 100644 index 0000000000..33e7b739dd --- /dev/null +++ b/gst-libs/gst/player/gstplayer-signal-dispatcher.c @@ -0,0 +1,57 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * + * 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 "gstplayer-signal-dispatcher.h" +#include "gstplayer-signal-dispatcher-private.h" + +G_DEFINE_INTERFACE (GstPlayerSignalDispatcher, gst_player_signal_dispatcher, + G_TYPE_OBJECT); + +static void +gst_player_signal_dispatcher_default_init (G_GNUC_UNUSED + GstPlayerSignalDispatcherInterface * iface) +{ + +} + +void +gst_player_signal_dispatcher_dispatch (GstPlayerSignalDispatcher * self, + GstPlayer * player, GstPlayerSignalDispatcherFunc emitter, gpointer data, + GDestroyNotify destroy) +{ + GstPlayerSignalDispatcherInterface *iface; + + if (!self) { + emitter (data); + if (destroy) + destroy (data); + return; + } + + g_return_if_fail (GST_IS_PLAYER_SIGNAL_DISPATCHER (self)); + iface = GST_PLAYER_SIGNAL_DISPATCHER_GET_INTERFACE (self); + g_return_if_fail (iface->dispatch != NULL); + + iface->dispatch (self, player, emitter, data, destroy); +} diff --git a/gst-libs/gst/player/gstplayer-signal-dispatcher.h b/gst-libs/gst/player/gstplayer-signal-dispatcher.h new file mode 100644 index 0000000000..76338b2182 --- /dev/null +++ b/gst-libs/gst/player/gstplayer-signal-dispatcher.h @@ -0,0 +1,53 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * + * 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_PLAYER_SIGNAL_DISPATCHER_H__ +#define __GST_PLAYER_SIGNAL_DISPATCHER_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef struct _GstPlayerSignalDispatcher GstPlayerSignalDispatcher; +typedef struct _GstPlayerSignalDispatcherInterface GstPlayerSignalDispatcherInterface; + +#define GST_TYPE_PLAYER_SIGNAL_DISPATCHER (gst_player_signal_dispatcher_get_type ()) +#define GST_PLAYER_SIGNAL_DISPATCHER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PLAYER_SIGNAL_DISPATCHER, GstPlayerSignalDispatcher)) +#define GST_IS_PLAYER_SIGNAL_DISPATCHER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PLAYER_SIGNAL_DISPATCHER)) +#define GST_PLAYER_SIGNAL_DISPATCHER_GET_INTERFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GST_TYPE_PLAYER_SIGNAL_DISPATCHER, GstPlayerSignalDispatcherInterface)) + +typedef void (*GstPlayerSignalDispatcherFunc) (gpointer data); + +struct _GstPlayerSignalDispatcherInterface { + GTypeInterface parent_iface; + + void (*dispatch) (GstPlayerSignalDispatcher * self, + GstPlayer * player, + GstPlayerSignalDispatcherFunc emitter, + gpointer data, + GDestroyNotify destroy); +}; + +GType gst_player_signal_dispatcher_get_type (void); + +G_END_DECLS + +#endif /* __GST_PLAYER_SIGNAL_DISPATCHER_H__ */ diff --git a/gst-libs/gst/player/gstplayer-types.h b/gst-libs/gst/player/gstplayer-types.h new file mode 100644 index 0000000000..f6627e6d1f --- /dev/null +++ b/gst-libs/gst/player/gstplayer-types.h @@ -0,0 +1,35 @@ +/* GStreamer + * + * Copyright (C) 2015 Sebastian Dröge + * + * 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_PLAYER_TYPES_H__ +#define __GST_PLAYER_TYPES_H__ + +#include + +G_BEGIN_DECLS + +typedef struct _GstPlayer GstPlayer; +typedef struct _GstPlayerClass GstPlayerClass; + +G_END_DECLS + +#endif /* __GST_PLAYER_TYPES_H__ */ + + diff --git a/gst-libs/gst/player/gstplayer-video-overlay-video-renderer.c b/gst-libs/gst/player/gstplayer-video-overlay-video-renderer.c new file mode 100644 index 0000000000..ab8894fe1a --- /dev/null +++ b/gst-libs/gst/player/gstplayer-video-overlay-video-renderer.c @@ -0,0 +1,224 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * + * 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:gstplayer-videooverlayvideorenderer + * @short_description: Player Video Overlay Video Renderer + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstplayer-video-overlay-video-renderer.h" +#include "gstplayer.h" + +#include + +struct _GstPlayerVideoOverlayVideoRenderer +{ + GObject parent; + + GstVideoOverlay *video_overlay; + gpointer window_handle; +}; + +struct _GstPlayerVideoOverlayVideoRendererClass +{ + GObjectClass parent_class; +}; + +static void + gst_player_video_overlay_video_renderer_interface_init + (GstPlayerVideoRendererInterface * iface); + +enum +{ + VIDEO_OVERLAY_VIDEO_RENDERER_PROP_0, + VIDEO_OVERLAY_VIDEO_RENDERER_PROP_WINDOW_HANDLE, + VIDEO_OVERLAY_VIDEO_RENDERER_PROP_LAST +}; + +G_DEFINE_TYPE_WITH_CODE (GstPlayerVideoOverlayVideoRenderer, + gst_player_video_overlay_video_renderer, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GST_TYPE_PLAYER_VIDEO_RENDERER, + gst_player_video_overlay_video_renderer_interface_init)); + +static GParamSpec + * video_overlay_video_renderer_param_specs + [VIDEO_OVERLAY_VIDEO_RENDERER_PROP_LAST] = { NULL, }; + +static void +gst_player_video_overlay_video_renderer_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstPlayerVideoOverlayVideoRenderer *self = + GST_PLAYER_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; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_player_video_overlay_video_renderer_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstPlayerVideoOverlayVideoRenderer *self = + GST_PLAYER_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; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_player_video_overlay_video_renderer_finalize (GObject * object) +{ + GstPlayerVideoOverlayVideoRenderer *self = + GST_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER (object); + + if (self->video_overlay) + gst_object_unref (self->video_overlay); + + G_OBJECT_CLASS + (gst_player_video_overlay_video_renderer_parent_class)->finalize (object); +} + +static void + gst_player_video_overlay_video_renderer_class_init + (GstPlayerVideoOverlayVideoRendererClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->set_property = + gst_player_video_overlay_video_renderer_set_property; + gobject_class->get_property = + gst_player_video_overlay_video_renderer_get_property; + gobject_class->finalize = gst_player_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); + + g_object_class_install_properties (gobject_class, + VIDEO_OVERLAY_VIDEO_RENDERER_PROP_LAST, + video_overlay_video_renderer_param_specs); +} + +static void + gst_player_video_overlay_video_renderer_init + (G_GNUC_UNUSED GstPlayerVideoOverlayVideoRenderer * self) +{ +} + +static GstElement *gst_player_video_overlay_video_renderer_create_video_sink + (GstPlayerVideoRenderer * iface, GstPlayer * player) +{ + GstElement *video_overlay; + GstPlayerVideoOverlayVideoRenderer *self = + GST_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER (iface); + + if (self->video_overlay) + gst_object_unref (self->video_overlay); + + video_overlay = gst_player_get_pipeline (player); + 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); + + return NULL; +} + +static void + gst_player_video_overlay_video_renderer_interface_init + (GstPlayerVideoRendererInterface * iface) +{ + iface->create_video_sink = + gst_player_video_overlay_video_renderer_create_video_sink; +} + +/** + * gst_player_video_overlay_video_renderer_new: + * @window_handle: (allow-none): Window handle to use or %NULL + * + * Returns: (transfer full): + */ +GstPlayerVideoRenderer * +gst_player_video_overlay_video_renderer_new (gpointer window_handle) +{ + return g_object_new (GST_TYPE_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER, + "window-handle", window_handle, NULL); +} + +/** + * gst_player_video_overlay_video_renderer_set_window_handle: + * @self: #GstPlayerVideoRenderer instance + * @window_handle: handle referencing to the platform specific window + * + * Sets the platform specific window handle into which the video + * should be rendered + **/ +void gst_player_video_overlay_video_renderer_set_window_handle + (GstPlayerVideoOverlayVideoRenderer * self, gpointer window_handle) +{ + g_return_if_fail (GST_IS_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER (self)); + + g_object_set (self, "window-handle", window_handle, NULL); +} + +/** + * gst_player_video_overlay_video_renderer_get_window_handle: + * @self: #GstPlayerVideoRenderer instance + * + * Returns: (transfer none): The currently set, platform specific window + * handle + */ +gpointer + gst_player_video_overlay_video_renderer_get_window_handle + (GstPlayerVideoOverlayVideoRenderer * self) { + gpointer window_handle; + + g_return_val_if_fail (GST_IS_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER (self), + NULL); + + g_object_get (self, "window-handle", &window_handle, NULL); + + return window_handle; +} diff --git a/gst-libs/gst/player/gstplayer-video-overlay-video-renderer.h b/gst-libs/gst/player/gstplayer-video-overlay-video-renderer.h new file mode 100644 index 0000000000..72a420b43d --- /dev/null +++ b/gst-libs/gst/player/gstplayer-video-overlay-video-renderer.h @@ -0,0 +1,49 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * + * 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_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER_H__ +#define __GST_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef struct _GstPlayerVideoOverlayVideoRenderer + GstPlayerVideoOverlayVideoRenderer; +typedef struct _GstPlayerVideoOverlayVideoRendererClass + GstPlayerVideoOverlayVideoRendererClass; + +#define GST_TYPE_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER (gst_player_video_overlay_video_renderer_get_type ()) +#define GST_IS_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER)) +#define GST_IS_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER)) +#define GST_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER, GstPlayerVideoOverlayVideoRendererClass)) +#define GST_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER, GstPlayerVideoOverlayVideoRenderer)) +#define GST_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER, GstPlayerVideoOverlayVideoRendererClass)) +#define GST_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER_CAST(obj) ((GstPlayerVideoOverlayVideoRenderer*)(obj)) + +GType gst_player_video_overlay_video_renderer_get_type (void); +GstPlayerVideoRenderer * gst_player_video_overlay_video_renderer_new (gpointer window_handle); +void gst_player_video_overlay_video_renderer_set_window_handle (GstPlayerVideoOverlayVideoRenderer * self, gpointer window_handle); +gpointer gst_player_video_overlay_video_renderer_get_window_handle (GstPlayerVideoOverlayVideoRenderer * self); + +G_END_DECLS + +#endif /* __GST_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER_H__ */ diff --git a/gst-libs/gst/player/gstplayer-video-renderer-private.h b/gst-libs/gst/player/gstplayer-video-renderer-private.h new file mode 100644 index 0000000000..6ecab15f03 --- /dev/null +++ b/gst-libs/gst/player/gstplayer-video-renderer-private.h @@ -0,0 +1,33 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * + * 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_PLAYER_VIDEO_RENDERER_PRIVATE_H__ +#define __GST_PLAYER_VIDEO_RENDERER_PRIVATE_H__ + +#include + +G_BEGIN_DECLS + +G_GNUC_INTERNAL GstElement * gst_player_video_renderer_create_video_sink (GstPlayerVideoRenderer * + self, GstPlayer * player); + +G_END_DECLS + +#endif /* __GST_PLAYER_VIDEO_RENDERER_PRIVATE_H__ */ diff --git a/gst-libs/gst/player/gstplayer-video-renderer.c b/gst-libs/gst/player/gstplayer-video-renderer.c new file mode 100644 index 0000000000..6faf6a83fc --- /dev/null +++ b/gst-libs/gst/player/gstplayer-video-renderer.c @@ -0,0 +1,49 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * + * 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 "gstplayer-video-renderer.h" +#include "gstplayer-video-renderer-private.h" + +G_DEFINE_INTERFACE (GstPlayerVideoRenderer, gst_player_video_renderer, + G_TYPE_OBJECT); + +static void +gst_player_video_renderer_default_init (G_GNUC_UNUSED + GstPlayerVideoRendererInterface * iface) +{ + +} + +GstElement * +gst_player_video_renderer_create_video_sink (GstPlayerVideoRenderer * self, + GstPlayer * player) +{ + GstPlayerVideoRendererInterface *iface; + + g_return_val_if_fail (GST_IS_PLAYER_VIDEO_RENDERER (self), NULL); + iface = GST_PLAYER_VIDEO_RENDERER_GET_INTERFACE (self); + g_return_val_if_fail (iface->create_video_sink != NULL, NULL); + + return iface->create_video_sink (self, player); +} diff --git a/gst-libs/gst/player/gstplayer-video-renderer.h b/gst-libs/gst/player/gstplayer-video-renderer.h new file mode 100644 index 0000000000..b9df5c9245 --- /dev/null +++ b/gst-libs/gst/player/gstplayer-video-renderer.h @@ -0,0 +1,47 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * + * 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_PLAYER_VIDEO_RENDERER_H__ +#define __GST_PLAYER_VIDEO_RENDERER_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef struct _GstPlayerVideoRenderer GstPlayerVideoRenderer; +typedef struct _GstPlayerVideoRendererInterface GstPlayerVideoRendererInterface; + +#define GST_TYPE_PLAYER_VIDEO_RENDERER (gst_player_video_renderer_get_type ()) +#define GST_PLAYER_VIDEO_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PLAYER_VIDEO_RENDERER, GstPlayerVideoRenderer)) +#define GST_IS_PLAYER_VIDEO_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PLAYER_VIDEO_RENDERER)) +#define GST_PLAYER_VIDEO_RENDERER_GET_INTERFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GST_TYPE_PLAYER_VIDEO_RENDERER, GstPlayerVideoRendererInterface)) + +struct _GstPlayerVideoRendererInterface { + GTypeInterface parent_iface; + + GstElement * (*create_video_sink) (GstPlayerVideoRenderer * self, GstPlayer * player); +}; + +GType gst_player_video_renderer_get_type (void); + +G_END_DECLS + +#endif /* __GST_PLAYER_VIDEO_RENDERER_H__ */ diff --git a/gst-libs/gst/player/gstplayer-visualization.c b/gst-libs/gst/player/gstplayer-visualization.c new file mode 100644 index 0000000000..79976a7b2e --- /dev/null +++ b/gst-libs/gst/player/gstplayer-visualization.c @@ -0,0 +1,178 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * Copyright (C) 2015 Brijesh Singh + * + * 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:gstplayer-visualization + * @short_description: Player Visualization + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstplayer-visualization.h" + +#include + +static GMutex vis_lock; +static GQueue vis_list = G_QUEUE_INIT; +static guint32 vis_cookie; + +G_DEFINE_BOXED_TYPE (GstPlayerVisualization, gst_player_visualization, + (GBoxedCopyFunc) gst_player_visualization_copy, + (GBoxedFreeFunc) gst_player_visualization_free); + +/** + * gst_player_visualization_free: + * @vis: #GstPlayerVisualization instance + * + * Frees a #GstPlayerVisualization. + */ +void +gst_player_visualization_free (GstPlayerVisualization * vis) +{ + g_return_if_fail (vis != NULL); + + g_free (vis->name); + g_free (vis->description); + g_free (vis); +} + +/** + * gst_player_visualization_copy: + * @vis: #GstPlayerVisualization instance + * + * Makes a copy of the #GstPlayerVisualization. The result must be + * freed using gst_player_visualization_free(). + * + * Returns: (transfer full): an allocated copy of @vis. + */ +GstPlayerVisualization * +gst_player_visualization_copy (const GstPlayerVisualization * vis) +{ + GstPlayerVisualization *ret; + + g_return_val_if_fail (vis != NULL, NULL); + + ret = g_new0 (GstPlayerVisualization, 1); + ret->name = vis->name ? g_strdup (vis->name) : NULL; + ret->description = vis->description ? g_strdup (vis->description) : NULL; + + return ret; +} + +/** + * gst_player_visualizations_free: + * @viss: a %NULL terminated array of #GstPlayerVisualization to free + * + * Frees a %NULL terminated array of #GstPlayerVisualization. + */ +void +gst_player_visualizations_free (GstPlayerVisualization ** viss) +{ + GstPlayerVisualization **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_player_update_visualization_list (void) +{ + GList *features; + GList *l; + guint32 cookie; + GstPlayerVisualization *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_player_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 (GstPlayerVisualization, 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_player_visualizations_get: + * + * Returns: (transfer full) (array zero-terminated=1) (element-type GstPlayerVisualization): + * a %NULL terminated array containing all available + * visualizations. Use gst_player_visualizations_free() after + * usage. + */ +GstPlayerVisualization ** +gst_player_visualizations_get (void) +{ + gint i = 0; + GList *l; + GstPlayerVisualization **ret; + + gst_player_update_visualization_list (); + + g_mutex_lock (&vis_lock); + ret = g_new0 (GstPlayerVisualization *, g_queue_get_length (&vis_list) + 1); + for (l = vis_list.head; l; l = l->next) + ret[i++] = gst_player_visualization_copy (l->data); + g_mutex_unlock (&vis_lock); + + return ret; +} diff --git a/gst-libs/gst/player/gstplayer-visualization.h b/gst-libs/gst/player/gstplayer-visualization.h new file mode 100644 index 0000000000..7382773b8a --- /dev/null +++ b/gst-libs/gst/player/gstplayer-visualization.h @@ -0,0 +1,52 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * Copyright (C) 2015 Brijesh Singh + * + * 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_PLAYER_VISUALIZATION_H__ +#define __GST_PLAYER_VISUALIZATION_H__ + +#include + +G_BEGIN_DECLS + +typedef struct _GstPlayerVisualization GstPlayerVisualization; +/** + * GstPlayerVisualization: + * @name: name of the visualization. + * @description: description of the visualization. + * + * A #GstPlayerVisualization descriptor. + */ +struct _GstPlayerVisualization { + gchar *name; + gchar *description; +}; + +GType gst_player_visualization_get_type (void); + +GstPlayerVisualization * gst_player_visualization_copy (const GstPlayerVisualization *vis); +void gst_player_visualization_free (GstPlayerVisualization *vis); + +GstPlayerVisualization ** gst_player_visualizations_get (void); +void gst_player_visualizations_free (GstPlayerVisualization **viss); + +G_END_DECLS + +#endif /* __GST_PLAYER_VISUALIZATION_H__ */ diff --git a/gst-libs/gst/player/gstplayer.c b/gst-libs/gst/player/gstplayer.c new file mode 100644 index 0000000000..94892b7e3b --- /dev/null +++ b/gst-libs/gst/player/gstplayer.c @@ -0,0 +1,3801 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * Copyright (C) 2015 Brijesh Singh + * + * 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:gstplayer + * @short_description: Player + * + */ + +/* 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 "gstplayer.h" +#include "gstplayer-signal-dispatcher-private.h" +#include "gstplayer-video-renderer-private.h" +#include "gstplayer-media-info-private.h" + +#include +#include +#include +#include +#include + +#include + +GST_DEBUG_CATEGORY_STATIC (gst_player_debug); +#define GST_CAT_DEFAULT gst_player_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 + +GQuark +gst_player_error_quark (void) +{ + static GQuark quark; + + if (!quark) + quark = g_quark_from_static_string ("gst-player-error-quark"); + + return quark; +} + +enum +{ + PROP_0, + PROP_VIDEO_RENDERER, + PROP_SIGNAL_DISPATCHER, + 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_POSITION_UPDATE_INTERVAL, + PROP_LAST +}; + +enum +{ + 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 +{ + 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 _GstPlayer +{ + GstObject parent; + + GstPlayerVideoRenderer *video_renderer; + GstPlayerSignalDispatcher *signal_dispatcher; + + gchar *uri; + gchar *suburi; + + GThread *thread; + GMutex lock; + GCond cond; + GMainContext *context; + GMainLoop *loop; + + GstElement *playbin; + GstBus *bus; + GstState target_state, current_state; + gboolean is_live, is_eos; + GSource *tick_source, *ready_timeout_source; + + gdouble rate; + guint position_update_interval_ms; + + GstPlayerState app_state; + gint buffering; + + GstTagList *global_tags; + GstPlayerMediaInfo *media_info; + + GstElement *current_vis_element; + + /* 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; +}; + +struct _GstPlayerClass +{ + GstObjectClass parent_class; +}; + +#define parent_class gst_player_parent_class +G_DEFINE_TYPE (GstPlayer, gst_player, GST_TYPE_OBJECT); + +static guint signals[SIGNAL_LAST] = { 0, }; +static GParamSpec *param_specs[PROP_LAST] = { NULL, }; + +static void gst_player_dispose (GObject * object); +static void gst_player_finalize (GObject * object); +static void gst_player_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_player_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_player_constructed (GObject * object); + +static gpointer gst_player_main (gpointer data); + +static void gst_player_seek_internal_locked (GstPlayer * self); +static gboolean gst_player_stop_internal (gpointer user_data); +static gboolean gst_player_pause_internal (gpointer user_data); +static gboolean gst_player_play_internal (gpointer user_data); +static gboolean gst_player_set_rate_internal (gpointer user_data); +static gboolean gst_player_set_position_update_interval_internal (gpointer + user_data); +static void change_state (GstPlayer * self, GstPlayerState state); + +static GstPlayerMediaInfo *gst_player_media_info_create (GstPlayer * self); + +static void gst_player_streams_info_create (GstPlayer * self, + GstPlayerMediaInfo * media_info, const gchar * prop, GType type); +static void gst_player_stream_info_update (GstPlayer * self, + GstPlayerStreamInfo * s); +static void gst_player_stream_info_update_tags_and_caps (GstPlayer * self, + GstPlayerStreamInfo * s); +static GstPlayerStreamInfo *gst_player_stream_info_find (GstPlayerMediaInfo * + media_info, GType type, gint stream_index); +static GstPlayerStreamInfo *gst_player_stream_info_get_current (GstPlayer * + self, const gchar * prop, GType type); + +static void gst_player_video_info_update (GstPlayer * self, + GstPlayerStreamInfo * stream_info); +static void gst_player_audio_info_update (GstPlayer * self, + GstPlayerStreamInfo * stream_info); +static void gst_player_subtitle_info_update (GstPlayer * self, + GstPlayerStreamInfo * stream_info); + +static void emit_media_info_updated_signal (GstPlayer * self); + +static void *get_title (GstTagList * tags); +static void *get_container_format (GstTagList * tags); +static void *get_from_tags (GstPlayer * self, GstPlayerMediaInfo * media_info, + void *(*func) (GstTagList *)); +static void *get_cover_sample (GstTagList * tags); + +static void +gst_player_init (GstPlayer * self) +{ + GST_TRACE_OBJECT (self, "Initializing"); + + self = gst_player_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->position_update_interval_ms = DEFAULT_POSITION_UPDATE_INTERVAL_MS; + self->seek_pending = FALSE; + self->seek_position = GST_CLOCK_TIME_NONE; + self->last_seek_time = GST_CLOCK_TIME_NONE; + + GST_TRACE_OBJECT (self, "Initialized"); +} + +static void +gst_player_class_init (GstPlayerClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->set_property = gst_player_set_property; + gobject_class->get_property = gst_player_get_property; + gobject_class->dispose = gst_player_dispose; + gobject_class->finalize = gst_player_finalize; + gobject_class->constructed = gst_player_constructed; + + param_specs[PROP_VIDEO_RENDERER] = + g_param_spec_object ("video-renderer", + "Video Renderer", "Video renderer to use for rendering videos", + GST_TYPE_PLAYER_VIDEO_RENDERER, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_SIGNAL_DISPATCHER] = + g_param_spec_object ("signal-dispatcher", + "Signal Dispatcher", "Dispatcher for the signals to e.g. event loops", + GST_TYPE_PLAYER_SIGNAL_DISPATCHER, + 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_PLAYER_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_PLAYER_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_PLAYER_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_PLAYER_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_POSITION_UPDATE_INTERVAL] = + g_param_spec_uint ("position-update-interval", "Position update interval", + "Interval in milliseconds between two position-updated signals." + "Pass 0 to stop updating the position.", + 0, 10000, DEFAULT_POSITION_UPDATE_INTERVAL_MS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); + + 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_PLAYER_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, 1, G_TYPE_ERROR); + + 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_INT, G_TYPE_INT); + + 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_PLAYER_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, 1, G_TYPE_ERROR); + + 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); +} + +static void +gst_player_dispose (GObject * object) +{ + GstPlayer *self = GST_PLAYER (object); + + GST_TRACE_OBJECT (self, "Stopping main thread"); + + if (self->loop) { + g_main_loop_quit (self->loop); + + g_thread_join (self->thread); + self->thread = NULL; + + g_main_loop_unref (self->loop); + self->loop = NULL; + + g_main_context_unref (self->context); + self->context = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_player_finalize (GObject * object) +{ + GstPlayer *self = GST_PLAYER (object); + + GST_TRACE_OBJECT (self, "Finalizing"); + + g_free (self->uri); + g_free (self->suburi); + if (self->global_tags) + gst_tag_list_unref (self->global_tags); + if (self->video_renderer) + g_object_unref (self->video_renderer); + if (self->signal_dispatcher) + g_object_unref (self->signal_dispatcher); + if (self->current_vis_element) + gst_object_unref (self->current_vis_element); + g_mutex_clear (&self->lock); + g_cond_clear (&self->cond); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_player_constructed (GObject * object) +{ + GstPlayer *self = GST_PLAYER (object); + + GST_TRACE_OBJECT (self, "Constructed"); + + g_mutex_lock (&self->lock); + self->thread = g_thread_new ("GstPlayer", gst_player_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_player_set_uri_internal (gpointer user_data) +{ + GstPlayer *self = user_data; + + gst_player_stop_internal (self); + + 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); + + /* if have suburi from previous playback then free it */ + if (self->suburi) { + g_free (self->suburi); + self->suburi = NULL; + g_object_set (self->playbin, "suburi", NULL, NULL); + } + + g_mutex_unlock (&self->lock); + + return G_SOURCE_REMOVE; +} + +static gboolean +gst_player_set_suburi_internal (gpointer user_data) +{ + GstPlayer *self = user_data; + GstClockTime position; + GstState target_state; + + /* save the state and position */ + target_state = self->target_state; + position = gst_player_get_position (self); + + gst_player_stop_internal (self); + 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_object_set (self->playbin, "uri", self->uri, NULL); + + g_mutex_unlock (&self->lock); + + /* restore state and position */ + if (position != GST_CLOCK_TIME_NONE) + gst_player_seek (self, position); + if (target_state == GST_STATE_PAUSED) + gst_player_pause_internal (self); + else if (target_state == GST_STATE_PLAYING) + gst_player_play_internal (self); + + return G_SOURCE_REMOVE; +} + +static void +gst_player_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstPlayer *self = GST_PLAYER (object); + + switch (prop_id) { + case PROP_VIDEO_RENDERER: + self->video_renderer = g_value_dup_object (value); + break; + case PROP_SIGNAL_DISPATCHER: + self->signal_dispatcher = g_value_dup_object (value); + break; + case PROP_URI:{ + g_mutex_lock (&self->lock); + g_free (self->uri); + + 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_player_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_player_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)); + g_mutex_unlock (&self->lock); + + gst_player_set_rate_internal (self); + 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_POSITION_UPDATE_INTERVAL: + g_mutex_lock (&self->lock); + self->position_update_interval_ms = g_value_get_uint (value); + GST_DEBUG_OBJECT (self, "Set position update interval=%u ms", + g_value_get_uint (value)); + g_mutex_unlock (&self->lock); + + gst_player_set_position_update_interval_internal (self); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_player_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstPlayer *self = GST_PLAYER (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 has-suburi=%d", + g_value_get_boolean (value)); + break; + case PROP_POSITION:{ + gint64 position = 0; + + gst_element_query_position (self->playbin, GST_FORMAT_TIME, &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:{ + gint64 duration = 0; + + gst_element_query_duration (self->playbin, GST_FORMAT_TIME, &duration); + g_value_set_uint64 (value, duration); + GST_TRACE_OBJECT (self, "Returning duration=%" GST_TIME_FORMAT, + GST_TIME_ARGS (g_value_get_uint64 (value))); + break; + } + case PROP_MEDIA_INFO:{ + GstPlayerMediaInfo *media_info = gst_player_get_media_info (self); + g_value_set_object (value, media_info); + g_object_unref (media_info); + break; + } + case PROP_CURRENT_AUDIO_TRACK:{ + GstPlayerAudioInfo *audio_info = + gst_player_get_current_audio_track (self); + g_value_set_object (value, audio_info); + g_object_unref (audio_info); + break; + } + case PROP_CURRENT_VIDEO_TRACK:{ + GstPlayerVideoInfo *video_info = + gst_player_get_current_video_track (self); + g_value_set_object (value, video_info); + g_object_unref (video_info); + break; + } + case PROP_CURRENT_SUBTITLE_TRACK:{ + GstPlayerSubtitleInfo *subtitle_info = + gst_player_get_current_subtitle_track (self); + g_value_set_object (value, subtitle_info); + g_object_unref (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, gst_player_get_rate (self)); + 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_POSITION_UPDATE_INTERVAL: + g_mutex_lock (&self->lock); + g_value_set_uint (value, gst_player_get_position_update_interval (self)); + g_mutex_unlock (&self->lock); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +main_loop_running_cb (gpointer user_data) +{ + GstPlayer *self = GST_PLAYER (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; +} + +typedef struct +{ + GstPlayer *player; + GstPlayerState state; +} StateChangedSignalData; + +static void +state_changed_dispatch (gpointer user_data) +{ + StateChangedSignalData *data = user_data; + + g_signal_emit (data->player, signals[SIGNAL_STATE_CHANGED], 0, data->state); +} + +static void +state_changed_signal_data_free (StateChangedSignalData * data) +{ + g_object_unref (data->player); + g_free (data); +} + +static void +change_state (GstPlayer * self, GstPlayerState state) +{ + if (state == self->app_state) + return; + + GST_DEBUG_OBJECT (self, "Changing app state from %s to %s", + gst_player_state_get_name (self->app_state), + gst_player_state_get_name (state)); + self->app_state = state; + + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_STATE_CHANGED], 0, NULL, NULL, NULL) != 0) { + StateChangedSignalData *data = g_new (StateChangedSignalData, 1); + + data->player = g_object_ref (self); + data->state = state; + gst_player_signal_dispatcher_dispatch (self->signal_dispatcher, self, + state_changed_dispatch, data, + (GDestroyNotify) state_changed_signal_data_free); + } +} + +typedef struct +{ + GstPlayer *player; + GstClockTime position; +} PositionUpdatedSignalData; + +static void +position_updated_dispatch (gpointer user_data) +{ + PositionUpdatedSignalData *data = user_data; + + if (data->player->target_state >= GST_STATE_PAUSED) { + g_signal_emit (data->player, signals[SIGNAL_POSITION_UPDATED], 0, + data->position); + g_object_notify_by_pspec (G_OBJECT (data->player), + param_specs[PROP_POSITION]); + } +} + +static void +position_updated_signal_data_free (PositionUpdatedSignalData * data) +{ + g_object_unref (data->player); + g_free (data); +} + +static gboolean +tick_cb (gpointer user_data) +{ + GstPlayer *self = GST_PLAYER (user_data); + gint64 position; + + if (self->target_state >= GST_STATE_PAUSED + && gst_element_query_position (self->playbin, GST_FORMAT_TIME, + &position)) { + GST_LOG_OBJECT (self, "Position %" GST_TIME_FORMAT, + GST_TIME_ARGS (position)); + + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_POSITION_UPDATED], 0, NULL, NULL, NULL) != 0) { + PositionUpdatedSignalData *data = g_new (PositionUpdatedSignalData, 1); + + data->player = g_object_ref (self); + data->position = position; + gst_player_signal_dispatcher_dispatch (self->signal_dispatcher, self, + position_updated_dispatch, data, + (GDestroyNotify) position_updated_signal_data_free); + } + } + + return G_SOURCE_CONTINUE; +} + +static void +add_tick_source (GstPlayer * self) +{ + if (self->tick_source) + return; + + if (!self->position_update_interval_ms) + return; + + self->tick_source = g_timeout_source_new (self->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 (GstPlayer * 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) +{ + GstPlayer *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 (GstPlayer * 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 (GstPlayer * 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; +} + +typedef struct +{ + GstPlayer *player; + GError *err; +} ErrorSignalData; + +static void +error_dispatch (gpointer user_data) +{ + ErrorSignalData *data = user_data; + + g_signal_emit (data->player, signals[SIGNAL_ERROR], 0, data->err); +} + +static void +free_error_signal_data (ErrorSignalData * data) +{ + g_object_unref (data->player); + g_clear_error (&data->err); + g_free (data); +} + +static void +emit_error (GstPlayer * self, GError * err) +{ + GST_ERROR_OBJECT (self, "Error: %s (%s, %d)", err->message, + g_quark_to_string (err->domain), err->code); + + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_ERROR], 0, NULL, NULL, NULL) != 0) { + ErrorSignalData *data = g_new (ErrorSignalData, 1); + + data->player = g_object_ref (self); + data->err = g_error_copy (err); + gst_player_signal_dispatcher_dispatch (self->signal_dispatcher, self, + error_dispatch, data, (GDestroyNotify) free_error_signal_data); + } + + 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_PLAYER_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; + if (self->seek_source) { + g_source_destroy (self->seek_source); + g_source_unref (self->seek_source); + self->seek_source = NULL; + } + 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 (GstPlayer * self, const gchar * name) +{ + gchar *full_name; + + full_name = g_strdup_printf ("gst-player.%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); +} + +typedef struct +{ + GstPlayer *player; + GError *err; +} WarningSignalData; + +static void +warning_dispatch (gpointer user_data) +{ + WarningSignalData *data = user_data; + + g_signal_emit (data->player, signals[SIGNAL_WARNING], 0, data->err); +} + +static void +free_warning_signal_data (WarningSignalData * data) +{ + g_object_unref (data->player); + g_clear_error (&data->err); + g_free (data); +} + +static void +emit_warning (GstPlayer * self, GError * err) +{ + GST_ERROR_OBJECT (self, "Warning: %s (%s, %d)", err->message, + g_quark_to_string (err->domain), err->code); + + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_WARNING], 0, NULL, NULL, NULL) != 0) { + WarningSignalData *data = g_new (WarningSignalData, 1); + + data->player = g_object_ref (self); + data->err = g_error_copy (err); + gst_player_signal_dispatcher_dispatch (self->signal_dispatcher, self, + warning_dispatch, data, (GDestroyNotify) free_warning_signal_data); + } + + g_error_free (err); +} + +static void +error_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data) +{ + GstPlayer *self = GST_PLAYER (user_data); + GError *err, *player_err; + gchar *name, *debug, *message, *full_message; + + dump_dot_file (self, "error"); + + gst_message_parse_error (msg, &err, &debug); + + 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\n", name, err->message); + if (debug != NULL) + GST_ERROR_OBJECT (self, "Additional debug info:\n%s\n", debug); + + player_err = + g_error_new_literal (GST_PLAYER_ERROR, GST_PLAYER_ERROR_FAILED, + full_message); + emit_error (self, player_err); + + 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) +{ + GstPlayer *self = GST_PLAYER (user_data); + GError *err, *player_err; + gchar *name, *debug, *message, *full_message; + + dump_dot_file (self, "warning"); + + gst_message_parse_warning (msg, &err, &debug); + + 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\n", name, + err->message); + if (debug != NULL) + GST_WARNING_OBJECT (self, "Additional debug info:\n%s\n", debug); + + player_err = + g_error_new_literal (GST_PLAYER_ERROR, GST_PLAYER_ERROR_FAILED, + full_message); + emit_warning (self, player_err); + + g_clear_error (&err); + g_free (debug); + g_free (name); + g_free (full_message); + g_free (message); +} + +static void +eos_dispatch (gpointer user_data) +{ + g_signal_emit (user_data, signals[SIGNAL_END_OF_STREAM], 0); +} + +static void +eos_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg, + gpointer user_data) +{ + GstPlayer *self = GST_PLAYER (user_data); + + GST_DEBUG_OBJECT (self, "End of stream"); + + tick_cb (self); + remove_tick_source (self); + + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_END_OF_STREAM], 0, NULL, NULL, NULL) != 0) { + gst_player_signal_dispatcher_dispatch (self->signal_dispatcher, self, + eos_dispatch, g_object_ref (self), (GDestroyNotify) g_object_unref); + } + change_state (self, GST_PLAYER_STATE_STOPPED); + self->buffering = 100; + self->is_eos = TRUE; +} + +typedef struct +{ + GstPlayer *player; + gint percent; +} BufferingSignalData; + +static void +buffering_dispatch (gpointer user_data) +{ + BufferingSignalData *data = user_data; + + if (data->player->target_state >= GST_STATE_PAUSED) { + g_signal_emit (data->player, signals[SIGNAL_BUFFERING], 0, data->percent); + } +} + +static void +buffering_signal_data_free (BufferingSignalData * data) +{ + g_object_unref (data->player); + g_free (data); +} + +static void +buffering_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data) +{ + GstPlayer *self = GST_PLAYER (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) { + emit_error (self, g_error_new (GST_PLAYER_ERROR, GST_PLAYER_ERROR_FAILED, + "Failed to handle buffering")); + return; + } + + change_state (self, GST_PLAYER_STATE_BUFFERING); + } + + if (self->buffering != percent) { + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_BUFFERING], 0, NULL, NULL, NULL) != 0) { + BufferingSignalData *data = g_new (BufferingSignalData, 1); + + data->player = g_object_ref (self); + data->percent = percent; + gst_player_signal_dispatcher_dispatch (self->signal_dispatcher, self, + buffering_dispatch, data, + (GDestroyNotify) buffering_signal_data_free); + } + + self->buffering = percent; + } + + + 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) + emit_error (self, g_error_new (GST_PLAYER_ERROR, GST_PLAYER_ERROR_FAILED, + "Failed to handle buffering")); + } 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_PLAYER_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) +{ + GstPlayer *self = GST_PLAYER (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) + emit_error (self, g_error_new (GST_PLAYER_ERROR, GST_PLAYER_ERROR_FAILED, + "Failed to handle clock loss")); + } +} + +typedef struct +{ + GstPlayer *player; + gint width, height; +} VideoDimensionsChangedSignalData; + +static void +video_dimensions_changed_dispatch (gpointer user_data) +{ + VideoDimensionsChangedSignalData *data = user_data; + + if (data->player->target_state >= GST_STATE_PAUSED) { + g_signal_emit (data->player, signals[SIGNAL_VIDEO_DIMENSIONS_CHANGED], 0, + data->width, data->height); + } +} + +static void +video_dimensions_changed_signal_data_free (VideoDimensionsChangedSignalData * + data) +{ + g_object_unref (data->player); + g_free (data); +} + +static void +check_video_dimensions_changed (GstPlayer * self) +{ + GstElement *video_sink; + GstPad *video_sink_pad; + GstCaps *caps; + GstVideoInfo info; + gint 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: + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_VIDEO_DIMENSIONS_CHANGED], 0, NULL, NULL, NULL) != 0) { + VideoDimensionsChangedSignalData *data = + g_new (VideoDimensionsChangedSignalData, 1); + + data->player = g_object_ref (self); + data->width = width; + data->height = height; + gst_player_signal_dispatcher_dispatch (self->signal_dispatcher, self, + video_dimensions_changed_dispatch, data, + (GDestroyNotify) video_dimensions_changed_signal_data_free); + } +} + +static void +notify_caps_cb (G_GNUC_UNUSED GObject * object, + G_GNUC_UNUSED GParamSpec * pspec, gpointer user_data) +{ + GstPlayer *self = GST_PLAYER (user_data); + + check_video_dimensions_changed (self); +} + +typedef struct +{ + GstPlayer *player; + GstClockTime duration; +} DurationChangedSignalData; + +static void +duration_changed_dispatch (gpointer user_data) +{ + DurationChangedSignalData *data = user_data; + + if (data->player->target_state >= GST_STATE_PAUSED) { + g_signal_emit (data->player, signals[SIGNAL_DURATION_CHANGED], 0, + data->duration); + g_object_notify_by_pspec (G_OBJECT (data->player), + param_specs[PROP_DURATION]); + } +} + +static void +duration_changed_signal_data_free (DurationChangedSignalData * data) +{ + g_object_unref (data->player); + g_free (data); +} + +static void +emit_duration_changed (GstPlayer * self, GstClockTime duration) +{ + GST_DEBUG_OBJECT (self, "Duration changed %" GST_TIME_FORMAT, + GST_TIME_ARGS (duration)); + + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_DURATION_CHANGED], 0, NULL, NULL, NULL) != 0) { + DurationChangedSignalData *data = g_new (DurationChangedSignalData, 1); + + data->player = g_object_ref (self); + data->duration = duration; + gst_player_signal_dispatcher_dispatch (self->signal_dispatcher, self, + duration_changed_dispatch, data, + (GDestroyNotify) duration_changed_signal_data_free); + } +} + +typedef struct +{ + GstPlayer *player; + GstClockTime position; +} SeekDoneSignalData; + +static void +seek_done_dispatch (gpointer user_data) +{ + SeekDoneSignalData *data = user_data; + + g_signal_emit (data->player, signals[SIGNAL_SEEK_DONE], 0, data->position); +} + +static void +seek_done_signal_data_free (SeekDoneSignalData * data) +{ + g_object_unref (data->player); + g_free (data); +} + +static void +emit_seek_done (GstPlayer * self) +{ + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_SEEK_DONE], 0, NULL, NULL, NULL) != 0) { + SeekDoneSignalData *data = g_new (SeekDoneSignalData, 1); + + data->player = g_object_ref (self); + data->position = gst_player_get_position (self); + gst_player_signal_dispatcher_dispatch (self->signal_dispatcher, self, + seek_done_dispatch, data, (GDestroyNotify) seek_done_signal_data_free); + } +} + +static void +state_changed_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, + gpointer user_data) +{ + GstPlayer *self = GST_PLAYER (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_player_media_info_create (self); + g_mutex_unlock (&self->lock); + emit_media_info_updated_signal (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); + gst_element_query_duration (self->playbin, GST_FORMAT_TIME, &duration); + emit_duration_changed (self, duration); + } + + 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"); + if (self->seek_source) { + g_source_destroy (self->seek_source); + g_source_unref (self->seek_source); + self->seek_source = NULL; + } + 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_player_seek_internal_locked (self); + } else { + GST_DEBUG_OBJECT (self, "Seek finished"); + emit_seek_done (self); + } + } + + if (self->seek_position != GST_CLOCK_TIME_NONE) { + GST_DEBUG_OBJECT (self, "Seeking now that we reached PAUSED state"); + gst_player_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) + emit_error (self, g_error_new (GST_PLAYER_ERROR, + GST_PLAYER_ERROR_FAILED, "Failed to play")); + } else if (self->buffering == 100) { + change_state (self, GST_PLAYER_STATE_PAUSED); + } + } else { + g_mutex_unlock (&self->lock); + } + } else if (new_state == GST_STATE_PLAYING + && pending_state == GST_STATE_VOID_PENDING) { + + /* 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_PLAYER_STATE_PLAYING); + } + } else if (new_state == GST_STATE_READY && old_state > GST_STATE_READY) { + change_state (self, GST_PLAYER_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_PLAYER_STATE_BUFFERING); + } + } +} + +static void +duration_changed_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg, + gpointer user_data) +{ + GstPlayer *self = GST_PLAYER (user_data); + gint64 duration; + + if (gst_element_query_duration (self->playbin, GST_FORMAT_TIME, &duration)) { + emit_duration_changed (self, duration); + } +} + +static void +latency_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg, + gpointer user_data) +{ + GstPlayer *self = GST_PLAYER (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) +{ + GstPlayer *self = GST_PLAYER (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) + emit_error (self, g_error_new (GST_PLAYER_ERROR, GST_PLAYER_ERROR_FAILED, + "Failed to change to requested state %s", + gst_element_state_get_name (state))); +} + +static void +media_info_update (GstPlayer * self, GstPlayerMediaInfo * 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) +{ + GstPlayer *self = GST_PLAYER (user_data); + GstTagList *tags = NULL; + + gst_message_parse_tag (msg, &tags); + + /* + * NOTE: Inorder to get global tag you must apply the following patches in + * your gstreamer build. + * + * http://cgit.freedesktop.org/gstreamer/gst-plugins-good/commit/?id=9119fbd774093e3ae762c8652acd80d54b2c3b45 + * http://cgit.freedesktop.org/gstreamer/gstreamer/commit/?id=18b058100940bdcaed86fa412e3582a02871f995 + */ + GST_DEBUG_OBJECT (self, "recieved %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); + emit_media_info_updated_signal (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) +{ + GstPlayer *self = GST_PLAYER (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; + + g_mutex_lock (&self->lock); + g_free (self->uri); + + self->uri = g_strdup (new_location); + g_mutex_unlock (&self->lock); + + gst_player_set_uri_internal (self); + + if (target_state == GST_STATE_PAUSED) + gst_player_pause_internal (self); + else if (target_state == GST_STATE_PLAYING) + gst_player_play_internal (self); + } + } +} + +static void +player_set_flag (GstPlayer * 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 +player_clear_flag (GstPlayer * 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); +} + +typedef struct +{ + GstPlayer *player; + GstPlayerMediaInfo *info; +} MediaInfoUpdatedSignalData; + +static void +media_info_updated_dispatch (gpointer user_data) +{ + MediaInfoUpdatedSignalData *data = user_data; + + if (data->player->target_state >= GST_STATE_PAUSED) { + g_signal_emit (data->player, signals[SIGNAL_MEDIA_INFO_UPDATED], 0, + data->info); + } +} + +static void +free_media_info_updated_signal_data (MediaInfoUpdatedSignalData * data) +{ + g_object_unref (data->player); + g_object_unref (data->info); + g_free (data); +} + +/* + * emit_media_info_updated_signal: + * + * create a new copy of self->media_info object and emits the newly created + * copy to user application. The newly created media_info will be unref'ed + * as part of signal finalize method. + */ +static void +emit_media_info_updated_signal (GstPlayer * self) +{ + MediaInfoUpdatedSignalData *data = g_new (MediaInfoUpdatedSignalData, 1); + data->player = g_object_ref (self); + g_mutex_lock (&self->lock); + data->info = gst_player_media_info_copy (self->media_info); + g_mutex_unlock (&self->lock); + + gst_player_signal_dispatcher_dispatch (self->signal_dispatcher, self, + media_info_updated_dispatch, data, + (GDestroyNotify) free_media_info_updated_signal_data); +} + +static GstCaps * +get_caps (GstPlayer * self, gint stream_index, GType type) +{ + GstPad *pad = NULL; + GstCaps *caps = NULL; + + if (type == GST_TYPE_PLAYER_VIDEO_INFO) + g_signal_emit_by_name (G_OBJECT (self->playbin), + "get-video-pad", stream_index, &pad); + else if (type == GST_TYPE_PLAYER_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_player_subtitle_info_update (GstPlayer * self, + GstPlayerStreamInfo * stream_info) +{ + GstPlayerSubtitleInfo *info = (GstPlayerSubtitleInfo *) 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) { + g_object_get (G_OBJECT (self->playbin), "current-text", &text_index, + NULL); + if (text_index == gst_player_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_player_video_info_update (GstPlayer * self, + GstPlayerStreamInfo * stream_info) +{ + GstPlayerVideoInfo *info = (GstPlayerVideoInfo *) 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_player_audio_info_update (GstPlayer * self, + GstPlayerStreamInfo * stream_info) +{ + GstPlayerAudioInfo *info = (GstPlayerAudioInfo *) 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->bitrate); +} + +static GstPlayerStreamInfo * +gst_player_stream_info_find (GstPlayerMediaInfo * media_info, + GType type, gint stream_index) +{ + GList *list, *l; + GstPlayerStreamInfo *info = NULL; + + if (!media_info) + return NULL; + + list = gst_player_media_info_get_stream_list (media_info); + for (l = list; l != NULL; l = l->next) { + info = (GstPlayerStreamInfo *) l->data; + if ((G_OBJECT_TYPE (info) == type) && (info->stream_index == stream_index)) { + return info; + } + } + + return NULL; +} + +static gboolean +is_track_enabled (GstPlayer * self, gint pos) +{ + gint flags; + + g_object_get (G_OBJECT (self->playbin), "flags", &flags, NULL); + + if ((flags & pos)) + return TRUE; + + return FALSE; +} + +static GstPlayerStreamInfo * +gst_player_stream_info_get_current (GstPlayer * self, const gchar * prop, + GType type) +{ + gint current; + GstPlayerStreamInfo *info; + + if (!self->media_info) + return NULL; + + g_object_get (G_OBJECT (self->playbin), prop, ¤t, NULL); + g_mutex_lock (&self->lock); + info = gst_player_stream_info_find (self->media_info, type, current); + if (info) + info = gst_player_stream_info_copy (info); + g_mutex_unlock (&self->lock); + + return info; +} + +static void +gst_player_stream_info_update (GstPlayer * self, GstPlayerStreamInfo * s) +{ + if (GST_IS_PLAYER_VIDEO_INFO (s)) + gst_player_video_info_update (self, s); + else if (GST_IS_PLAYER_AUDIO_INFO (s)) + gst_player_audio_info_update (self, s); + else + gst_player_subtitle_info_update (self, s); +} + +static gchar * +stream_info_get_codec (GstPlayerStreamInfo * s) +{ + const gchar *type; + GstTagList *tags; + gchar *codec = NULL; + + if (GST_IS_PLAYER_VIDEO_INFO (s)) + type = GST_TAG_VIDEO_CODEC; + else if (GST_IS_PLAYER_AUDIO_INFO (s)) + type = GST_TAG_AUDIO_CODEC; + else + type = GST_TAG_SUBTITLE_CODEC; + + tags = gst_player_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_player_stream_info_get_caps (s); + if (caps) { + codec = gst_pb_utils_get_codec_description (caps); + } + } + + return codec; +} + +static void +gst_player_stream_info_update_tags_and_caps (GstPlayer * self, + GstPlayerStreamInfo * s) +{ + GstTagList *tags; + gint stream_index; + + stream_index = gst_player_stream_info_get_index (s); + + if (GST_IS_PLAYER_VIDEO_INFO (s)) + g_signal_emit_by_name (self->playbin, "get-video-tags", + stream_index, &tags); + else if (GST_IS_PLAYER_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_player_stream_info_get_stream_type (s), stream_index, + s->tags, s->caps); + + gst_player_stream_info_update (self, s); +} + +static void +gst_player_streams_info_create (GstPlayer * self, + GstPlayerMediaInfo * media_info, const gchar * prop, GType type) +{ + gint i; + gint total = -1; + GstPlayerStreamInfo *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_player_stream_info_find (media_info, type, i); + + if (!s) { + /* create a new stream info instance */ + s = gst_player_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_PLAYER_AUDIO_INFO (s)) + media_info->audio_stream_list = g_list_append + (media_info->audio_stream_list, s); + else if (GST_IS_PLAYER_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_player_stream_info_get_stream_type (s), i); + } + + gst_player_stream_info_update_tags_and_caps (self, s); + } +} + +static void +video_changed_cb (G_GNUC_UNUSED GObject * object, gpointer user_data) +{ + GstPlayer *self = GST_PLAYER (user_data); + + g_mutex_lock (&self->lock); + gst_player_streams_info_create (self, self->media_info, + "n-video", GST_TYPE_PLAYER_VIDEO_INFO); + g_mutex_unlock (&self->lock); +} + +static void +audio_changed_cb (G_GNUC_UNUSED GObject * object, gpointer user_data) +{ + GstPlayer *self = GST_PLAYER (user_data); + + g_mutex_lock (&self->lock); + gst_player_streams_info_create (self, self->media_info, + "n-audio", GST_TYPE_PLAYER_AUDIO_INFO); + g_mutex_unlock (&self->lock); +} + +static void +subtitle_changed_cb (G_GNUC_UNUSED GObject * object, gpointer user_data) +{ + GstPlayer *self = GST_PLAYER (user_data); + + g_mutex_lock (&self->lock); + gst_player_streams_info_create (self, self->media_info, + "n-text", GST_TYPE_PLAYER_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 (GstPlayer * self, GstPlayerMediaInfo * 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_player_get_video_streams (media_info); l != NULL; l = l->next) { + GstTagList *tags; + + tags = gst_player_stream_info_get_tags ((GstPlayerStreamInfo *) l->data); + if (tags) + ret = func (tags); + + if (ret) + return ret; + } + + GST_DEBUG_OBJECT (self, "trying audio tags"); + for (l = gst_player_get_audio_streams (media_info); l != NULL; l = l->next) { + GstTagList *tags; + + tags = gst_player_stream_info_get_tags ((GstPlayerStreamInfo *) 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 GstPlayerMediaInfo * +gst_player_media_info_create (GstPlayer * self) +{ + GstPlayerMediaInfo *media_info; + GstQuery *query; + + GST_DEBUG_OBJECT (self, "begin"); + media_info = gst_player_media_info_new (self->uri); + media_info->duration = gst_player_get_duration (self); + media_info->tags = self->global_tags; + 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); + + /* create audio/video/sub streams */ + gst_player_streams_info_create (self, media_info, "n-video", + GST_TYPE_PLAYER_VIDEO_INFO); + gst_player_streams_info_create (self, media_info, "n-audio", + GST_TYPE_PLAYER_AUDIO_INFO); + gst_player_streams_info_create (self, media_info, "n-text", + GST_TYPE_PLAYER_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 container: %s image_sample %p", + media_info->uri, media_info->title, GST_TIME_ARGS (media_info->duration), + media_info->seekable ? "yes" : "no", media_info->container, + media_info->image_sample); + + GST_DEBUG_OBJECT (self, "end"); + return media_info; +} + +static void +tags_changed_cb (GstPlayer * self, gint stream_index, GType type) +{ + GstPlayerStreamInfo *s; + + if (!self->media_info) + return; + + /* update the stream information */ + g_mutex_lock (&self->lock); + s = gst_player_stream_info_find (self->media_info, type, stream_index); + gst_player_stream_info_update_tags_and_caps (self, s); + g_mutex_unlock (&self->lock); + + emit_media_info_updated_signal (self); +} + +static void +video_tags_changed_cb (G_GNUC_UNUSED GstElement * playbin, gint stream_index, + gpointer user_data) +{ + tags_changed_cb (GST_PLAYER (user_data), stream_index, + GST_TYPE_PLAYER_VIDEO_INFO); +} + +static void +audio_tags_changed_cb (G_GNUC_UNUSED GstElement * playbin, gint stream_index, + gpointer user_data) +{ + tags_changed_cb (GST_PLAYER (user_data), stream_index, + GST_TYPE_PLAYER_AUDIO_INFO); +} + +static void +subtitle_tags_changed_cb (G_GNUC_UNUSED GstElement * playbin, gint stream_index, + gpointer user_data) +{ + tags_changed_cb (GST_PLAYER (user_data), stream_index, + GST_TYPE_PLAYER_SUBTITLE_INFO); +} + +static void +volume_changed_dispatch (gpointer user_data) +{ + g_signal_emit (user_data, signals[SIGNAL_VOLUME_CHANGED], 0); + g_object_notify_by_pspec (G_OBJECT (user_data), param_specs[PROP_VOLUME]); +} + +static void +volume_notify_cb (G_GNUC_UNUSED GObject * obj, G_GNUC_UNUSED GParamSpec * pspec, + GstPlayer * self) +{ + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_VOLUME_CHANGED], 0, NULL, NULL, NULL) != 0) { + gst_player_signal_dispatcher_dispatch (self->signal_dispatcher, self, + volume_changed_dispatch, g_object_ref (self), + (GDestroyNotify) g_object_unref); + } +} + +static void +mute_changed_dispatch (gpointer user_data) +{ + g_signal_emit (user_data, signals[SIGNAL_MUTE_CHANGED], 0); + g_object_notify_by_pspec (G_OBJECT (user_data), param_specs[PROP_MUTE]); +} + +static void +mute_notify_cb (G_GNUC_UNUSED GObject * obj, G_GNUC_UNUSED GParamSpec * pspec, + GstPlayer * self) +{ + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_MUTE_CHANGED], 0, NULL, NULL, NULL) != 0) { + gst_player_signal_dispatcher_dispatch (self->signal_dispatcher, self, + mute_changed_dispatch, g_object_ref (self), + (GDestroyNotify) g_object_unref); + } +} + +static gpointer +gst_player_main (gpointer data) +{ + GstPlayer *self = GST_PLAYER (data); + GstBus *bus; + GSource *source; + GSource *bus_source; + GstElement *scaletempo; + + 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); + + self->playbin = gst_element_factory_make ("playbin", "playbin"); + + if (self->video_renderer) { + GstElement *video_sink = + gst_player_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) { + if (gst_plugin_feature_check_version (GST_PLUGIN_FEATURE + (gst_element_get_factory (scaletempo)), 1, 6, 1)) { + g_object_set (self->playbin, "audio-filter", scaletempo, NULL); + } else { + gst_object_unref (scaletempo); + g_warning ("GstPlayer: scaletempo >= 1.6.1 is needed for preserving " + "audio pitch during trick modes"); + } + } else { + g_warning ("GstPlayer: scaletempo element not available. Audio pitch " + "will not be preserved during trick modes"); + } + + self->bus = bus = gst_element_get_bus (self->playbin); + bus_source = gst_bus_create_watch (bus); + g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func, + NULL, NULL); + g_source_attach (bus_source, self->context); + + 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); + + 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); + + self->target_state = GST_STATE_NULL; + self->current_state = GST_STATE_NULL; + change_state (self, GST_PLAYER_STATE_STOPPED); + self->buffering = 100; + self->is_eos = FALSE; + self->is_live = FALSE; + + GST_TRACE_OBJECT (self, "Starting main loop"); + g_main_loop_run (self->loop); + GST_TRACE_OBJECT (self, "Stopped main loop"); + + g_source_destroy (bus_source); + g_source_unref (bus_source); + 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; + } + + if (self->seek_source) + g_source_unref (self->seek_source); + self->seek_source = NULL; + 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_player_init_once (G_GNUC_UNUSED gpointer user_data) +{ + gst_init (NULL, NULL); + + GST_DEBUG_CATEGORY_INIT (gst_player_debug, "gst-player", 0, "GstPlayer"); + gst_player_error_quark (); + + return NULL; +} + +/** + * gst_player_new: + * + * Returns: a new #GstPlayer instance + */ +GstPlayer * +gst_player_new (void) +{ + return gst_player_new_full (NULL, NULL); +} + +/** + * gst_player_new_full: + * @video_renderer: (transfer full) (allow-none): GstPlayerVideoRenderer to use + * @signal_dispatcher: (transfer full) (allow-none): GstPlayerSignalDispatcher to use + * + * Creates a new #GstPlayer instance that uses @signal_dispatcher to dispatch + * signals to some event loop system, or emits signals directly if NULL is + * passed. See gst_player_g_main_context_signal_dispatcher_new(). + * + * 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: a new #GstPlayer instance + */ +GstPlayer * +gst_player_new_full (GstPlayerVideoRenderer * video_renderer, + GstPlayerSignalDispatcher * signal_dispatcher) +{ + static GOnce once = G_ONCE_INIT; + GstPlayer *self; + + g_once (&once, gst_player_init_once, NULL); + + self = + g_object_new (GST_TYPE_PLAYER, "video-renderer", video_renderer, + "signal-dispatcher", signal_dispatcher, NULL); + + if (video_renderer) + g_object_unref (video_renderer); + if (signal_dispatcher) + g_object_unref (signal_dispatcher); + + return self; +} + +static gboolean +gst_player_play_internal (gpointer user_data) +{ + GstPlayer *self = GST_PLAYER (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_PLAYER_STATE_BUFFERING); + + if (self->current_state >= GST_STATE_PAUSED && !self->is_eos) { + 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_NO_PREROLL) { + self->is_live = TRUE; + GST_DEBUG_OBJECT (self, "Pipeline is live"); + } + + if (state_ret == GST_STATE_CHANGE_FAILURE) { + emit_error (self, g_error_new (GST_PLAYER_ERROR, GST_PLAYER_ERROR_FAILED, + "Failed to play")); + 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_player_stop_internal (self); + gst_player_play_internal (self); + } + } + + return G_SOURCE_REMOVE; +} + +/** + * gst_player_play: + * @player: #GstPlayer instance + * + * Request to play the loaded stream. + */ +void +gst_player_play (GstPlayer * self) +{ + g_return_if_fail (GST_IS_PLAYER (self)); + + g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT, + gst_player_play_internal, self, NULL); +} + +static gboolean +gst_player_pause_internal (gpointer user_data) +{ + GstPlayer *self = GST_PLAYER (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_PLAYER_STATE_BUFFERING); + + state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED); + if (state_ret == GST_STATE_CHANGE_FAILURE) { + emit_error (self, g_error_new (GST_PLAYER_ERROR, GST_PLAYER_ERROR_FAILED, + "Failed to pause")); + 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_player_stop_internal (self); + gst_player_pause_internal (self); + } + } + + return G_SOURCE_REMOVE; +} + +/** + * gst_player_pause: + * @player: #GstPlayer instance + * + * Pauses the current stream. + */ +void +gst_player_pause (GstPlayer * self) +{ + g_return_if_fail (GST_IS_PLAYER (self)); + + g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT, + gst_player_pause_internal, self, NULL); +} + +static gboolean +gst_player_stop_internal (gpointer user_data) +{ + GstPlayer *self = GST_PLAYER (user_data); + + GST_DEBUG_OBJECT (self, "Stop"); + + 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, GST_PLAYER_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; + if (self->seek_source) { + g_source_destroy (self->seek_source); + g_source_unref (self->seek_source); + self->seek_source = NULL; + } + self->seek_position = GST_CLOCK_TIME_NONE; + self->last_seek_time = GST_CLOCK_TIME_NONE; + self->rate = 1.0; + g_mutex_unlock (&self->lock); + + return G_SOURCE_REMOVE; +} + +/** + * gst_player_stop: + * @player: #GstPlayer instance + * + * Stops playing the current stream and resets to the first position + * in the stream. + */ +void +gst_player_stop (GstPlayer * self) +{ + g_return_if_fail (GST_IS_PLAYER (self)); + + g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT, + gst_player_stop_internal, self, NULL); +} + +/* Must be called with lock from main context, releases lock! */ +static void +gst_player_seek_internal_locked (GstPlayer * self) +{ + gboolean ret; + GstClockTime position; + gdouble rate; + GstStateChangeReturn state_ret; + GstEvent *s_event; + GstSeekFlags flags = 0; + + if (self->seek_source) { + g_source_destroy (self->seek_source); + g_source_unref (self->seek_source); + self->seek_source = NULL; + } + + /* 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) { + emit_error (self, g_error_new (GST_PLAYER_ERROR, GST_PLAYER_ERROR_FAILED, + "Failed to seek")); + 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; + +#if GST_CHECK_VERSION(1,5,0) + if (rate != 1.0) { + flags |= GST_SEEK_FLAG_TRICKMODE; + } +#endif + + if (rate >= 0.0) { + s_event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags, + GST_SEEK_TYPE_SET, position, GST_SEEK_TYPE_NONE, 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) + emit_error (self, g_error_new (GST_PLAYER_ERROR, GST_PLAYER_ERROR_FAILED, + "Failed to seek to %" GST_TIME_FORMAT, GST_TIME_ARGS (position))); + + g_mutex_lock (&self->lock); +} + +static gboolean +gst_player_seek_internal (gpointer user_data) +{ + GstPlayer *self = GST_PLAYER (user_data); + + g_mutex_lock (&self->lock); + gst_player_seek_internal_locked (self); + g_mutex_unlock (&self->lock); + + return G_SOURCE_REMOVE; +} + +static gboolean +gst_player_set_rate_internal (gpointer user_data) +{ + GstPlayer *self = user_data; + + g_mutex_lock (&self->lock); + + self->seek_position = gst_player_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_player_seek_internal, self, NULL); + g_source_attach (self->seek_source, self->context); + } + } + + g_mutex_unlock (&self->lock); + + return G_SOURCE_REMOVE; +} + +/** + * gst_player_set_rate: + * @player: #GstPlayer instance + * @rate: playback rate + * + * Playback at specified rate + */ +void +gst_player_set_rate (GstPlayer * self, gdouble rate) +{ + g_return_if_fail (GST_IS_PLAYER (self)); + g_return_if_fail (rate != 0.0); + + g_mutex_lock (&self->lock); + self->rate = rate; + g_mutex_unlock (&self->lock); + + gst_player_set_rate_internal (self); +} + +/** + * gst_player_get_rate: + * @player: #GstPlayer instance + * + * Returns: current playback rate + */ +gdouble +gst_player_get_rate (GstPlayer * self) +{ + g_return_val_if_fail (GST_IS_PLAYER (self), DEFAULT_RATE); + + return self->rate; +} + +static gboolean +gst_player_set_position_update_interval_internal (gpointer user_data) +{ + GstPlayer *self = user_data; + + g_mutex_lock (&self->lock); + + if (self->tick_source) { + remove_tick_source (self); + add_tick_source (self); + } + + g_mutex_unlock (&self->lock); + + return G_SOURCE_REMOVE; +} + +/** + * gst_player_set_position_update_interval: + * @player: #GstPlayer instance + * @interval: interval in ms + * + * Set interval in milliseconds between two position-updated signals. + * Pass 0 to stop updating the position. + */ +void +gst_player_set_position_update_interval (GstPlayer * self, guint interval) +{ + g_return_if_fail (GST_IS_PLAYER (self)); + g_return_if_fail (interval <= 10000); + + g_mutex_lock (&self->lock); + self->position_update_interval_ms = interval; + g_mutex_unlock (&self->lock); + + gst_player_set_position_update_interval_internal (self); +} + +/** + * gst_player_get_position_update_interval: + * @player: #GstPlayer instance + * + * Returns: current position update interval in milliseconds + */ +guint +gst_player_get_position_update_interval (GstPlayer * self) +{ + g_return_val_if_fail (GST_IS_PLAYER (self), + DEFAULT_POSITION_UPDATE_INTERVAL_MS); + + return self->position_update_interval_ms; +} + +/** + * gst_player_seek: + * @player: #GstPlayer instance + * @position: position to seek in nanoseconds + * + * Seeks the currently-playing stream to the absolute @position time + * in nanoseconds. + */ +void +gst_player_seek (GstPlayer * self, GstClockTime position) +{ + g_return_if_fail (GST_IS_PLAYER (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_player_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_player_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); +} + +/** + * gst_player_get_uri: + * @player: #GstPlayer 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. + */ +gchar * +gst_player_get_uri (GstPlayer * self) +{ + gchar *val; + + g_return_val_if_fail (GST_IS_PLAYER (self), DEFAULT_URI); + + g_object_get (self, "uri", &val, NULL); + + return val; +} + +/** + * gst_player_set_uri: + * @player: #GstPlayer instance + * @uri: next URI to play. + * + * Sets the next URI to play. + */ +void +gst_player_set_uri (GstPlayer * self, const gchar * val) +{ + g_return_if_fail (GST_IS_PLAYER (self)); + + g_object_set (self, "uri", val, NULL); +} + +/** + * gst_player_get_position: + * @player: #GstPlayer instance + * + * Returns: the absolute position time, in nanoseconds, of the + * currently-playing stream. + */ +GstClockTime +gst_player_get_position (GstPlayer * self) +{ + GstClockTime val; + + g_return_val_if_fail (GST_IS_PLAYER (self), DEFAULT_POSITION); + + g_object_get (self, "position", &val, NULL); + + return val; +} + +/** + * gst_player_get_duration: + * @player: #GstPlayer instance + * + * Retrieves the duration of the media stream that self represents. + * + * Returns: the duration of the currently-playing media stream, in + * nanoseconds. + */ +GstClockTime +gst_player_get_duration (GstPlayer * self) +{ + GstClockTime val; + + g_return_val_if_fail (GST_IS_PLAYER (self), DEFAULT_DURATION); + + g_object_get (self, "duration", &val, NULL); + + return val; +} + +/** + * gst_player_get_volume: + * @player: #GstPlayer instance + * + * Returns the current volume level, as a percentage between 0 and 1. + * + * Returns: the volume as percentage between 0 and 1. + */ +gdouble +gst_player_get_volume (GstPlayer * self) +{ + gdouble val; + + g_return_val_if_fail (GST_IS_PLAYER (self), DEFAULT_VOLUME); + + g_object_get (self, "volume", &val, NULL); + + return val; +} + +/** + * gst_player_set_volume: + * @player: #GstPlayer 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. + */ +void +gst_player_set_volume (GstPlayer * self, gdouble val) +{ + g_return_if_fail (GST_IS_PLAYER (self)); + + g_object_set (self, "volume", val, NULL); +} + +/** + * gst_player_get_mute: + * @player: #GstPlayer instance + * + * Returns: %TRUE if the currently-playing stream is muted. + */ +gboolean +gst_player_get_mute (GstPlayer * self) +{ + gboolean val; + + g_return_val_if_fail (GST_IS_PLAYER (self), DEFAULT_MUTE); + + g_object_get (self, "mute", &val, NULL); + + return val; +} + +/** + * gst_player_set_mute: + * @player: #GstPlayer instance + * @val: Mute state the should be set + * + * %TRUE if the currently-playing stream should be muted. + */ +void +gst_player_set_mute (GstPlayer * self, gboolean val) +{ + g_return_if_fail (GST_IS_PLAYER (self)); + + g_object_set (self, "mute", val, NULL); +} + +/** + * gst_player_get_pipeline: + * @player: #GstPlayer instance + * + * Returns: (transfer full): The internal playbin instance + */ +GstElement * +gst_player_get_pipeline (GstPlayer * self) +{ + GstElement *val; + + g_return_val_if_fail (GST_IS_PLAYER (self), NULL); + + g_object_get (self, "pipeline", &val, NULL); + + return val; +} + +/** + * gst_player_get_media_info: + * @player: #GstPlayer instance + * + * A Function to get the current media info #GstPlayerMediaInfo instance. + * + * Returns: (transfer full): media info instance. + * + * The caller should free it with g_object_unref() + */ +GstPlayerMediaInfo * +gst_player_get_media_info (GstPlayer * self) +{ + GstPlayerMediaInfo *info; + + g_return_val_if_fail (GST_IS_PLAYER (self), NULL); + + if (!self->media_info) + return NULL; + + g_mutex_lock (&self->lock); + info = gst_player_media_info_copy (self->media_info); + g_mutex_unlock (&self->lock); + + return info; +} + +/** + * gst_player_get_current_audio_track: + * @player: #GstPlayer instance + * + * A Function to get current audio #GstPlayerAudioInfo instance. + * + * Returns: (transfer full): current audio track. + * + * The caller should free it with g_object_unref() + */ +GstPlayerAudioInfo * +gst_player_get_current_audio_track (GstPlayer * self) +{ + GstPlayerAudioInfo *info; + + g_return_val_if_fail (GST_IS_PLAYER (self), NULL); + + if (!is_track_enabled (self, GST_PLAY_FLAG_AUDIO)) + return NULL; + + info = (GstPlayerAudioInfo *) gst_player_stream_info_get_current (self, + "current-audio", GST_TYPE_PLAYER_AUDIO_INFO); + return info; +} + +/** + * gst_player_get_current_video_track: + * @player: #GstPlayer instance + * + * A Function to get current video #GstPlayerVideoInfo instance. + * + * Returns: (transfer full): current video track. + * + * The caller should free it with g_object_unref() + */ +GstPlayerVideoInfo * +gst_player_get_current_video_track (GstPlayer * self) +{ + GstPlayerVideoInfo *info; + + g_return_val_if_fail (GST_IS_PLAYER (self), NULL); + + if (!is_track_enabled (self, GST_PLAY_FLAG_VIDEO)) + return NULL; + + info = (GstPlayerVideoInfo *) gst_player_stream_info_get_current (self, + "current-video", GST_TYPE_PLAYER_VIDEO_INFO); + return info; +} + +/** + * gst_player_get_current_subtitle_track: + * @player: #GstPlayer instance + * + * A Function to get current subtitle #GstPlayerSubtitleInfo instance. + * + * Returns: (transfer none): current subtitle track. + * + * The caller should free it with g_object_unref() + */ +GstPlayerSubtitleInfo * +gst_player_get_current_subtitle_track (GstPlayer * self) +{ + GstPlayerSubtitleInfo *info; + + g_return_val_if_fail (GST_IS_PLAYER (self), NULL); + + if (!is_track_enabled (self, GST_PLAY_FLAG_SUBTITLE)) + return NULL; + + info = (GstPlayerSubtitleInfo *) gst_player_stream_info_get_current (self, + "current-text", GST_TYPE_PLAYER_SUBTITLE_INFO); + return info; +} + +/** + * gst_player_set_audio_track: + * @player: #GstPlayer instance + * @stream_index: stream index + * + * Returns: %TRUE or %FALSE + * + * Sets the audio track @stream_idex. + */ +gboolean +gst_player_set_audio_track (GstPlayer * self, gint stream_index) +{ + GstPlayerStreamInfo *info; + + g_return_val_if_fail (GST_IS_PLAYER (self), 0); + + g_mutex_lock (&self->lock); + info = gst_player_stream_info_find (self->media_info, + GST_TYPE_PLAYER_AUDIO_INFO, stream_index); + g_mutex_unlock (&self->lock); + if (!info) { + GST_ERROR_OBJECT (self, "invalid audio stream index %d", stream_index); + return FALSE; + } + + g_object_set (G_OBJECT (self->playbin), "current-audio", stream_index, NULL); + GST_DEBUG_OBJECT (self, "set stream index '%d'", stream_index); + return TRUE; +} + +/** + * gst_player_set_video_track: + * @player: #GstPlayer instance + * @stream_index: stream index + * + * Returns: %TRUE or %FALSE + * + * Sets the video track @stream_index. + */ +gboolean +gst_player_set_video_track (GstPlayer * self, gint stream_index) +{ + GstPlayerStreamInfo *info; + + g_return_val_if_fail (GST_IS_PLAYER (self), 0); + + /* check if stream_index exist in our internal media_info list */ + g_mutex_lock (&self->lock); + info = gst_player_stream_info_find (self->media_info, + GST_TYPE_PLAYER_VIDEO_INFO, stream_index); + g_mutex_unlock (&self->lock); + if (!info) { + GST_ERROR_OBJECT (self, "invalid video stream index %d", stream_index); + return FALSE; + } + + g_object_set (G_OBJECT (self->playbin), "current-video", stream_index, NULL); + GST_DEBUG_OBJECT (self, "set stream index '%d'", stream_index); + return TRUE; +} + +/** + * gst_player_set_subtitle_track: + * @player: #GstPlayer instance + * @stream_index: stream index + * + * Returns: %TRUE or %FALSE + * + * Sets the subtitle strack @stream_index. + */ +gboolean +gst_player_set_subtitle_track (GstPlayer * self, gint stream_index) +{ + GstPlayerStreamInfo *info; + + g_return_val_if_fail (GST_IS_PLAYER (self), 0); + + g_mutex_lock (&self->lock); + info = gst_player_stream_info_find (self->media_info, + GST_TYPE_PLAYER_SUBTITLE_INFO, stream_index); + g_mutex_unlock (&self->lock); + if (!info) { + GST_ERROR_OBJECT (self, "invalid subtitle stream index %d", stream_index); + return FALSE; + } + + g_object_set (G_OBJECT (self->playbin), "current-text", stream_index, NULL); + GST_DEBUG_OBJECT (self, "set stream index '%d'", stream_index); + return TRUE; +} + +/** + * gst_player_set_audio_track_enabled: + * @player: #GstPlayer instance + * @enabled: TRUE or FALSE + * + * Enable or disable the current audio track. + */ +void +gst_player_set_audio_track_enabled (GstPlayer * self, gboolean enabled) +{ + g_return_if_fail (GST_IS_PLAYER (self)); + + if (enabled) + player_set_flag (self, GST_PLAY_FLAG_AUDIO); + else + player_clear_flag (self, GST_PLAY_FLAG_AUDIO); + + GST_DEBUG_OBJECT (self, "track is '%s'", enabled ? "Enabled" : "Disabled"); +} + +/** + * gst_player_set_video_track_enabled: + * @player: #GstPlayer instance + * @enabled: TRUE or FALSE + * + * Enable or disable the current video track. + */ +void +gst_player_set_video_track_enabled (GstPlayer * self, gboolean enabled) +{ + g_return_if_fail (GST_IS_PLAYER (self)); + + if (enabled) + player_set_flag (self, GST_PLAY_FLAG_VIDEO); + else + player_clear_flag (self, GST_PLAY_FLAG_VIDEO); + + GST_DEBUG_OBJECT (self, "track is '%s'", enabled ? "Enabled" : "Disabled"); +} + +/** + * gst_player_set_subtitle_track_enabled: + * @player: #GstPlayer instance + * @enabled: TRUE or FALSE + * + * Enable or disable the current subtitle track. + */ +void +gst_player_set_subtitle_track_enabled (GstPlayer * self, gboolean enabled) +{ + g_return_if_fail (GST_IS_PLAYER (self)); + + if (enabled) + player_set_flag (self, GST_PLAY_FLAG_SUBTITLE); + else + player_clear_flag (self, GST_PLAY_FLAG_SUBTITLE); + + GST_DEBUG_OBJECT (self, "track is '%s'", enabled ? "Enabled" : "Disabled"); +} + +/** + * gst_player_set_subtitle_uri: + * @player: #GstPlayer instance + * @uri: subtitle URI + * + * Returns: %TRUE or %FALSE + * + * Sets the external subtitle URI. + */ +gboolean +gst_player_set_subtitle_uri (GstPlayer * self, const gchar * suburi) +{ + g_return_val_if_fail (GST_IS_PLAYER (self), FALSE); + + g_mutex_lock (&self->lock); + g_free (self->suburi); + self->suburi = g_strdup (suburi); + g_mutex_unlock (&self->lock); + + gst_player_set_suburi_internal (self); + + return TRUE; +} + +/** + * gst_player_get_subtitle_uri: + * @player: #GstPlayer instance + * + * current subtitle URI + * + * Returns: (transfer full): URI of the current external subtitle. + * g_free() after usage. + */ +gchar * +gst_player_get_subtitle_uri (GstPlayer * self) +{ + gchar *val = NULL; + + g_return_val_if_fail (GST_IS_PLAYER (self), NULL); + + g_object_get (self, "suburi", &val, NULL); + + return val; +} + +/** + * gst_player_set_visualization: + * @player: #GstPlayer instance + * @name: visualization element obtained from + * #gst_player_visualizations_get() + * + * Returns: %TRUE if the visualizations was set correctly. Otherwise, + * %FALSE. + */ +gboolean +gst_player_set_visualization (GstPlayer * self, const gchar * name) +{ + g_return_val_if_fail (GST_IS_PLAYER (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_player_get_current_visualization: + * @player: #GstPlayer instance + * + * Returns: (transfer full): Name of the currently enabled visualization. + * g_free() after usage. + */ +gchar * +gst_player_get_current_visualization (GstPlayer * self) +{ + gchar *name = NULL; + GstElement *vis_plugin = NULL; + + g_return_val_if_fail (GST_IS_PLAYER (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_player_set_visualization_enabled: + * @player: #GstPlayer instance + * @enabled: TRUE or FALSE + * + * Enable or disable the visualization. + */ +void +gst_player_set_visualization_enabled (GstPlayer * self, gboolean enabled) +{ + g_return_if_fail (GST_IS_PLAYER (self)); + + if (enabled) + player_set_flag (self, GST_PLAY_FLAG_VIS); + else + player_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_PLAYER_COLOR_BALANCE_BRIGHTNESS */ {"BRIGHTNESS", "brightness"}, + /* GST_PLAYER_COLOR_BALANCE_CONTRAST */ {"CONTRAST", "contrast"}, + /* GST_PLAYER_COLOR_BALANCE_SATURATION */ {"SATURATION", "saturation"}, + /* GST_PLAYER_COLOR_BALANCE_HUE */ {"HUE", "hue"}, +}; + +static GstColorBalanceChannel * +gst_player_color_balance_find_channel (GstPlayer * self, + GstPlayerColorBalanceType type) +{ + GstColorBalanceChannel *channel; + const GList *l, *channels; + + if (type < GST_PLAYER_COLOR_BALANCE_BRIGHTNESS || + type > GST_PLAYER_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_player_has_color_balance: + * @player:#GstPlayer instance + * + * Checks whether the @player has color balance support available. + * + * Returns: %TRUE if @player has color balance support. Otherwise, + * %FALSE. + */ +gboolean +gst_player_has_color_balance (GstPlayer * self) +{ + const GList *channels; + + g_return_val_if_fail (GST_IS_PLAYER (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_player_set_color_balance: + * @player: #GstPlayer instance + * @type: #GstPlayerColorBalanceType + * @value: The new value for the @type, ranged [0,1] + * + * Sets the current value of the indicated channel @type to the passed + * value. + */ +void +gst_player_set_color_balance (GstPlayer * self, GstPlayerColorBalanceType type, + gdouble value) +{ + GstColorBalanceChannel *channel; + gdouble new_val; + + g_return_if_fail (GST_IS_PLAYER (self)); + g_return_if_fail (value >= 0.0 && value <= 1.0); + + if (!GST_IS_COLOR_BALANCE (self->playbin)) + return; + + channel = gst_player_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_player_get_color_balance: + * @player: #GstPlayer instance + * @type: #GstPlayerColorBalanceType + * + * Retrieve the current value of the indicated @type. + * + * Returns: The current value of @type, between [0,1]. In case of + * error -1 is returned. + */ +gdouble +gst_player_get_color_balance (GstPlayer * self, GstPlayerColorBalanceType type) +{ + GstColorBalanceChannel *channel; + gint value; + + g_return_val_if_fail (GST_IS_PLAYER (self), -1); + + if (!GST_IS_COLOR_BALANCE (self->playbin)) + return -1; + + channel = gst_player_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); +} + +#define C_ENUM(v) ((gint) v) +#define C_FLAGS(v) ((guint) v) + +GType +gst_player_color_balance_type_get_type (void) +{ + static gsize id = 0; + static const GEnumValue values[] = { + {C_ENUM (GST_PLAYER_COLOR_BALANCE_HUE), "GST_PLAYER_COLOR_BALANCE_HUE", + "hue"}, + {C_ENUM (GST_PLAYER_COLOR_BALANCE_BRIGHTNESS), + "GST_PLAYER_COLOR_BALANCE_BRIGHTNESS", "brightness"}, + {C_ENUM (GST_PLAYER_COLOR_BALANCE_SATURATION), + "GST_PLAYER_COLOR_BALANCE_SATURATION", "saturation"}, + {C_ENUM (GST_PLAYER_COLOR_BALANCE_CONTRAST), + "GST_PLAYER_COLOR_BALANCE_CONTRAST", "contrast"}, + {0, NULL, NULL} + }; + + if (g_once_init_enter (&id)) { + GType tmp = g_enum_register_static ("GstPlayerColorBalanceType", values); + g_once_init_leave (&id, tmp); + } + + return (GType) id; +} + +/** + * gst_player_color_balance_type_get_name: + * @type: a #GstPlayerColorBalanceType + * + * Gets a string representing the given color balance type. + * + * Returns: (transfer none): a string with the name of the color + * balance type. + */ +const gchar * +gst_player_color_balance_type_get_name (GstPlayerColorBalanceType type) +{ + g_return_val_if_fail (type >= GST_PLAYER_COLOR_BALANCE_BRIGHTNESS && + type <= GST_PLAYER_COLOR_BALANCE_HUE, NULL); + + return cb_channel_map[type].name; +} + +GType +gst_player_state_get_type (void) +{ + static gsize id = 0; + static const GEnumValue values[] = { + {C_ENUM (GST_PLAYER_STATE_STOPPED), "GST_PLAYER_STATE_STOPPED", "stopped"}, + {C_ENUM (GST_PLAYER_STATE_BUFFERING), "GST_PLAYER_STATE_BUFFERING", + "buffering"}, + {C_ENUM (GST_PLAYER_STATE_PAUSED), "GST_PLAYER_STATE_PAUSED", "paused"}, + {C_ENUM (GST_PLAYER_STATE_PLAYING), "GST_PLAYER_STATE_PLAYING", "playing"}, + {0, NULL, NULL} + }; + + if (g_once_init_enter (&id)) { + GType tmp = g_enum_register_static ("GstPlayerState", values); + g_once_init_leave (&id, tmp); + } + + return (GType) id; +} + +/** + * gst_player_state_get_name: + * @state: a #GstPlayerState + * + * Gets a string representing the given state. + * + * Returns: (transfer none): a string with the name of the state. + */ +const gchar * +gst_player_state_get_name (GstPlayerState state) +{ + switch (state) { + case GST_PLAYER_STATE_STOPPED: + return "stopped"; + case GST_PLAYER_STATE_BUFFERING: + return "buffering"; + case GST_PLAYER_STATE_PAUSED: + return "paused"; + case GST_PLAYER_STATE_PLAYING: + return "playing"; + } + + g_assert_not_reached (); + return NULL; +} + +GType +gst_player_error_get_type (void) +{ + static gsize id = 0; + static const GEnumValue values[] = { + {C_ENUM (GST_PLAYER_ERROR_FAILED), "GST_PLAYER_ERROR_FAILED", "failed"}, + {0, NULL, NULL} + }; + + if (g_once_init_enter (&id)) { + GType tmp = g_enum_register_static ("GstPlayerError", values); + g_once_init_leave (&id, tmp); + } + + return (GType) id; +} + +/** + * gst_player_error_get_name: + * @error: a #GstPlayerError + * + * Gets a string representing the given error. + * + * Returns: (transfer none): a string with the given error. + */ +const gchar * +gst_player_error_get_name (GstPlayerError error) +{ + switch (error) { + case GST_PLAYER_ERROR_FAILED: + return "failed"; + } + + g_assert_not_reached (); + return NULL; +} diff --git a/gst-libs/gst/player/gstplayer.h b/gst-libs/gst/player/gstplayer.h new file mode 100644 index 0000000000..29287bdc66 --- /dev/null +++ b/gst-libs/gst/player/gstplayer.h @@ -0,0 +1,184 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * + * 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_PLAYER_H__ +#define __GST_PLAYER_H__ + +#include +#include +#include +#include +#include + +G_BEGIN_DECLS + +GType gst_player_state_get_type (void); +#define GST_TYPE_PLAYER_STATE (gst_player_state_get_type ()) + +/** + * GstPlayerState: + * @GST_PLAYER_STATE_STOPPED: the player is stopped. + * @GST_PLAYER_STATE_BUFFERING: the player is buffering. + * @GST_PLAYER_STATE_PAUSED: the player is paused. + * @GST_PLAYER_STATE_PLAYING: the player is currently playing a + * stream. + */ +typedef enum +{ + GST_PLAYER_STATE_STOPPED, + GST_PLAYER_STATE_BUFFERING, + GST_PLAYER_STATE_PAUSED, + GST_PLAYER_STATE_PLAYING +} GstPlayerState; + +const gchar *gst_player_state_get_name (GstPlayerState state); + +GQuark gst_player_error_quark (void); +GType gst_player_error_get_type (void); +#define GST_PLAYER_ERROR (gst_player_error_quark ()) +#define GST_TYPE_PLAYER_ERROR (gst_player_error_get_type ()) + +/** + * GstPlayerError: + * @GST_PLAYER_ERROR_FAILED: generic error. + */ +typedef enum { + GST_PLAYER_ERROR_FAILED = 0 +} GstPlayerError; + +const gchar *gst_player_error_get_name (GstPlayerError error); + +GType gst_player_color_balance_type_get_type (void); +#define GST_TYPE_PLAYER_COLOR_BALANCE_TYPE (gst_player_color_balance_type_get_type ()) + +/** + * GstPlayerColorBalanceType: + * @GST_PLAYER_COLOR_BALANCE_BRIGHTNESS: brightness or black level. + * @GST_PLAYER_COLOR_BALANCE_CONTRAST: contrast or luma gain. + * @GST_PLAYER_COLOR_BALANCE_SATURATION: color saturation or chroma + * gain. + * @GST_PLAYER_COLOR_BALANCE_HUE: hue or color balance. + */ +typedef enum +{ + GST_PLAYER_COLOR_BALANCE_BRIGHTNESS, + GST_PLAYER_COLOR_BALANCE_CONTRAST, + GST_PLAYER_COLOR_BALANCE_SATURATION, + GST_PLAYER_COLOR_BALANCE_HUE, +} GstPlayerColorBalanceType; + +const gchar *gst_player_color_balance_type_get_name (GstPlayerColorBalanceType type); + +#define GST_TYPE_PLAYER (gst_player_get_type ()) +#define GST_IS_PLAYER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PLAYER)) +#define GST_IS_PLAYER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PLAYER)) +#define GST_PLAYER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_PLAYER, GstPlayerClass)) +#define GST_PLAYER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PLAYER, GstPlayer)) +#define GST_PLAYER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_PLAYER, GstPlayerClass)) +#define GST_PLAYER_CAST(obj) ((GstPlayer*)(obj)) + + +GType gst_player_get_type (void); + +GstPlayer * gst_player_new (void); +GstPlayer * gst_player_new_full (GstPlayerVideoRenderer * video_renderer, GstPlayerSignalDispatcher * signal_dispatcher); + +void gst_player_play (GstPlayer * player); +void gst_player_pause (GstPlayer * player); +void gst_player_stop (GstPlayer * player); + +void gst_player_seek (GstPlayer * player, + GstClockTime position); +void gst_player_set_rate (GstPlayer * player, + gdouble rate); +gdouble gst_player_get_rate (GstPlayer * player); + +void gst_player_set_position_update_interval (GstPlayer * player, + guint interval); +guint gst_player_get_position_update_interval (GstPlayer * player); + +gchar * gst_player_get_uri (GstPlayer * player); +void gst_player_set_uri (GstPlayer * player, + const gchar * uri); + +GstClockTime gst_player_get_position (GstPlayer * player); +GstClockTime gst_player_get_duration (GstPlayer * player); + +gdouble gst_player_get_volume (GstPlayer * player); +void gst_player_set_volume (GstPlayer * player, + gdouble val); + +gboolean gst_player_get_mute (GstPlayer * player); +void gst_player_set_mute (GstPlayer * player, + gboolean val); + +GstElement * gst_player_get_pipeline (GstPlayer * player); + +void gst_player_set_video_track_enabled (GstPlayer * player, + gboolean enabled); + +void gst_player_set_audio_track_enabled (GstPlayer * player, + gboolean enabled); + +void gst_player_set_subtitle_track_enabled (GstPlayer * player, + gboolean enabled); + +gboolean gst_player_set_audio_track (GstPlayer *player, + gint stream_index); + +gboolean gst_player_set_video_track (GstPlayer *player, + gint stream_index); + +gboolean gst_player_set_subtitle_track (GstPlayer *player, + gint stream_index); + +GstPlayerMediaInfo * gst_player_get_media_info (GstPlayer * player); + +GstPlayerAudioInfo * gst_player_get_current_audio_track + (GstPlayer * player); + +GstPlayerVideoInfo * gst_player_get_current_video_track + (GstPlayer * player); + +GstPlayerSubtitleInfo * gst_player_get_current_subtitle_track + (GstPlayer * player); + +gboolean gst_player_set_subtitle_uri (GstPlayer * player, + const gchar *uri); +gchar * gst_player_get_subtitle_uri (GstPlayer * player); + +gboolean gst_player_set_visualization (GstPlayer * player, + const gchar *name); + +void gst_player_set_visualization_enabled (GstPlayer * player, + gboolean enabled); + +gchar * gst_player_get_current_visualization (GstPlayer * player); + +gboolean gst_player_has_color_balance (GstPlayer * player); +void gst_player_set_color_balance (GstPlayer * player, + GstPlayerColorBalanceType type, + gdouble value); +gdouble gst_player_get_color_balance (GstPlayer * player, + GstPlayerColorBalanceType type); + +G_END_DECLS + +#endif /* __GST_PLAYER_H__ */ diff --git a/gst-libs/gst/player/player.h b/gst-libs/gst/player/player.h new file mode 100644 index 0000000000..0fc91d6a08 --- /dev/null +++ b/gst-libs/gst/player/player.h @@ -0,0 +1,30 @@ +/* GStreamer + * + * Copyright (C) 2014 Sebastian Dröge + * + * 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 __PLAYER_H__ +#define __PLAYER_H__ + +#include +#include +#include +#include +#include + +#endif /* __PLAYER_H__ */ diff --git a/win32/common/libgstplayer.def b/win32/common/libgstplayer.def new file mode 100644 index 0000000000..c994674229 --- /dev/null +++ b/win32/common/libgstplayer.def @@ -0,0 +1,92 @@ +EXPORTS + gst_player_audio_info_get_bitrate + gst_player_audio_info_get_channels + gst_player_audio_info_get_language + gst_player_audio_info_get_max_bitrate + gst_player_audio_info_get_sample_rate + gst_player_audio_info_get_type + gst_player_color_balance_type_get_name + gst_player_color_balance_type_get_type + gst_player_error_get_name + gst_player_error_get_type + gst_player_error_quark + gst_player_g_main_context_signal_dispatcher_get_type + gst_player_g_main_context_signal_dispatcher_new + gst_player_get_audio_streams + gst_player_get_color_balance + gst_player_get_current_audio_track + gst_player_get_current_subtitle_track + gst_player_get_current_video_track + gst_player_get_current_visualization + gst_player_get_duration + gst_player_get_media_info + gst_player_get_mute + gst_player_get_pipeline + gst_player_get_position + gst_player_get_position_update_interval + gst_player_get_rate + gst_player_get_subtitle_streams + gst_player_get_subtitle_uri + gst_player_get_type + gst_player_get_uri + gst_player_get_video_streams + gst_player_get_volume + gst_player_has_color_balance + gst_player_media_info_get_container_format + gst_player_media_info_get_duration + gst_player_media_info_get_image_sample + gst_player_media_info_get_stream_list + gst_player_media_info_get_tags + gst_player_media_info_get_title + gst_player_media_info_get_type + gst_player_media_info_get_uri + gst_player_media_info_is_seekable + gst_player_new + gst_player_new_full + gst_player_pause + gst_player_play + gst_player_seek + gst_player_set_audio_track + gst_player_set_audio_track_enabled + gst_player_set_color_balance + gst_player_set_mute + gst_player_set_position_update_interval + gst_player_set_rate + gst_player_set_subtitle_track + gst_player_set_subtitle_track_enabled + gst_player_set_subtitle_uri + gst_player_set_uri + gst_player_set_video_track + gst_player_set_video_track_enabled + gst_player_set_visualization + gst_player_set_visualization_enabled + gst_player_set_volume + gst_player_signal_dispatcher_get_type + gst_player_state_get_name + gst_player_state_get_type + gst_player_stop + gst_player_stream_info_get_caps + gst_player_stream_info_get_codec + gst_player_stream_info_get_index + gst_player_stream_info_get_stream_type + gst_player_stream_info_get_tags + gst_player_stream_info_get_type + gst_player_subtitle_info_get_language + gst_player_subtitle_info_get_type + gst_player_video_info_get_bitrate + gst_player_video_info_get_framerate + gst_player_video_info_get_height + gst_player_video_info_get_max_bitrate + gst_player_video_info_get_pixel_aspect_ratio + gst_player_video_info_get_type + gst_player_video_info_get_width + gst_player_video_overlay_video_renderer_get_type + gst_player_video_overlay_video_renderer_get_window_handle + gst_player_video_overlay_video_renderer_new + gst_player_video_overlay_video_renderer_set_window_handle + gst_player_video_renderer_get_type + gst_player_visualization_copy + gst_player_visualization_free + gst_player_visualization_get_type + gst_player_visualizations_free + gst_player_visualizations_get