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