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