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