63bbf64e9a0651606ef71ec191f2bd2262b3d4d1
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-bad / gst-libs / gst / play / gstplay.c
1 /* GStreamer
2  *
3  * Copyright (C) 2014-2015 Sebastian Dröge <sebastian@centricular.com>
4  * Copyright (C) 2015 Brijesh Singh <brijesh.ksingh@gmail.com>
5  * Copyright (C) 2019-2020 Stephan Hesse <stephan@emliri.com>
6  * Copyright (C) 2020 Philippe Normand <philn@igalia.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23
24 /**
25  * SECTION:gstplay
26  * @title: GstPlay
27  * @short_description: Player
28  * @symbols:
29  * - GstPlay
30  *
31  * Since: 1.20
32  */
33
34 /* TODO:
35  *
36  * - Equalizer
37  * - Gapless playback
38  * - Frame stepping
39  * - Subtitle font, connection speed
40  * - Deinterlacing
41  * - Buffering control (-> progressive downloading)
42  * - Playlist/queue object
43  * - Custom video sink (e.g. embed in GL scene)
44  *
45  */
46
47 #ifdef HAVE_CONFIG_H
48 #include "config.h"
49 #endif
50
51 #include "gstplay.h"
52 #include "gstplay-video-renderer-private.h"
53 #include "gstplay-media-info-private.h"
54 #include "gstplay-message-private.h"
55
56 #include <gst/gst.h>
57 #include <gst/video/video.h>
58 #include <gst/video/colorbalance.h>
59 #include <gst/tag/tag.h>
60 #include <gst/pbutils/descriptions.h>
61
62 #include <string.h>
63
64 GST_DEBUG_CATEGORY_STATIC (gst_play_debug);
65 #define GST_CAT_DEFAULT gst_play_debug
66
67 #define DEFAULT_URI NULL
68 #define DEFAULT_POSITION GST_CLOCK_TIME_NONE
69 #define DEFAULT_DURATION GST_CLOCK_TIME_NONE
70 #define DEFAULT_VOLUME 1.0
71 #define DEFAULT_MUTE FALSE
72 #define DEFAULT_RATE 1.0
73 #define DEFAULT_POSITION_UPDATE_INTERVAL_MS 100
74 #define DEFAULT_AUDIO_VIDEO_OFFSET 0
75 #define DEFAULT_SUBTITLE_VIDEO_OFFSET 0
76
77 /**
78  * gst_play_error_quark:
79  * Since: 1.20
80  */
81 GQuark
82 gst_play_error_quark (void)
83 {
84   return g_quark_from_static_string ("gst-play-error-quark");
85 }
86
87 static GQuark QUARK_CONFIG;
88
89 /* Keep ConfigQuarkId and _config_quark_strings ordered and synced */
90 typedef enum
91 {
92   CONFIG_QUARK_USER_AGENT = 0,
93   CONFIG_QUARK_POSITION_INTERVAL_UPDATE,
94   CONFIG_QUARK_ACCURATE_SEEK,
95
96   CONFIG_QUARK_MAX
97 } ConfigQuarkId;
98
99 static const gchar *_config_quark_strings[] = {
100   "user-agent",
101   "position-interval-update",
102   "accurate-seek",
103 };
104
105 static GQuark _config_quark_table[CONFIG_QUARK_MAX];
106
107 #define CONFIG_QUARK(q) _config_quark_table[CONFIG_QUARK_##q]
108
109 enum
110 {
111   PROP_0,
112   PROP_VIDEO_RENDERER,
113   PROP_URI,
114   PROP_SUBURI,
115   PROP_POSITION,
116   PROP_DURATION,
117   PROP_MEDIA_INFO,
118   PROP_CURRENT_AUDIO_TRACK,
119   PROP_CURRENT_VIDEO_TRACK,
120   PROP_CURRENT_SUBTITLE_TRACK,
121   PROP_VOLUME,
122   PROP_MUTE,
123   PROP_RATE,
124   PROP_PIPELINE,
125   PROP_VIDEO_MULTIVIEW_MODE,
126   PROP_VIDEO_MULTIVIEW_FLAGS,
127   PROP_AUDIO_VIDEO_OFFSET,
128   PROP_SUBTITLE_VIDEO_OFFSET,
129   PROP_LAST
130 };
131
132 enum
133 {
134   GST_PLAY_FLAG_VIDEO = (1 << 0),
135   GST_PLAY_FLAG_AUDIO = (1 << 1),
136   GST_PLAY_FLAG_SUBTITLE = (1 << 2),
137   GST_PLAY_FLAG_VIS = (1 << 3)
138 };
139
140 struct _GstPlay
141 {
142   GstObject parent;
143
144   GstPlayVideoRenderer *video_renderer;
145
146   gchar *uri;
147   gchar *redirect_uri;
148   gchar *suburi;
149
150   GThread *thread;
151   GMutex lock;
152   GCond cond;
153   GMainContext *context;
154   GMainLoop *loop;
155
156   GstBus *api_bus;
157
158   GstElement *playbin;
159   GstBus *bus;
160   GstState target_state, current_state;
161   gboolean is_live, is_eos;
162   GSource *tick_source, *ready_timeout_source;
163
164   GstClockTime cached_duration;
165   gint64 cached_position;
166
167   gdouble rate;
168
169   GstPlayState app_state;
170
171   gint buffering_percent;
172
173   GstTagList *global_tags;
174   GstPlayMediaInfo *media_info;
175
176   GstElement *current_vis_element;
177
178   GstStructure *config;
179
180   /* Protected by lock */
181   gboolean seek_pending;        /* Only set from main context */
182   GstClockTime last_seek_time;  /* Only set from main context */
183   GSource *seek_source;
184   GstClockTime seek_position;
185
186   /* For playbin3 */
187   gboolean use_playbin3;
188   GstStreamCollection *collection;
189   gchar *video_sid;
190   gchar *audio_sid;
191   gchar *subtitle_sid;
192   gulong stream_notify_id;
193 };
194
195 struct _GstPlayClass
196 {
197   GstObjectClass parent_class;
198 };
199
200 #define parent_class gst_play_parent_class
201 G_DEFINE_TYPE (GstPlay, gst_play, GST_TYPE_OBJECT);
202
203 static GParamSpec *param_specs[PROP_LAST] = { NULL, };
204
205 static void gst_play_dispose (GObject * object);
206 static void gst_play_finalize (GObject * object);
207 static void gst_play_set_property (GObject * object, guint prop_id,
208     const GValue * value, GParamSpec * pspec);
209 static void gst_play_get_property (GObject * object, guint prop_id,
210     GValue * value, GParamSpec * pspec);
211 static void gst_play_constructed (GObject * object);
212
213 static gpointer gst_play_main (gpointer data);
214
215 static void gst_play_set_playbin_video_sink (GstPlay * self);
216
217 static void gst_play_seek_internal_locked (GstPlay * self);
218 static void gst_play_stop_internal (GstPlay * self, gboolean transient);
219 static gboolean gst_play_pause_internal (gpointer user_data);
220 static gboolean gst_play_play_internal (gpointer user_data);
221 static gboolean gst_play_seek_internal (gpointer user_data);
222 static void gst_play_set_rate_internal (GstPlay * self);
223 static void change_state (GstPlay * self, GstPlayState state);
224
225 static GstPlayMediaInfo *gst_play_media_info_create (GstPlay * self);
226
227 static void gst_play_streams_info_create (GstPlay * self,
228     GstPlayMediaInfo * media_info, const gchar * prop, GType type);
229 static void gst_play_stream_info_update (GstPlay * self, GstPlayStreamInfo * s);
230 static void gst_play_stream_info_update_tags_and_caps (GstPlay * self,
231     GstPlayStreamInfo * s);
232 static GstPlayStreamInfo *gst_play_stream_info_find (GstPlayMediaInfo *
233     media_info, GType type, gint stream_index);
234 static GstPlayStreamInfo *gst_play_stream_info_get_current (GstPlay *
235     self, const gchar * prop, GType type);
236
237 static void gst_play_video_info_update (GstPlay * self,
238     GstPlayStreamInfo * stream_info);
239 static void gst_play_audio_info_update (GstPlay * self,
240     GstPlayStreamInfo * stream_info);
241 static void gst_play_subtitle_info_update (GstPlay * self,
242     GstPlayStreamInfo * stream_info);
243
244 /* For playbin3 */
245 static void gst_play_streams_info_create_from_collection (GstPlay * self,
246     GstPlayMediaInfo * media_info, GstStreamCollection * collection);
247 static void gst_play_stream_info_update_from_stream (GstPlay * self,
248     GstPlayStreamInfo * s, GstStream * stream);
249 static GstPlayStreamInfo *gst_play_stream_info_find_from_stream_id
250     (GstPlayMediaInfo * media_info, const gchar * stream_id);
251 static GstPlayStreamInfo *gst_play_stream_info_get_current_from_stream_id
252     (GstPlay * self, const gchar * stream_id, GType type);
253 static void stream_notify_cb (GstStreamCollection * collection,
254     GstStream * stream, GParamSpec * pspec, GstPlay * self);
255
256 static void on_media_info_updated (GstPlay * self);
257
258 static void *get_title (GstTagList * tags);
259 static void *get_container_format (GstTagList * tags);
260 static void *get_from_tags (GstPlay * self, GstPlayMediaInfo * media_info,
261     void *(*func) (GstTagList *));
262 static void *get_cover_sample (GstTagList * tags);
263
264 static void remove_seek_source (GstPlay * self);
265
266 static gboolean query_position (GstPlay * self, GstClockTime * position);
267
268 static void
269 gst_play_init (GstPlay * self)
270 {
271   GST_TRACE_OBJECT (self, "Initializing");
272
273   self = gst_play_get_instance_private (self);
274
275   g_mutex_init (&self->lock);
276   g_cond_init (&self->cond);
277
278   self->context = g_main_context_new ();
279   self->loop = g_main_loop_new (self->context, FALSE);
280   self->api_bus = gst_bus_new ();
281
282   /* *INDENT-OFF* */
283   self->config = gst_structure_new_id (QUARK_CONFIG,
284       CONFIG_QUARK (POSITION_INTERVAL_UPDATE), G_TYPE_UINT, DEFAULT_POSITION_UPDATE_INTERVAL_MS,
285       CONFIG_QUARK (ACCURATE_SEEK), G_TYPE_BOOLEAN, FALSE,
286       NULL);
287   /* *INDENT-ON* */
288
289   self->seek_pending = FALSE;
290   self->seek_position = GST_CLOCK_TIME_NONE;
291   self->last_seek_time = GST_CLOCK_TIME_NONE;
292
293   self->cached_position = 0;
294   self->cached_duration = GST_CLOCK_TIME_NONE;
295
296   GST_TRACE_OBJECT (self, "Initialized");
297 }
298
299 /*
300  * Works same as gst_structure_set to set field/type/value triplets on message data
301  */
302 static void
303 api_bus_post_message (GstPlay * self, GstPlayMessage message_type,
304     const gchar * firstfield, ...)
305 {
306   GstStructure *message_data = NULL;
307   GstMessage *msg = NULL;
308   va_list varargs;
309
310   GST_INFO ("Posting API-bus message-type: %s",
311       gst_play_message_get_name (message_type));
312   message_data = gst_structure_new (GST_PLAY_MESSAGE_DATA,
313       GST_PLAY_MESSAGE_DATA_TYPE, GST_TYPE_PLAY_MESSAGE, message_type, NULL);
314
315   va_start (varargs, firstfield);
316   gst_structure_set_valist (message_data, firstfield, varargs);
317   va_end (varargs);
318
319   msg = gst_message_new_custom (GST_MESSAGE_APPLICATION,
320       GST_OBJECT (self), message_data);
321   GST_DEBUG ("Created message with payload: [ %" GST_PTR_FORMAT " ]",
322       message_data);
323   gst_bus_post (self->api_bus, msg);
324 }
325
326 static void
327 config_quark_initialize (void)
328 {
329   gint i;
330
331   QUARK_CONFIG = g_quark_from_static_string ("play-config");
332
333   if (G_N_ELEMENTS (_config_quark_strings) != CONFIG_QUARK_MAX)
334     g_warning ("the quark table is not consistent! %d != %d",
335         (int) G_N_ELEMENTS (_config_quark_strings), CONFIG_QUARK_MAX);
336
337   for (i = 0; i < CONFIG_QUARK_MAX; i++) {
338     _config_quark_table[i] =
339         g_quark_from_static_string (_config_quark_strings[i]);
340   }
341 }
342
343 static void
344 gst_play_class_init (GstPlayClass * klass)
345 {
346   GObjectClass *gobject_class = (GObjectClass *) klass;
347
348   gobject_class->set_property = gst_play_set_property;
349   gobject_class->get_property = gst_play_get_property;
350   gobject_class->dispose = gst_play_dispose;
351   gobject_class->finalize = gst_play_finalize;
352   gobject_class->constructed = gst_play_constructed;
353
354   param_specs[PROP_VIDEO_RENDERER] =
355       g_param_spec_object ("video-renderer",
356       "Video Renderer", "Video renderer to use for rendering videos",
357       GST_TYPE_PLAY_VIDEO_RENDERER, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
358
359   param_specs[PROP_URI] = g_param_spec_string ("uri", "URI", "Current URI",
360       DEFAULT_URI, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
361
362   param_specs[PROP_SUBURI] = g_param_spec_string ("suburi", "Subtitle URI",
363       "Current Subtitle URI", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
364
365   param_specs[PROP_POSITION] =
366       g_param_spec_uint64 ("position", "Position", "Current Position",
367       0, G_MAXUINT64, DEFAULT_POSITION,
368       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
369
370   param_specs[PROP_MEDIA_INFO] =
371       g_param_spec_object ("media-info", "Media Info",
372       "Current media information", GST_TYPE_PLAY_MEDIA_INFO,
373       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
374
375   param_specs[PROP_CURRENT_AUDIO_TRACK] =
376       g_param_spec_object ("current-audio-track", "Current Audio Track",
377       "Current audio track information", GST_TYPE_PLAY_AUDIO_INFO,
378       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
379
380   param_specs[PROP_CURRENT_VIDEO_TRACK] =
381       g_param_spec_object ("current-video-track", "Current Video Track",
382       "Current video track information", GST_TYPE_PLAY_VIDEO_INFO,
383       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
384
385   param_specs[PROP_CURRENT_SUBTITLE_TRACK] =
386       g_param_spec_object ("current-subtitle-track", "Current Subtitle Track",
387       "Current audio subtitle information", GST_TYPE_PLAY_SUBTITLE_INFO,
388       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
389
390   param_specs[PROP_DURATION] =
391       g_param_spec_uint64 ("duration", "Duration", "Duration",
392       0, G_MAXUINT64, DEFAULT_DURATION,
393       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
394
395   param_specs[PROP_VOLUME] =
396       g_param_spec_double ("volume", "Volume", "Volume",
397       0, 10.0, DEFAULT_VOLUME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
398
399   param_specs[PROP_MUTE] =
400       g_param_spec_boolean ("mute", "Mute", "Mute",
401       DEFAULT_MUTE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
402
403   param_specs[PROP_PIPELINE] =
404       g_param_spec_object ("pipeline", "Pipeline",
405       "GStreamer pipeline that is used",
406       GST_TYPE_ELEMENT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
407
408   param_specs[PROP_RATE] =
409       g_param_spec_double ("rate", "rate", "Playback rate",
410       -64.0, 64.0, DEFAULT_RATE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
411
412   param_specs[PROP_VIDEO_MULTIVIEW_MODE] =
413       g_param_spec_enum ("video-multiview-mode",
414       "Multiview Mode Override",
415       "Re-interpret a video stream as one of several frame-packed stereoscopic modes.",
416       GST_TYPE_VIDEO_MULTIVIEW_FRAME_PACKING,
417       GST_VIDEO_MULTIVIEW_FRAME_PACKING_NONE,
418       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
419
420   param_specs[PROP_VIDEO_MULTIVIEW_FLAGS] =
421       g_param_spec_flags ("video-multiview-flags",
422       "Multiview Flags Override",
423       "Override details of the multiview frame layout",
424       GST_TYPE_VIDEO_MULTIVIEW_FLAGS, GST_VIDEO_MULTIVIEW_FLAGS_NONE,
425       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
426
427   param_specs[PROP_AUDIO_VIDEO_OFFSET] =
428       g_param_spec_int64 ("audio-video-offset", "Audio Video Offset",
429       "The synchronisation offset between audio and video in nanoseconds",
430       G_MININT64, G_MAXINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
431
432   param_specs[PROP_SUBTITLE_VIDEO_OFFSET] =
433       g_param_spec_int64 ("subtitle-video-offset", "Text Video Offset",
434       "The synchronisation offset between text and video in nanoseconds",
435       G_MININT64, G_MAXINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
436
437   g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
438
439   config_quark_initialize ();
440 }
441
442 static void
443 gst_play_dispose (GObject * object)
444 {
445   GstPlay *self = GST_PLAY (object);
446
447   GST_TRACE_OBJECT (self, "Stopping main thread");
448
449   gst_bus_set_flushing (self->api_bus, TRUE);
450
451   if (self->loop) {
452     g_main_loop_quit (self->loop);
453
454     if (self->thread != g_thread_self ())
455       g_thread_join (self->thread);
456     else
457       g_thread_unref (self->thread);
458     self->thread = NULL;
459
460     g_main_loop_unref (self->loop);
461     self->loop = NULL;
462
463     g_main_context_unref (self->context);
464     self->context = NULL;
465   }
466
467   gst_clear_object (&self->api_bus);
468
469   G_OBJECT_CLASS (parent_class)->dispose (object);
470 }
471
472 static void
473 gst_play_finalize (GObject * object)
474 {
475   GstPlay *self = GST_PLAY (object);
476
477   GST_TRACE_OBJECT (self, "Finalizing");
478
479   g_free (self->uri);
480   g_free (self->redirect_uri);
481   g_free (self->suburi);
482   g_free (self->video_sid);
483   g_free (self->audio_sid);
484   g_free (self->subtitle_sid);
485   if (self->global_tags)
486     gst_tag_list_unref (self->global_tags);
487   if (self->video_renderer)
488     g_object_unref (self->video_renderer);
489   if (self->current_vis_element)
490     gst_object_unref (self->current_vis_element);
491   if (self->config)
492     gst_structure_free (self->config);
493   if (self->collection)
494     gst_object_unref (self->collection);
495   if (self->media_info)
496     g_object_unref (self->media_info);
497   g_mutex_clear (&self->lock);
498   g_cond_clear (&self->cond);
499
500   G_OBJECT_CLASS (parent_class)->finalize (object);
501 }
502
503 static void
504 gst_play_constructed (GObject * object)
505 {
506   GstPlay *self = GST_PLAY (object);
507
508   GST_TRACE_OBJECT (self, "Constructed");
509
510   g_mutex_lock (&self->lock);
511   self->thread = g_thread_new ("GstPlay", gst_play_main, self);
512   while (!self->loop || !g_main_loop_is_running (self->loop))
513     g_cond_wait (&self->cond, &self->lock);
514
515   gst_play_set_playbin_video_sink (self);
516   g_mutex_unlock (&self->lock);
517
518   G_OBJECT_CLASS (parent_class)->constructed (object);
519 }
520
521 static gboolean
522 gst_play_set_uri_internal (gpointer user_data)
523 {
524   GstPlay *self = user_data;
525
526   gst_play_stop_internal (self, FALSE);
527
528   g_mutex_lock (&self->lock);
529
530   GST_DEBUG_OBJECT (self, "Changing URI to '%s'", GST_STR_NULL (self->uri));
531
532   g_object_set (self->playbin, "uri", self->uri, NULL);
533
534   api_bus_post_message (self, GST_PLAY_MESSAGE_URI_LOADED,
535       GST_PLAY_MESSAGE_DATA_URI, G_TYPE_STRING, self->uri, NULL);
536
537   g_object_set (self->playbin, "suburi", NULL, NULL);
538
539   g_mutex_unlock (&self->lock);
540
541   return G_SOURCE_REMOVE;
542 }
543
544 static gboolean
545 gst_play_set_suburi_internal (gpointer user_data)
546 {
547   GstPlay *self = user_data;
548   GstClockTime position;
549   GstState target_state;
550
551   /* save the state and position */
552   target_state = self->target_state;
553   position = gst_play_get_position (self);
554
555   gst_play_stop_internal (self, TRUE);
556   g_mutex_lock (&self->lock);
557
558   GST_DEBUG_OBJECT (self, "Changing SUBURI to '%s'",
559       GST_STR_NULL (self->suburi));
560
561   g_object_set (self->playbin, "suburi", self->suburi, NULL);
562
563   g_mutex_unlock (&self->lock);
564
565   /* restore state and position */
566   if (position != GST_CLOCK_TIME_NONE)
567     gst_play_seek (self, position);
568   if (target_state == GST_STATE_PAUSED)
569     gst_play_pause_internal (self);
570   else if (target_state == GST_STATE_PLAYING)
571     gst_play_play_internal (self);
572
573   return G_SOURCE_REMOVE;
574 }
575
576 static void
577 gst_play_set_rate_internal (GstPlay * self)
578 {
579   self->seek_position = gst_play_get_position (self);
580
581   /* If there is no seek being dispatch to the main context currently do that,
582    * otherwise we just updated the rate so that it will be taken by
583    * the seek handler from the main context instead of the old one.
584    */
585   if (!self->seek_source) {
586     /* If no seek is pending then create new seek source */
587     if (!self->seek_pending) {
588       self->seek_source = g_idle_source_new ();
589       g_source_set_callback (self->seek_source,
590           (GSourceFunc) gst_play_seek_internal, self, NULL);
591       g_source_attach (self->seek_source, self->context);
592     }
593   }
594 }
595
596 static void
597 gst_play_set_playbin_video_sink (GstPlay * self)
598 {
599   GstElement *video_sink = NULL;
600
601   if (self->video_renderer != NULL)
602     video_sink =
603         gst_play_video_renderer_create_video_sink (self->video_renderer, self);
604   if (video_sink)
605     g_object_set (self->playbin, "video-sink", video_sink, NULL);
606 }
607
608 static void
609 gst_play_set_property (GObject * object, guint prop_id,
610     const GValue * value, GParamSpec * pspec)
611 {
612   GstPlay *self = GST_PLAY (object);
613
614   switch (prop_id) {
615     case PROP_VIDEO_RENDERER:
616       g_mutex_lock (&self->lock);
617       g_clear_object (&self->video_renderer);
618       self->video_renderer = g_value_dup_object (value);
619
620       // When the video_renderer is a GstPlayerWrappedVideoRenderer it cannot be set
621       // at construction time because it requires a valid pipeline which is created
622       // only after GstPlay has been constructed. That is why the video renderer is
623       // set *after* GstPlay has been constructed.
624       if (self->thread) {
625         gst_play_set_playbin_video_sink (self);
626       }
627       g_mutex_unlock (&self->lock);
628       break;
629     case PROP_URI:{
630       g_mutex_lock (&self->lock);
631       g_free (self->uri);
632       g_free (self->redirect_uri);
633       self->redirect_uri = NULL;
634
635       g_free (self->suburi);
636       self->suburi = NULL;
637
638       self->uri = g_value_dup_string (value);
639       GST_DEBUG_OBJECT (self, "Set uri=%s", GST_STR_NULL (self->uri));
640       g_mutex_unlock (&self->lock);
641
642       g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT,
643           gst_play_set_uri_internal, self, NULL);
644       break;
645     }
646     case PROP_SUBURI:{
647       g_mutex_lock (&self->lock);
648       g_free (self->suburi);
649
650       self->suburi = g_value_dup_string (value);
651       GST_DEBUG_OBJECT (self, "Set suburi=%s", self->suburi);
652       g_mutex_unlock (&self->lock);
653
654       g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT,
655           gst_play_set_suburi_internal, self, NULL);
656       break;
657     }
658     case PROP_VOLUME:
659       GST_DEBUG_OBJECT (self, "Set volume=%lf", g_value_get_double (value));
660       g_object_set_property (G_OBJECT (self->playbin), "volume", value);
661       break;
662     case PROP_RATE:
663       g_mutex_lock (&self->lock);
664       self->rate = g_value_get_double (value);
665       GST_DEBUG_OBJECT (self, "Set rate=%lf", g_value_get_double (value));
666       gst_play_set_rate_internal (self);
667       g_mutex_unlock (&self->lock);
668       break;
669     case PROP_MUTE:
670       GST_DEBUG_OBJECT (self, "Set mute=%d", g_value_get_boolean (value));
671       g_object_set_property (G_OBJECT (self->playbin), "mute", value);
672       break;
673     case PROP_VIDEO_MULTIVIEW_MODE:
674       GST_DEBUG_OBJECT (self, "Set multiview mode=%u",
675           g_value_get_enum (value));
676       g_object_set_property (G_OBJECT (self->playbin), "video-multiview-mode",
677           value);
678       break;
679     case PROP_VIDEO_MULTIVIEW_FLAGS:
680       GST_DEBUG_OBJECT (self, "Set multiview flags=%x",
681           g_value_get_flags (value));
682       g_object_set_property (G_OBJECT (self->playbin), "video-multiview-flags",
683           value);
684       break;
685     case PROP_AUDIO_VIDEO_OFFSET:
686       g_object_set_property (G_OBJECT (self->playbin), "av-offset", value);
687       break;
688     case PROP_SUBTITLE_VIDEO_OFFSET:
689       g_object_set_property (G_OBJECT (self->playbin), "text-offset", value);
690       break;
691     default:
692       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
693       break;
694   }
695 }
696
697 static void
698 gst_play_get_property (GObject * object, guint prop_id,
699     GValue * value, GParamSpec * pspec)
700 {
701   GstPlay *self = GST_PLAY (object);
702
703   switch (prop_id) {
704     case PROP_URI:
705       g_mutex_lock (&self->lock);
706       g_value_set_string (value, self->uri);
707       g_mutex_unlock (&self->lock);
708       break;
709     case PROP_SUBURI:
710       g_mutex_lock (&self->lock);
711       g_value_set_string (value, self->suburi);
712       g_mutex_unlock (&self->lock);
713       GST_DEBUG_OBJECT (self, "Returning suburi=%s",
714           g_value_get_string (value));
715       break;
716     case PROP_POSITION:{
717       GstClockTime position = GST_CLOCK_TIME_NONE;
718       query_position (self, &position);
719       g_value_set_uint64 (value, position);
720       GST_TRACE_OBJECT (self, "Returning position=%" GST_TIME_FORMAT,
721           GST_TIME_ARGS (g_value_get_uint64 (value)));
722       break;
723     }
724     case PROP_DURATION:{
725       g_value_set_uint64 (value, self->cached_duration);
726       GST_TRACE_OBJECT (self, "Returning duration=%" GST_TIME_FORMAT,
727           GST_TIME_ARGS (g_value_get_uint64 (value)));
728       break;
729     }
730     case PROP_MEDIA_INFO:{
731       GstPlayMediaInfo *media_info = gst_play_get_media_info (self);
732       g_value_take_object (value, media_info);
733       break;
734     }
735     case PROP_CURRENT_AUDIO_TRACK:{
736       GstPlayAudioInfo *audio_info = gst_play_get_current_audio_track (self);
737       g_value_take_object (value, audio_info);
738       break;
739     }
740     case PROP_CURRENT_VIDEO_TRACK:{
741       GstPlayVideoInfo *video_info = gst_play_get_current_video_track (self);
742       g_value_take_object (value, video_info);
743       break;
744     }
745     case PROP_CURRENT_SUBTITLE_TRACK:{
746       GstPlaySubtitleInfo *subtitle_info =
747           gst_play_get_current_subtitle_track (self);
748       g_value_take_object (value, subtitle_info);
749       break;
750     }
751     case PROP_VOLUME:
752       g_object_get_property (G_OBJECT (self->playbin), "volume", value);
753       GST_TRACE_OBJECT (self, "Returning volume=%lf",
754           g_value_get_double (value));
755       break;
756     case PROP_RATE:
757       g_mutex_lock (&self->lock);
758       g_value_set_double (value, self->rate);
759       g_mutex_unlock (&self->lock);
760       break;
761     case PROP_MUTE:
762       g_object_get_property (G_OBJECT (self->playbin), "mute", value);
763       GST_TRACE_OBJECT (self, "Returning mute=%d", g_value_get_boolean (value));
764       break;
765     case PROP_PIPELINE:
766       g_value_set_object (value, self->playbin);
767       break;
768     case PROP_VIDEO_MULTIVIEW_MODE:{
769       g_object_get_property (G_OBJECT (self->playbin), "video-multiview-mode",
770           value);
771       GST_TRACE_OBJECT (self, "Return multiview mode=%d",
772           g_value_get_enum (value));
773       break;
774     }
775     case PROP_VIDEO_MULTIVIEW_FLAGS:{
776       g_object_get_property (G_OBJECT (self->playbin), "video-multiview-flags",
777           value);
778       GST_TRACE_OBJECT (self, "Return multiview flags=%x",
779           g_value_get_flags (value));
780       break;
781     }
782     case PROP_AUDIO_VIDEO_OFFSET:
783       g_object_get_property (G_OBJECT (self->playbin), "av-offset", value);
784       break;
785     case PROP_SUBTITLE_VIDEO_OFFSET:
786       g_object_get_property (G_OBJECT (self->playbin), "text-offset", value);
787       break;
788     default:
789       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
790       break;
791   }
792 }
793
794 static gboolean
795 main_loop_running_cb (gpointer user_data)
796 {
797   GstPlay *self = GST_PLAY (user_data);
798
799   GST_TRACE_OBJECT (self, "Main loop running now");
800
801   g_mutex_lock (&self->lock);
802   g_cond_signal (&self->cond);
803   g_mutex_unlock (&self->lock);
804
805   return G_SOURCE_REMOVE;
806 }
807
808 static void
809 change_state (GstPlay * self, GstPlayState state)
810 {
811   if (state == self->app_state)
812     return;
813
814   GST_DEBUG_OBJECT (self, "Changing app state from %s to %s",
815       gst_play_state_get_name (self->app_state),
816       gst_play_state_get_name (state));
817
818   self->app_state = state;
819
820   api_bus_post_message (self, GST_PLAY_MESSAGE_STATE_CHANGED,
821       GST_PLAY_MESSAGE_DATA_PLAY_STATE, GST_TYPE_PLAY_STATE,
822       self->app_state, NULL);
823 }
824
825 static gboolean
826 tick_cb (gpointer user_data)
827 {
828   GstPlay *self = GST_PLAY (user_data);
829   GstClockTime position;
830   if (query_position (self, &position)) {
831     api_bus_post_message (self, GST_PLAY_MESSAGE_POSITION_UPDATED,
832         GST_PLAY_MESSAGE_DATA_POSITION, GST_TYPE_CLOCK_TIME, position, NULL);
833   }
834
835   return G_SOURCE_CONTINUE;
836 }
837
838 /*
839  * Returns true when position is queried and differed from cached position.
840  * Sets position to cached value, and to queried value if position can be queried
841  * and different.
842  */
843 static gboolean
844 query_position (GstPlay * self, GstClockTime * position)
845 {
846   gint64 current_position;
847   *position = self->cached_position;
848   if (self->target_state >= GST_STATE_PAUSED
849       && gst_element_query_position (self->playbin, GST_FORMAT_TIME,
850           &current_position)) {
851     GST_LOG_OBJECT (self, "Queried position %" GST_TIME_FORMAT,
852         GST_TIME_ARGS (current_position));
853     if (self->cached_position != current_position) {
854       self->cached_position = current_position;
855       *position = (GstClockTime) current_position;
856       return TRUE;
857     }
858   }
859   return FALSE;
860 }
861
862 static void
863 add_tick_source (GstPlay * self)
864 {
865   guint position_update_interval_ms;
866
867   if (self->tick_source)
868     return;
869
870   position_update_interval_ms =
871       gst_play_config_get_position_update_interval (self->config);
872   if (!position_update_interval_ms)
873     return;
874
875   self->tick_source = g_timeout_source_new (position_update_interval_ms);
876   g_source_set_callback (self->tick_source, (GSourceFunc) tick_cb, self, NULL);
877   g_source_attach (self->tick_source, self->context);
878 }
879
880 static void
881 remove_tick_source (GstPlay * self)
882 {
883   if (!self->tick_source)
884     return;
885
886   g_source_destroy (self->tick_source);
887   g_source_unref (self->tick_source);
888   self->tick_source = NULL;
889 }
890
891 static gboolean
892 ready_timeout_cb (gpointer user_data)
893 {
894   GstPlay *self = user_data;
895
896   if (self->target_state <= GST_STATE_READY) {
897     GST_DEBUG_OBJECT (self, "Setting pipeline to NULL state");
898     self->target_state = GST_STATE_NULL;
899     self->current_state = GST_STATE_NULL;
900     gst_element_set_state (self->playbin, GST_STATE_NULL);
901   }
902
903   return G_SOURCE_REMOVE;
904 }
905
906 static void
907 add_ready_timeout_source (GstPlay * self)
908 {
909   if (self->ready_timeout_source)
910     return;
911
912   self->ready_timeout_source = g_timeout_source_new_seconds (60);
913   g_source_set_callback (self->ready_timeout_source,
914       (GSourceFunc) ready_timeout_cb, self, NULL);
915   g_source_attach (self->ready_timeout_source, self->context);
916 }
917
918 static void
919 remove_ready_timeout_source (GstPlay * self)
920 {
921   if (!self->ready_timeout_source)
922     return;
923
924   g_source_destroy (self->ready_timeout_source);
925   g_source_unref (self->ready_timeout_source);
926   self->ready_timeout_source = NULL;
927 }
928
929
930 static void
931 on_error (GstPlay * self, GError * err, const GstStructure * details)
932 {
933   GST_ERROR_OBJECT (self, "Error: %s (%s, %d)", err->message,
934       g_quark_to_string (err->domain), err->code);
935
936   api_bus_post_message (self, GST_PLAY_MESSAGE_ERROR,
937       GST_PLAY_MESSAGE_DATA_ERROR, G_TYPE_ERROR, err,
938       GST_PLAY_MESSAGE_DATA_ERROR_DETAILS, GST_TYPE_STRUCTURE, details, NULL);
939
940   g_error_free (err);
941
942   remove_tick_source (self);
943   remove_ready_timeout_source (self);
944
945   self->target_state = GST_STATE_NULL;
946   self->current_state = GST_STATE_NULL;
947   self->is_live = FALSE;
948   self->is_eos = FALSE;
949   gst_element_set_state (self->playbin, GST_STATE_NULL);
950   change_state (self, GST_PLAY_STATE_STOPPED);
951   self->buffering_percent = 100;
952
953   g_mutex_lock (&self->lock);
954   if (self->media_info) {
955     g_object_unref (self->media_info);
956     self->media_info = NULL;
957   }
958
959   if (self->global_tags) {
960     gst_tag_list_unref (self->global_tags);
961     self->global_tags = NULL;
962   }
963
964   self->seek_pending = FALSE;
965   remove_seek_source (self);
966   self->seek_position = GST_CLOCK_TIME_NONE;
967   self->last_seek_time = GST_CLOCK_TIME_NONE;
968   g_mutex_unlock (&self->lock);
969 }
970
971 static void
972 dump_dot_file (GstPlay * self, const gchar * name)
973 {
974   gchar *full_name;
975
976   full_name = g_strdup_printf ("gst-play.%p.%s", self, name);
977
978   GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (self->playbin),
979       GST_DEBUG_GRAPH_SHOW_ALL, full_name);
980
981   g_free (full_name);
982 }
983
984 static void
985 error_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
986 {
987   GstPlay *self = GST_PLAY (user_data);
988   GError *err, *play_err;
989   gchar *name, *debug, *message, *full_message;
990   const GstStructure *details = NULL;
991
992   dump_dot_file (self, "error");
993
994   gst_message_parse_error (msg, &err, &debug);
995   gst_message_parse_error_details (msg, &details);
996
997   name = gst_object_get_path_string (msg->src);
998   message = gst_error_get_message (err->domain, err->code);
999
1000   if (debug)
1001     full_message =
1002         g_strdup_printf ("Error from element %s: %s\n%s\n%s", name, message,
1003         err->message, debug);
1004   else
1005     full_message =
1006         g_strdup_printf ("Error from element %s: %s\n%s", name, message,
1007         err->message);
1008
1009   GST_ERROR_OBJECT (self, "ERROR: from element %s: %s", name, err->message);
1010   if (debug != NULL)
1011     GST_ERROR_OBJECT (self, "Additional debug info: %s", debug);
1012
1013   play_err =
1014       g_error_new_literal (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED, full_message);
1015   on_error (self, play_err, details);
1016
1017   g_clear_error (&err);
1018   g_free (debug);
1019   g_free (name);
1020   g_free (full_message);
1021   g_free (message);
1022 }
1023
1024 static void
1025 warning_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
1026 {
1027   GstPlay *self = GST_PLAY (user_data);
1028   GError *err, *play_err;
1029   gchar *name, *debug, *message, *full_message;
1030   const GstStructure *details = NULL;
1031
1032   dump_dot_file (self, "warning");
1033
1034   gst_message_parse_warning (msg, &err, &debug);
1035   gst_message_parse_warning_details (msg, &details);
1036
1037   name = gst_object_get_path_string (msg->src);
1038   message = gst_error_get_message (err->domain, err->code);
1039
1040   if (debug)
1041     full_message =
1042         g_strdup_printf ("Warning from element %s: %s\n%s\n%s", name, message,
1043         err->message, debug);
1044   else
1045     full_message =
1046         g_strdup_printf ("Warning from element %s: %s\n%s", name, message,
1047         err->message);
1048
1049   GST_WARNING_OBJECT (self, "WARNING: from element %s: %s", name, err->message);
1050   if (debug != NULL)
1051     GST_WARNING_OBJECT (self, "Additional debug info: %s", debug);
1052
1053   play_err =
1054       g_error_new_literal (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED, full_message);
1055
1056   GST_WARNING_OBJECT (self, "Warning: %s (%s, %d)", err->message,
1057       g_quark_to_string (err->domain), err->code);
1058
1059   api_bus_post_message (self, GST_PLAY_MESSAGE_WARNING,
1060       GST_PLAY_MESSAGE_DATA_WARNING, G_TYPE_ERROR, play_err,
1061       GST_PLAY_MESSAGE_DATA_WARNING_DETAILS, GST_TYPE_STRUCTURE, details, NULL);
1062
1063   g_clear_error (&play_err);
1064   g_clear_error (&err);
1065   g_free (debug);
1066   g_free (name);
1067   g_free (full_message);
1068   g_free (message);
1069 }
1070
1071 static void
1072 eos_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg,
1073     gpointer user_data)
1074 {
1075   GstPlay *self = GST_PLAY (user_data);
1076
1077   GST_DEBUG_OBJECT (self, "End of stream");
1078
1079   tick_cb (self);
1080   remove_tick_source (self);
1081
1082   api_bus_post_message (self, GST_PLAY_MESSAGE_END_OF_STREAM, NULL);
1083
1084   change_state (self, GST_PLAY_STATE_STOPPED);
1085   self->buffering_percent = 100;
1086   self->is_eos = TRUE;
1087 }
1088
1089 static void
1090 buffering_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
1091 {
1092   GstPlay *self = GST_PLAY (user_data);
1093   gint percent;
1094
1095   if (self->target_state < GST_STATE_PAUSED)
1096     return;
1097   if (self->is_live)
1098     return;
1099
1100   gst_message_parse_buffering (msg, &percent);
1101   GST_LOG_OBJECT (self, "Buffering %d%%", percent);
1102
1103   if (percent < 100 && self->target_state >= GST_STATE_PAUSED) {
1104     GstStateChangeReturn state_ret;
1105
1106     GST_DEBUG_OBJECT (self, "Waiting for buffering to finish");
1107     state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED);
1108
1109     if (state_ret == GST_STATE_CHANGE_FAILURE) {
1110       on_error (self, g_error_new (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED,
1111               "Failed to handle buffering"), NULL);
1112       return;
1113     }
1114
1115     change_state (self, GST_PLAY_STATE_BUFFERING);
1116   }
1117
1118   if (self->buffering_percent != percent) {
1119     self->buffering_percent = percent;
1120
1121     api_bus_post_message (self, GST_PLAY_MESSAGE_BUFFERING,
1122         GST_PLAY_MESSAGE_DATA_BUFFERING_PERCENT, G_TYPE_UINT, percent, NULL);
1123   }
1124
1125   g_mutex_lock (&self->lock);
1126   if (percent == 100 && (self->seek_position != GST_CLOCK_TIME_NONE ||
1127           self->seek_pending)) {
1128     g_mutex_unlock (&self->lock);
1129
1130     GST_DEBUG_OBJECT (self, "Buffering finished - seek pending");
1131   } else if (percent == 100 && self->target_state >= GST_STATE_PLAYING
1132       && self->current_state >= GST_STATE_PAUSED) {
1133     GstStateChangeReturn state_ret;
1134
1135     g_mutex_unlock (&self->lock);
1136
1137     GST_DEBUG_OBJECT (self, "Buffering finished - going to PLAYING");
1138     state_ret = gst_element_set_state (self->playbin, GST_STATE_PLAYING);
1139     /* Application state change is happening when the state change happened */
1140     if (state_ret == GST_STATE_CHANGE_FAILURE)
1141       on_error (self, g_error_new (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED,
1142               "Failed to handle buffering"), NULL);
1143   } else if (percent == 100 && self->target_state >= GST_STATE_PAUSED) {
1144     g_mutex_unlock (&self->lock);
1145
1146     GST_DEBUG_OBJECT (self, "Buffering finished - staying PAUSED");
1147     change_state (self, GST_PLAY_STATE_PAUSED);
1148   } else {
1149     g_mutex_unlock (&self->lock);
1150   }
1151 }
1152
1153 static void
1154 clock_lost_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg,
1155     gpointer user_data)
1156 {
1157   GstPlay *self = GST_PLAY (user_data);
1158   GstStateChangeReturn state_ret;
1159
1160   GST_DEBUG_OBJECT (self, "Clock lost");
1161   if (self->target_state >= GST_STATE_PLAYING) {
1162     state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED);
1163     if (state_ret != GST_STATE_CHANGE_FAILURE)
1164       state_ret = gst_element_set_state (self->playbin, GST_STATE_PLAYING);
1165
1166     if (state_ret == GST_STATE_CHANGE_FAILURE)
1167       on_error (self, g_error_new (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED,
1168               "Failed to handle clock loss"), NULL);
1169   }
1170 }
1171
1172
1173 static void
1174 check_video_dimensions_changed (GstPlay * self)
1175 {
1176   GstElement *video_sink;
1177   GstPad *video_sink_pad;
1178   GstCaps *caps;
1179   GstVideoInfo info;
1180   guint width = 0, height = 0;
1181
1182   g_object_get (self->playbin, "video-sink", &video_sink, NULL);
1183   if (!video_sink)
1184     goto out;
1185
1186   video_sink_pad = gst_element_get_static_pad (video_sink, "sink");
1187   if (!video_sink_pad) {
1188     gst_object_unref (video_sink);
1189     goto out;
1190   }
1191
1192   caps = gst_pad_get_current_caps (video_sink_pad);
1193
1194   if (caps) {
1195     if (gst_video_info_from_caps (&info, caps)) {
1196       info.width = info.width * info.par_n / info.par_d;
1197
1198       GST_DEBUG_OBJECT (self, "Video dimensions changed: %dx%d", info.width,
1199           info.height);
1200       width = info.width;
1201       height = info.height;
1202     }
1203
1204     gst_caps_unref (caps);
1205   }
1206   gst_object_unref (video_sink_pad);
1207   gst_object_unref (video_sink);
1208
1209 out:
1210   api_bus_post_message (self, GST_PLAY_MESSAGE_VIDEO_DIMENSIONS_CHANGED,
1211       GST_PLAY_MESSAGE_DATA_VIDEO_WIDTH, G_TYPE_UINT, width,
1212       GST_PLAY_MESSAGE_DATA_VIDEO_HEIGHT, G_TYPE_UINT, height, NULL);
1213 }
1214
1215 static void
1216 notify_caps_cb (G_GNUC_UNUSED GObject * object,
1217     G_GNUC_UNUSED GParamSpec * pspec, gpointer user_data)
1218 {
1219   GstPlay *self = GST_PLAY (user_data);
1220
1221   check_video_dimensions_changed (self);
1222 }
1223
1224 static void
1225 on_duration_changed (GstPlay * self, GstClockTime duration)
1226 {
1227   gboolean updated = FALSE;
1228
1229   if (self->cached_duration == duration)
1230     return;
1231
1232   GST_DEBUG_OBJECT (self, "Duration changed %" GST_TIME_FORMAT,
1233       GST_TIME_ARGS (duration));
1234
1235   g_mutex_lock (&self->lock);
1236   self->cached_duration = duration;
1237   if (self->media_info) {
1238     self->media_info->duration = duration;
1239     updated = TRUE;
1240   }
1241   g_mutex_unlock (&self->lock);
1242
1243   api_bus_post_message (self, GST_PLAY_MESSAGE_DURATION_CHANGED,
1244       GST_PLAY_MESSAGE_DATA_DURATION, GST_TYPE_CLOCK_TIME,
1245       gst_play_get_duration (self), NULL);
1246
1247   if (updated) {
1248     on_media_info_updated (self);
1249   }
1250 }
1251
1252 static void
1253 on_seek_done (GstPlay * self)
1254 {
1255   api_bus_post_message (self, GST_PLAY_MESSAGE_SEEK_DONE,
1256       GST_PLAY_MESSAGE_DATA_POSITION, GST_TYPE_CLOCK_TIME,
1257       gst_play_get_position (self), NULL);
1258 }
1259
1260 static void
1261 state_changed_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
1262     gpointer user_data)
1263 {
1264   GstPlay *self = GST_PLAY (user_data);
1265   GstState old_state, new_state, pending_state;
1266
1267   gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
1268
1269   if (GST_MESSAGE_SRC (msg) == GST_OBJECT (self->playbin)) {
1270     gchar *transition_name;
1271
1272     GST_DEBUG_OBJECT (self, "Changed state old: %s new: %s pending: %s",
1273         gst_element_state_get_name (old_state),
1274         gst_element_state_get_name (new_state),
1275         gst_element_state_get_name (pending_state));
1276
1277     transition_name = g_strdup_printf ("%s_%s",
1278         gst_element_state_get_name (old_state),
1279         gst_element_state_get_name (new_state));
1280     dump_dot_file (self, transition_name);
1281     g_free (transition_name);
1282
1283     self->current_state = new_state;
1284
1285     if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED
1286         && pending_state == GST_STATE_VOID_PENDING) {
1287       GstElement *video_sink;
1288       GstPad *video_sink_pad;
1289       gint64 duration = -1;
1290
1291       GST_DEBUG_OBJECT (self, "Initial PAUSED - pre-rolled");
1292
1293       g_mutex_lock (&self->lock);
1294       if (self->media_info)
1295         g_object_unref (self->media_info);
1296       self->media_info = gst_play_media_info_create (self);
1297       g_mutex_unlock (&self->lock);
1298       on_media_info_updated (self);
1299
1300       g_object_get (self->playbin, "video-sink", &video_sink, NULL);
1301
1302       if (video_sink) {
1303         video_sink_pad = gst_element_get_static_pad (video_sink, "sink");
1304
1305         if (video_sink_pad) {
1306           g_signal_connect (video_sink_pad, "notify::caps",
1307               (GCallback) notify_caps_cb, self);
1308           gst_object_unref (video_sink_pad);
1309         }
1310         gst_object_unref (video_sink);
1311       }
1312
1313       check_video_dimensions_changed (self);
1314       if (gst_element_query_duration (self->playbin, GST_FORMAT_TIME,
1315               &duration)) {
1316         on_duration_changed (self, duration);
1317       } else {
1318         self->cached_duration = GST_CLOCK_TIME_NONE;
1319       }
1320     }
1321
1322     if (new_state == GST_STATE_PAUSED
1323         && pending_state == GST_STATE_VOID_PENDING) {
1324       remove_tick_source (self);
1325
1326       g_mutex_lock (&self->lock);
1327       if (self->seek_pending) {
1328         self->seek_pending = FALSE;
1329
1330         if (!self->media_info->seekable) {
1331           GST_DEBUG_OBJECT (self, "Media is not seekable");
1332           remove_seek_source (self);
1333           self->seek_position = GST_CLOCK_TIME_NONE;
1334           self->last_seek_time = GST_CLOCK_TIME_NONE;
1335         } else if (self->seek_source) {
1336           GST_DEBUG_OBJECT (self, "Seek finished but new seek is pending");
1337           gst_play_seek_internal_locked (self);
1338         } else {
1339           GST_DEBUG_OBJECT (self, "Seek finished");
1340           on_seek_done (self);
1341         }
1342       }
1343
1344       if (self->seek_position != GST_CLOCK_TIME_NONE) {
1345         GST_DEBUG_OBJECT (self, "Seeking now that we reached PAUSED state");
1346         gst_play_seek_internal_locked (self);
1347         g_mutex_unlock (&self->lock);
1348       } else if (!self->seek_pending) {
1349         g_mutex_unlock (&self->lock);
1350
1351         tick_cb (self);
1352
1353         if (self->target_state >= GST_STATE_PLAYING
1354             && self->buffering_percent == 100) {
1355           GstStateChangeReturn state_ret;
1356
1357           state_ret = gst_element_set_state (self->playbin, GST_STATE_PLAYING);
1358           if (state_ret == GST_STATE_CHANGE_FAILURE)
1359             on_error (self, g_error_new (GST_PLAY_ERROR,
1360                     GST_PLAY_ERROR_FAILED, "Failed to play"), NULL);
1361         } else if (self->buffering_percent == 100) {
1362           change_state (self, GST_PLAY_STATE_PAUSED);
1363         }
1364       } else {
1365         g_mutex_unlock (&self->lock);
1366       }
1367     } else if (new_state == GST_STATE_PLAYING
1368         && pending_state == GST_STATE_VOID_PENDING) {
1369       /* api_bus_post_message (self, GST_PLAY_MESSAGE_POSITION_UPDATED, */
1370       /*     GST_PLAY_MESSAGE_DATA_POSITION, GST_TYPE_CLOCK_TIME, 0, NULL); */
1371
1372       /* If no seek is currently pending, add the tick source. This can happen
1373        * if we seeked already but the state-change message was still queued up */
1374       if (!self->seek_pending) {
1375         add_tick_source (self);
1376         change_state (self, GST_PLAY_STATE_PLAYING);
1377       }
1378     } else if (new_state == GST_STATE_READY && old_state > GST_STATE_READY) {
1379       change_state (self, GST_PLAY_STATE_STOPPED);
1380     } else {
1381       /* Otherwise we neither reached PLAYING nor PAUSED, so must
1382        * wait for something to happen... i.e. are BUFFERING now */
1383       change_state (self, GST_PLAY_STATE_BUFFERING);
1384     }
1385   }
1386 }
1387
1388 static void
1389 duration_changed_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg,
1390     gpointer user_data)
1391 {
1392   GstPlay *self = GST_PLAY (user_data);
1393   gint64 duration = GST_CLOCK_TIME_NONE;
1394
1395   if (gst_element_query_duration (self->playbin, GST_FORMAT_TIME, &duration)) {
1396     on_duration_changed (self, duration);
1397   }
1398 }
1399
1400 static void
1401 latency_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg,
1402     gpointer user_data)
1403 {
1404   GstPlay *self = GST_PLAY (user_data);
1405
1406   GST_DEBUG_OBJECT (self, "Latency changed");
1407
1408   gst_bin_recalculate_latency (GST_BIN (self->playbin));
1409 }
1410
1411 static void
1412 request_state_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
1413     gpointer user_data)
1414 {
1415   GstPlay *self = GST_PLAY (user_data);
1416   GstState state;
1417   GstStateChangeReturn state_ret;
1418
1419   gst_message_parse_request_state (msg, &state);
1420
1421   GST_DEBUG_OBJECT (self, "State %s requested",
1422       gst_element_state_get_name (state));
1423
1424   self->target_state = state;
1425   state_ret = gst_element_set_state (self->playbin, state);
1426   if (state_ret == GST_STATE_CHANGE_FAILURE)
1427     on_error (self, g_error_new (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED,
1428             "Failed to change to requested state %s",
1429             gst_element_state_get_name (state)), NULL);
1430 }
1431
1432 static void
1433 media_info_update (GstPlay * self, GstPlayMediaInfo * info)
1434 {
1435   g_free (info->title);
1436   info->title = get_from_tags (self, info, get_title);
1437
1438   g_free (info->container);
1439   info->container = get_from_tags (self, info, get_container_format);
1440
1441   if (info->image_sample)
1442     gst_sample_unref (info->image_sample);
1443   info->image_sample = get_from_tags (self, info, get_cover_sample);
1444
1445   GST_DEBUG_OBJECT (self, "title: %s, container: %s "
1446       "image_sample: %p", info->title, info->container, info->image_sample);
1447 }
1448
1449 static void
1450 tags_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
1451 {
1452   GstPlay *self = GST_PLAY (user_data);
1453   GstTagList *tags = NULL;
1454
1455   gst_message_parse_tag (msg, &tags);
1456
1457   GST_DEBUG_OBJECT (self, "received %s tags",
1458       gst_tag_list_get_scope (tags) ==
1459       GST_TAG_SCOPE_GLOBAL ? "global" : "stream");
1460
1461   if (gst_tag_list_get_scope (tags) == GST_TAG_SCOPE_GLOBAL) {
1462     g_mutex_lock (&self->lock);
1463     if (self->media_info) {
1464       if (self->media_info->tags)
1465         gst_tag_list_unref (self->media_info->tags);
1466       self->media_info->tags = gst_tag_list_ref (tags);
1467       media_info_update (self, self->media_info);
1468       g_mutex_unlock (&self->lock);
1469       on_media_info_updated (self);
1470     } else {
1471       if (self->global_tags)
1472         gst_tag_list_unref (self->global_tags);
1473       self->global_tags = gst_tag_list_ref (tags);
1474       g_mutex_unlock (&self->lock);
1475     }
1476   }
1477
1478   gst_tag_list_unref (tags);
1479 }
1480
1481 static void
1482 element_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
1483 {
1484   GstPlay *self = GST_PLAY (user_data);
1485   const GstStructure *s;
1486
1487   s = gst_message_get_structure (msg);
1488   if (gst_structure_has_name (s, "redirect")) {
1489     const gchar *new_location;
1490
1491     new_location = gst_structure_get_string (s, "new-location");
1492     if (!new_location) {
1493       const GValue *locations_list, *location_val;
1494       guint i, size;
1495
1496       locations_list = gst_structure_get_value (s, "locations");
1497       size = gst_value_list_get_size (locations_list);
1498       for (i = 0; i < size; ++i) {
1499         const GstStructure *location_s;
1500
1501         location_val = gst_value_list_get_value (locations_list, i);
1502         if (!GST_VALUE_HOLDS_STRUCTURE (location_val))
1503           continue;
1504
1505         location_s = (const GstStructure *) g_value_get_boxed (location_val);
1506         if (!gst_structure_has_name (location_s, "redirect"))
1507           continue;
1508
1509         new_location = gst_structure_get_string (location_s, "new-location");
1510         if (new_location)
1511           break;
1512       }
1513     }
1514
1515     if (new_location) {
1516       GstState target_state;
1517
1518       GST_DEBUG_OBJECT (self, "Redirect to '%s'", new_location);
1519
1520       /* Remember target state and restore after setting the URI */
1521       target_state = self->target_state;
1522
1523       gst_play_stop_internal (self, TRUE);
1524
1525       g_mutex_lock (&self->lock);
1526       g_free (self->redirect_uri);
1527       self->redirect_uri = g_strdup (new_location);
1528       g_object_set (self->playbin, "uri", self->redirect_uri, NULL);
1529       g_mutex_unlock (&self->lock);
1530
1531       if (target_state == GST_STATE_PAUSED)
1532         gst_play_pause_internal (self);
1533       else if (target_state == GST_STATE_PLAYING)
1534         gst_play_play_internal (self);
1535     }
1536   }
1537 }
1538
1539 /* Must be called with lock */
1540 static gboolean
1541 update_stream_collection (GstPlay * self, GstStreamCollection * collection)
1542 {
1543   if (self->collection && self->collection == collection)
1544     return FALSE;
1545
1546   if (self->collection && self->stream_notify_id)
1547     g_signal_handler_disconnect (self->collection, self->stream_notify_id);
1548
1549   gst_object_replace ((GstObject **) & self->collection,
1550       (GstObject *) collection);
1551   if (self->media_info) {
1552     gst_object_unref (self->media_info);
1553     self->media_info = gst_play_media_info_create (self);
1554   }
1555
1556   self->stream_notify_id =
1557       g_signal_connect (self->collection, "stream-notify",
1558       G_CALLBACK (stream_notify_cb), self);
1559
1560   return TRUE;
1561 }
1562
1563 static void
1564 stream_collection_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
1565     gpointer user_data)
1566 {
1567   GstPlay *self = GST_PLAY (user_data);
1568   GstStreamCollection *collection = NULL;
1569   gboolean updated = FALSE;
1570
1571   gst_message_parse_stream_collection (msg, &collection);
1572
1573   if (!collection)
1574     return;
1575
1576   g_mutex_lock (&self->lock);
1577   updated = update_stream_collection (self, collection);
1578   gst_object_unref (collection);
1579   g_mutex_unlock (&self->lock);
1580
1581   if (self->media_info && updated)
1582     on_media_info_updated (self);
1583 }
1584
1585 static void
1586 streams_selected_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
1587     gpointer user_data)
1588 {
1589   GstPlay *self = GST_PLAY (user_data);
1590   GstStreamCollection *collection = NULL;
1591   gboolean updated = FALSE;
1592   guint i, len;
1593
1594   gst_message_parse_streams_selected (msg, &collection);
1595
1596   if (!collection)
1597     return;
1598
1599   g_mutex_lock (&self->lock);
1600   updated = update_stream_collection (self, collection);
1601   gst_object_unref (collection);
1602
1603   g_free (self->video_sid);
1604   g_free (self->audio_sid);
1605   g_free (self->subtitle_sid);
1606   self->video_sid = NULL;
1607   self->audio_sid = NULL;
1608   self->subtitle_sid = NULL;
1609
1610   len = gst_message_streams_selected_get_size (msg);
1611   for (i = 0; i < len; i++) {
1612     GstStream *stream;
1613     GstStreamType stream_type;
1614     const gchar *stream_id;
1615     gchar **current_sid;
1616     stream = gst_message_streams_selected_get_stream (msg, i);
1617     stream_type = gst_stream_get_stream_type (stream);
1618     stream_id = gst_stream_get_stream_id (stream);
1619     if (stream_type & GST_STREAM_TYPE_AUDIO)
1620       current_sid = &self->audio_sid;
1621     else if (stream_type & GST_STREAM_TYPE_VIDEO)
1622       current_sid = &self->video_sid;
1623     else if (stream_type & GST_STREAM_TYPE_TEXT)
1624       current_sid = &self->subtitle_sid;
1625     else {
1626       GST_WARNING_OBJECT (self,
1627           "Unknown stream-id %s with type 0x%x", stream_id, stream_type);
1628       continue;
1629     }
1630
1631     if (G_UNLIKELY (*current_sid)) {
1632       GST_FIXME_OBJECT (self,
1633           "Multiple streams are selected for type %s, choose the first one",
1634           gst_stream_type_get_name (stream_type));
1635       continue;
1636     }
1637
1638     *current_sid = g_strdup (stream_id);
1639   }
1640   g_mutex_unlock (&self->lock);
1641
1642   if (self->media_info && updated)
1643     on_media_info_updated (self);
1644 }
1645
1646 static void
1647 play_set_flag (GstPlay * self, gint pos)
1648 {
1649   gint flags;
1650
1651   g_object_get (self->playbin, "flags", &flags, NULL);
1652   flags |= pos;
1653   g_object_set (self->playbin, "flags", flags, NULL);
1654
1655   GST_DEBUG_OBJECT (self, "setting flags=%#x", flags);
1656 }
1657
1658 static void
1659 play_clear_flag (GstPlay * self, gint pos)
1660 {
1661   gint flags;
1662
1663   g_object_get (self->playbin, "flags", &flags, NULL);
1664   flags &= ~pos;
1665   g_object_set (self->playbin, "flags", flags, NULL);
1666
1667   GST_DEBUG_OBJECT (self, "setting flags=%#x", flags);
1668 }
1669
1670 /*
1671  * on_media_info_updated:
1672  *
1673  * create a new copy of self->media_info object and post it to the user
1674  * application.
1675  */
1676 static void
1677 on_media_info_updated (GstPlay * self)
1678 {
1679   GstPlayMediaInfo *media_info_copy;
1680
1681   g_mutex_lock (&self->lock);
1682   media_info_copy = gst_play_media_info_copy (self->media_info);
1683   g_mutex_unlock (&self->lock);
1684
1685   api_bus_post_message (self, GST_PLAY_MESSAGE_MEDIA_INFO_UPDATED,
1686       GST_PLAY_MESSAGE_DATA_MEDIA_INFO, GST_TYPE_PLAY_MEDIA_INFO,
1687       media_info_copy, NULL);
1688   g_object_unref (media_info_copy);
1689 }
1690
1691 static GstCaps *
1692 get_caps (GstPlay * self, gint stream_index, GType type)
1693 {
1694   GstPad *pad = NULL;
1695   GstCaps *caps = NULL;
1696
1697   if (type == GST_TYPE_PLAY_VIDEO_INFO)
1698     g_signal_emit_by_name (G_OBJECT (self->playbin),
1699         "get-video-pad", stream_index, &pad);
1700   else if (type == GST_TYPE_PLAY_AUDIO_INFO)
1701     g_signal_emit_by_name (G_OBJECT (self->playbin),
1702         "get-audio-pad", stream_index, &pad);
1703   else
1704     g_signal_emit_by_name (G_OBJECT (self->playbin),
1705         "get-text-pad", stream_index, &pad);
1706
1707   if (pad) {
1708     caps = gst_pad_get_current_caps (pad);
1709     gst_object_unref (pad);
1710   }
1711
1712   return caps;
1713 }
1714
1715 static void
1716 gst_play_subtitle_info_update (GstPlay * self, GstPlayStreamInfo * stream_info)
1717 {
1718   GstPlaySubtitleInfo *info = (GstPlaySubtitleInfo *) stream_info;
1719
1720   if (stream_info->tags) {
1721
1722     /* free the old language info */
1723     g_free (info->language);
1724     info->language = NULL;
1725
1726     /* First try to get the language full name from tag, if name is not
1727      * available then try language code. If we find the language code
1728      * then use gstreamer api to translate code to full name.
1729      */
1730     gst_tag_list_get_string (stream_info->tags, GST_TAG_LANGUAGE_NAME,
1731         &info->language);
1732     if (!info->language) {
1733       gchar *lang_code = NULL;
1734
1735       gst_tag_list_get_string (stream_info->tags, GST_TAG_LANGUAGE_CODE,
1736           &lang_code);
1737       if (lang_code) {
1738         info->language = g_strdup (gst_tag_get_language_name (lang_code));
1739         g_free (lang_code);
1740       }
1741     }
1742
1743     /* If we are still failed to find language name then check if external
1744      * subtitle is loaded and compare the stream index between current sub
1745      * stream index with our stream index and if matches then declare it as
1746      * external subtitle and use the filename.
1747      */
1748     if (!info->language) {
1749       gint text_index = -1;
1750       gchar *suburi = NULL;
1751
1752       g_object_get (G_OBJECT (self->playbin), "current-suburi", &suburi, NULL);
1753       if (suburi) {
1754         if (self->use_playbin3) {
1755           if (g_str_equal (self->subtitle_sid, stream_info->stream_id))
1756             info->language = g_path_get_basename (suburi);
1757         } else {
1758           g_object_get (G_OBJECT (self->playbin), "current-text", &text_index,
1759               NULL);
1760           if (text_index == gst_play_stream_info_get_index (stream_info))
1761             info->language = g_path_get_basename (suburi);
1762         }
1763         g_free (suburi);
1764       }
1765     }
1766
1767   } else {
1768     g_free (info->language);
1769     info->language = NULL;
1770   }
1771
1772   GST_DEBUG_OBJECT (self, "language=%s", info->language);
1773 }
1774
1775 static void
1776 gst_play_video_info_update (GstPlay * self, GstPlayStreamInfo * stream_info)
1777 {
1778   GstPlayVideoInfo *info = (GstPlayVideoInfo *) stream_info;
1779
1780   if (stream_info->caps) {
1781     GstStructure *s;
1782
1783     s = gst_caps_get_structure (stream_info->caps, 0);
1784     if (s) {
1785       gint width, height;
1786       gint fps_n, fps_d;
1787       gint par_n, par_d;
1788
1789       if (gst_structure_get_int (s, "width", &width))
1790         info->width = width;
1791       else
1792         info->width = -1;
1793
1794       if (gst_structure_get_int (s, "height", &height))
1795         info->height = height;
1796       else
1797         info->height = -1;
1798
1799       if (gst_structure_get_fraction (s, "framerate", &fps_n, &fps_d)) {
1800         info->framerate_num = fps_n;
1801         info->framerate_denom = fps_d;
1802       } else {
1803         info->framerate_num = 0;
1804         info->framerate_denom = 1;
1805       }
1806
1807
1808       if (gst_structure_get_fraction (s, "pixel-aspect-ratio", &par_n, &par_d)) {
1809         info->par_num = par_n;
1810         info->par_denom = par_d;
1811       } else {
1812         info->par_num = 1;
1813         info->par_denom = 1;
1814       }
1815     }
1816   } else {
1817     info->width = info->height = -1;
1818     info->par_num = info->par_denom = 1;
1819     info->framerate_num = 0;
1820     info->framerate_denom = 1;
1821   }
1822
1823   if (stream_info->tags) {
1824     guint bitrate, max_bitrate;
1825
1826     if (gst_tag_list_get_uint (stream_info->tags, GST_TAG_BITRATE, &bitrate))
1827       info->bitrate = bitrate;
1828     else
1829       info->bitrate = -1;
1830
1831     if (gst_tag_list_get_uint (stream_info->tags, GST_TAG_MAXIMUM_BITRATE,
1832             &max_bitrate) || gst_tag_list_get_uint (stream_info->tags,
1833             GST_TAG_NOMINAL_BITRATE, &max_bitrate))
1834       info->max_bitrate = max_bitrate;
1835     else
1836       info->max_bitrate = -1;
1837   } else {
1838     info->bitrate = info->max_bitrate = -1;
1839   }
1840
1841   GST_DEBUG_OBJECT (self, "width=%d height=%d fps=%.2f par=%d:%d "
1842       "bitrate=%d max_bitrate=%d", info->width, info->height,
1843       (gdouble) info->framerate_num / info->framerate_denom,
1844       info->par_num, info->par_denom, info->bitrate, info->max_bitrate);
1845 }
1846
1847 static void
1848 gst_play_audio_info_update (GstPlay * self, GstPlayStreamInfo * stream_info)
1849 {
1850   GstPlayAudioInfo *info = (GstPlayAudioInfo *) stream_info;
1851
1852   if (stream_info->caps) {
1853     GstStructure *s;
1854
1855     s = gst_caps_get_structure (stream_info->caps, 0);
1856     if (s) {
1857       gint rate, channels;
1858
1859       if (gst_structure_get_int (s, "rate", &rate))
1860         info->sample_rate = rate;
1861       else
1862         info->sample_rate = -1;
1863
1864       if (gst_structure_get_int (s, "channels", &channels))
1865         info->channels = channels;
1866       else
1867         info->channels = 0;
1868     }
1869   } else {
1870     info->sample_rate = -1;
1871     info->channels = 0;
1872   }
1873
1874   if (stream_info->tags) {
1875     guint bitrate, max_bitrate;
1876
1877     if (gst_tag_list_get_uint (stream_info->tags, GST_TAG_BITRATE, &bitrate))
1878       info->bitrate = bitrate;
1879     else
1880       info->bitrate = -1;
1881
1882     if (gst_tag_list_get_uint (stream_info->tags, GST_TAG_MAXIMUM_BITRATE,
1883             &max_bitrate) || gst_tag_list_get_uint (stream_info->tags,
1884             GST_TAG_NOMINAL_BITRATE, &max_bitrate))
1885       info->max_bitrate = max_bitrate;
1886     else
1887       info->max_bitrate = -1;
1888
1889     /* if we have old language the free it */
1890     g_free (info->language);
1891     info->language = NULL;
1892
1893     /* First try to get the language full name from tag, if name is not
1894      * available then try language code. If we find the language code
1895      * then use gstreamer api to translate code to full name.
1896      */
1897     gst_tag_list_get_string (stream_info->tags, GST_TAG_LANGUAGE_NAME,
1898         &info->language);
1899     if (!info->language) {
1900       gchar *lang_code = NULL;
1901
1902       gst_tag_list_get_string (stream_info->tags, GST_TAG_LANGUAGE_CODE,
1903           &lang_code);
1904       if (lang_code) {
1905         info->language = g_strdup (gst_tag_get_language_name (lang_code));
1906         g_free (lang_code);
1907       }
1908     }
1909   } else {
1910     g_free (info->language);
1911     info->language = NULL;
1912     info->max_bitrate = info->bitrate = -1;
1913   }
1914
1915   GST_DEBUG_OBJECT (self, "language=%s rate=%d channels=%d bitrate=%d "
1916       "max_bitrate=%d", info->language, info->sample_rate, info->channels,
1917       info->bitrate, info->max_bitrate);
1918 }
1919
1920 static GstPlayStreamInfo *
1921 gst_play_stream_info_find (GstPlayMediaInfo * media_info,
1922     GType type, gint stream_index)
1923 {
1924   GList *list, *l;
1925   GstPlayStreamInfo *info = NULL;
1926
1927   if (!media_info)
1928     return NULL;
1929
1930   list = gst_play_media_info_get_stream_list (media_info);
1931   for (l = list; l != NULL; l = l->next) {
1932     info = (GstPlayStreamInfo *) l->data;
1933     if ((G_OBJECT_TYPE (info) == type) && (info->stream_index == stream_index)) {
1934       return info;
1935     }
1936   }
1937
1938   return NULL;
1939 }
1940
1941 static GstPlayStreamInfo *
1942 gst_play_stream_info_find_from_stream_id (GstPlayMediaInfo * media_info,
1943     const gchar * stream_id)
1944 {
1945   GList *list, *l;
1946   GstPlayStreamInfo *info = NULL;
1947
1948   if (!media_info)
1949     return NULL;
1950
1951   list = gst_play_media_info_get_stream_list (media_info);
1952   for (l = list; l != NULL; l = l->next) {
1953     info = (GstPlayStreamInfo *) l->data;
1954     if (g_str_equal (info->stream_id, stream_id)) {
1955       return info;
1956     }
1957   }
1958
1959   return NULL;
1960 }
1961
1962 static gboolean
1963 is_track_enabled (GstPlay * self, gint pos)
1964 {
1965   gint flags;
1966
1967   g_object_get (G_OBJECT (self->playbin), "flags", &flags, NULL);
1968
1969   if ((flags & pos))
1970     return TRUE;
1971
1972   return FALSE;
1973 }
1974
1975 static GstPlayStreamInfo *
1976 gst_play_stream_info_get_current (GstPlay * self, const gchar * prop,
1977     GType type)
1978 {
1979   gint current;
1980   GstPlayStreamInfo *info;
1981
1982   if (!self->media_info)
1983     return NULL;
1984
1985   g_object_get (G_OBJECT (self->playbin), prop, &current, NULL);
1986   g_mutex_lock (&self->lock);
1987   info = gst_play_stream_info_find (self->media_info, type, current);
1988   if (info)
1989     info = gst_play_stream_info_copy (info);
1990   g_mutex_unlock (&self->lock);
1991
1992   return info;
1993 }
1994
1995 static GstPlayStreamInfo *
1996 gst_play_stream_info_get_current_from_stream_id (GstPlay * self,
1997     const gchar * stream_id, GType type)
1998 {
1999   GstPlayStreamInfo *info;
2000
2001   if (!self->media_info || !stream_id)
2002     return NULL;
2003
2004   g_mutex_lock (&self->lock);
2005   info = gst_play_stream_info_find_from_stream_id (self->media_info, stream_id);
2006   if (info && G_OBJECT_TYPE (info) == type)
2007     info = gst_play_stream_info_copy (info);
2008   else
2009     info = NULL;
2010   g_mutex_unlock (&self->lock);
2011
2012   return info;
2013 }
2014
2015 static void
2016 stream_notify_cb (GstStreamCollection * collection, GstStream * stream,
2017     GParamSpec * pspec, GstPlay * self)
2018 {
2019   GstPlayStreamInfo *info;
2020   const gchar *stream_id;
2021   gboolean emit_signal = FALSE;
2022
2023   if (!self->media_info)
2024     return;
2025
2026   if (G_PARAM_SPEC_VALUE_TYPE (pspec) != GST_TYPE_CAPS &&
2027       G_PARAM_SPEC_VALUE_TYPE (pspec) != GST_TYPE_TAG_LIST)
2028     return;
2029
2030   stream_id = gst_stream_get_stream_id (stream);
2031   g_mutex_lock (&self->lock);
2032   info = gst_play_stream_info_find_from_stream_id (self->media_info, stream_id);
2033   if (info) {
2034     gst_play_stream_info_update_from_stream (self, info, stream);
2035     emit_signal = TRUE;
2036   }
2037   g_mutex_unlock (&self->lock);
2038
2039   if (emit_signal)
2040     on_media_info_updated (self);
2041 }
2042
2043 static void
2044 gst_play_stream_info_update (GstPlay * self, GstPlayStreamInfo * s)
2045 {
2046   if (GST_IS_PLAY_VIDEO_INFO (s))
2047     gst_play_video_info_update (self, s);
2048   else if (GST_IS_PLAY_AUDIO_INFO (s))
2049     gst_play_audio_info_update (self, s);
2050   else
2051     gst_play_subtitle_info_update (self, s);
2052 }
2053
2054 static gchar *
2055 stream_info_get_codec (GstPlayStreamInfo * s)
2056 {
2057   const gchar *type;
2058   GstTagList *tags;
2059   gchar *codec = NULL;
2060
2061   if (GST_IS_PLAY_VIDEO_INFO (s))
2062     type = GST_TAG_VIDEO_CODEC;
2063   else if (GST_IS_PLAY_AUDIO_INFO (s))
2064     type = GST_TAG_AUDIO_CODEC;
2065   else
2066     type = GST_TAG_SUBTITLE_CODEC;
2067
2068   tags = gst_play_stream_info_get_tags (s);
2069   if (tags) {
2070     gst_tag_list_get_string (tags, type, &codec);
2071     if (!codec)
2072       gst_tag_list_get_string (tags, GST_TAG_CODEC, &codec);
2073   }
2074
2075   if (!codec) {
2076     GstCaps *caps;
2077     caps = gst_play_stream_info_get_caps (s);
2078     if (caps) {
2079       codec = gst_pb_utils_get_codec_description (caps);
2080     }
2081   }
2082
2083   return codec;
2084 }
2085
2086 static void
2087 gst_play_stream_info_update_tags_and_caps (GstPlay * self,
2088     GstPlayStreamInfo * s)
2089 {
2090   GstTagList *tags;
2091   gint stream_index;
2092
2093   stream_index = gst_play_stream_info_get_index (s);
2094
2095   if (GST_IS_PLAY_VIDEO_INFO (s))
2096     g_signal_emit_by_name (self->playbin, "get-video-tags",
2097         stream_index, &tags);
2098   else if (GST_IS_PLAY_AUDIO_INFO (s))
2099     g_signal_emit_by_name (self->playbin, "get-audio-tags",
2100         stream_index, &tags);
2101   else
2102     g_signal_emit_by_name (self->playbin, "get-text-tags", stream_index, &tags);
2103
2104   if (s->tags)
2105     gst_tag_list_unref (s->tags);
2106   s->tags = tags;
2107
2108   if (s->caps)
2109     gst_caps_unref (s->caps);
2110   s->caps = get_caps (self, stream_index, G_OBJECT_TYPE (s));
2111
2112   g_free (s->codec);
2113   s->codec = stream_info_get_codec (s);
2114
2115   GST_DEBUG_OBJECT (self, "%s index: %d tags: %p caps: %p",
2116       gst_play_stream_info_get_stream_type (s), stream_index, s->tags, s->caps);
2117
2118   gst_play_stream_info_update (self, s);
2119 }
2120
2121 static void
2122 gst_play_streams_info_create (GstPlay * self,
2123     GstPlayMediaInfo * media_info, const gchar * prop, GType type)
2124 {
2125   gint i;
2126   gint total = -1;
2127   GstPlayStreamInfo *s;
2128
2129   if (!media_info)
2130     return;
2131
2132   g_object_get (G_OBJECT (self->playbin), prop, &total, NULL);
2133
2134   GST_DEBUG_OBJECT (self, "%s: %d", prop, total);
2135
2136   for (i = 0; i < total; i++) {
2137     /* check if stream already exist in the list */
2138     s = gst_play_stream_info_find (media_info, type, i);
2139
2140     if (!s) {
2141       /* create a new stream info instance */
2142       s = gst_play_stream_info_new (i, type);
2143
2144       /* add the object in stream list */
2145       media_info->stream_list = g_list_append (media_info->stream_list, s);
2146
2147       /* based on type, add the object in its corresponding stream_ list */
2148       if (GST_IS_PLAY_AUDIO_INFO (s))
2149         media_info->audio_stream_list = g_list_append
2150             (media_info->audio_stream_list, s);
2151       else if (GST_IS_PLAY_VIDEO_INFO (s))
2152         media_info->video_stream_list = g_list_append
2153             (media_info->video_stream_list, s);
2154       else
2155         media_info->subtitle_stream_list = g_list_append
2156             (media_info->subtitle_stream_list, s);
2157
2158       GST_DEBUG_OBJECT (self, "create %s stream stream_index: %d",
2159           gst_play_stream_info_get_stream_type (s), i);
2160     }
2161
2162     gst_play_stream_info_update_tags_and_caps (self, s);
2163   }
2164 }
2165
2166 static void
2167 gst_play_stream_info_update_from_stream (GstPlay * self,
2168     GstPlayStreamInfo * s, GstStream * stream)
2169 {
2170   if (s->tags)
2171     gst_tag_list_unref (s->tags);
2172   s->tags = gst_stream_get_tags (stream);
2173
2174   if (s->caps)
2175     gst_caps_unref (s->caps);
2176   s->caps = gst_stream_get_caps (stream);
2177
2178   g_free (s->codec);
2179   s->codec = stream_info_get_codec (s);
2180
2181   GST_DEBUG_OBJECT (self, "%s index: %d tags: %p caps: %p",
2182       gst_play_stream_info_get_stream_type (s), s->stream_index,
2183       s->tags, s->caps);
2184
2185   gst_play_stream_info_update (self, s);
2186 }
2187
2188 static void
2189 gst_play_streams_info_create_from_collection (GstPlay * self,
2190     GstPlayMediaInfo * media_info, GstStreamCollection * collection)
2191 {
2192   guint i;
2193   guint total;
2194   GstPlayStreamInfo *s;
2195   guint n_audio = 0;
2196   guint n_video = 0;
2197   guint n_text = 0;
2198
2199   if (!media_info || !collection)
2200     return;
2201
2202   total = gst_stream_collection_get_size (collection);
2203
2204   for (i = 0; i < total; i++) {
2205     GstStream *stream = gst_stream_collection_get_stream (collection, i);
2206     GstStreamType stream_type = gst_stream_get_stream_type (stream);
2207     const gchar *stream_id = gst_stream_get_stream_id (stream);
2208
2209     if (stream_type & GST_STREAM_TYPE_AUDIO) {
2210       s = gst_play_stream_info_new (n_audio, GST_TYPE_PLAY_AUDIO_INFO);
2211       n_audio++;
2212     } else if (stream_type & GST_STREAM_TYPE_VIDEO) {
2213       s = gst_play_stream_info_new (n_video, GST_TYPE_PLAY_VIDEO_INFO);
2214       n_video++;
2215     } else if (stream_type & GST_STREAM_TYPE_TEXT) {
2216       s = gst_play_stream_info_new (n_text, GST_TYPE_PLAY_SUBTITLE_INFO);
2217       n_text++;
2218     } else {
2219       GST_DEBUG_OBJECT (self, "Unknown type stream %d", i);
2220       continue;
2221     }
2222
2223     s->stream_id = g_strdup (stream_id);
2224
2225     /* add the object in stream list */
2226     media_info->stream_list = g_list_append (media_info->stream_list, s);
2227
2228     /* based on type, add the object in its corresponding stream_ list */
2229     if (GST_IS_PLAY_AUDIO_INFO (s))
2230       media_info->audio_stream_list = g_list_append
2231           (media_info->audio_stream_list, s);
2232     else if (GST_IS_PLAY_VIDEO_INFO (s))
2233       media_info->video_stream_list = g_list_append
2234           (media_info->video_stream_list, s);
2235     else
2236       media_info->subtitle_stream_list = g_list_append
2237           (media_info->subtitle_stream_list, s);
2238
2239     GST_DEBUG_OBJECT (self, "create %s stream stream_index: %d",
2240         gst_play_stream_info_get_stream_type (s), s->stream_index);
2241
2242     gst_play_stream_info_update_from_stream (self, s, stream);
2243   }
2244 }
2245
2246 static void
2247 video_changed_cb (G_GNUC_UNUSED GObject * object, gpointer user_data)
2248 {
2249   GstPlay *self = GST_PLAY (user_data);
2250
2251   g_mutex_lock (&self->lock);
2252   gst_play_streams_info_create (self, self->media_info,
2253       "n-video", GST_TYPE_PLAY_VIDEO_INFO);
2254   g_mutex_unlock (&self->lock);
2255 }
2256
2257 static void
2258 audio_changed_cb (G_GNUC_UNUSED GObject * object, gpointer user_data)
2259 {
2260   GstPlay *self = GST_PLAY (user_data);
2261
2262   g_mutex_lock (&self->lock);
2263   gst_play_streams_info_create (self, self->media_info,
2264       "n-audio", GST_TYPE_PLAY_AUDIO_INFO);
2265   g_mutex_unlock (&self->lock);
2266 }
2267
2268 static void
2269 subtitle_changed_cb (G_GNUC_UNUSED GObject * object, gpointer user_data)
2270 {
2271   GstPlay *self = GST_PLAY (user_data);
2272
2273   g_mutex_lock (&self->lock);
2274   gst_play_streams_info_create (self, self->media_info,
2275       "n-text", GST_TYPE_PLAY_SUBTITLE_INFO);
2276   g_mutex_unlock (&self->lock);
2277 }
2278
2279 static void *
2280 get_title (GstTagList * tags)
2281 {
2282   gchar *title = NULL;
2283
2284   gst_tag_list_get_string (tags, GST_TAG_TITLE, &title);
2285   if (!title)
2286     gst_tag_list_get_string (tags, GST_TAG_TITLE_SORTNAME, &title);
2287
2288   return title;
2289 }
2290
2291 static void *
2292 get_container_format (GstTagList * tags)
2293 {
2294   gchar *container = NULL;
2295
2296   gst_tag_list_get_string (tags, GST_TAG_CONTAINER_FORMAT, &container);
2297
2298   /* TODO: If container is not available then maybe consider
2299    * parsing caps or file extension to guess the container format.
2300    */
2301
2302   return container;
2303 }
2304
2305 static void *
2306 get_from_tags (GstPlay * self, GstPlayMediaInfo * media_info,
2307     void *(*func) (GstTagList *))
2308 {
2309   GList *l;
2310   void *ret = NULL;
2311
2312   if (media_info->tags) {
2313     ret = func (media_info->tags);
2314     if (ret)
2315       return ret;
2316   }
2317
2318   /* if global tag does not exit then try video and audio streams */
2319   GST_DEBUG_OBJECT (self, "trying video tags");
2320   for (l = gst_play_media_info_get_video_streams (media_info); l != NULL;
2321       l = l->next) {
2322     GstTagList *tags;
2323
2324     tags = gst_play_stream_info_get_tags ((GstPlayStreamInfo *) l->data);
2325     if (tags)
2326       ret = func (tags);
2327
2328     if (ret)
2329       return ret;
2330   }
2331
2332   GST_DEBUG_OBJECT (self, "trying audio tags");
2333   for (l = gst_play_media_info_get_audio_streams (media_info); l != NULL;
2334       l = l->next) {
2335     GstTagList *tags;
2336
2337     tags = gst_play_stream_info_get_tags ((GstPlayStreamInfo *) l->data);
2338     if (tags)
2339       ret = func (tags);
2340
2341     if (ret)
2342       return ret;
2343   }
2344
2345   GST_DEBUG_OBJECT (self, "failed to get the information from tags");
2346   return NULL;
2347 }
2348
2349 static void *
2350 get_cover_sample (GstTagList * tags)
2351 {
2352   GstSample *cover_sample = NULL;
2353
2354   gst_tag_list_get_sample (tags, GST_TAG_IMAGE, &cover_sample);
2355   if (!cover_sample)
2356     gst_tag_list_get_sample (tags, GST_TAG_PREVIEW_IMAGE, &cover_sample);
2357
2358   return cover_sample;
2359 }
2360
2361 static GstPlayMediaInfo *
2362 gst_play_media_info_create (GstPlay * self)
2363 {
2364   GstPlayMediaInfo *media_info;
2365   GstQuery *query;
2366
2367   GST_DEBUG_OBJECT (self, "begin");
2368   media_info = gst_play_media_info_new (self->uri);
2369   media_info->duration = gst_play_get_duration (self);
2370   media_info->tags = self->global_tags;
2371   media_info->is_live = self->is_live;
2372   self->global_tags = NULL;
2373
2374   query = gst_query_new_seeking (GST_FORMAT_TIME);
2375   if (gst_element_query (self->playbin, query))
2376     gst_query_parse_seeking (query, NULL, &media_info->seekable, NULL, NULL);
2377   gst_query_unref (query);
2378
2379   if (self->use_playbin3 && self->collection) {
2380     gst_play_streams_info_create_from_collection (self, media_info,
2381         self->collection);
2382   } else {
2383     /* create audio/video/sub streams */
2384     gst_play_streams_info_create (self, media_info, "n-video",
2385         GST_TYPE_PLAY_VIDEO_INFO);
2386     gst_play_streams_info_create (self, media_info, "n-audio",
2387         GST_TYPE_PLAY_AUDIO_INFO);
2388     gst_play_streams_info_create (self, media_info, "n-text",
2389         GST_TYPE_PLAY_SUBTITLE_INFO);
2390   }
2391
2392   media_info->title = get_from_tags (self, media_info, get_title);
2393   media_info->container =
2394       get_from_tags (self, media_info, get_container_format);
2395   media_info->image_sample = get_from_tags (self, media_info, get_cover_sample);
2396
2397   GST_DEBUG_OBJECT (self, "uri: %s title: %s duration: %" GST_TIME_FORMAT
2398       " seekable: %s live: %s container: %s image_sample %p",
2399       media_info->uri, media_info->title, GST_TIME_ARGS (media_info->duration),
2400       media_info->seekable ? "yes" : "no", media_info->is_live ? "yes" : "no",
2401       media_info->container, media_info->image_sample);
2402
2403   GST_DEBUG_OBJECT (self, "end");
2404   return media_info;
2405 }
2406
2407 static void
2408 tags_changed_cb (GstPlay * self, gint stream_index, GType type)
2409 {
2410   GstPlayStreamInfo *s;
2411
2412   if (!self->media_info)
2413     return;
2414
2415   /* update the stream information */
2416   g_mutex_lock (&self->lock);
2417   s = gst_play_stream_info_find (self->media_info, type, stream_index);
2418   gst_play_stream_info_update_tags_and_caps (self, s);
2419   g_mutex_unlock (&self->lock);
2420
2421   on_media_info_updated (self);
2422 }
2423
2424 static void
2425 video_tags_changed_cb (G_GNUC_UNUSED GstElement * playbin, gint stream_index,
2426     gpointer user_data)
2427 {
2428   tags_changed_cb (GST_PLAY (user_data), stream_index,
2429       GST_TYPE_PLAY_VIDEO_INFO);
2430 }
2431
2432 static void
2433 audio_tags_changed_cb (G_GNUC_UNUSED GstElement * playbin, gint stream_index,
2434     gpointer user_data)
2435 {
2436   tags_changed_cb (GST_PLAY (user_data), stream_index,
2437       GST_TYPE_PLAY_AUDIO_INFO);
2438 }
2439
2440 static void
2441 subtitle_tags_changed_cb (G_GNUC_UNUSED GstElement * playbin, gint stream_index,
2442     gpointer user_data)
2443 {
2444   tags_changed_cb (GST_PLAY (user_data), stream_index,
2445       GST_TYPE_PLAY_SUBTITLE_INFO);
2446 }
2447
2448 static void
2449 volume_notify_cb (G_GNUC_UNUSED GObject * obj, G_GNUC_UNUSED GParamSpec * pspec,
2450     GstPlay * self)
2451 {
2452   api_bus_post_message (self, GST_PLAY_MESSAGE_VOLUME_CHANGED,
2453       GST_PLAY_MESSAGE_DATA_VOLUME, G_TYPE_DOUBLE,
2454       gst_play_get_volume (self), NULL);
2455 }
2456
2457 static void
2458 mute_notify_cb (G_GNUC_UNUSED GObject * obj, G_GNUC_UNUSED GParamSpec * pspec,
2459     GstPlay * self)
2460 {
2461
2462   api_bus_post_message (self, GST_PLAY_MESSAGE_MUTE_CHANGED,
2463       GST_PLAY_MESSAGE_DATA_IS_MUTED, G_TYPE_BOOLEAN,
2464       gst_play_get_mute (self), NULL);
2465 }
2466
2467 static void
2468 source_setup_cb (GstElement * playbin, GstElement * source, GstPlay * self)
2469 {
2470   gchar *user_agent;
2471
2472   user_agent = gst_play_config_get_user_agent (self->config);
2473   if (user_agent) {
2474     GParamSpec *prop;
2475
2476     prop = g_object_class_find_property (G_OBJECT_GET_CLASS (source),
2477         "user-agent");
2478     if (prop && prop->value_type == G_TYPE_STRING) {
2479       GST_INFO_OBJECT (self, "Setting source user-agent: %s", user_agent);
2480       g_object_set (source, "user-agent", user_agent, NULL);
2481     }
2482
2483     g_free (user_agent);
2484   }
2485 }
2486
2487 static gpointer
2488 gst_play_main (gpointer data)
2489 {
2490   GstPlay *self = GST_PLAY (data);
2491   GstBus *bus;
2492   GSource *source;
2493   GstElement *scaletempo;
2494   const gchar *env;
2495
2496   GST_TRACE_OBJECT (self, "Starting main thread");
2497
2498   g_main_context_push_thread_default (self->context);
2499
2500   source = g_idle_source_new ();
2501   g_source_set_callback (source, (GSourceFunc) main_loop_running_cb, self,
2502       NULL);
2503   g_source_attach (source, self->context);
2504   g_source_unref (source);
2505
2506   env = g_getenv ("GST_PLAY_USE_PLAYBIN3");
2507   if (env && g_str_has_prefix (env, "1"))
2508     self->use_playbin3 = TRUE;
2509
2510   if (self->use_playbin3) {
2511     GST_DEBUG_OBJECT (self, "playbin3 enabled");
2512     self->playbin = gst_element_factory_make ("playbin3", "playbin3");
2513   } else {
2514     self->playbin = gst_element_factory_make ("playbin", "playbin");
2515   }
2516
2517   if (!self->playbin) {
2518     g_error ("GstPlay: 'playbin' element not found, please check your setup");
2519     g_assert_not_reached ();
2520   }
2521
2522   gst_object_ref_sink (self->playbin);
2523
2524   if (self->video_renderer) {
2525     gst_play_set_playbin_video_sink (self);
2526   }
2527
2528   scaletempo = gst_element_factory_make ("scaletempo", NULL);
2529   if (scaletempo) {
2530     g_object_set (self->playbin, "audio-filter", scaletempo, NULL);
2531   } else {
2532     g_warning ("GstPlay: scaletempo element not available. Audio pitch "
2533         "will not be preserved during trick modes");
2534   }
2535
2536   self->bus = bus = gst_element_get_bus (self->playbin);
2537   gst_bus_add_signal_watch (bus);
2538
2539   g_signal_connect (G_OBJECT (bus), "message::error", G_CALLBACK (error_cb),
2540       self);
2541   g_signal_connect (G_OBJECT (bus), "message::warning", G_CALLBACK (warning_cb),
2542       self);
2543   g_signal_connect (G_OBJECT (bus), "message::eos", G_CALLBACK (eos_cb), self);
2544   g_signal_connect (G_OBJECT (bus), "message::state-changed",
2545       G_CALLBACK (state_changed_cb), self);
2546   g_signal_connect (G_OBJECT (bus), "message::buffering",
2547       G_CALLBACK (buffering_cb), self);
2548   g_signal_connect (G_OBJECT (bus), "message::clock-lost",
2549       G_CALLBACK (clock_lost_cb), self);
2550   g_signal_connect (G_OBJECT (bus), "message::duration-changed",
2551       G_CALLBACK (duration_changed_cb), self);
2552   g_signal_connect (G_OBJECT (bus), "message::latency",
2553       G_CALLBACK (latency_cb), self);
2554   g_signal_connect (G_OBJECT (bus), "message::request-state",
2555       G_CALLBACK (request_state_cb), self);
2556   g_signal_connect (G_OBJECT (bus), "message::element",
2557       G_CALLBACK (element_cb), self);
2558   g_signal_connect (G_OBJECT (bus), "message::tag", G_CALLBACK (tags_cb), self);
2559
2560   if (self->use_playbin3) {
2561     g_signal_connect (G_OBJECT (bus), "message::stream-collection",
2562         G_CALLBACK (stream_collection_cb), self);
2563     g_signal_connect (G_OBJECT (bus), "message::streams-selected",
2564         G_CALLBACK (streams_selected_cb), self);
2565   } else {
2566     g_signal_connect (self->playbin, "video-changed",
2567         G_CALLBACK (video_changed_cb), self);
2568     g_signal_connect (self->playbin, "audio-changed",
2569         G_CALLBACK (audio_changed_cb), self);
2570     g_signal_connect (self->playbin, "text-changed",
2571         G_CALLBACK (subtitle_changed_cb), self);
2572
2573     g_signal_connect (self->playbin, "video-tags-changed",
2574         G_CALLBACK (video_tags_changed_cb), self);
2575     g_signal_connect (self->playbin, "audio-tags-changed",
2576         G_CALLBACK (audio_tags_changed_cb), self);
2577     g_signal_connect (self->playbin, "text-tags-changed",
2578         G_CALLBACK (subtitle_tags_changed_cb), self);
2579   }
2580
2581   g_signal_connect (self->playbin, "notify::volume",
2582       G_CALLBACK (volume_notify_cb), self);
2583   g_signal_connect (self->playbin, "notify::mute",
2584       G_CALLBACK (mute_notify_cb), self);
2585   g_signal_connect (self->playbin, "source-setup",
2586       G_CALLBACK (source_setup_cb), self);
2587
2588   self->target_state = GST_STATE_NULL;
2589   self->current_state = GST_STATE_NULL;
2590   change_state (self, GST_PLAY_STATE_STOPPED);
2591   self->buffering_percent = 100;
2592   self->is_eos = FALSE;
2593   self->is_live = FALSE;
2594   self->rate = 1.0;
2595
2596   GST_TRACE_OBJECT (self, "Starting main loop");
2597   g_main_loop_run (self->loop);
2598   GST_TRACE_OBJECT (self, "Stopped main loop");
2599
2600   gst_bus_remove_signal_watch (bus);
2601   gst_object_unref (bus);
2602
2603   remove_tick_source (self);
2604   remove_ready_timeout_source (self);
2605
2606   g_mutex_lock (&self->lock);
2607   if (self->media_info) {
2608     g_object_unref (self->media_info);
2609     self->media_info = NULL;
2610   }
2611
2612   remove_seek_source (self);
2613   g_mutex_unlock (&self->lock);
2614
2615   g_main_context_pop_thread_default (self->context);
2616
2617   self->target_state = GST_STATE_NULL;
2618   self->current_state = GST_STATE_NULL;
2619   if (self->playbin) {
2620     gst_element_set_state (self->playbin, GST_STATE_NULL);
2621     gst_object_unref (self->playbin);
2622     self->playbin = NULL;
2623   }
2624
2625   GST_TRACE_OBJECT (self, "Stopped main thread");
2626
2627   return NULL;
2628 }
2629
2630 static gpointer
2631 gst_play_init_once (G_GNUC_UNUSED gpointer user_data)
2632 {
2633   gst_init (NULL, NULL);
2634
2635   GST_DEBUG_CATEGORY_INIT (gst_play_debug, "gst-play", 0, "GstPlay");
2636   gst_play_error_quark ();
2637
2638   return NULL;
2639 }
2640
2641 /**
2642  * gst_play_new:
2643  * @video_renderer: (transfer full) (allow-none): GstPlayVideoRenderer to use
2644  *
2645  * Creates a new #GstPlay instance.
2646  *
2647  * Video is going to be rendered by @video_renderer, or if %NULL is provided
2648  * no special video set up will be done and some default handling will be
2649  * performed.
2650  *
2651  * Returns: (transfer full): a new #GstPlay instance
2652  * Since: 1.20
2653  */
2654 GstPlay *
2655 gst_play_new (GstPlayVideoRenderer * video_renderer)
2656 {
2657   static GOnce once = G_ONCE_INIT;
2658   GstPlay *self;
2659
2660   g_once (&once, gst_play_init_once, NULL);
2661
2662   self = g_object_new (GST_TYPE_PLAY, "video-renderer", video_renderer, NULL);
2663
2664   gst_object_ref_sink (self);
2665
2666   if (video_renderer)
2667     g_object_unref (video_renderer);
2668
2669   return self;
2670 }
2671
2672 /**
2673  * gst_play_get_message_bus:
2674  * @play: #GstPlay instance
2675  *
2676  * GstPlay API exposes a #GstBus instance which purpose is to provide data
2677  * structures representing play-internal events in form of #GstMessage<!-- -->s of
2678  * type GST_MESSAGE_APPLICATION.
2679  *
2680  * Each message carries a "play-message" field of type #GstPlayMessage.
2681  * Further fields of the message data are specific to each possible value of
2682  * that enumeration.
2683  *
2684  * Applications can consume the messages asynchronously within their own
2685  * event-loop / UI-thread etc. Note that in case the application does not
2686  * consume the messages, the bus will accumulate these internally and eventually
2687  * fill memory. To avoid that, the bus has to be set "flushing".
2688  *
2689  * Returns: (transfer full): The play message bus instance
2690  *
2691  * Since: 1.20
2692  */
2693 GstBus *
2694 gst_play_get_message_bus (GstPlay * self)
2695 {
2696   return g_object_ref (self->api_bus);
2697 }
2698
2699 static gboolean
2700 gst_play_play_internal (gpointer user_data)
2701 {
2702   GstPlay *self = GST_PLAY (user_data);
2703   GstStateChangeReturn state_ret;
2704
2705   GST_DEBUG_OBJECT (self, "Play");
2706
2707   g_mutex_lock (&self->lock);
2708   if (!self->uri) {
2709     g_mutex_unlock (&self->lock);
2710     return G_SOURCE_REMOVE;
2711   }
2712   g_mutex_unlock (&self->lock);
2713
2714   remove_ready_timeout_source (self);
2715   self->target_state = GST_STATE_PLAYING;
2716
2717   if (self->current_state < GST_STATE_PAUSED)
2718     change_state (self, GST_PLAY_STATE_BUFFERING);
2719
2720   if (self->current_state >= GST_STATE_PAUSED && !self->is_eos
2721       && self->buffering_percent >= 100
2722       && !(self->seek_position != GST_CLOCK_TIME_NONE || self->seek_pending)) {
2723     state_ret = gst_element_set_state (self->playbin, GST_STATE_PLAYING);
2724   } else {
2725     state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED);
2726   }
2727
2728   if (state_ret == GST_STATE_CHANGE_FAILURE) {
2729     on_error (self, g_error_new (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED,
2730             "Failed to play"), NULL);
2731     return G_SOURCE_REMOVE;
2732   } else if (state_ret == GST_STATE_CHANGE_NO_PREROLL) {
2733     self->is_live = TRUE;
2734     GST_DEBUG_OBJECT (self, "Pipeline is live");
2735   }
2736
2737   if (self->is_eos) {
2738     gboolean ret;
2739
2740     GST_DEBUG_OBJECT (self, "Was EOS, seeking to beginning");
2741     self->is_eos = FALSE;
2742     ret =
2743         gst_element_seek_simple (self->playbin, GST_FORMAT_TIME,
2744         GST_SEEK_FLAG_FLUSH, 0);
2745     if (!ret) {
2746       GST_ERROR_OBJECT (self, "Seek to beginning failed");
2747       gst_play_stop_internal (self, TRUE);
2748       gst_play_play_internal (self);
2749     }
2750   }
2751
2752   return G_SOURCE_REMOVE;
2753 }
2754
2755 /**
2756  * gst_play_play:
2757  * @play: #GstPlay instance
2758  *
2759  * Request to play the loaded stream.
2760  * Since: 1.20
2761  */
2762 void
2763 gst_play_play (GstPlay * self)
2764 {
2765   g_return_if_fail (GST_IS_PLAY (self));
2766
2767   g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT,
2768       gst_play_play_internal, self, NULL);
2769 }
2770
2771 static gboolean
2772 gst_play_pause_internal (gpointer user_data)
2773 {
2774   GstPlay *self = GST_PLAY (user_data);
2775   GstStateChangeReturn state_ret;
2776
2777   GST_DEBUG_OBJECT (self, "Pause");
2778
2779   g_mutex_lock (&self->lock);
2780   if (!self->uri) {
2781     g_mutex_unlock (&self->lock);
2782     return G_SOURCE_REMOVE;
2783   }
2784   g_mutex_unlock (&self->lock);
2785
2786   tick_cb (self);
2787   remove_tick_source (self);
2788   remove_ready_timeout_source (self);
2789
2790   self->target_state = GST_STATE_PAUSED;
2791
2792   if (self->current_state < GST_STATE_PAUSED)
2793     change_state (self, GST_PLAY_STATE_BUFFERING);
2794
2795   state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED);
2796   if (state_ret == GST_STATE_CHANGE_FAILURE) {
2797     on_error (self, g_error_new (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED,
2798             "Failed to pause"), NULL);
2799     return G_SOURCE_REMOVE;
2800   } else if (state_ret == GST_STATE_CHANGE_NO_PREROLL) {
2801     self->is_live = TRUE;
2802     GST_DEBUG_OBJECT (self, "Pipeline is live");
2803   }
2804
2805   if (self->is_eos) {
2806     gboolean ret;
2807
2808     GST_DEBUG_OBJECT (self, "Was EOS, seeking to beginning");
2809     self->is_eos = FALSE;
2810     ret =
2811         gst_element_seek_simple (self->playbin, GST_FORMAT_TIME,
2812         GST_SEEK_FLAG_FLUSH, 0);
2813     if (!ret) {
2814       GST_ERROR_OBJECT (self, "Seek to beginning failed");
2815       gst_play_stop_internal (self, TRUE);
2816       gst_play_pause_internal (self);
2817     }
2818   }
2819
2820   return G_SOURCE_REMOVE;
2821 }
2822
2823 /**
2824  * gst_play_pause:
2825  * @play: #GstPlay instance
2826  *
2827  * Pauses the current stream.
2828  * Since: 1.20
2829  */
2830 void
2831 gst_play_pause (GstPlay * self)
2832 {
2833   g_return_if_fail (GST_IS_PLAY (self));
2834
2835   g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT,
2836       gst_play_pause_internal, self, NULL);
2837 }
2838
2839 static void
2840 gst_play_stop_internal (GstPlay * self, gboolean transient)
2841 {
2842   /* directly return if we're already stopped */
2843   if (self->current_state <= GST_STATE_READY &&
2844       self->target_state <= GST_STATE_READY)
2845     return;
2846
2847   GST_DEBUG_OBJECT (self, "Stop (transient %d)", transient);
2848
2849   tick_cb (self);
2850   remove_tick_source (self);
2851
2852   add_ready_timeout_source (self);
2853
2854   self->target_state = GST_STATE_NULL;
2855   self->current_state = GST_STATE_READY;
2856   self->is_live = FALSE;
2857   self->is_eos = FALSE;
2858   gst_bus_set_flushing (self->bus, TRUE);
2859   gst_element_set_state (self->playbin, GST_STATE_READY);
2860   gst_bus_set_flushing (self->bus, FALSE);
2861   change_state (self, transient
2862       && self->app_state !=
2863       GST_PLAY_STATE_STOPPED ? GST_PLAY_STATE_BUFFERING :
2864       GST_PLAY_STATE_STOPPED);
2865   self->buffering_percent = 100;
2866   self->cached_duration = GST_CLOCK_TIME_NONE;
2867   g_mutex_lock (&self->lock);
2868   if (self->media_info) {
2869     g_object_unref (self->media_info);
2870     self->media_info = NULL;
2871   }
2872   if (self->global_tags) {
2873     gst_tag_list_unref (self->global_tags);
2874     self->global_tags = NULL;
2875   }
2876   self->seek_pending = FALSE;
2877   remove_seek_source (self);
2878   self->seek_position = GST_CLOCK_TIME_NONE;
2879   self->last_seek_time = GST_CLOCK_TIME_NONE;
2880   self->rate = 1.0;
2881   if (self->collection) {
2882     if (self->stream_notify_id)
2883       g_signal_handler_disconnect (self->collection, self->stream_notify_id);
2884     self->stream_notify_id = 0;
2885     gst_object_unref (self->collection);
2886     self->collection = NULL;
2887   }
2888   g_free (self->video_sid);
2889   g_free (self->audio_sid);
2890   g_free (self->subtitle_sid);
2891   self->video_sid = NULL;
2892   self->audio_sid = NULL;
2893   self->subtitle_sid = NULL;
2894   g_mutex_unlock (&self->lock);
2895 }
2896
2897 static gboolean
2898 gst_play_stop_internal_dispatch (gpointer user_data)
2899 {
2900   GstPlay *self = GST_PLAY (user_data);
2901
2902   gst_play_stop_internal (self, FALSE);
2903
2904   return G_SOURCE_REMOVE;
2905 }
2906
2907
2908 /**
2909  * gst_play_stop:
2910  * @play: #GstPlay instance
2911  *
2912  * Stops playing the current stream and resets to the first position
2913  * in the stream.
2914  * Since: 1.20
2915  */
2916 void
2917 gst_play_stop (GstPlay * self)
2918 {
2919   g_return_if_fail (GST_IS_PLAY (self));
2920
2921   g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT,
2922       gst_play_stop_internal_dispatch, self, NULL);
2923 }
2924
2925 /* Must be called with lock from main context, releases lock! */
2926 static void
2927 gst_play_seek_internal_locked (GstPlay * self)
2928 {
2929   gboolean ret;
2930   GstClockTime position;
2931   gdouble rate;
2932   GstStateChangeReturn state_ret;
2933   GstEvent *s_event;
2934   GstSeekFlags flags = 0;
2935   gboolean accurate = FALSE;
2936
2937   remove_seek_source (self);
2938
2939   /* Only seek in PAUSED */
2940   if (self->current_state < GST_STATE_PAUSED) {
2941     return;
2942   } else if (self->current_state != GST_STATE_PAUSED) {
2943     g_mutex_unlock (&self->lock);
2944     state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED);
2945     if (state_ret == GST_STATE_CHANGE_FAILURE) {
2946       on_error (self, g_error_new (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED,
2947               "Failed to seek"), NULL);
2948       g_mutex_lock (&self->lock);
2949       return;
2950     }
2951     g_mutex_lock (&self->lock);
2952     return;
2953   }
2954
2955   self->last_seek_time = gst_util_get_timestamp ();
2956   position = self->seek_position;
2957   self->seek_position = GST_CLOCK_TIME_NONE;
2958   self->seek_pending = TRUE;
2959   rate = self->rate;
2960   g_mutex_unlock (&self->lock);
2961
2962   remove_tick_source (self);
2963   self->is_eos = FALSE;
2964
2965   flags |= GST_SEEK_FLAG_FLUSH;
2966
2967   accurate = gst_play_config_get_seek_accurate (self->config);
2968
2969   if (accurate) {
2970     flags |= GST_SEEK_FLAG_ACCURATE;
2971   } else {
2972     flags &= ~GST_SEEK_FLAG_ACCURATE;
2973   }
2974
2975   if (rate != 1.0) {
2976     flags |= GST_SEEK_FLAG_TRICKMODE;
2977   }
2978
2979   if (rate >= 0.0) {
2980     s_event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags,
2981         GST_SEEK_TYPE_SET, position, GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE);
2982   } else {
2983     s_event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags,
2984         GST_SEEK_TYPE_SET, G_GINT64_CONSTANT (0), GST_SEEK_TYPE_SET, position);
2985   }
2986
2987   GST_DEBUG_OBJECT (self, "Seek with rate %.2lf to %" GST_TIME_FORMAT,
2988       rate, GST_TIME_ARGS (position));
2989
2990   ret = gst_element_send_event (self->playbin, s_event);
2991   if (!ret)
2992     on_error (self, g_error_new (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED,
2993             "Failed to seek to %" GST_TIME_FORMAT, GST_TIME_ARGS (position)),
2994         NULL);
2995
2996   g_mutex_lock (&self->lock);
2997 }
2998
2999 static gboolean
3000 gst_play_seek_internal (gpointer user_data)
3001 {
3002   GstPlay *self = GST_PLAY (user_data);
3003
3004   g_mutex_lock (&self->lock);
3005   gst_play_seek_internal_locked (self);
3006   g_mutex_unlock (&self->lock);
3007
3008   return G_SOURCE_REMOVE;
3009 }
3010
3011 /**
3012  * gst_play_set_rate:
3013  * @play: #GstPlay instance
3014  * @rate: playback rate
3015  *
3016  * Playback at specified rate
3017  * Since: 1.20
3018  */
3019 void
3020 gst_play_set_rate (GstPlay * self, gdouble rate)
3021 {
3022   g_return_if_fail (GST_IS_PLAY (self));
3023   g_return_if_fail (rate != 0.0);
3024
3025   g_object_set (self, "rate", rate, NULL);
3026 }
3027
3028 /**
3029  * gst_play_get_rate:
3030  * @play: #GstPlay instance
3031  *
3032  * Returns: current playback rate
3033  * Since: 1.20
3034  */
3035 gdouble
3036 gst_play_get_rate (GstPlay * self)
3037 {
3038   gdouble val;
3039
3040   g_return_val_if_fail (GST_IS_PLAY (self), DEFAULT_RATE);
3041
3042   g_object_get (self, "rate", &val, NULL);
3043
3044   return val;
3045 }
3046
3047 /**
3048  * gst_play_seek:
3049  * @play: #GstPlay instance
3050  * @position: position to seek in nanoseconds
3051  *
3052  * Seeks the currently-playing stream to the absolute @position time
3053  * in nanoseconds.
3054  * Since: 1.20
3055  */
3056 void
3057 gst_play_seek (GstPlay * self, GstClockTime position)
3058 {
3059   g_return_if_fail (GST_IS_PLAY (self));
3060   g_return_if_fail (GST_CLOCK_TIME_IS_VALID (position));
3061
3062   g_mutex_lock (&self->lock);
3063   if (self->media_info && !self->media_info->seekable) {
3064     GST_DEBUG_OBJECT (self, "Media is not seekable");
3065     g_mutex_unlock (&self->lock);
3066     return;
3067   }
3068
3069   self->seek_position = position;
3070
3071   /* If there is no seek being dispatch to the main context currently do that,
3072    * otherwise we just updated the seek position so that it will be taken by
3073    * the seek handler from the main context instead of the old one.
3074    */
3075   if (!self->seek_source) {
3076     GstClockTime now = gst_util_get_timestamp ();
3077
3078     /* If no seek is pending or it was started more than 250 mseconds ago seek
3079      * immediately, otherwise wait until the 250 mseconds have passed */
3080     if (!self->seek_pending || (now - self->last_seek_time > 250 * GST_MSECOND)) {
3081       self->seek_source = g_idle_source_new ();
3082       g_source_set_callback (self->seek_source,
3083           (GSourceFunc) gst_play_seek_internal, self, NULL);
3084       GST_TRACE_OBJECT (self, "Dispatching seek to position %" GST_TIME_FORMAT,
3085           GST_TIME_ARGS (position));
3086       g_source_attach (self->seek_source, self->context);
3087     } else {
3088       guint delay = 250000 - (now - self->last_seek_time) / 1000;
3089
3090       /* Note that last_seek_time must be set to something at this point and
3091        * it must be smaller than 250 mseconds */
3092       self->seek_source = g_timeout_source_new (delay);
3093       g_source_set_callback (self->seek_source,
3094           (GSourceFunc) gst_play_seek_internal, self, NULL);
3095
3096       GST_TRACE_OBJECT (self,
3097           "Delaying seek to position %" GST_TIME_FORMAT " by %u us",
3098           GST_TIME_ARGS (position), delay);
3099       g_source_attach (self->seek_source, self->context);
3100     }
3101   }
3102   g_mutex_unlock (&self->lock);
3103 }
3104
3105 static void
3106 remove_seek_source (GstPlay * self)
3107 {
3108   if (!self->seek_source)
3109     return;
3110
3111   g_source_destroy (self->seek_source);
3112   g_source_unref (self->seek_source);
3113   self->seek_source = NULL;
3114 }
3115
3116 /**
3117  * gst_play_get_uri:
3118  * @play: #GstPlay instance
3119  *
3120  * Gets the URI of the currently-playing stream.
3121  *
3122  * Returns: (transfer full) (nullable): a string containing the URI of the
3123  * currently-playing stream. g_free() after usage.
3124  * Since: 1.20
3125  */
3126 gchar *
3127 gst_play_get_uri (GstPlay * self)
3128 {
3129   gchar *val;
3130
3131   g_return_val_if_fail (GST_IS_PLAY (self), DEFAULT_URI);
3132
3133   g_object_get (self, "uri", &val, NULL);
3134
3135   return val;
3136 }
3137
3138 /**
3139  * gst_play_set_uri:
3140  * @play: #GstPlay instance
3141  * @uri: (nullable): next URI to play.
3142  *
3143  * Sets the next URI to play.
3144  * Since: 1.20
3145  */
3146 void
3147 gst_play_set_uri (GstPlay * self, const gchar * val)
3148 {
3149   g_return_if_fail (GST_IS_PLAY (self));
3150
3151   g_object_set (self, "uri", val, NULL);
3152 }
3153
3154 /**
3155  * gst_play_set_subtitle_uri:
3156  * @play: #GstPlay instance
3157  * @uri: (nullable): subtitle URI
3158  *
3159  * Sets the external subtitle URI. This should be combined with a call to
3160  * gst_play_set_subtitle_track_enabled(@play, TRUE) so the subtitles are actually
3161  * rendered.
3162  * Since: 1.20
3163  */
3164 void
3165 gst_play_set_subtitle_uri (GstPlay * self, const gchar * suburi)
3166 {
3167   g_return_if_fail (GST_IS_PLAY (self));
3168
3169   g_object_set (self, "suburi", suburi, NULL);
3170 }
3171
3172 /**
3173  * gst_play_get_subtitle_uri:
3174  * @play: #GstPlay instance
3175  *
3176  * current subtitle URI
3177  *
3178  * Returns: (transfer full) (nullable): URI of the current external subtitle.
3179  *   g_free() after usage.
3180  * Since: 1.20
3181  */
3182 gchar *
3183 gst_play_get_subtitle_uri (GstPlay * self)
3184 {
3185   gchar *val = NULL;
3186
3187   g_return_val_if_fail (GST_IS_PLAY (self), NULL);
3188
3189   g_object_get (self, "suburi", &val, NULL);
3190
3191   return val;
3192 }
3193
3194 /**
3195  * gst_play_get_position:
3196  * @play: #GstPlay instance
3197  *
3198  * Returns: the absolute position time, in nanoseconds, of the
3199  * currently-playing stream.
3200  * Since: 1.20
3201  */
3202 GstClockTime
3203 gst_play_get_position (GstPlay * self)
3204 {
3205   GstClockTime val;
3206
3207   g_return_val_if_fail (GST_IS_PLAY (self), DEFAULT_POSITION);
3208
3209   g_object_get (self, "position", &val, NULL);
3210
3211   return val;
3212 }
3213
3214 /**
3215  * gst_play_get_duration:
3216  * @play: #GstPlay instance
3217  *
3218  * Retrieves the duration of the media stream that self represents.
3219  *
3220  * Returns: the duration of the currently-playing media stream, in
3221  * nanoseconds.
3222  * Since: 1.20
3223  */
3224 GstClockTime
3225 gst_play_get_duration (GstPlay * self)
3226 {
3227   GstClockTime val;
3228
3229   g_return_val_if_fail (GST_IS_PLAY (self), DEFAULT_DURATION);
3230
3231   g_object_get (self, "duration", &val, NULL);
3232
3233   return val;
3234 }
3235
3236 /**
3237  * gst_play_get_volume:
3238  * @play: #GstPlay instance
3239  *
3240  * Returns the current volume level, as a percentage between 0 and 1.
3241  *
3242  * Returns: the volume as percentage between 0 and 1.
3243  * Since: 1.20
3244  */
3245 gdouble
3246 gst_play_get_volume (GstPlay * self)
3247 {
3248   gdouble val;
3249
3250   g_return_val_if_fail (GST_IS_PLAY (self), DEFAULT_VOLUME);
3251
3252   g_object_get (self, "volume", &val, NULL);
3253
3254   return val;
3255 }
3256
3257 /**
3258  * gst_play_set_volume:
3259  * @play: #GstPlay instance
3260  * @val: the new volume level, as a percentage between 0 and 1
3261  *
3262  * Sets the volume level of the stream as a percentage between 0 and 1.
3263  * Since: 1.20
3264  */
3265 void
3266 gst_play_set_volume (GstPlay * self, gdouble val)
3267 {
3268   g_return_if_fail (GST_IS_PLAY (self));
3269
3270   g_object_set (self, "volume", val, NULL);
3271 }
3272
3273 /**
3274  * gst_play_get_mute:
3275  * @play: #GstPlay instance
3276  *
3277  * Returns: %TRUE if the currently-playing stream is muted.
3278  * Since: 1.20
3279  */
3280 gboolean
3281 gst_play_get_mute (GstPlay * self)
3282 {
3283   gboolean val;
3284
3285   g_return_val_if_fail (GST_IS_PLAY (self), DEFAULT_MUTE);
3286
3287   g_object_get (self, "mute", &val, NULL);
3288
3289   return val;
3290 }
3291
3292 /**
3293  * gst_play_set_mute:
3294  * @play: #GstPlay instance
3295  * @val: Mute state the should be set
3296  *
3297  * %TRUE if the currently-playing stream should be muted.
3298  * Since: 1.20
3299  */
3300 void
3301 gst_play_set_mute (GstPlay * self, gboolean val)
3302 {
3303   g_return_if_fail (GST_IS_PLAY (self));
3304
3305   g_object_set (self, "mute", val, NULL);
3306 }
3307
3308 /**
3309  * gst_play_get_pipeline:
3310  * @play: #GstPlay instance
3311  *
3312  * Returns: (transfer full): The internal playbin instance.
3313  *
3314  * The caller should free it with g_object_unref()
3315  * Since: 1.20
3316  */
3317 GstElement *
3318 gst_play_get_pipeline (GstPlay * self)
3319 {
3320   GstElement *val;
3321
3322   g_return_val_if_fail (GST_IS_PLAY (self), NULL);
3323
3324   g_object_get (self, "pipeline", &val, NULL);
3325
3326   return val;
3327 }
3328
3329 /**
3330  * gst_play_get_media_info:
3331  * @play: #GstPlay instance
3332  *
3333  * A Function to get the current media info #GstPlayMediaInfo instance.
3334  *
3335  * Returns: (transfer full) (nullable): media info instance.
3336  *
3337  * The caller should free it with g_object_unref()
3338  * Since: 1.20
3339  */
3340 GstPlayMediaInfo *
3341 gst_play_get_media_info (GstPlay * self)
3342 {
3343   GstPlayMediaInfo *info;
3344
3345   g_return_val_if_fail (GST_IS_PLAY (self), NULL);
3346
3347   if (!self->media_info)
3348     return NULL;
3349
3350   g_mutex_lock (&self->lock);
3351   info = gst_play_media_info_copy (self->media_info);
3352   g_mutex_unlock (&self->lock);
3353
3354   return info;
3355 }
3356
3357 /**
3358  * gst_play_get_current_audio_track:
3359  * @play: #GstPlay instance
3360  *
3361  * A Function to get current audio #GstPlayAudioInfo instance.
3362  *
3363  * Returns: (transfer full) (nullable): current audio track.
3364  *
3365  * The caller should free it with g_object_unref()
3366  * Since: 1.20
3367  */
3368 GstPlayAudioInfo *
3369 gst_play_get_current_audio_track (GstPlay * self)
3370 {
3371   GstPlayAudioInfo *info;
3372
3373   g_return_val_if_fail (GST_IS_PLAY (self), NULL);
3374
3375   if (!is_track_enabled (self, GST_PLAY_FLAG_AUDIO))
3376     return NULL;
3377
3378   if (self->use_playbin3) {
3379     info = (GstPlayAudioInfo *)
3380         gst_play_stream_info_get_current_from_stream_id (self,
3381         self->audio_sid, GST_TYPE_PLAY_AUDIO_INFO);
3382   } else {
3383     info = (GstPlayAudioInfo *) gst_play_stream_info_get_current (self,
3384         "current-audio", GST_TYPE_PLAY_AUDIO_INFO);
3385   }
3386
3387   return info;
3388 }
3389
3390 /**
3391  * gst_play_get_current_video_track:
3392  * @play: #GstPlay instance
3393  *
3394  * A Function to get current video #GstPlayVideoInfo instance.
3395  *
3396  * Returns: (transfer full) (nullable): current video track.
3397  *
3398  * The caller should free it with g_object_unref()
3399  * Since: 1.20
3400  */
3401 GstPlayVideoInfo *
3402 gst_play_get_current_video_track (GstPlay * self)
3403 {
3404   GstPlayVideoInfo *info;
3405
3406   g_return_val_if_fail (GST_IS_PLAY (self), NULL);
3407
3408   if (!is_track_enabled (self, GST_PLAY_FLAG_VIDEO))
3409     return NULL;
3410
3411   if (self->use_playbin3) {
3412     info = (GstPlayVideoInfo *)
3413         gst_play_stream_info_get_current_from_stream_id (self,
3414         self->video_sid, GST_TYPE_PLAY_VIDEO_INFO);
3415   } else {
3416     info = (GstPlayVideoInfo *) gst_play_stream_info_get_current (self,
3417         "current-video", GST_TYPE_PLAY_VIDEO_INFO);
3418   }
3419
3420   return info;
3421 }
3422
3423 /**
3424  * gst_play_get_current_subtitle_track:
3425  * @play: #GstPlay instance
3426  *
3427  * A Function to get current subtitle #GstPlaySubtitleInfo instance.
3428  *
3429  * Returns: (transfer full) (nullable): current subtitle track.
3430  *
3431  * The caller should free it with g_object_unref()
3432  * Since: 1.20
3433  */
3434 GstPlaySubtitleInfo *
3435 gst_play_get_current_subtitle_track (GstPlay * self)
3436 {
3437   GstPlaySubtitleInfo *info;
3438
3439   g_return_val_if_fail (GST_IS_PLAY (self), NULL);
3440
3441   if (!is_track_enabled (self, GST_PLAY_FLAG_SUBTITLE))
3442     return NULL;
3443
3444   if (self->use_playbin3) {
3445     info = (GstPlaySubtitleInfo *)
3446         gst_play_stream_info_get_current_from_stream_id (self,
3447         self->subtitle_sid, GST_TYPE_PLAY_SUBTITLE_INFO);
3448   } else {
3449     info = (GstPlaySubtitleInfo *) gst_play_stream_info_get_current (self,
3450         "current-text", GST_TYPE_PLAY_SUBTITLE_INFO);
3451   }
3452
3453   return info;
3454 }
3455
3456 /* Must be called with lock */
3457 static gboolean
3458 gst_play_select_streams (GstPlay * self)
3459 {
3460   GList *stream_list = NULL;
3461   gboolean ret = FALSE;
3462
3463   if (self->audio_sid)
3464     stream_list = g_list_append (stream_list, g_strdup (self->audio_sid));
3465   if (self->video_sid)
3466     stream_list = g_list_append (stream_list, g_strdup (self->video_sid));
3467   if (self->subtitle_sid)
3468     stream_list = g_list_append (stream_list, g_strdup (self->subtitle_sid));
3469
3470   g_mutex_unlock (&self->lock);
3471   if (stream_list) {
3472     ret = gst_element_send_event (self->playbin,
3473         gst_event_new_select_streams (stream_list));
3474     g_list_free_full (stream_list, g_free);
3475   } else {
3476     GST_ERROR_OBJECT (self, "No available streams for select-streams");
3477   }
3478   g_mutex_lock (&self->lock);
3479
3480   return ret;
3481 }
3482
3483 /**
3484  * gst_play_set_audio_track:
3485  * @play: #GstPlay instance
3486  * @stream_index: stream index
3487  *
3488  * Returns: %TRUE or %FALSE
3489  *
3490  * Sets the audio track @stream_index.
3491  * Since: 1.20
3492  */
3493 gboolean
3494 gst_play_set_audio_track (GstPlay * self, gint stream_index)
3495 {
3496   GstPlayStreamInfo *info;
3497   gboolean ret = TRUE;
3498
3499   g_return_val_if_fail (GST_IS_PLAY (self), 0);
3500
3501   g_mutex_lock (&self->lock);
3502   info = gst_play_stream_info_find (self->media_info,
3503       GST_TYPE_PLAY_AUDIO_INFO, stream_index);
3504   g_mutex_unlock (&self->lock);
3505   if (!info) {
3506     GST_ERROR_OBJECT (self, "invalid audio stream index %d", stream_index);
3507     return FALSE;
3508   }
3509
3510   if (self->use_playbin3) {
3511     g_mutex_lock (&self->lock);
3512     g_free (self->audio_sid);
3513     self->audio_sid = g_strdup (info->stream_id);
3514     ret = gst_play_select_streams (self);
3515     g_mutex_unlock (&self->lock);
3516   } else {
3517     g_object_set (G_OBJECT (self->playbin), "current-audio", stream_index,
3518         NULL);
3519   }
3520
3521   GST_DEBUG_OBJECT (self, "set stream index '%d'", stream_index);
3522   return ret;
3523 }
3524
3525 /**
3526  * gst_play_set_video_track:
3527  * @play: #GstPlay instance
3528  * @stream_index: stream index
3529  *
3530  * Returns: %TRUE or %FALSE
3531  *
3532  * Sets the video track @stream_index.
3533  * Since: 1.20
3534  */
3535 gboolean
3536 gst_play_set_video_track (GstPlay * self, gint stream_index)
3537 {
3538   GstPlayStreamInfo *info;
3539   gboolean ret = TRUE;
3540
3541   g_return_val_if_fail (GST_IS_PLAY (self), 0);
3542
3543   /* check if stream_index exist in our internal media_info list */
3544   g_mutex_lock (&self->lock);
3545   info = gst_play_stream_info_find (self->media_info,
3546       GST_TYPE_PLAY_VIDEO_INFO, stream_index);
3547   g_mutex_unlock (&self->lock);
3548   if (!info) {
3549     GST_ERROR_OBJECT (self, "invalid video stream index %d", stream_index);
3550     return FALSE;
3551   }
3552
3553   if (self->use_playbin3) {
3554     g_mutex_lock (&self->lock);
3555     g_free (self->video_sid);
3556     self->video_sid = g_strdup (info->stream_id);
3557     ret = gst_play_select_streams (self);
3558     g_mutex_unlock (&self->lock);
3559   } else {
3560     g_object_set (G_OBJECT (self->playbin), "current-video", stream_index,
3561         NULL);
3562   }
3563
3564   GST_DEBUG_OBJECT (self, "set stream index '%d'", stream_index);
3565   return ret;
3566 }
3567
3568 /**
3569  * gst_play_set_subtitle_track:
3570  * @play: #GstPlay instance
3571  * @stream_index: stream index
3572  *
3573  * Returns: %TRUE or %FALSE
3574  *
3575  * Sets the subtitle stack @stream_index.
3576  * Since: 1.20
3577  */
3578 gboolean
3579 gst_play_set_subtitle_track (GstPlay * self, gint stream_index)
3580 {
3581   GstPlayStreamInfo *info;
3582   gboolean ret = TRUE;
3583
3584   g_return_val_if_fail (GST_IS_PLAY (self), 0);
3585
3586   g_mutex_lock (&self->lock);
3587   info = gst_play_stream_info_find (self->media_info,
3588       GST_TYPE_PLAY_SUBTITLE_INFO, stream_index);
3589   g_mutex_unlock (&self->lock);
3590   if (!info) {
3591     GST_ERROR_OBJECT (self, "invalid subtitle stream index %d", stream_index);
3592     return FALSE;
3593   }
3594
3595   if (self->use_playbin3) {
3596     g_mutex_lock (&self->lock);
3597     g_free (self->subtitle_sid);
3598     self->subtitle_sid = g_strdup (info->stream_id);
3599     ret = gst_play_select_streams (self);
3600     g_mutex_unlock (&self->lock);
3601   } else {
3602     g_object_set (G_OBJECT (self->playbin), "current-text", stream_index, NULL);
3603   }
3604
3605   GST_DEBUG_OBJECT (self, "set stream index '%d'", stream_index);
3606   return ret;
3607 }
3608
3609 /**
3610  * gst_play_set_audio_track_enabled:
3611  * @play: #GstPlay instance
3612  * @enabled: TRUE or FALSE
3613  *
3614  * Enable or disable the current audio track.
3615  * Since: 1.20
3616  */
3617 void
3618 gst_play_set_audio_track_enabled (GstPlay * self, gboolean enabled)
3619 {
3620   g_return_if_fail (GST_IS_PLAY (self));
3621
3622   if (enabled)
3623     play_set_flag (self, GST_PLAY_FLAG_AUDIO);
3624   else
3625     play_clear_flag (self, GST_PLAY_FLAG_AUDIO);
3626
3627   GST_DEBUG_OBJECT (self, "track is '%s'", enabled ? "Enabled" : "Disabled");
3628 }
3629
3630 /**
3631  * gst_play_set_video_track_enabled:
3632  * @play: #GstPlay instance
3633  * @enabled: TRUE or FALSE
3634  *
3635  * Enable or disable the current video track.
3636  * Since: 1.20
3637  */
3638 void
3639 gst_play_set_video_track_enabled (GstPlay * self, gboolean enabled)
3640 {
3641   g_return_if_fail (GST_IS_PLAY (self));
3642
3643   if (enabled)
3644     play_set_flag (self, GST_PLAY_FLAG_VIDEO);
3645   else
3646     play_clear_flag (self, GST_PLAY_FLAG_VIDEO);
3647
3648   GST_DEBUG_OBJECT (self, "track is '%s'", enabled ? "Enabled" : "Disabled");
3649 }
3650
3651 /**
3652  * gst_play_set_subtitle_track_enabled:
3653  * @play: #GstPlay instance
3654  * @enabled: TRUE or FALSE
3655  *
3656  * Enable or disable the current subtitle track.
3657  * Since: 1.20
3658  */
3659 void
3660 gst_play_set_subtitle_track_enabled (GstPlay * self, gboolean enabled)
3661 {
3662   g_return_if_fail (GST_IS_PLAY (self));
3663
3664   if (enabled)
3665     play_set_flag (self, GST_PLAY_FLAG_SUBTITLE);
3666   else
3667     play_clear_flag (self, GST_PLAY_FLAG_SUBTITLE);
3668
3669   GST_DEBUG_OBJECT (self, "track is '%s'", enabled ? "Enabled" : "Disabled");
3670 }
3671
3672 /**
3673  * gst_play_set_visualization:
3674  * @play: #GstPlay instance
3675  * @name: (nullable): visualization element obtained from
3676  * #gst_play_visualizations_get()
3677  *
3678  * Returns: %TRUE if the visualizations was set correctly. Otherwise,
3679  * %FALSE.
3680  * Since: 1.20
3681  */
3682 gboolean
3683 gst_play_set_visualization (GstPlay * self, const gchar * name)
3684 {
3685   g_return_val_if_fail (GST_IS_PLAY (self), FALSE);
3686
3687   g_mutex_lock (&self->lock);
3688   if (self->current_vis_element) {
3689     gst_object_unref (self->current_vis_element);
3690     self->current_vis_element = NULL;
3691   }
3692
3693   if (name) {
3694     self->current_vis_element = gst_element_factory_make (name, NULL);
3695     if (!self->current_vis_element)
3696       goto error_no_element;
3697     gst_object_ref_sink (self->current_vis_element);
3698   }
3699   g_object_set (self->playbin, "vis-plugin", self->current_vis_element, NULL);
3700
3701   g_mutex_unlock (&self->lock);
3702   GST_DEBUG_OBJECT (self, "set vis-plugin to '%s'", name);
3703
3704   return TRUE;
3705
3706 error_no_element:
3707   g_mutex_unlock (&self->lock);
3708   GST_WARNING_OBJECT (self, "could not find visualization '%s'", name);
3709   return FALSE;
3710 }
3711
3712 /**
3713  * gst_play_get_current_visualization:
3714  * @play: #GstPlay instance
3715  *
3716  * Returns: (transfer full) (nullable): Name of the currently enabled
3717  *   visualization.
3718  *   g_free() after usage.
3719  * Since: 1.20
3720  */
3721 gchar *
3722 gst_play_get_current_visualization (GstPlay * self)
3723 {
3724   gchar *name = NULL;
3725   GstElement *vis_plugin = NULL;
3726
3727   g_return_val_if_fail (GST_IS_PLAY (self), NULL);
3728
3729   if (!is_track_enabled (self, GST_PLAY_FLAG_VIS))
3730     return NULL;
3731
3732   g_object_get (self->playbin, "vis-plugin", &vis_plugin, NULL);
3733
3734   if (vis_plugin) {
3735     GstElementFactory *factory = gst_element_get_factory (vis_plugin);
3736     if (factory)
3737       name = g_strdup (gst_plugin_feature_get_name (factory));
3738     gst_object_unref (vis_plugin);
3739   }
3740
3741   GST_DEBUG_OBJECT (self, "vis-plugin '%s' %p", name, vis_plugin);
3742
3743   return name;
3744 }
3745
3746 /**
3747  * gst_play_set_visualization_enabled:
3748  * @play: #GstPlay instance
3749  * @enabled: TRUE or FALSE
3750  *
3751  * Enable or disable the visualization.
3752  * Since: 1.20
3753  */
3754 void
3755 gst_play_set_visualization_enabled (GstPlay * self, gboolean enabled)
3756 {
3757   g_return_if_fail (GST_IS_PLAY (self));
3758
3759   if (enabled)
3760     play_set_flag (self, GST_PLAY_FLAG_VIS);
3761   else
3762     play_clear_flag (self, GST_PLAY_FLAG_VIS);
3763
3764   GST_DEBUG_OBJECT (self, "visualization is '%s'",
3765       enabled ? "Enabled" : "Disabled");
3766 }
3767
3768 struct CBChannelMap
3769 {
3770   const gchar *label;           /* channel label name */
3771   const gchar *name;            /* get_name () */
3772 };
3773
3774 static const struct CBChannelMap cb_channel_map[] = {
3775   /* GST_PLAY_COLOR_BALANCE_BRIGHTNESS */ {"BRIGHTNESS", "brightness"},
3776   /* GST_PLAY_COLOR_BALANCE_CONTRAST   */ {"CONTRAST", "contrast"},
3777   /* GST_PLAY_COLOR_BALANCE_SATURATION */ {"SATURATION", "saturation"},
3778   /* GST_PLAY_COLOR_BALANCE_HUE        */ {"HUE", "hue"},
3779 };
3780
3781 static GstColorBalanceChannel *
3782 gst_play_color_balance_find_channel (GstPlay * self,
3783     GstPlayColorBalanceType type)
3784 {
3785   GstColorBalanceChannel *channel;
3786   const GList *l, *channels;
3787
3788   if (type < GST_PLAY_COLOR_BALANCE_BRIGHTNESS ||
3789       type > GST_PLAY_COLOR_BALANCE_HUE)
3790     return NULL;
3791
3792   channels =
3793       gst_color_balance_list_channels (GST_COLOR_BALANCE (self->playbin));
3794   for (l = channels; l; l = l->next) {
3795     channel = l->data;
3796     if (g_strrstr (channel->label, cb_channel_map[type].label))
3797       return channel;
3798   }
3799
3800   return NULL;
3801 }
3802
3803 /**
3804  * gst_play_has_color_balance:
3805  * @play:#GstPlay instance
3806  *
3807  * Checks whether the @play has color balance support available.
3808  *
3809  * Returns: %TRUE if @play has color balance support. Otherwise,
3810  *   %FALSE.
3811  * Since: 1.20
3812  */
3813 gboolean
3814 gst_play_has_color_balance (GstPlay * self)
3815 {
3816   const GList *channels;
3817
3818   g_return_val_if_fail (GST_IS_PLAY (self), FALSE);
3819
3820   if (!GST_IS_COLOR_BALANCE (self->playbin))
3821     return FALSE;
3822
3823   channels =
3824       gst_color_balance_list_channels (GST_COLOR_BALANCE (self->playbin));
3825   return (channels != NULL);
3826 }
3827
3828 /**
3829  * gst_play_set_color_balance:
3830  * @play: #GstPlay instance
3831  * @type: #GstPlayColorBalanceType
3832  * @value: The new value for the @type, ranged [0,1]
3833  *
3834  * Sets the current value of the indicated channel @type to the passed
3835  * value.
3836  * Since: 1.20
3837  */
3838 void
3839 gst_play_set_color_balance (GstPlay * self, GstPlayColorBalanceType type,
3840     gdouble value)
3841 {
3842   GstColorBalanceChannel *channel;
3843   gdouble new_val;
3844
3845   g_return_if_fail (GST_IS_PLAY (self));
3846   g_return_if_fail (value >= 0.0 && value <= 1.0);
3847
3848   if (!GST_IS_COLOR_BALANCE (self->playbin))
3849     return;
3850
3851   channel = gst_play_color_balance_find_channel (self, type);
3852   if (!channel)
3853     return;
3854
3855   value = CLAMP (value, 0.0, 1.0);
3856
3857   /* Convert to channel range */
3858   new_val = channel->min_value + value * ((gdouble) channel->max_value -
3859       (gdouble) channel->min_value);
3860
3861   gst_color_balance_set_value (GST_COLOR_BALANCE (self->playbin), channel,
3862       new_val);
3863 }
3864
3865 /**
3866  * gst_play_get_color_balance:
3867  * @play: #GstPlay instance
3868  * @type: #GstPlayColorBalanceType
3869  *
3870  * Retrieve the current value of the indicated @type.
3871  *
3872  * Returns: The current value of @type, between [0,1]. In case of
3873  *   error -1 is returned.
3874  * Since: 1.20
3875  */
3876 gdouble
3877 gst_play_get_color_balance (GstPlay * self, GstPlayColorBalanceType type)
3878 {
3879   GstColorBalanceChannel *channel;
3880   gint value;
3881
3882   g_return_val_if_fail (GST_IS_PLAY (self), -1);
3883
3884   if (!GST_IS_COLOR_BALANCE (self->playbin))
3885     return -1;
3886
3887   channel = gst_play_color_balance_find_channel (self, type);
3888   if (!channel)
3889     return -1;
3890
3891   value = gst_color_balance_get_value (GST_COLOR_BALANCE (self->playbin),
3892       channel);
3893
3894   return ((gdouble) value -
3895       (gdouble) channel->min_value) / ((gdouble) channel->max_value -
3896       (gdouble) channel->min_value);
3897 }
3898
3899 /**
3900  * gst_play_get_multiview_mode:
3901  * @play: #GstPlay instance
3902  *
3903  * Retrieve the current value of the indicated @type.
3904  *
3905  * Returns: The current value of @type, Default: -1 "none"
3906  *
3907  * Since: 1.20
3908  */
3909 GstVideoMultiviewFramePacking
3910 gst_play_get_multiview_mode (GstPlay * self)
3911 {
3912   GstVideoMultiviewFramePacking val = GST_VIDEO_MULTIVIEW_FRAME_PACKING_NONE;
3913
3914   g_return_val_if_fail (GST_IS_PLAY (self),
3915       GST_VIDEO_MULTIVIEW_FRAME_PACKING_NONE);
3916
3917   g_object_get (self, "video-multiview-mode", &val, NULL);
3918
3919   return val;
3920 }
3921
3922 /**
3923  * gst_play_set_multiview_mode:
3924  * @play: #GstPlay instance
3925  * @mode: The new value for the @type
3926  *
3927  * Sets the current value of the indicated mode @type to the passed
3928  * value.
3929  *
3930  * Since: 1.20
3931  */
3932 void
3933 gst_play_set_multiview_mode (GstPlay * self, GstVideoMultiviewFramePacking mode)
3934 {
3935   g_return_if_fail (GST_IS_PLAY (self));
3936
3937   g_object_set (self, "video-multiview-mode", mode, NULL);
3938 }
3939
3940 /**
3941  * gst_play_get_multiview_flags:
3942  * @play: #GstPlay instance
3943  *
3944  * Retrieve the current value of the indicated @type.
3945  *
3946  * Returns: The current value of @type, Default: 0x00000000 "none
3947  *
3948  * Since: 1.20
3949  */
3950 GstVideoMultiviewFlags
3951 gst_play_get_multiview_flags (GstPlay * self)
3952 {
3953   GstVideoMultiviewFlags val = GST_VIDEO_MULTIVIEW_FLAGS_NONE;
3954
3955   g_return_val_if_fail (GST_IS_PLAY (self), val);
3956
3957   g_object_get (self, "video-multiview-flags", &val, NULL);
3958
3959   return val;
3960 }
3961
3962 /**
3963  * gst_play_set_multiview_flags:
3964  * @play: #GstPlay instance
3965  * @flags: The new value for the @type
3966  *
3967  * Sets the current value of the indicated mode @type to the passed
3968  * value.
3969  *
3970  * Since: 1.20
3971  */
3972 void
3973 gst_play_set_multiview_flags (GstPlay * self, GstVideoMultiviewFlags flags)
3974 {
3975   g_return_if_fail (GST_IS_PLAY (self));
3976
3977   g_object_set (self, "video-multiview-flags", flags, NULL);
3978 }
3979
3980 /**
3981  * gst_play_get_audio_video_offset:
3982  * @play: #GstPlay instance
3983  *
3984  * Retrieve the current value of audio-video-offset property
3985  *
3986  * Returns: The current value of audio-video-offset in nanoseconds
3987  *
3988  * Since: 1.20
3989  */
3990 gint64
3991 gst_play_get_audio_video_offset (GstPlay * self)
3992 {
3993   gint64 val = 0;
3994
3995   g_return_val_if_fail (GST_IS_PLAY (self), DEFAULT_AUDIO_VIDEO_OFFSET);
3996
3997   g_object_get (self, "audio-video-offset", &val, NULL);
3998
3999   return val;
4000 }
4001
4002 /**
4003  * gst_play_set_audio_video_offset:
4004  * @play: #GstPlay instance
4005  * @offset: #gint64 in nanoseconds
4006  *
4007  * Sets audio-video-offset property by value of @offset
4008  *
4009  * Since: 1.20
4010  */
4011 void
4012 gst_play_set_audio_video_offset (GstPlay * self, gint64 offset)
4013 {
4014   g_return_if_fail (GST_IS_PLAY (self));
4015
4016   g_object_set (self, "audio-video-offset", offset, NULL);
4017 }
4018
4019 /**
4020  * gst_play_get_subtitle_video_offset:
4021  * @play: #GstPlay instance
4022  *
4023  * Retrieve the current value of subtitle-video-offset property
4024  *
4025  * Returns: The current value of subtitle-video-offset in nanoseconds
4026  *
4027  * Since: 1.20
4028  */
4029 gint64
4030 gst_play_get_subtitle_video_offset (GstPlay * self)
4031 {
4032   gint64 val = 0;
4033
4034   g_return_val_if_fail (GST_IS_PLAY (self), DEFAULT_SUBTITLE_VIDEO_OFFSET);
4035
4036   g_object_get (self, "subtitle-video-offset", &val, NULL);
4037
4038   return val;
4039 }
4040
4041 /**
4042  * gst_play_set_subtitle_video_offset:
4043  * @play: #GstPlay instance
4044  * @offset: #gint64 in nanoseconds
4045  *
4046  * Sets subtitle-video-offset property by value of @offset
4047  *
4048  * Since: 1.20
4049  */
4050 void
4051 gst_play_set_subtitle_video_offset (GstPlay * self, gint64 offset)
4052 {
4053   g_return_if_fail (GST_IS_PLAY (self));
4054
4055   g_object_set (self, "subtitle-video-offset", offset, NULL);
4056 }
4057
4058
4059 #define C_ENUM(v) ((gint) v)
4060 #define C_FLAGS(v) ((guint) v)
4061
4062 GType
4063 gst_play_color_balance_type_get_type (void)
4064 {
4065   static gsize id = 0;
4066   static const GEnumValue values[] = {
4067     {C_ENUM (GST_PLAY_COLOR_BALANCE_HUE), "GST_PLAY_COLOR_BALANCE_HUE",
4068         "hue"},
4069     {C_ENUM (GST_PLAY_COLOR_BALANCE_BRIGHTNESS),
4070         "GST_PLAY_COLOR_BALANCE_BRIGHTNESS", "brightness"},
4071     {C_ENUM (GST_PLAY_COLOR_BALANCE_SATURATION),
4072         "GST_PLAY_COLOR_BALANCE_SATURATION", "saturation"},
4073     {C_ENUM (GST_PLAY_COLOR_BALANCE_CONTRAST),
4074         "GST_PLAY_COLOR_BALANCE_CONTRAST", "contrast"},
4075     {0, NULL, NULL}
4076   };
4077
4078   if (g_once_init_enter (&id)) {
4079     GType tmp = g_enum_register_static ("GstPlayColorBalanceType", values);
4080     g_once_init_leave (&id, tmp);
4081   }
4082
4083   return (GType) id;
4084 }
4085
4086 /**
4087  * gst_play_color_balance_type_get_name:
4088  * @type: a #GstPlayColorBalanceType
4089  *
4090  * Gets a string representing the given color balance type.
4091  *
4092  * Returns: (transfer none): a string with the name of the color
4093  *   balance type.
4094  * Since: 1.20
4095  */
4096 const gchar *
4097 gst_play_color_balance_type_get_name (GstPlayColorBalanceType type)
4098 {
4099   g_return_val_if_fail (type >= GST_PLAY_COLOR_BALANCE_BRIGHTNESS &&
4100       type <= GST_PLAY_COLOR_BALANCE_HUE, NULL);
4101
4102   return cb_channel_map[type].name;
4103 }
4104
4105 GType
4106 gst_play_state_get_type (void)
4107 {
4108   static gsize id = 0;
4109   static const GEnumValue values[] = {
4110     {C_ENUM (GST_PLAY_STATE_STOPPED), "GST_PLAY_STATE_STOPPED", "stopped"},
4111     {C_ENUM (GST_PLAY_STATE_BUFFERING), "GST_PLAY_STATE_BUFFERING",
4112         "buffering"},
4113     {C_ENUM (GST_PLAY_STATE_PAUSED), "GST_PLAY_STATE_PAUSED", "paused"},
4114     {C_ENUM (GST_PLAY_STATE_PLAYING), "GST_PLAY_STATE_PLAYING", "playing"},
4115     {0, NULL, NULL}
4116   };
4117
4118   if (g_once_init_enter (&id)) {
4119     GType tmp = g_enum_register_static ("GstPlayState", values);
4120     g_once_init_leave (&id, tmp);
4121   }
4122
4123   return (GType) id;
4124 }
4125
4126 GType
4127 gst_play_message_get_type (void)
4128 {
4129   static gsize id = 0;
4130   static const GEnumValue values[] = {
4131     {C_ENUM (GST_PLAY_MESSAGE_URI_LOADED), "GST_PLAY_MESSAGE_URI_LOADED",
4132         "uri-loaded"},
4133     {C_ENUM (GST_PLAY_MESSAGE_POSITION_UPDATED),
4134         "GST_PLAY_MESSAGE_POSITION_UPDATED", "position-updated"},
4135     {C_ENUM (GST_PLAY_MESSAGE_DURATION_CHANGED),
4136         "GST_PLAY_MESSAGE_DURATION_CHANGED", "duration-changed"},
4137     {C_ENUM (GST_PLAY_MESSAGE_STATE_CHANGED),
4138         "GST_PLAY_MESSAGE_STATE_CHANGED", "state-changed"},
4139     {C_ENUM (GST_PLAY_MESSAGE_BUFFERING), "GST_PLAY_MESSAGE_BUFFERING",
4140         "buffering"},
4141     {C_ENUM (GST_PLAY_MESSAGE_END_OF_STREAM),
4142         "GST_PLAY_MESSAGE_END_OF_STREAM", "end-of-stream"},
4143     {C_ENUM (GST_PLAY_MESSAGE_ERROR), "GST_PLAY_MESSAGE_ERROR", "error"},
4144     {C_ENUM (GST_PLAY_MESSAGE_WARNING), "GST_PLAY_MESSAGE_WARNING",
4145         "warning"},
4146     {C_ENUM (GST_PLAY_MESSAGE_VIDEO_DIMENSIONS_CHANGED),
4147           "GST_PLAY_MESSAGE_VIDEO_DIMENSIONS_CHANGED",
4148         "video-dimensions-changed"},
4149     {C_ENUM (GST_PLAY_MESSAGE_MEDIA_INFO_UPDATED),
4150         "GST_PLAY_MESSAGE_MEDIA_INFO_UPDATED", "media-info-updated"},
4151     {C_ENUM (GST_PLAY_MESSAGE_VOLUME_CHANGED),
4152         "GST_PLAY_MESSAGE_VOLUME_CHANGED", "volume-changed"},
4153     {C_ENUM (GST_PLAY_MESSAGE_MUTE_CHANGED),
4154         "GST_PLAY_MESSAGE_MUTE_CHANGED", "mute-changed"},
4155     {C_ENUM (GST_PLAY_MESSAGE_SEEK_DONE), "GST_PLAY_MESSAGE_SEEK_DONE",
4156         "seek-done"},
4157     {0, NULL, NULL}
4158   };
4159
4160   if (g_once_init_enter (&id)) {
4161     GType tmp = g_enum_register_static ("GstPlayMessage", values);
4162     g_once_init_leave (&id, tmp);
4163   }
4164
4165   return (GType) id;
4166 }
4167
4168 /**
4169  * gst_play_state_get_name:
4170  * @state: a #GstPlayState
4171  *
4172  * Gets a string representing the given state.
4173  *
4174  * Returns: (transfer none): a string with the name of the state.
4175  * Since: 1.20
4176  */
4177 const gchar *
4178 gst_play_state_get_name (GstPlayState state)
4179 {
4180   switch (state) {
4181     case GST_PLAY_STATE_STOPPED:
4182       return "stopped";
4183     case GST_PLAY_STATE_BUFFERING:
4184       return "buffering";
4185     case GST_PLAY_STATE_PAUSED:
4186       return "paused";
4187     case GST_PLAY_STATE_PLAYING:
4188       return "playing";
4189   }
4190
4191   g_assert_not_reached ();
4192   return NULL;
4193 }
4194
4195 /**
4196  * gst_play_message_get_name:
4197  * @message_type: a #GstPlayMessage
4198  *
4199  * Returns: (transfer none): a string with the name of the message.
4200  * Since: 1.20
4201  */
4202 const gchar *
4203 gst_play_message_get_name (GstPlayMessage message_type)
4204 {
4205   GEnumClass *enum_class;
4206   GEnumValue *enum_value;
4207   enum_class = g_type_class_ref (GST_TYPE_PLAY_MESSAGE);
4208   enum_value = g_enum_get_value (enum_class, message_type);
4209   g_assert (enum_value != NULL);
4210   g_type_class_unref (enum_class);
4211   return enum_value->value_name;
4212 }
4213
4214 GType
4215 gst_play_error_get_type (void)
4216 {
4217   static gsize id = 0;
4218   static const GEnumValue values[] = {
4219     {C_ENUM (GST_PLAY_ERROR_FAILED), "GST_PLAY_ERROR_FAILED", "failed"},
4220     {0, NULL, NULL}
4221   };
4222
4223   if (g_once_init_enter (&id)) {
4224     GType tmp = g_enum_register_static ("GstPlayError", values);
4225     g_once_init_leave (&id, tmp);
4226   }
4227
4228   return (GType) id;
4229 }
4230
4231 /**
4232  * gst_play_error_get_name:
4233  * @error: a #GstPlayError
4234  *
4235  * Gets a string representing the given error.
4236  *
4237  * Returns: (transfer none): a string with the given error.
4238  * Since: 1.20
4239  */
4240 const gchar *
4241 gst_play_error_get_name (GstPlayError error)
4242 {
4243   switch (error) {
4244     case GST_PLAY_ERROR_FAILED:
4245       return "failed";
4246   }
4247
4248   g_assert_not_reached ();
4249   return NULL;
4250 }
4251
4252 /**
4253  * gst_play_set_config:
4254  * @play: #GstPlay instance
4255  * @config: (transfer full): a #GstStructure
4256  *
4257  * Set the configuration of the play. If the play is already configured, and
4258  * the configuration haven't change, this function will return %TRUE. If the
4259  * play is not in the GST_PLAY_STATE_STOPPED, this method will return %FALSE
4260  * and active configuration will remain.
4261  *
4262  * @config is a #GstStructure that contains the configuration parameters for
4263  * the play.
4264  *
4265  * This function takes ownership of @config.
4266  *
4267  * Returns: %TRUE when the configuration could be set.
4268  * Since: 1.20
4269  */
4270 gboolean
4271 gst_play_set_config (GstPlay * self, GstStructure * config)
4272 {
4273   g_return_val_if_fail (GST_IS_PLAY (self), FALSE);
4274   g_return_val_if_fail (config != NULL, FALSE);
4275
4276   g_mutex_lock (&self->lock);
4277
4278   if (self->app_state != GST_PLAY_STATE_STOPPED) {
4279     GST_INFO_OBJECT (self, "can't change config while play is %s",
4280         gst_play_state_get_name (self->app_state));
4281     g_mutex_unlock (&self->lock);
4282     return FALSE;
4283   }
4284
4285   if (self->config)
4286     gst_structure_free (self->config);
4287   self->config = config;
4288   g_mutex_unlock (&self->lock);
4289
4290   return TRUE;
4291 }
4292
4293 /**
4294  * gst_play_get_config:
4295  * @play: #GstPlay instance
4296  *
4297  * Get a copy of the current configuration of the play. This configuration
4298  * can either be modified and used for the gst_play_set_config() call
4299  * or it must be freed after usage.
4300  *
4301  * Returns: (transfer full): a copy of the current configuration of @play. Use
4302  * gst_structure_free() after usage or gst_play_set_config().
4303  *
4304  * Since: 1.20
4305  */
4306 GstStructure *
4307 gst_play_get_config (GstPlay * self)
4308 {
4309   GstStructure *ret;
4310
4311   g_return_val_if_fail (GST_IS_PLAY (self), NULL);
4312
4313   g_mutex_lock (&self->lock);
4314   ret = gst_structure_copy (self->config);
4315   g_mutex_unlock (&self->lock);
4316
4317   return ret;
4318 }
4319
4320 /**
4321  * gst_play_config_set_user_agent:
4322  * @config: a #GstPlay configuration
4323  * @agent: (nullable): the string to use as user agent
4324  *
4325  * Set the user agent to pass to the server if @play needs to connect
4326  * to a server during playback. This is typically used when playing HTTP
4327  * or RTSP streams.
4328  *
4329  * Since: 1.20
4330  */
4331 void
4332 gst_play_config_set_user_agent (GstStructure * config, const gchar * agent)
4333 {
4334   g_return_if_fail (config != NULL);
4335   g_return_if_fail (agent != NULL);
4336
4337   gst_structure_id_set (config,
4338       CONFIG_QUARK (USER_AGENT), G_TYPE_STRING, agent, NULL);
4339 }
4340
4341 /**
4342  * gst_play_config_get_user_agent:
4343  * @config: a #GstPlay configuration
4344  *
4345  * Return the user agent which has been configured using
4346  * gst_play_config_set_user_agent() if any.
4347  *
4348  * Returns: (transfer full) (nullable): the configured agent, or %NULL
4349  * Since: 1.20
4350  */
4351 gchar *
4352 gst_play_config_get_user_agent (const GstStructure * config)
4353 {
4354   gchar *agent = NULL;
4355
4356   g_return_val_if_fail (config != NULL, NULL);
4357
4358   gst_structure_id_get (config,
4359       CONFIG_QUARK (USER_AGENT), G_TYPE_STRING, &agent, NULL);
4360
4361   return agent;
4362 }
4363
4364 /**
4365  * gst_play_config_set_position_update_interval:
4366  * @config: a #GstPlay configuration
4367  * @interval: interval in ms
4368  *
4369  * set desired interval in milliseconds between two position-updated messages.
4370  * pass 0 to stop updating the position.
4371  * Since: 1.20
4372  */
4373 void
4374 gst_play_config_set_position_update_interval (GstStructure * config,
4375     guint interval)
4376 {
4377   g_return_if_fail (config != NULL);
4378   g_return_if_fail (interval <= 10000);
4379
4380   gst_structure_id_set (config,
4381       CONFIG_QUARK (POSITION_INTERVAL_UPDATE), G_TYPE_UINT, interval, NULL);
4382 }
4383
4384 /**
4385  * gst_play_config_get_position_update_interval:
4386  * @config: a #GstPlay configuration
4387  *
4388  * Returns: current position update interval in milliseconds
4389  *
4390  * Since: 1.20
4391  */
4392 guint
4393 gst_play_config_get_position_update_interval (const GstStructure * config)
4394 {
4395   guint interval = DEFAULT_POSITION_UPDATE_INTERVAL_MS;
4396
4397   g_return_val_if_fail (config != NULL, DEFAULT_POSITION_UPDATE_INTERVAL_MS);
4398
4399   gst_structure_id_get (config,
4400       CONFIG_QUARK (POSITION_INTERVAL_UPDATE), G_TYPE_UINT, &interval, NULL);
4401
4402   return interval;
4403 }
4404
4405 /**
4406  * gst_play_config_set_seek_accurate:
4407  * @config: a #GstPlay configuration
4408  * @accurate: accurate seek or not
4409  *
4410  * Enable or disable accurate seeking. When enabled, elements will try harder
4411  * to seek as accurately as possible to the requested seek position. Generally
4412  * it will be slower especially for formats that don't have any indexes or
4413  * timestamp markers in the stream.
4414  *
4415  * If accurate seeking is disabled, elements will seek as close as the request
4416  * position without slowing down seeking too much.
4417  *
4418  * Accurate seeking is disabled by default.
4419  *
4420  * Since: 1.20
4421  */
4422 void
4423 gst_play_config_set_seek_accurate (GstStructure * config, gboolean accurate)
4424 {
4425   g_return_if_fail (config != NULL);
4426
4427   gst_structure_id_set (config,
4428       CONFIG_QUARK (ACCURATE_SEEK), G_TYPE_BOOLEAN, accurate, NULL);
4429 }
4430
4431 /**
4432  * gst_play_config_get_seek_accurate:
4433  * @config: a #GstPlay configuration
4434  *
4435  * Returns: %TRUE if accurate seeking is enabled
4436  *
4437  * Since: 1.20
4438  */
4439 gboolean
4440 gst_play_config_get_seek_accurate (const GstStructure * config)
4441 {
4442   gboolean accurate = FALSE;
4443
4444   g_return_val_if_fail (config != NULL, FALSE);
4445
4446   gst_structure_id_get (config,
4447       CONFIG_QUARK (ACCURATE_SEEK), G_TYPE_BOOLEAN, &accurate, NULL);
4448
4449   return accurate;
4450 }
4451
4452 /**
4453  * gst_play_get_video_snapshot:
4454  * @play: #GstPlay instance
4455  * @format: output format of the video snapshot
4456  * @config: (allow-none): Additional configuration
4457  *
4458  * Get a snapshot of the currently selected video stream, if any. The format can be
4459  * selected with @format and optional configuration is possible with @config
4460  * Currently supported settings are:
4461  * - width, height of type G_TYPE_INT
4462  * - pixel-aspect-ratio of type GST_TYPE_FRACTION
4463  *  Except for GST_PLAY_THUMBNAIL_RAW_NATIVE format, if no config is set, pixel-aspect-ratio would be 1/1
4464  *
4465  * Returns: (transfer full) (nullable):  Current video snapshot sample or %NULL on failure
4466  *
4467  * Since: 1.20
4468  */
4469 GstSample *
4470 gst_play_get_video_snapshot (GstPlay * self,
4471     GstPlaySnapshotFormat format, const GstStructure * config)
4472 {
4473   gint video_tracks = 0;
4474   GstSample *sample = NULL;
4475   GstCaps *caps = NULL;
4476   gint width = -1;
4477   gint height = -1;
4478   gint par_n = 1;
4479   gint par_d = 1;
4480   g_return_val_if_fail (GST_IS_PLAY (self), NULL);
4481
4482   g_object_get (self->playbin, "n-video", &video_tracks, NULL);
4483   if (video_tracks == 0) {
4484     GST_DEBUG_OBJECT (self, "total video track num is 0");
4485     return NULL;
4486   }
4487
4488   switch (format) {
4489     case GST_PLAY_THUMBNAIL_RAW_xRGB:
4490       caps = gst_caps_new_simple ("video/x-raw",
4491           "format", G_TYPE_STRING, "xRGB", NULL);
4492       break;
4493     case GST_PLAY_THUMBNAIL_RAW_BGRx:
4494       caps = gst_caps_new_simple ("video/x-raw",
4495           "format", G_TYPE_STRING, "BGRx", NULL);
4496       break;
4497     case GST_PLAY_THUMBNAIL_JPG:
4498       caps = gst_caps_new_empty_simple ("image/jpeg");
4499       break;
4500     case GST_PLAY_THUMBNAIL_PNG:
4501       caps = gst_caps_new_empty_simple ("image/png");
4502       break;
4503     case GST_PLAY_THUMBNAIL_RAW_NATIVE:
4504     default:
4505       caps = gst_caps_new_empty_simple ("video/x-raw");
4506       break;
4507   }
4508
4509   if (NULL != config) {
4510     if (!gst_structure_get_int (config, "width", &width))
4511       width = -1;
4512     if (!gst_structure_get_int (config, "height", &height))
4513       height = -1;
4514     if (!gst_structure_get_fraction (config, "pixel-aspect-ratio", &par_n,
4515             &par_d)) {
4516       if (format != GST_PLAY_THUMBNAIL_RAW_NATIVE) {
4517         par_n = 1;
4518         par_d = 1;
4519       } else {
4520         par_n = 0;
4521         par_d = 0;
4522       }
4523     }
4524   }
4525
4526   if (width > 0 && height > 0) {
4527     gst_caps_set_simple (caps, "width", G_TYPE_INT, width,
4528         "height", G_TYPE_INT, height, NULL);
4529   }
4530
4531   if (format != GST_PLAY_THUMBNAIL_RAW_NATIVE) {
4532     gst_caps_set_simple (caps, "pixel-aspect-ratio", GST_TYPE_FRACTION,
4533         par_n, par_d, NULL);
4534   } else if (NULL != config && par_n != 0 && par_d != 0) {
4535     gst_caps_set_simple (caps, "pixel-aspect-ratio", GST_TYPE_FRACTION,
4536         par_n, par_d, NULL);
4537   }
4538
4539   g_signal_emit_by_name (self->playbin, "convert-sample", caps, &sample);
4540   gst_caps_unref (caps);
4541   if (!sample) {
4542     GST_WARNING_OBJECT (self, "Failed to retrieve or convert video frame");
4543     return NULL;
4544   }
4545
4546   return sample;
4547 }
4548
4549 /**
4550  * gst_play_is_play_message:
4551  * @msg: A #GstMessage
4552  *
4553  * Returns: A #gboolean indicating wheter the passes message represents a #GstPlay message or not.
4554  *
4555  * Since: 1.20
4556  */
4557 gboolean
4558 gst_play_is_play_message (GstMessage * msg)
4559 {
4560   const GstStructure *data = NULL;
4561   g_return_val_if_fail (GST_IS_MESSAGE (msg), FALSE);
4562
4563   data = gst_message_get_structure (msg);
4564   g_return_val_if_fail (data, FALSE);
4565
4566   return g_str_equal (gst_structure_get_name (data), GST_PLAY_MESSAGE_DATA);
4567 }
4568
4569 #define PARSE_MESSAGE_FIELD(msg, field, value_type, value) G_STMT_START { \
4570     const GstStructure *data = NULL;                                      \
4571     g_return_if_fail (gst_play_is_play_message (msg));                    \
4572     data = gst_message_get_structure (msg);                               \
4573     gst_structure_get (data, field, value_type, value, NULL);             \
4574 } G_STMT_END
4575
4576 /**
4577  * gst_play_message_parse_type:
4578  * @msg: A #GstMessage
4579  * @type: (out) (optional): the resulting message type
4580  *
4581  * Parse the given @msg and extract its #GstPlayMessage type.
4582  *
4583  * Since: 1.20
4584  */
4585 void
4586 gst_play_message_parse_type (GstMessage * msg, GstPlayMessage * type)
4587 {
4588   PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_TYPE,
4589       GST_TYPE_PLAY_MESSAGE, type);
4590 }
4591
4592 /**
4593  * gst_play_message_parse_duration_updated:
4594  * @msg: A #GstMessage
4595  * @duration: (out) (optional): the resulting duration
4596  *
4597  * Parse the given duration @msg and extract the corresponding #GstClockTime
4598  *
4599  * Since: 1.20
4600  */
4601 void
4602 gst_play_message_parse_duration_updated (GstMessage * msg,
4603     GstClockTime * duration)
4604 {
4605   PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_DURATION,
4606       GST_TYPE_CLOCK_TIME, duration);
4607 }
4608
4609 /**
4610  * gst_play_message_parse_position_updated:
4611  * @msg: A #GstMessage
4612  * @position: (out) (optional): the resulting position
4613  *
4614  * Parse the given position @msg and extract the corresponding #GstClockTime
4615  *
4616  * Since: 1.20
4617  */
4618 void
4619 gst_play_message_parse_position_updated (GstMessage * msg,
4620     GstClockTime * position)
4621 {
4622   PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_POSITION,
4623       GST_TYPE_CLOCK_TIME, position);
4624 }
4625
4626 /**
4627  * gst_play_message_parse_state_changed:
4628  * @msg: A #GstMessage
4629  * @state: (out) (optional): the resulting play state
4630  *
4631  * Parse the given state @msg and extract the corresponding #GstPlayState
4632  *
4633  * Since: 1.20
4634  */
4635 void
4636 gst_play_message_parse_state_changed (GstMessage * msg, GstPlayState * state)
4637 {
4638   PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_PLAY_STATE,
4639       GST_TYPE_PLAY_STATE, state);
4640 }
4641
4642 /**
4643  * gst_play_message_parse_buffering_percent:
4644  * @msg: A #GstMessage
4645  * @percent: (out) (optional): the resulting buffering percent
4646  *
4647  * Parse the given buffering-percent @msg and extract the corresponding value
4648  *
4649  * Since: 1.20
4650  */
4651 void
4652 gst_play_message_parse_buffering_percent (GstMessage * msg, guint * percent)
4653 {
4654   PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_BUFFERING_PERCENT,
4655       G_TYPE_UINT, percent);
4656 }
4657
4658 /**
4659  * gst_play_message_parse_error:
4660  * @msg: A #GstMessage
4661  * @error: (out) (optional) (transfer full): the resulting error
4662  * @details: (out) (optional) (nullable) (transfer full): A #GstStructure containing additional details about the error
4663  *
4664  * Parse the given error @msg and extract the corresponding #GError
4665  *
4666  * Since: 1.20
4667  */
4668 void
4669 gst_play_message_parse_error (GstMessage * msg, GError ** error,
4670     GstStructure ** details)
4671 {
4672   PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_ERROR, G_TYPE_ERROR, error);
4673   PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_ERROR, GST_TYPE_STRUCTURE,
4674       details);
4675 }
4676
4677 /**
4678  * gst_play_message_parse_warning:
4679  * @msg: A #GstMessage
4680  * @error: (out) (optional) (transfer full): the resulting warning
4681  * @details: (out) (optional) (nullable) (transfer full): A #GstStructure containing additional details about the warning
4682  *
4683  * Parse the given error @msg and extract the corresponding #GError warning
4684  *
4685  * Since: 1.20
4686  */
4687 void
4688 gst_play_message_parse_warning (GstMessage * msg, GError ** error,
4689     GstStructure ** details)
4690 {
4691   PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_WARNING, G_TYPE_ERROR, error);
4692   PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_WARNING, GST_TYPE_STRUCTURE,
4693       details);
4694 }
4695
4696 /**
4697  * gst_play_message_parse_video_dimensions_changed:
4698  * @msg: A #GstMessage
4699  * @width: (out) (optional): the resulting video width
4700  * @height: (out) (optional): the resulting video height
4701  *
4702  * Parse the given @msg and extract the corresponding video dimensions
4703  *
4704  * Since: 1.20
4705  */
4706 void
4707 gst_play_message_parse_video_dimensions_changed (GstMessage * msg,
4708     guint * width, guint * height)
4709 {
4710   PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_VIDEO_WIDTH,
4711       G_TYPE_UINT, width);
4712   PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_VIDEO_HEIGHT,
4713       G_TYPE_UINT, height);
4714 }
4715
4716 /**
4717  * gst_play_message_parse_media_info_updated:
4718  * @msg: A #GstMessage
4719  * @info: (out) (optional) (transfer full): the resulting media info
4720  *
4721  * Parse the given @msg and extract the corresponding media information
4722  *
4723  * Since: 1.20
4724  */
4725 void
4726 gst_play_message_parse_media_info_updated (GstMessage * msg,
4727     GstPlayMediaInfo ** info)
4728 {
4729   PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_MEDIA_INFO,
4730       GST_TYPE_PLAY_MEDIA_INFO, info);
4731 }
4732
4733 /**
4734  * gst_play_message_parse_volume_changed:
4735  * @msg: A #GstMessage
4736  * @volume: (out) (optional): the resulting audio volume
4737  *
4738  * Parse the given @msg and extract the corresponding audio volume
4739  *
4740  * Since: 1.20
4741  */
4742 void
4743 gst_play_message_parse_volume_changed (GstMessage * msg, gdouble * volume)
4744 {
4745   PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_VOLUME, G_TYPE_DOUBLE,
4746       volume);
4747 }
4748
4749 /**
4750  * gst_play_message_parse_muted_changed:
4751  * @msg: A #GstMessage
4752  * @muted: (out) (optional): the resulting audio muted state
4753  *
4754  * Parse the given @msg and extract the corresponding audio muted state
4755  *
4756  * Since: 1.20
4757  */
4758 void
4759 gst_play_message_parse_muted_changed (GstMessage * msg, gboolean * muted)
4760 {
4761   PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_IS_MUTED, G_TYPE_BOOLEAN,
4762       muted);
4763 }