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