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>
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.
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.
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.
27 * @short_description: Player
39 * - Subtitle font, connection speed
41 * - Buffering control (-> progressive downloading)
42 * - Playlist/queue object
43 * - Custom video sink (e.g. embed in GL scene)
52 #include "gstplay-video-renderer-private.h"
53 #include "gstplay-media-info-private.h"
54 #include "gstplay-message-private.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>
64 GST_DEBUG_CATEGORY_STATIC (gst_play_debug);
65 #define GST_CAT_DEFAULT gst_play_debug
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
78 * gst_play_error_quark:
82 gst_play_error_quark (void)
84 return g_quark_from_static_string ("gst-play-error-quark");
87 static GQuark QUARK_CONFIG;
89 /* Keep ConfigQuarkId and _config_quark_strings ordered and synced */
92 CONFIG_QUARK_USER_AGENT = 0,
93 CONFIG_QUARK_POSITION_INTERVAL_UPDATE,
94 CONFIG_QUARK_ACCURATE_SEEK,
99 static const gchar *_config_quark_strings[] = {
101 "position-interval-update",
105 static GQuark _config_quark_table[CONFIG_QUARK_MAX];
107 #define CONFIG_QUARK(q) _config_quark_table[CONFIG_QUARK_##q]
118 PROP_CURRENT_AUDIO_TRACK,
119 PROP_CURRENT_VIDEO_TRACK,
120 PROP_CURRENT_SUBTITLE_TRACK,
125 PROP_VIDEO_MULTIVIEW_MODE,
126 PROP_VIDEO_MULTIVIEW_FLAGS,
127 PROP_AUDIO_VIDEO_OFFSET,
128 PROP_SUBTITLE_VIDEO_OFFSET,
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)
144 GstPlayVideoRenderer *video_renderer;
153 GMainContext *context;
160 GstState target_state, current_state;
161 gboolean is_live, is_eos;
162 GSource *tick_source, *ready_timeout_source;
164 GstClockTime cached_duration;
165 gint64 cached_position;
169 GstPlayState app_state;
171 gint buffering_percent;
173 GstTagList *global_tags;
174 GstPlayMediaInfo *media_info;
176 GstElement *current_vis_element;
178 GstStructure *config;
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;
187 gboolean use_playbin3;
188 GstStreamCollection *collection;
192 gulong stream_notify_id;
197 GstObjectClass parent_class;
200 #define parent_class gst_play_parent_class
201 G_DEFINE_TYPE (GstPlay, gst_play, GST_TYPE_OBJECT);
203 static GParamSpec *param_specs[PROP_LAST] = { NULL, };
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);
213 static gpointer gst_play_main (gpointer data);
215 static void gst_play_set_playbin_video_sink (GstPlay * self);
217 static void gst_play_seek_internal_locked (GstPlay * self);
218 static void gst_play_stop_internal (GstPlay * self, gboolean transient);
219 static gboolean gst_play_pause_internal (gpointer user_data);
220 static gboolean gst_play_play_internal (gpointer user_data);
221 static gboolean gst_play_seek_internal (gpointer user_data);
222 static void gst_play_set_rate_internal (GstPlay * self);
223 static void change_state (GstPlay * self, GstPlayState state);
225 static GstPlayMediaInfo *gst_play_media_info_create (GstPlay * self);
227 static void gst_play_streams_info_create (GstPlay * self,
228 GstPlayMediaInfo * media_info, const gchar * prop, GType type);
229 static void gst_play_stream_info_update (GstPlay * self, GstPlayStreamInfo * s);
230 static void gst_play_stream_info_update_tags_and_caps (GstPlay * self,
231 GstPlayStreamInfo * s);
232 static GstPlayStreamInfo *gst_play_stream_info_find (GstPlayMediaInfo *
233 media_info, GType type, gint stream_index);
234 static GstPlayStreamInfo *gst_play_stream_info_get_current (GstPlay *
235 self, const gchar * prop, GType type);
237 static void gst_play_video_info_update (GstPlay * self,
238 GstPlayStreamInfo * stream_info);
239 static void gst_play_audio_info_update (GstPlay * self,
240 GstPlayStreamInfo * stream_info);
241 static void gst_play_subtitle_info_update (GstPlay * self,
242 GstPlayStreamInfo * stream_info);
245 static void gst_play_streams_info_create_from_collection (GstPlay * self,
246 GstPlayMediaInfo * media_info, GstStreamCollection * collection);
247 static void gst_play_stream_info_update_from_stream (GstPlay * self,
248 GstPlayStreamInfo * s, GstStream * stream);
249 static GstPlayStreamInfo *gst_play_stream_info_find_from_stream_id
250 (GstPlayMediaInfo * media_info, const gchar * stream_id);
251 static GstPlayStreamInfo *gst_play_stream_info_get_current_from_stream_id
252 (GstPlay * self, const gchar * stream_id, GType type);
253 static void stream_notify_cb (GstStreamCollection * collection,
254 GstStream * stream, GParamSpec * pspec, GstPlay * self);
256 static void on_media_info_updated (GstPlay * self);
258 static void *get_title (GstTagList * tags);
259 static void *get_container_format (GstTagList * tags);
260 static void *get_from_tags (GstPlay * self, GstPlayMediaInfo * media_info,
261 void *(*func) (GstTagList *));
262 static void *get_cover_sample (GstTagList * tags);
264 static void remove_seek_source (GstPlay * self);
266 static gboolean query_position (GstPlay * self, GstClockTime * position);
269 gst_play_init (GstPlay * self)
271 GST_TRACE_OBJECT (self, "Initializing");
273 self = gst_play_get_instance_private (self);
275 g_mutex_init (&self->lock);
276 g_cond_init (&self->cond);
278 self->context = g_main_context_new ();
279 self->loop = g_main_loop_new (self->context, FALSE);
280 self->api_bus = gst_bus_new ();
283 self->config = gst_structure_new_id (QUARK_CONFIG,
284 CONFIG_QUARK (POSITION_INTERVAL_UPDATE), G_TYPE_UINT, DEFAULT_POSITION_UPDATE_INTERVAL_MS,
285 CONFIG_QUARK (ACCURATE_SEEK), G_TYPE_BOOLEAN, FALSE,
289 self->seek_pending = FALSE;
290 self->seek_position = GST_CLOCK_TIME_NONE;
291 self->last_seek_time = GST_CLOCK_TIME_NONE;
293 self->cached_position = 0;
294 self->cached_duration = GST_CLOCK_TIME_NONE;
296 GST_TRACE_OBJECT (self, "Initialized");
300 * Works same as gst_structure_set to set field/type/value triplets on message data
303 api_bus_post_message (GstPlay * self, GstPlayMessage message_type,
304 const gchar * firstfield, ...)
306 GstStructure *message_data = NULL;
307 GstMessage *msg = NULL;
310 GST_INFO ("Posting API-bus message-type: %s",
311 gst_play_message_get_name (message_type));
312 message_data = gst_structure_new (GST_PLAY_MESSAGE_DATA,
313 GST_PLAY_MESSAGE_DATA_TYPE, GST_TYPE_PLAY_MESSAGE, message_type, NULL);
315 va_start (varargs, firstfield);
316 gst_structure_set_valist (message_data, firstfield, varargs);
319 msg = gst_message_new_custom (GST_MESSAGE_APPLICATION,
320 GST_OBJECT (self), message_data);
321 GST_DEBUG ("Created message with payload: [ %" GST_PTR_FORMAT " ]",
323 gst_bus_post (self->api_bus, msg);
327 config_quark_initialize (void)
331 QUARK_CONFIG = g_quark_from_static_string ("play-config");
333 if (G_N_ELEMENTS (_config_quark_strings) != CONFIG_QUARK_MAX)
334 g_warning ("the quark table is not consistent! %d != %d",
335 (int) G_N_ELEMENTS (_config_quark_strings), CONFIG_QUARK_MAX);
337 for (i = 0; i < CONFIG_QUARK_MAX; i++) {
338 _config_quark_table[i] =
339 g_quark_from_static_string (_config_quark_strings[i]);
344 gst_play_class_init (GstPlayClass * klass)
346 GObjectClass *gobject_class = (GObjectClass *) klass;
348 gobject_class->set_property = gst_play_set_property;
349 gobject_class->get_property = gst_play_get_property;
350 gobject_class->dispose = gst_play_dispose;
351 gobject_class->finalize = gst_play_finalize;
352 gobject_class->constructed = gst_play_constructed;
354 param_specs[PROP_VIDEO_RENDERER] =
355 g_param_spec_object ("video-renderer",
356 "Video Renderer", "Video renderer to use for rendering videos",
357 GST_TYPE_PLAY_VIDEO_RENDERER, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
359 param_specs[PROP_URI] = g_param_spec_string ("uri", "URI", "Current URI",
360 DEFAULT_URI, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
362 param_specs[PROP_SUBURI] = g_param_spec_string ("suburi", "Subtitle URI",
363 "Current Subtitle URI", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
365 param_specs[PROP_POSITION] =
366 g_param_spec_uint64 ("position", "Position", "Current Position",
367 0, G_MAXUINT64, DEFAULT_POSITION,
368 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
370 param_specs[PROP_MEDIA_INFO] =
371 g_param_spec_object ("media-info", "Media Info",
372 "Current media information", GST_TYPE_PLAY_MEDIA_INFO,
373 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
375 param_specs[PROP_CURRENT_AUDIO_TRACK] =
376 g_param_spec_object ("current-audio-track", "Current Audio Track",
377 "Current audio track information", GST_TYPE_PLAY_AUDIO_INFO,
378 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
380 param_specs[PROP_CURRENT_VIDEO_TRACK] =
381 g_param_spec_object ("current-video-track", "Current Video Track",
382 "Current video track information", GST_TYPE_PLAY_VIDEO_INFO,
383 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
385 param_specs[PROP_CURRENT_SUBTITLE_TRACK] =
386 g_param_spec_object ("current-subtitle-track", "Current Subtitle Track",
387 "Current audio subtitle information", GST_TYPE_PLAY_SUBTITLE_INFO,
388 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
390 param_specs[PROP_DURATION] =
391 g_param_spec_uint64 ("duration", "Duration", "Duration",
392 0, G_MAXUINT64, DEFAULT_DURATION,
393 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
395 param_specs[PROP_VOLUME] =
396 g_param_spec_double ("volume", "Volume", "Volume",
397 0, 10.0, DEFAULT_VOLUME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
399 param_specs[PROP_MUTE] =
400 g_param_spec_boolean ("mute", "Mute", "Mute",
401 DEFAULT_MUTE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
403 param_specs[PROP_PIPELINE] =
404 g_param_spec_object ("pipeline", "Pipeline",
405 "GStreamer pipeline that is used",
406 GST_TYPE_ELEMENT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
408 param_specs[PROP_RATE] =
409 g_param_spec_double ("rate", "rate", "Playback rate",
410 -64.0, 64.0, DEFAULT_RATE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
412 param_specs[PROP_VIDEO_MULTIVIEW_MODE] =
413 g_param_spec_enum ("video-multiview-mode",
414 "Multiview Mode Override",
415 "Re-interpret a video stream as one of several frame-packed stereoscopic modes.",
416 GST_TYPE_VIDEO_MULTIVIEW_FRAME_PACKING,
417 GST_VIDEO_MULTIVIEW_FRAME_PACKING_NONE,
418 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
420 param_specs[PROP_VIDEO_MULTIVIEW_FLAGS] =
421 g_param_spec_flags ("video-multiview-flags",
422 "Multiview Flags Override",
423 "Override details of the multiview frame layout",
424 GST_TYPE_VIDEO_MULTIVIEW_FLAGS, GST_VIDEO_MULTIVIEW_FLAGS_NONE,
425 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
427 param_specs[PROP_AUDIO_VIDEO_OFFSET] =
428 g_param_spec_int64 ("audio-video-offset", "Audio Video Offset",
429 "The synchronisation offset between audio and video in nanoseconds",
430 G_MININT64, G_MAXINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
432 param_specs[PROP_SUBTITLE_VIDEO_OFFSET] =
433 g_param_spec_int64 ("subtitle-video-offset", "Text Video Offset",
434 "The synchronisation offset between text and video in nanoseconds",
435 G_MININT64, G_MAXINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
437 g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
439 config_quark_initialize ();
443 gst_play_dispose (GObject * object)
445 GstPlay *self = GST_PLAY (object);
447 GST_TRACE_OBJECT (self, "Stopping main thread");
449 gst_bus_set_flushing (self->api_bus, TRUE);
452 g_main_loop_quit (self->loop);
454 if (self->thread != g_thread_self ())
455 g_thread_join (self->thread);
457 g_thread_unref (self->thread);
460 g_main_loop_unref (self->loop);
463 g_main_context_unref (self->context);
464 self->context = NULL;
467 gst_clear_object (&self->api_bus);
469 G_OBJECT_CLASS (parent_class)->dispose (object);
473 gst_play_finalize (GObject * object)
475 GstPlay *self = GST_PLAY (object);
477 GST_TRACE_OBJECT (self, "Finalizing");
480 g_free (self->redirect_uri);
481 g_free (self->suburi);
482 g_free (self->video_sid);
483 g_free (self->audio_sid);
484 g_free (self->subtitle_sid);
485 if (self->global_tags)
486 gst_tag_list_unref (self->global_tags);
487 if (self->video_renderer)
488 g_object_unref (self->video_renderer);
489 if (self->current_vis_element)
490 gst_object_unref (self->current_vis_element);
492 gst_structure_free (self->config);
493 if (self->collection)
494 gst_object_unref (self->collection);
495 if (self->media_info)
496 g_object_unref (self->media_info);
497 g_mutex_clear (&self->lock);
498 g_cond_clear (&self->cond);
500 G_OBJECT_CLASS (parent_class)->finalize (object);
504 gst_play_constructed (GObject * object)
506 GstPlay *self = GST_PLAY (object);
508 GST_TRACE_OBJECT (self, "Constructed");
510 g_mutex_lock (&self->lock);
511 self->thread = g_thread_new ("GstPlay", gst_play_main, self);
512 while (!self->loop || !g_main_loop_is_running (self->loop))
513 g_cond_wait (&self->cond, &self->lock);
515 gst_play_set_playbin_video_sink (self);
516 g_mutex_unlock (&self->lock);
518 G_OBJECT_CLASS (parent_class)->constructed (object);
522 gst_play_set_uri_internal (gpointer user_data)
524 GstPlay *self = user_data;
526 gst_play_stop_internal (self, FALSE);
528 g_mutex_lock (&self->lock);
530 GST_DEBUG_OBJECT (self, "Changing URI to '%s'", GST_STR_NULL (self->uri));
532 g_object_set (self->playbin, "uri", self->uri, NULL);
534 api_bus_post_message (self, GST_PLAY_MESSAGE_URI_LOADED,
535 GST_PLAY_MESSAGE_DATA_URI, G_TYPE_STRING, self->uri, NULL);
537 g_object_set (self->playbin, "suburi", NULL, NULL);
539 g_mutex_unlock (&self->lock);
541 return G_SOURCE_REMOVE;
545 gst_play_set_suburi_internal (gpointer user_data)
547 GstPlay *self = user_data;
548 GstClockTime position;
549 GstState target_state;
551 /* save the state and position */
552 target_state = self->target_state;
553 position = gst_play_get_position (self);
555 gst_play_stop_internal (self, TRUE);
556 g_mutex_lock (&self->lock);
558 GST_DEBUG_OBJECT (self, "Changing SUBURI to '%s'",
559 GST_STR_NULL (self->suburi));
561 g_object_set (self->playbin, "suburi", self->suburi, NULL);
563 g_mutex_unlock (&self->lock);
565 /* restore state and position */
566 if (position != GST_CLOCK_TIME_NONE)
567 gst_play_seek (self, position);
568 if (target_state == GST_STATE_PAUSED)
569 gst_play_pause_internal (self);
570 else if (target_state == GST_STATE_PLAYING)
571 gst_play_play_internal (self);
573 return G_SOURCE_REMOVE;
577 gst_play_set_rate_internal (GstPlay * self)
579 self->seek_position = gst_play_get_position (self);
581 /* If there is no seek being dispatch to the main context currently do that,
582 * otherwise we just updated the rate so that it will be taken by
583 * the seek handler from the main context instead of the old one.
585 if (!self->seek_source) {
586 /* If no seek is pending then create new seek source */
587 if (!self->seek_pending) {
588 self->seek_source = g_idle_source_new ();
589 g_source_set_callback (self->seek_source,
590 (GSourceFunc) gst_play_seek_internal, self, NULL);
591 g_source_attach (self->seek_source, self->context);
597 gst_play_set_playbin_video_sink (GstPlay * self)
599 GstElement *video_sink = NULL;
601 if (self->video_renderer != NULL)
603 gst_play_video_renderer_create_video_sink (self->video_renderer, self);
605 g_object_set (self->playbin, "video-sink", video_sink, NULL);
609 gst_play_set_property (GObject * object, guint prop_id,
610 const GValue * value, GParamSpec * pspec)
612 GstPlay *self = GST_PLAY (object);
615 case PROP_VIDEO_RENDERER:
616 g_mutex_lock (&self->lock);
617 g_clear_object (&self->video_renderer);
618 self->video_renderer = g_value_dup_object (value);
620 // When the video_renderer is a GstPlayerWrappedVideoRenderer it cannot be set
621 // at construction time because it requires a valid pipeline which is created
622 // only after GstPlay has been constructed. That is why the video renderer is
623 // set *after* GstPlay has been constructed.
625 gst_play_set_playbin_video_sink (self);
627 g_mutex_unlock (&self->lock);
630 g_mutex_lock (&self->lock);
632 g_free (self->redirect_uri);
633 self->redirect_uri = NULL;
635 g_free (self->suburi);
638 self->uri = g_value_dup_string (value);
639 GST_DEBUG_OBJECT (self, "Set uri=%s", GST_STR_NULL (self->uri));
640 g_mutex_unlock (&self->lock);
642 g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT,
643 gst_play_set_uri_internal, self, NULL);
647 g_mutex_lock (&self->lock);
648 g_free (self->suburi);
650 self->suburi = g_value_dup_string (value);
651 GST_DEBUG_OBJECT (self, "Set suburi=%s", self->suburi);
652 g_mutex_unlock (&self->lock);
654 g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT,
655 gst_play_set_suburi_internal, self, NULL);
659 GST_DEBUG_OBJECT (self, "Set volume=%lf", g_value_get_double (value));
660 g_object_set_property (G_OBJECT (self->playbin), "volume", value);
663 g_mutex_lock (&self->lock);
664 self->rate = g_value_get_double (value);
665 GST_DEBUG_OBJECT (self, "Set rate=%lf", g_value_get_double (value));
666 gst_play_set_rate_internal (self);
667 g_mutex_unlock (&self->lock);
670 GST_DEBUG_OBJECT (self, "Set mute=%d", g_value_get_boolean (value));
671 g_object_set_property (G_OBJECT (self->playbin), "mute", value);
673 case PROP_VIDEO_MULTIVIEW_MODE:
674 GST_DEBUG_OBJECT (self, "Set multiview mode=%u",
675 g_value_get_enum (value));
676 g_object_set_property (G_OBJECT (self->playbin), "video-multiview-mode",
679 case PROP_VIDEO_MULTIVIEW_FLAGS:
680 GST_DEBUG_OBJECT (self, "Set multiview flags=%x",
681 g_value_get_flags (value));
682 g_object_set_property (G_OBJECT (self->playbin), "video-multiview-flags",
685 case PROP_AUDIO_VIDEO_OFFSET:
686 g_object_set_property (G_OBJECT (self->playbin), "av-offset", value);
688 case PROP_SUBTITLE_VIDEO_OFFSET:
689 g_object_set_property (G_OBJECT (self->playbin), "text-offset", value);
692 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
698 gst_play_get_property (GObject * object, guint prop_id,
699 GValue * value, GParamSpec * pspec)
701 GstPlay *self = GST_PLAY (object);
705 g_mutex_lock (&self->lock);
706 g_value_set_string (value, self->uri);
707 g_mutex_unlock (&self->lock);
710 g_mutex_lock (&self->lock);
711 g_value_set_string (value, self->suburi);
712 g_mutex_unlock (&self->lock);
713 GST_DEBUG_OBJECT (self, "Returning suburi=%s",
714 g_value_get_string (value));
717 GstClockTime position = GST_CLOCK_TIME_NONE;
718 query_position (self, &position);
719 g_value_set_uint64 (value, position);
720 GST_TRACE_OBJECT (self, "Returning position=%" GST_TIME_FORMAT,
721 GST_TIME_ARGS (g_value_get_uint64 (value)));
725 g_value_set_uint64 (value, self->cached_duration);
726 GST_TRACE_OBJECT (self, "Returning duration=%" GST_TIME_FORMAT,
727 GST_TIME_ARGS (g_value_get_uint64 (value)));
730 case PROP_MEDIA_INFO:{
731 GstPlayMediaInfo *media_info = gst_play_get_media_info (self);
732 g_value_take_object (value, media_info);
735 case PROP_CURRENT_AUDIO_TRACK:{
736 GstPlayAudioInfo *audio_info = gst_play_get_current_audio_track (self);
737 g_value_take_object (value, audio_info);
740 case PROP_CURRENT_VIDEO_TRACK:{
741 GstPlayVideoInfo *video_info = gst_play_get_current_video_track (self);
742 g_value_take_object (value, video_info);
745 case PROP_CURRENT_SUBTITLE_TRACK:{
746 GstPlaySubtitleInfo *subtitle_info =
747 gst_play_get_current_subtitle_track (self);
748 g_value_take_object (value, subtitle_info);
752 g_object_get_property (G_OBJECT (self->playbin), "volume", value);
753 GST_TRACE_OBJECT (self, "Returning volume=%lf",
754 g_value_get_double (value));
757 g_mutex_lock (&self->lock);
758 g_value_set_double (value, self->rate);
759 g_mutex_unlock (&self->lock);
762 g_object_get_property (G_OBJECT (self->playbin), "mute", value);
763 GST_TRACE_OBJECT (self, "Returning mute=%d", g_value_get_boolean (value));
766 g_value_set_object (value, self->playbin);
768 case PROP_VIDEO_MULTIVIEW_MODE:{
769 g_object_get_property (G_OBJECT (self->playbin), "video-multiview-mode",
771 GST_TRACE_OBJECT (self, "Return multiview mode=%d",
772 g_value_get_enum (value));
775 case PROP_VIDEO_MULTIVIEW_FLAGS:{
776 g_object_get_property (G_OBJECT (self->playbin), "video-multiview-flags",
778 GST_TRACE_OBJECT (self, "Return multiview flags=%x",
779 g_value_get_flags (value));
782 case PROP_AUDIO_VIDEO_OFFSET:
783 g_object_get_property (G_OBJECT (self->playbin), "av-offset", value);
785 case PROP_SUBTITLE_VIDEO_OFFSET:
786 g_object_get_property (G_OBJECT (self->playbin), "text-offset", value);
789 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
795 main_loop_running_cb (gpointer user_data)
797 GstPlay *self = GST_PLAY (user_data);
799 GST_TRACE_OBJECT (self, "Main loop running now");
801 g_mutex_lock (&self->lock);
802 g_cond_signal (&self->cond);
803 g_mutex_unlock (&self->lock);
805 return G_SOURCE_REMOVE;
809 change_state (GstPlay * self, GstPlayState state)
811 if (state == self->app_state)
814 GST_DEBUG_OBJECT (self, "Changing app state from %s to %s",
815 gst_play_state_get_name (self->app_state),
816 gst_play_state_get_name (state));
818 self->app_state = state;
820 api_bus_post_message (self, GST_PLAY_MESSAGE_STATE_CHANGED,
821 GST_PLAY_MESSAGE_DATA_PLAY_STATE, GST_TYPE_PLAY_STATE,
822 self->app_state, NULL);
826 tick_cb (gpointer user_data)
828 GstPlay *self = GST_PLAY (user_data);
829 GstClockTime position;
830 if (query_position (self, &position)) {
831 api_bus_post_message (self, GST_PLAY_MESSAGE_POSITION_UPDATED,
832 GST_PLAY_MESSAGE_DATA_POSITION, GST_TYPE_CLOCK_TIME, position, NULL);
835 return G_SOURCE_CONTINUE;
839 * Returns true when position is queried and differed from cached position.
840 * Sets position to cached value, and to queried value if position can be queried
844 query_position (GstPlay * self, GstClockTime * position)
846 gint64 current_position;
847 *position = self->cached_position;
848 if (self->target_state >= GST_STATE_PAUSED
849 && gst_element_query_position (self->playbin, GST_FORMAT_TIME,
850 ¤t_position)) {
851 GST_LOG_OBJECT (self, "Queried position %" GST_TIME_FORMAT,
852 GST_TIME_ARGS (current_position));
853 if (self->cached_position != current_position) {
854 self->cached_position = current_position;
855 *position = (GstClockTime) current_position;
863 add_tick_source (GstPlay * self)
865 guint position_update_interval_ms;
867 if (self->tick_source)
870 position_update_interval_ms =
871 gst_play_config_get_position_update_interval (self->config);
872 if (!position_update_interval_ms)
875 self->tick_source = g_timeout_source_new (position_update_interval_ms);
876 g_source_set_callback (self->tick_source, (GSourceFunc) tick_cb, self, NULL);
877 g_source_attach (self->tick_source, self->context);
881 remove_tick_source (GstPlay * self)
883 if (!self->tick_source)
886 g_source_destroy (self->tick_source);
887 g_source_unref (self->tick_source);
888 self->tick_source = NULL;
892 ready_timeout_cb (gpointer user_data)
894 GstPlay *self = user_data;
896 if (self->target_state <= GST_STATE_READY) {
897 GST_DEBUG_OBJECT (self, "Setting pipeline to NULL state");
898 self->target_state = GST_STATE_NULL;
899 self->current_state = GST_STATE_NULL;
900 gst_element_set_state (self->playbin, GST_STATE_NULL);
903 return G_SOURCE_REMOVE;
907 add_ready_timeout_source (GstPlay * self)
909 if (self->ready_timeout_source)
912 self->ready_timeout_source = g_timeout_source_new_seconds (60);
913 g_source_set_callback (self->ready_timeout_source,
914 (GSourceFunc) ready_timeout_cb, self, NULL);
915 g_source_attach (self->ready_timeout_source, self->context);
919 remove_ready_timeout_source (GstPlay * self)
921 if (!self->ready_timeout_source)
924 g_source_destroy (self->ready_timeout_source);
925 g_source_unref (self->ready_timeout_source);
926 self->ready_timeout_source = NULL;
931 on_error (GstPlay * self, GError * err, const GstStructure * details)
933 GST_ERROR_OBJECT (self, "Error: %s (%s, %d)", err->message,
934 g_quark_to_string (err->domain), err->code);
936 api_bus_post_message (self, GST_PLAY_MESSAGE_ERROR,
937 GST_PLAY_MESSAGE_DATA_ERROR, G_TYPE_ERROR, err,
938 GST_PLAY_MESSAGE_DATA_ERROR_DETAILS, GST_TYPE_STRUCTURE, details, NULL);
942 remove_tick_source (self);
943 remove_ready_timeout_source (self);
945 self->target_state = GST_STATE_NULL;
946 self->current_state = GST_STATE_NULL;
947 self->is_live = FALSE;
948 self->is_eos = FALSE;
949 gst_element_set_state (self->playbin, GST_STATE_NULL);
950 change_state (self, GST_PLAY_STATE_STOPPED);
951 self->buffering_percent = 100;
953 g_mutex_lock (&self->lock);
954 if (self->media_info) {
955 g_object_unref (self->media_info);
956 self->media_info = NULL;
959 if (self->global_tags) {
960 gst_tag_list_unref (self->global_tags);
961 self->global_tags = NULL;
964 self->seek_pending = FALSE;
965 remove_seek_source (self);
966 self->seek_position = GST_CLOCK_TIME_NONE;
967 self->last_seek_time = GST_CLOCK_TIME_NONE;
968 g_mutex_unlock (&self->lock);
972 dump_dot_file (GstPlay * self, const gchar * name)
976 full_name = g_strdup_printf ("gst-play.%p.%s", self, name);
978 GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (self->playbin),
979 GST_DEBUG_GRAPH_SHOW_ALL, full_name);
985 error_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
987 GstPlay *self = GST_PLAY (user_data);
988 GError *err, *play_err;
989 gchar *name, *debug, *message, *full_message;
990 const GstStructure *details = NULL;
992 dump_dot_file (self, "error");
994 gst_message_parse_error (msg, &err, &debug);
995 gst_message_parse_error_details (msg, &details);
997 name = gst_object_get_path_string (msg->src);
998 message = gst_error_get_message (err->domain, err->code);
1002 g_strdup_printf ("Error from element %s: %s\n%s\n%s", name, message,
1003 err->message, debug);
1006 g_strdup_printf ("Error from element %s: %s\n%s", name, message,
1009 GST_ERROR_OBJECT (self, "ERROR: from element %s: %s", name, err->message);
1011 GST_ERROR_OBJECT (self, "Additional debug info: %s", debug);
1014 g_error_new_literal (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED, full_message);
1015 on_error (self, play_err, details);
1017 g_clear_error (&err);
1020 g_free (full_message);
1025 warning_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
1027 GstPlay *self = GST_PLAY (user_data);
1028 GError *err, *play_err;
1029 gchar *name, *debug, *message, *full_message;
1030 const GstStructure *details = NULL;
1032 dump_dot_file (self, "warning");
1034 gst_message_parse_warning (msg, &err, &debug);
1035 gst_message_parse_warning_details (msg, &details);
1037 name = gst_object_get_path_string (msg->src);
1038 message = gst_error_get_message (err->domain, err->code);
1042 g_strdup_printf ("Warning from element %s: %s\n%s\n%s", name, message,
1043 err->message, debug);
1046 g_strdup_printf ("Warning from element %s: %s\n%s", name, message,
1049 GST_WARNING_OBJECT (self, "WARNING: from element %s: %s", name, err->message);
1051 GST_WARNING_OBJECT (self, "Additional debug info: %s", debug);
1054 g_error_new_literal (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED, full_message);
1056 GST_WARNING_OBJECT (self, "Warning: %s (%s, %d)", err->message,
1057 g_quark_to_string (err->domain), err->code);
1059 api_bus_post_message (self, GST_PLAY_MESSAGE_WARNING,
1060 GST_PLAY_MESSAGE_DATA_WARNING, G_TYPE_ERROR, play_err,
1061 GST_PLAY_MESSAGE_DATA_WARNING_DETAILS, GST_TYPE_STRUCTURE, details, NULL);
1063 g_clear_error (&play_err);
1064 g_clear_error (&err);
1067 g_free (full_message);
1072 eos_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg,
1075 GstPlay *self = GST_PLAY (user_data);
1077 GST_DEBUG_OBJECT (self, "End of stream");
1080 remove_tick_source (self);
1082 api_bus_post_message (self, GST_PLAY_MESSAGE_END_OF_STREAM, NULL);
1084 change_state (self, GST_PLAY_STATE_STOPPED);
1085 self->buffering_percent = 100;
1086 self->is_eos = TRUE;
1090 buffering_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
1092 GstPlay *self = GST_PLAY (user_data);
1095 if (self->target_state < GST_STATE_PAUSED)
1100 gst_message_parse_buffering (msg, &percent);
1101 GST_LOG_OBJECT (self, "Buffering %d%%", percent);
1103 if (percent < 100 && self->target_state >= GST_STATE_PAUSED) {
1104 GstStateChangeReturn state_ret;
1106 GST_DEBUG_OBJECT (self, "Waiting for buffering to finish");
1107 state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED);
1109 if (state_ret == GST_STATE_CHANGE_FAILURE) {
1110 on_error (self, g_error_new (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED,
1111 "Failed to handle buffering"), NULL);
1115 change_state (self, GST_PLAY_STATE_BUFFERING);
1118 if (self->buffering_percent != percent) {
1119 self->buffering_percent = percent;
1121 api_bus_post_message (self, GST_PLAY_MESSAGE_BUFFERING,
1122 GST_PLAY_MESSAGE_DATA_BUFFERING_PERCENT, G_TYPE_UINT, percent, NULL);
1125 g_mutex_lock (&self->lock);
1126 if (percent == 100 && (self->seek_position != GST_CLOCK_TIME_NONE ||
1127 self->seek_pending)) {
1128 g_mutex_unlock (&self->lock);
1130 GST_DEBUG_OBJECT (self, "Buffering finished - seek pending");
1131 } else if (percent == 100 && self->target_state >= GST_STATE_PLAYING
1132 && self->current_state >= GST_STATE_PAUSED) {
1133 GstStateChangeReturn state_ret;
1135 g_mutex_unlock (&self->lock);
1137 GST_DEBUG_OBJECT (self, "Buffering finished - going to PLAYING");
1138 state_ret = gst_element_set_state (self->playbin, GST_STATE_PLAYING);
1139 /* Application state change is happening when the state change happened */
1140 if (state_ret == GST_STATE_CHANGE_FAILURE)
1141 on_error (self, g_error_new (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED,
1142 "Failed to handle buffering"), NULL);
1143 } else if (percent == 100 && self->target_state >= GST_STATE_PAUSED) {
1144 g_mutex_unlock (&self->lock);
1146 GST_DEBUG_OBJECT (self, "Buffering finished - staying PAUSED");
1147 change_state (self, GST_PLAY_STATE_PAUSED);
1149 g_mutex_unlock (&self->lock);
1154 clock_lost_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg,
1157 GstPlay *self = GST_PLAY (user_data);
1158 GstStateChangeReturn state_ret;
1160 GST_DEBUG_OBJECT (self, "Clock lost");
1161 if (self->target_state >= GST_STATE_PLAYING) {
1162 state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED);
1163 if (state_ret != GST_STATE_CHANGE_FAILURE)
1164 state_ret = gst_element_set_state (self->playbin, GST_STATE_PLAYING);
1166 if (state_ret == GST_STATE_CHANGE_FAILURE)
1167 on_error (self, g_error_new (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED,
1168 "Failed to handle clock loss"), NULL);
1174 check_video_dimensions_changed (GstPlay * self)
1176 GstElement *video_sink;
1177 GstPad *video_sink_pad;
1180 guint width = 0, height = 0;
1182 g_object_get (self->playbin, "video-sink", &video_sink, NULL);
1186 video_sink_pad = gst_element_get_static_pad (video_sink, "sink");
1187 if (!video_sink_pad) {
1188 gst_object_unref (video_sink);
1192 caps = gst_pad_get_current_caps (video_sink_pad);
1195 if (gst_video_info_from_caps (&info, caps)) {
1196 info.width = info.width * info.par_n / info.par_d;
1198 GST_DEBUG_OBJECT (self, "Video dimensions changed: %dx%d", info.width,
1201 height = info.height;
1204 gst_caps_unref (caps);
1206 gst_object_unref (video_sink_pad);
1207 gst_object_unref (video_sink);
1210 api_bus_post_message (self, GST_PLAY_MESSAGE_VIDEO_DIMENSIONS_CHANGED,
1211 GST_PLAY_MESSAGE_DATA_VIDEO_WIDTH, G_TYPE_UINT, width,
1212 GST_PLAY_MESSAGE_DATA_VIDEO_HEIGHT, G_TYPE_UINT, height, NULL);
1216 notify_caps_cb (G_GNUC_UNUSED GObject * object,
1217 G_GNUC_UNUSED GParamSpec * pspec, gpointer user_data)
1219 GstPlay *self = GST_PLAY (user_data);
1221 check_video_dimensions_changed (self);
1225 on_duration_changed (GstPlay * self, GstClockTime duration)
1227 gboolean updated = FALSE;
1229 if (self->cached_duration == duration)
1232 GST_DEBUG_OBJECT (self, "Duration changed %" GST_TIME_FORMAT,
1233 GST_TIME_ARGS (duration));
1235 g_mutex_lock (&self->lock);
1236 self->cached_duration = duration;
1237 if (self->media_info) {
1238 self->media_info->duration = duration;
1241 g_mutex_unlock (&self->lock);
1243 api_bus_post_message (self, GST_PLAY_MESSAGE_DURATION_CHANGED,
1244 GST_PLAY_MESSAGE_DATA_DURATION, GST_TYPE_CLOCK_TIME,
1245 gst_play_get_duration (self), NULL);
1248 on_media_info_updated (self);
1253 on_seek_done (GstPlay * self)
1255 api_bus_post_message (self, GST_PLAY_MESSAGE_SEEK_DONE,
1256 GST_PLAY_MESSAGE_DATA_POSITION, GST_TYPE_CLOCK_TIME,
1257 gst_play_get_position (self), NULL);
1261 state_changed_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
1264 GstPlay *self = GST_PLAY (user_data);
1265 GstState old_state, new_state, pending_state;
1267 gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
1269 if (GST_MESSAGE_SRC (msg) == GST_OBJECT (self->playbin)) {
1270 gchar *transition_name;
1272 GST_DEBUG_OBJECT (self, "Changed state old: %s new: %s pending: %s",
1273 gst_element_state_get_name (old_state),
1274 gst_element_state_get_name (new_state),
1275 gst_element_state_get_name (pending_state));
1277 transition_name = g_strdup_printf ("%s_%s",
1278 gst_element_state_get_name (old_state),
1279 gst_element_state_get_name (new_state));
1280 dump_dot_file (self, transition_name);
1281 g_free (transition_name);
1283 self->current_state = new_state;
1285 if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED
1286 && pending_state == GST_STATE_VOID_PENDING) {
1287 GstElement *video_sink;
1288 GstPad *video_sink_pad;
1289 gint64 duration = -1;
1291 GST_DEBUG_OBJECT (self, "Initial PAUSED - pre-rolled");
1293 g_mutex_lock (&self->lock);
1294 if (self->media_info)
1295 g_object_unref (self->media_info);
1296 self->media_info = gst_play_media_info_create (self);
1297 g_mutex_unlock (&self->lock);
1298 on_media_info_updated (self);
1300 g_object_get (self->playbin, "video-sink", &video_sink, NULL);
1303 video_sink_pad = gst_element_get_static_pad (video_sink, "sink");
1305 if (video_sink_pad) {
1306 g_signal_connect (video_sink_pad, "notify::caps",
1307 (GCallback) notify_caps_cb, self);
1308 gst_object_unref (video_sink_pad);
1310 gst_object_unref (video_sink);
1313 check_video_dimensions_changed (self);
1314 if (gst_element_query_duration (self->playbin, GST_FORMAT_TIME,
1316 on_duration_changed (self, duration);
1318 self->cached_duration = GST_CLOCK_TIME_NONE;
1322 if (new_state == GST_STATE_PAUSED
1323 && pending_state == GST_STATE_VOID_PENDING) {
1324 remove_tick_source (self);
1326 g_mutex_lock (&self->lock);
1327 if (self->seek_pending) {
1328 self->seek_pending = FALSE;
1330 if (!self->media_info->seekable) {
1331 GST_DEBUG_OBJECT (self, "Media is not seekable");
1332 remove_seek_source (self);
1333 self->seek_position = GST_CLOCK_TIME_NONE;
1334 self->last_seek_time = GST_CLOCK_TIME_NONE;
1335 } else if (self->seek_source) {
1336 GST_DEBUG_OBJECT (self, "Seek finished but new seek is pending");
1337 gst_play_seek_internal_locked (self);
1339 GST_DEBUG_OBJECT (self, "Seek finished");
1340 on_seek_done (self);
1344 if (self->seek_position != GST_CLOCK_TIME_NONE) {
1345 GST_DEBUG_OBJECT (self, "Seeking now that we reached PAUSED state");
1346 gst_play_seek_internal_locked (self);
1347 g_mutex_unlock (&self->lock);
1348 } else if (!self->seek_pending) {
1349 g_mutex_unlock (&self->lock);
1353 if (self->target_state >= GST_STATE_PLAYING
1354 && self->buffering_percent == 100) {
1355 GstStateChangeReturn state_ret;
1357 state_ret = gst_element_set_state (self->playbin, GST_STATE_PLAYING);
1358 if (state_ret == GST_STATE_CHANGE_FAILURE)
1359 on_error (self, g_error_new (GST_PLAY_ERROR,
1360 GST_PLAY_ERROR_FAILED, "Failed to play"), NULL);
1361 } else if (self->buffering_percent == 100) {
1362 change_state (self, GST_PLAY_STATE_PAUSED);
1365 g_mutex_unlock (&self->lock);
1367 } else if (new_state == GST_STATE_PLAYING
1368 && pending_state == GST_STATE_VOID_PENDING) {
1369 /* api_bus_post_message (self, GST_PLAY_MESSAGE_POSITION_UPDATED, */
1370 /* GST_PLAY_MESSAGE_DATA_POSITION, GST_TYPE_CLOCK_TIME, 0, NULL); */
1372 /* If no seek is currently pending, add the tick source. This can happen
1373 * if we seeked already but the state-change message was still queued up */
1374 if (!self->seek_pending) {
1375 add_tick_source (self);
1376 change_state (self, GST_PLAY_STATE_PLAYING);
1378 } else if (new_state == GST_STATE_READY && old_state > GST_STATE_READY) {
1379 change_state (self, GST_PLAY_STATE_STOPPED);
1381 /* Otherwise we neither reached PLAYING nor PAUSED, so must
1382 * wait for something to happen... i.e. are BUFFERING now */
1383 change_state (self, GST_PLAY_STATE_BUFFERING);
1389 duration_changed_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg,
1392 GstPlay *self = GST_PLAY (user_data);
1393 gint64 duration = GST_CLOCK_TIME_NONE;
1395 if (gst_element_query_duration (self->playbin, GST_FORMAT_TIME, &duration)) {
1396 on_duration_changed (self, duration);
1401 latency_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg,
1404 GstPlay *self = GST_PLAY (user_data);
1406 GST_DEBUG_OBJECT (self, "Latency changed");
1408 gst_bin_recalculate_latency (GST_BIN (self->playbin));
1412 request_state_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
1415 GstPlay *self = GST_PLAY (user_data);
1417 GstStateChangeReturn state_ret;
1419 gst_message_parse_request_state (msg, &state);
1421 GST_DEBUG_OBJECT (self, "State %s requested",
1422 gst_element_state_get_name (state));
1424 self->target_state = state;
1425 state_ret = gst_element_set_state (self->playbin, state);
1426 if (state_ret == GST_STATE_CHANGE_FAILURE)
1427 on_error (self, g_error_new (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED,
1428 "Failed to change to requested state %s",
1429 gst_element_state_get_name (state)), NULL);
1433 media_info_update (GstPlay * self, GstPlayMediaInfo * info)
1435 g_free (info->title);
1436 info->title = get_from_tags (self, info, get_title);
1438 g_free (info->container);
1439 info->container = get_from_tags (self, info, get_container_format);
1441 if (info->image_sample)
1442 gst_sample_unref (info->image_sample);
1443 info->image_sample = get_from_tags (self, info, get_cover_sample);
1445 GST_DEBUG_OBJECT (self, "title: %s, container: %s "
1446 "image_sample: %p", info->title, info->container, info->image_sample);
1450 tags_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
1452 GstPlay *self = GST_PLAY (user_data);
1453 GstTagList *tags = NULL;
1455 gst_message_parse_tag (msg, &tags);
1457 GST_DEBUG_OBJECT (self, "received %s tags",
1458 gst_tag_list_get_scope (tags) ==
1459 GST_TAG_SCOPE_GLOBAL ? "global" : "stream");
1461 if (gst_tag_list_get_scope (tags) == GST_TAG_SCOPE_GLOBAL) {
1462 g_mutex_lock (&self->lock);
1463 if (self->media_info) {
1464 if (self->media_info->tags)
1465 gst_tag_list_unref (self->media_info->tags);
1466 self->media_info->tags = gst_tag_list_ref (tags);
1467 media_info_update (self, self->media_info);
1468 g_mutex_unlock (&self->lock);
1469 on_media_info_updated (self);
1471 if (self->global_tags)
1472 gst_tag_list_unref (self->global_tags);
1473 self->global_tags = gst_tag_list_ref (tags);
1474 g_mutex_unlock (&self->lock);
1478 gst_tag_list_unref (tags);
1482 element_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
1484 GstPlay *self = GST_PLAY (user_data);
1485 const GstStructure *s;
1487 s = gst_message_get_structure (msg);
1488 if (gst_structure_has_name (s, "redirect")) {
1489 const gchar *new_location;
1491 new_location = gst_structure_get_string (s, "new-location");
1492 if (!new_location) {
1493 const GValue *locations_list, *location_val;
1496 locations_list = gst_structure_get_value (s, "locations");
1497 size = gst_value_list_get_size (locations_list);
1498 for (i = 0; i < size; ++i) {
1499 const GstStructure *location_s;
1501 location_val = gst_value_list_get_value (locations_list, i);
1502 if (!GST_VALUE_HOLDS_STRUCTURE (location_val))
1505 location_s = (const GstStructure *) g_value_get_boxed (location_val);
1506 if (!gst_structure_has_name (location_s, "redirect"))
1509 new_location = gst_structure_get_string (location_s, "new-location");
1516 GstState target_state;
1518 GST_DEBUG_OBJECT (self, "Redirect to '%s'", new_location);
1520 /* Remember target state and restore after setting the URI */
1521 target_state = self->target_state;
1523 gst_play_stop_internal (self, TRUE);
1525 g_mutex_lock (&self->lock);
1526 g_free (self->redirect_uri);
1527 self->redirect_uri = g_strdup (new_location);
1528 g_object_set (self->playbin, "uri", self->redirect_uri, NULL);
1529 g_mutex_unlock (&self->lock);
1531 if (target_state == GST_STATE_PAUSED)
1532 gst_play_pause_internal (self);
1533 else if (target_state == GST_STATE_PLAYING)
1534 gst_play_play_internal (self);
1539 /* Must be called with lock */
1541 update_stream_collection (GstPlay * self, GstStreamCollection * collection)
1543 if (self->collection && self->collection == collection)
1546 if (self->collection && self->stream_notify_id)
1547 g_signal_handler_disconnect (self->collection, self->stream_notify_id);
1549 gst_object_replace ((GstObject **) & self->collection,
1550 (GstObject *) collection);
1551 if (self->media_info) {
1552 gst_object_unref (self->media_info);
1553 self->media_info = gst_play_media_info_create (self);
1556 self->stream_notify_id =
1557 g_signal_connect (self->collection, "stream-notify",
1558 G_CALLBACK (stream_notify_cb), self);
1564 stream_collection_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
1567 GstPlay *self = GST_PLAY (user_data);
1568 GstStreamCollection *collection = NULL;
1569 gboolean updated = FALSE;
1571 gst_message_parse_stream_collection (msg, &collection);
1576 g_mutex_lock (&self->lock);
1577 updated = update_stream_collection (self, collection);
1578 gst_object_unref (collection);
1579 g_mutex_unlock (&self->lock);
1581 if (self->media_info && updated)
1582 on_media_info_updated (self);
1586 streams_selected_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
1589 GstPlay *self = GST_PLAY (user_data);
1590 GstStreamCollection *collection = NULL;
1591 gboolean updated = FALSE;
1594 gst_message_parse_streams_selected (msg, &collection);
1599 g_mutex_lock (&self->lock);
1600 updated = update_stream_collection (self, collection);
1601 gst_object_unref (collection);
1603 g_free (self->video_sid);
1604 g_free (self->audio_sid);
1605 g_free (self->subtitle_sid);
1606 self->video_sid = NULL;
1607 self->audio_sid = NULL;
1608 self->subtitle_sid = NULL;
1610 len = gst_message_streams_selected_get_size (msg);
1611 for (i = 0; i < len; i++) {
1613 GstStreamType stream_type;
1614 const gchar *stream_id;
1615 gchar **current_sid;
1616 stream = gst_message_streams_selected_get_stream (msg, i);
1617 stream_type = gst_stream_get_stream_type (stream);
1618 stream_id = gst_stream_get_stream_id (stream);
1619 if (stream_type & GST_STREAM_TYPE_AUDIO)
1620 current_sid = &self->audio_sid;
1621 else if (stream_type & GST_STREAM_TYPE_VIDEO)
1622 current_sid = &self->video_sid;
1623 else if (stream_type & GST_STREAM_TYPE_TEXT)
1624 current_sid = &self->subtitle_sid;
1626 GST_WARNING_OBJECT (self,
1627 "Unknown stream-id %s with type 0x%x", stream_id, stream_type);
1631 if (G_UNLIKELY (*current_sid)) {
1632 GST_FIXME_OBJECT (self,
1633 "Multiple streams are selected for type %s, choose the first one",
1634 gst_stream_type_get_name (stream_type));
1638 *current_sid = g_strdup (stream_id);
1640 g_mutex_unlock (&self->lock);
1642 if (self->media_info && updated)
1643 on_media_info_updated (self);
1647 play_set_flag (GstPlay * self, gint pos)
1651 g_object_get (self->playbin, "flags", &flags, NULL);
1653 g_object_set (self->playbin, "flags", flags, NULL);
1655 GST_DEBUG_OBJECT (self, "setting flags=%#x", flags);
1659 play_clear_flag (GstPlay * self, gint pos)
1663 g_object_get (self->playbin, "flags", &flags, NULL);
1665 g_object_set (self->playbin, "flags", flags, NULL);
1667 GST_DEBUG_OBJECT (self, "setting flags=%#x", flags);
1671 * on_media_info_updated:
1673 * create a new copy of self->media_info object and post it to the user
1677 on_media_info_updated (GstPlay * self)
1679 GstPlayMediaInfo *media_info_copy;
1681 g_mutex_lock (&self->lock);
1682 media_info_copy = gst_play_media_info_copy (self->media_info);
1683 g_mutex_unlock (&self->lock);
1685 api_bus_post_message (self, GST_PLAY_MESSAGE_MEDIA_INFO_UPDATED,
1686 GST_PLAY_MESSAGE_DATA_MEDIA_INFO, GST_TYPE_PLAY_MEDIA_INFO,
1687 media_info_copy, NULL);
1688 g_object_unref (media_info_copy);
1692 get_caps (GstPlay * self, gint stream_index, GType type)
1695 GstCaps *caps = NULL;
1697 if (type == GST_TYPE_PLAY_VIDEO_INFO)
1698 g_signal_emit_by_name (G_OBJECT (self->playbin),
1699 "get-video-pad", stream_index, &pad);
1700 else if (type == GST_TYPE_PLAY_AUDIO_INFO)
1701 g_signal_emit_by_name (G_OBJECT (self->playbin),
1702 "get-audio-pad", stream_index, &pad);
1704 g_signal_emit_by_name (G_OBJECT (self->playbin),
1705 "get-text-pad", stream_index, &pad);
1708 caps = gst_pad_get_current_caps (pad);
1709 gst_object_unref (pad);
1716 gst_play_subtitle_info_update (GstPlay * self, GstPlayStreamInfo * stream_info)
1718 GstPlaySubtitleInfo *info = (GstPlaySubtitleInfo *) stream_info;
1720 if (stream_info->tags) {
1722 /* free the old language info */
1723 g_free (info->language);
1724 info->language = NULL;
1726 /* First try to get the language full name from tag, if name is not
1727 * available then try language code. If we find the language code
1728 * then use gstreamer api to translate code to full name.
1730 gst_tag_list_get_string (stream_info->tags, GST_TAG_LANGUAGE_NAME,
1732 if (!info->language) {
1733 gchar *lang_code = NULL;
1735 gst_tag_list_get_string (stream_info->tags, GST_TAG_LANGUAGE_CODE,
1738 info->language = g_strdup (gst_tag_get_language_name (lang_code));
1743 /* If we are still failed to find language name then check if external
1744 * subtitle is loaded and compare the stream index between current sub
1745 * stream index with our stream index and if matches then declare it as
1746 * external subtitle and use the filename.
1748 if (!info->language) {
1749 gint text_index = -1;
1750 gchar *suburi = NULL;
1752 g_object_get (G_OBJECT (self->playbin), "current-suburi", &suburi, NULL);
1754 if (self->use_playbin3) {
1755 if (g_str_equal (self->subtitle_sid, stream_info->stream_id))
1756 info->language = g_path_get_basename (suburi);
1758 g_object_get (G_OBJECT (self->playbin), "current-text", &text_index,
1760 if (text_index == gst_play_stream_info_get_index (stream_info))
1761 info->language = g_path_get_basename (suburi);
1768 g_free (info->language);
1769 info->language = NULL;
1772 GST_DEBUG_OBJECT (self, "language=%s", info->language);
1776 gst_play_video_info_update (GstPlay * self, GstPlayStreamInfo * stream_info)
1778 GstPlayVideoInfo *info = (GstPlayVideoInfo *) stream_info;
1780 if (stream_info->caps) {
1783 s = gst_caps_get_structure (stream_info->caps, 0);
1789 if (gst_structure_get_int (s, "width", &width))
1790 info->width = width;
1794 if (gst_structure_get_int (s, "height", &height))
1795 info->height = height;
1799 if (gst_structure_get_fraction (s, "framerate", &fps_n, &fps_d)) {
1800 info->framerate_num = fps_n;
1801 info->framerate_denom = fps_d;
1803 info->framerate_num = 0;
1804 info->framerate_denom = 1;
1808 if (gst_structure_get_fraction (s, "pixel-aspect-ratio", &par_n, &par_d)) {
1809 info->par_num = par_n;
1810 info->par_denom = par_d;
1813 info->par_denom = 1;
1817 info->width = info->height = -1;
1818 info->par_num = info->par_denom = 1;
1819 info->framerate_num = 0;
1820 info->framerate_denom = 1;
1823 if (stream_info->tags) {
1824 guint bitrate, max_bitrate;
1826 if (gst_tag_list_get_uint (stream_info->tags, GST_TAG_BITRATE, &bitrate))
1827 info->bitrate = bitrate;
1831 if (gst_tag_list_get_uint (stream_info->tags, GST_TAG_MAXIMUM_BITRATE,
1832 &max_bitrate) || gst_tag_list_get_uint (stream_info->tags,
1833 GST_TAG_NOMINAL_BITRATE, &max_bitrate))
1834 info->max_bitrate = max_bitrate;
1836 info->max_bitrate = -1;
1838 info->bitrate = info->max_bitrate = -1;
1841 GST_DEBUG_OBJECT (self, "width=%d height=%d fps=%.2f par=%d:%d "
1842 "bitrate=%d max_bitrate=%d", info->width, info->height,
1843 (gdouble) info->framerate_num / info->framerate_denom,
1844 info->par_num, info->par_denom, info->bitrate, info->max_bitrate);
1848 gst_play_audio_info_update (GstPlay * self, GstPlayStreamInfo * stream_info)
1850 GstPlayAudioInfo *info = (GstPlayAudioInfo *) stream_info;
1852 if (stream_info->caps) {
1855 s = gst_caps_get_structure (stream_info->caps, 0);
1857 gint rate, channels;
1859 if (gst_structure_get_int (s, "rate", &rate))
1860 info->sample_rate = rate;
1862 info->sample_rate = -1;
1864 if (gst_structure_get_int (s, "channels", &channels))
1865 info->channels = channels;
1870 info->sample_rate = -1;
1874 if (stream_info->tags) {
1875 guint bitrate, max_bitrate;
1877 if (gst_tag_list_get_uint (stream_info->tags, GST_TAG_BITRATE, &bitrate))
1878 info->bitrate = bitrate;
1882 if (gst_tag_list_get_uint (stream_info->tags, GST_TAG_MAXIMUM_BITRATE,
1883 &max_bitrate) || gst_tag_list_get_uint (stream_info->tags,
1884 GST_TAG_NOMINAL_BITRATE, &max_bitrate))
1885 info->max_bitrate = max_bitrate;
1887 info->max_bitrate = -1;
1889 /* if we have old language the free it */
1890 g_free (info->language);
1891 info->language = NULL;
1893 /* First try to get the language full name from tag, if name is not
1894 * available then try language code. If we find the language code
1895 * then use gstreamer api to translate code to full name.
1897 gst_tag_list_get_string (stream_info->tags, GST_TAG_LANGUAGE_NAME,
1899 if (!info->language) {
1900 gchar *lang_code = NULL;
1902 gst_tag_list_get_string (stream_info->tags, GST_TAG_LANGUAGE_CODE,
1905 info->language = g_strdup (gst_tag_get_language_name (lang_code));
1910 g_free (info->language);
1911 info->language = NULL;
1912 info->max_bitrate = info->bitrate = -1;
1915 GST_DEBUG_OBJECT (self, "language=%s rate=%d channels=%d bitrate=%d "
1916 "max_bitrate=%d", info->language, info->sample_rate, info->channels,
1917 info->bitrate, info->max_bitrate);
1920 static GstPlayStreamInfo *
1921 gst_play_stream_info_find (GstPlayMediaInfo * media_info,
1922 GType type, gint stream_index)
1925 GstPlayStreamInfo *info = NULL;
1930 list = gst_play_media_info_get_stream_list (media_info);
1931 for (l = list; l != NULL; l = l->next) {
1932 info = (GstPlayStreamInfo *) l->data;
1933 if ((G_OBJECT_TYPE (info) == type) && (info->stream_index == stream_index)) {
1941 static GstPlayStreamInfo *
1942 gst_play_stream_info_find_from_stream_id (GstPlayMediaInfo * media_info,
1943 const gchar * stream_id)
1946 GstPlayStreamInfo *info = NULL;
1951 list = gst_play_media_info_get_stream_list (media_info);
1952 for (l = list; l != NULL; l = l->next) {
1953 info = (GstPlayStreamInfo *) l->data;
1954 if (g_str_equal (info->stream_id, stream_id)) {
1963 is_track_enabled (GstPlay * self, gint pos)
1967 g_object_get (G_OBJECT (self->playbin), "flags", &flags, NULL);
1975 static GstPlayStreamInfo *
1976 gst_play_stream_info_get_current (GstPlay * self, const gchar * prop,
1980 GstPlayStreamInfo *info;
1982 if (!self->media_info)
1985 g_object_get (G_OBJECT (self->playbin), prop, ¤t, NULL);
1986 g_mutex_lock (&self->lock);
1987 info = gst_play_stream_info_find (self->media_info, type, current);
1989 info = gst_play_stream_info_copy (info);
1990 g_mutex_unlock (&self->lock);
1995 static GstPlayStreamInfo *
1996 gst_play_stream_info_get_current_from_stream_id (GstPlay * self,
1997 const gchar * stream_id, GType type)
1999 GstPlayStreamInfo *info;
2001 if (!self->media_info || !stream_id)
2004 g_mutex_lock (&self->lock);
2005 info = gst_play_stream_info_find_from_stream_id (self->media_info, stream_id);
2006 if (info && G_OBJECT_TYPE (info) == type)
2007 info = gst_play_stream_info_copy (info);
2010 g_mutex_unlock (&self->lock);
2016 stream_notify_cb (GstStreamCollection * collection, GstStream * stream,
2017 GParamSpec * pspec, GstPlay * self)
2019 GstPlayStreamInfo *info;
2020 const gchar *stream_id;
2021 gboolean emit_signal = FALSE;
2023 if (!self->media_info)
2026 if (G_PARAM_SPEC_VALUE_TYPE (pspec) != GST_TYPE_CAPS &&
2027 G_PARAM_SPEC_VALUE_TYPE (pspec) != GST_TYPE_TAG_LIST)
2030 stream_id = gst_stream_get_stream_id (stream);
2031 g_mutex_lock (&self->lock);
2032 info = gst_play_stream_info_find_from_stream_id (self->media_info, stream_id);
2034 gst_play_stream_info_update_from_stream (self, info, stream);
2037 g_mutex_unlock (&self->lock);
2040 on_media_info_updated (self);
2044 gst_play_stream_info_update (GstPlay * self, GstPlayStreamInfo * s)
2046 if (GST_IS_PLAY_VIDEO_INFO (s))
2047 gst_play_video_info_update (self, s);
2048 else if (GST_IS_PLAY_AUDIO_INFO (s))
2049 gst_play_audio_info_update (self, s);
2051 gst_play_subtitle_info_update (self, s);
2055 stream_info_get_codec (GstPlayStreamInfo * s)
2059 gchar *codec = NULL;
2061 if (GST_IS_PLAY_VIDEO_INFO (s))
2062 type = GST_TAG_VIDEO_CODEC;
2063 else if (GST_IS_PLAY_AUDIO_INFO (s))
2064 type = GST_TAG_AUDIO_CODEC;
2066 type = GST_TAG_SUBTITLE_CODEC;
2068 tags = gst_play_stream_info_get_tags (s);
2070 gst_tag_list_get_string (tags, type, &codec);
2072 gst_tag_list_get_string (tags, GST_TAG_CODEC, &codec);
2077 caps = gst_play_stream_info_get_caps (s);
2079 codec = gst_pb_utils_get_codec_description (caps);
2087 gst_play_stream_info_update_tags_and_caps (GstPlay * self,
2088 GstPlayStreamInfo * s)
2093 stream_index = gst_play_stream_info_get_index (s);
2095 if (GST_IS_PLAY_VIDEO_INFO (s))
2096 g_signal_emit_by_name (self->playbin, "get-video-tags",
2097 stream_index, &tags);
2098 else if (GST_IS_PLAY_AUDIO_INFO (s))
2099 g_signal_emit_by_name (self->playbin, "get-audio-tags",
2100 stream_index, &tags);
2102 g_signal_emit_by_name (self->playbin, "get-text-tags", stream_index, &tags);
2105 gst_tag_list_unref (s->tags);
2109 gst_caps_unref (s->caps);
2110 s->caps = get_caps (self, stream_index, G_OBJECT_TYPE (s));
2113 s->codec = stream_info_get_codec (s);
2115 GST_DEBUG_OBJECT (self, "%s index: %d tags: %p caps: %p",
2116 gst_play_stream_info_get_stream_type (s), stream_index, s->tags, s->caps);
2118 gst_play_stream_info_update (self, s);
2122 gst_play_streams_info_create (GstPlay * self,
2123 GstPlayMediaInfo * media_info, const gchar * prop, GType type)
2127 GstPlayStreamInfo *s;
2132 g_object_get (G_OBJECT (self->playbin), prop, &total, NULL);
2134 GST_DEBUG_OBJECT (self, "%s: %d", prop, total);
2136 for (i = 0; i < total; i++) {
2137 /* check if stream already exist in the list */
2138 s = gst_play_stream_info_find (media_info, type, i);
2141 /* create a new stream info instance */
2142 s = gst_play_stream_info_new (i, type);
2144 /* add the object in stream list */
2145 media_info->stream_list = g_list_append (media_info->stream_list, s);
2147 /* based on type, add the object in its corresponding stream_ list */
2148 if (GST_IS_PLAY_AUDIO_INFO (s))
2149 media_info->audio_stream_list = g_list_append
2150 (media_info->audio_stream_list, s);
2151 else if (GST_IS_PLAY_VIDEO_INFO (s))
2152 media_info->video_stream_list = g_list_append
2153 (media_info->video_stream_list, s);
2155 media_info->subtitle_stream_list = g_list_append
2156 (media_info->subtitle_stream_list, s);
2158 GST_DEBUG_OBJECT (self, "create %s stream stream_index: %d",
2159 gst_play_stream_info_get_stream_type (s), i);
2162 gst_play_stream_info_update_tags_and_caps (self, s);
2167 gst_play_stream_info_update_from_stream (GstPlay * self,
2168 GstPlayStreamInfo * s, GstStream * stream)
2171 gst_tag_list_unref (s->tags);
2172 s->tags = gst_stream_get_tags (stream);
2175 gst_caps_unref (s->caps);
2176 s->caps = gst_stream_get_caps (stream);
2179 s->codec = stream_info_get_codec (s);
2181 GST_DEBUG_OBJECT (self, "%s index: %d tags: %p caps: %p",
2182 gst_play_stream_info_get_stream_type (s), s->stream_index,
2185 gst_play_stream_info_update (self, s);
2189 gst_play_streams_info_create_from_collection (GstPlay * self,
2190 GstPlayMediaInfo * media_info, GstStreamCollection * collection)
2194 GstPlayStreamInfo *s;
2199 if (!media_info || !collection)
2202 total = gst_stream_collection_get_size (collection);
2204 for (i = 0; i < total; i++) {
2205 GstStream *stream = gst_stream_collection_get_stream (collection, i);
2206 GstStreamType stream_type = gst_stream_get_stream_type (stream);
2207 const gchar *stream_id = gst_stream_get_stream_id (stream);
2209 if (stream_type & GST_STREAM_TYPE_AUDIO) {
2210 s = gst_play_stream_info_new (n_audio, GST_TYPE_PLAY_AUDIO_INFO);
2212 } else if (stream_type & GST_STREAM_TYPE_VIDEO) {
2213 s = gst_play_stream_info_new (n_video, GST_TYPE_PLAY_VIDEO_INFO);
2215 } else if (stream_type & GST_STREAM_TYPE_TEXT) {
2216 s = gst_play_stream_info_new (n_text, GST_TYPE_PLAY_SUBTITLE_INFO);
2219 GST_DEBUG_OBJECT (self, "Unknown type stream %d", i);
2223 s->stream_id = g_strdup (stream_id);
2225 /* add the object in stream list */
2226 media_info->stream_list = g_list_append (media_info->stream_list, s);
2228 /* based on type, add the object in its corresponding stream_ list */
2229 if (GST_IS_PLAY_AUDIO_INFO (s))
2230 media_info->audio_stream_list = g_list_append
2231 (media_info->audio_stream_list, s);
2232 else if (GST_IS_PLAY_VIDEO_INFO (s))
2233 media_info->video_stream_list = g_list_append
2234 (media_info->video_stream_list, s);
2236 media_info->subtitle_stream_list = g_list_append
2237 (media_info->subtitle_stream_list, s);
2239 GST_DEBUG_OBJECT (self, "create %s stream stream_index: %d",
2240 gst_play_stream_info_get_stream_type (s), s->stream_index);
2242 gst_play_stream_info_update_from_stream (self, s, stream);
2247 video_changed_cb (G_GNUC_UNUSED GObject * object, gpointer user_data)
2249 GstPlay *self = GST_PLAY (user_data);
2251 g_mutex_lock (&self->lock);
2252 gst_play_streams_info_create (self, self->media_info,
2253 "n-video", GST_TYPE_PLAY_VIDEO_INFO);
2254 g_mutex_unlock (&self->lock);
2258 audio_changed_cb (G_GNUC_UNUSED GObject * object, gpointer user_data)
2260 GstPlay *self = GST_PLAY (user_data);
2262 g_mutex_lock (&self->lock);
2263 gst_play_streams_info_create (self, self->media_info,
2264 "n-audio", GST_TYPE_PLAY_AUDIO_INFO);
2265 g_mutex_unlock (&self->lock);
2269 subtitle_changed_cb (G_GNUC_UNUSED GObject * object, gpointer user_data)
2271 GstPlay *self = GST_PLAY (user_data);
2273 g_mutex_lock (&self->lock);
2274 gst_play_streams_info_create (self, self->media_info,
2275 "n-text", GST_TYPE_PLAY_SUBTITLE_INFO);
2276 g_mutex_unlock (&self->lock);
2280 get_title (GstTagList * tags)
2282 gchar *title = NULL;
2284 gst_tag_list_get_string (tags, GST_TAG_TITLE, &title);
2286 gst_tag_list_get_string (tags, GST_TAG_TITLE_SORTNAME, &title);
2292 get_container_format (GstTagList * tags)
2294 gchar *container = NULL;
2296 gst_tag_list_get_string (tags, GST_TAG_CONTAINER_FORMAT, &container);
2298 /* TODO: If container is not available then maybe consider
2299 * parsing caps or file extension to guess the container format.
2306 get_from_tags (GstPlay * self, GstPlayMediaInfo * media_info,
2307 void *(*func) (GstTagList *))
2312 if (media_info->tags) {
2313 ret = func (media_info->tags);
2318 /* if global tag does not exit then try video and audio streams */
2319 GST_DEBUG_OBJECT (self, "trying video tags");
2320 for (l = gst_play_media_info_get_video_streams (media_info); l != NULL;
2324 tags = gst_play_stream_info_get_tags ((GstPlayStreamInfo *) l->data);
2332 GST_DEBUG_OBJECT (self, "trying audio tags");
2333 for (l = gst_play_media_info_get_audio_streams (media_info); l != NULL;
2337 tags = gst_play_stream_info_get_tags ((GstPlayStreamInfo *) l->data);
2345 GST_DEBUG_OBJECT (self, "failed to get the information from tags");
2350 get_cover_sample (GstTagList * tags)
2352 GstSample *cover_sample = NULL;
2354 gst_tag_list_get_sample (tags, GST_TAG_IMAGE, &cover_sample);
2356 gst_tag_list_get_sample (tags, GST_TAG_PREVIEW_IMAGE, &cover_sample);
2358 return cover_sample;
2361 static GstPlayMediaInfo *
2362 gst_play_media_info_create (GstPlay * self)
2364 GstPlayMediaInfo *media_info;
2367 GST_DEBUG_OBJECT (self, "begin");
2368 media_info = gst_play_media_info_new (self->uri);
2369 media_info->duration = gst_play_get_duration (self);
2370 media_info->tags = self->global_tags;
2371 media_info->is_live = self->is_live;
2372 self->global_tags = NULL;
2374 query = gst_query_new_seeking (GST_FORMAT_TIME);
2375 if (gst_element_query (self->playbin, query))
2376 gst_query_parse_seeking (query, NULL, &media_info->seekable, NULL, NULL);
2377 gst_query_unref (query);
2379 if (self->use_playbin3 && self->collection) {
2380 gst_play_streams_info_create_from_collection (self, media_info,
2383 /* create audio/video/sub streams */
2384 gst_play_streams_info_create (self, media_info, "n-video",
2385 GST_TYPE_PLAY_VIDEO_INFO);
2386 gst_play_streams_info_create (self, media_info, "n-audio",
2387 GST_TYPE_PLAY_AUDIO_INFO);
2388 gst_play_streams_info_create (self, media_info, "n-text",
2389 GST_TYPE_PLAY_SUBTITLE_INFO);
2392 media_info->title = get_from_tags (self, media_info, get_title);
2393 media_info->container =
2394 get_from_tags (self, media_info, get_container_format);
2395 media_info->image_sample = get_from_tags (self, media_info, get_cover_sample);
2397 GST_DEBUG_OBJECT (self, "uri: %s title: %s duration: %" GST_TIME_FORMAT
2398 " seekable: %s live: %s container: %s image_sample %p",
2399 media_info->uri, media_info->title, GST_TIME_ARGS (media_info->duration),
2400 media_info->seekable ? "yes" : "no", media_info->is_live ? "yes" : "no",
2401 media_info->container, media_info->image_sample);
2403 GST_DEBUG_OBJECT (self, "end");
2408 tags_changed_cb (GstPlay * self, gint stream_index, GType type)
2410 GstPlayStreamInfo *s;
2412 if (!self->media_info)
2415 /* update the stream information */
2416 g_mutex_lock (&self->lock);
2417 s = gst_play_stream_info_find (self->media_info, type, stream_index);
2418 gst_play_stream_info_update_tags_and_caps (self, s);
2419 g_mutex_unlock (&self->lock);
2421 on_media_info_updated (self);
2425 video_tags_changed_cb (G_GNUC_UNUSED GstElement * playbin, gint stream_index,
2428 tags_changed_cb (GST_PLAY (user_data), stream_index,
2429 GST_TYPE_PLAY_VIDEO_INFO);
2433 audio_tags_changed_cb (G_GNUC_UNUSED GstElement * playbin, gint stream_index,
2436 tags_changed_cb (GST_PLAY (user_data), stream_index,
2437 GST_TYPE_PLAY_AUDIO_INFO);
2441 subtitle_tags_changed_cb (G_GNUC_UNUSED GstElement * playbin, gint stream_index,
2444 tags_changed_cb (GST_PLAY (user_data), stream_index,
2445 GST_TYPE_PLAY_SUBTITLE_INFO);
2449 volume_notify_cb (G_GNUC_UNUSED GObject * obj, G_GNUC_UNUSED GParamSpec * pspec,
2452 api_bus_post_message (self, GST_PLAY_MESSAGE_VOLUME_CHANGED,
2453 GST_PLAY_MESSAGE_DATA_VOLUME, G_TYPE_DOUBLE,
2454 gst_play_get_volume (self), NULL);
2458 mute_notify_cb (G_GNUC_UNUSED GObject * obj, G_GNUC_UNUSED GParamSpec * pspec,
2462 api_bus_post_message (self, GST_PLAY_MESSAGE_MUTE_CHANGED,
2463 GST_PLAY_MESSAGE_DATA_IS_MUTED, G_TYPE_BOOLEAN,
2464 gst_play_get_mute (self), NULL);
2468 source_setup_cb (GstElement * playbin, GstElement * source, GstPlay * self)
2472 user_agent = gst_play_config_get_user_agent (self->config);
2476 prop = g_object_class_find_property (G_OBJECT_GET_CLASS (source),
2478 if (prop && prop->value_type == G_TYPE_STRING) {
2479 GST_INFO_OBJECT (self, "Setting source user-agent: %s", user_agent);
2480 g_object_set (source, "user-agent", user_agent, NULL);
2483 g_free (user_agent);
2488 gst_play_main (gpointer data)
2490 GstPlay *self = GST_PLAY (data);
2493 GstElement *scaletempo;
2496 GST_TRACE_OBJECT (self, "Starting main thread");
2498 g_main_context_push_thread_default (self->context);
2500 source = g_idle_source_new ();
2501 g_source_set_callback (source, (GSourceFunc) main_loop_running_cb, self,
2503 g_source_attach (source, self->context);
2504 g_source_unref (source);
2506 env = g_getenv ("GST_PLAY_USE_PLAYBIN3");
2507 if (env && g_str_has_prefix (env, "1"))
2508 self->use_playbin3 = TRUE;
2510 if (self->use_playbin3) {
2511 GST_DEBUG_OBJECT (self, "playbin3 enabled");
2512 self->playbin = gst_element_factory_make ("playbin3", "playbin3");
2514 self->playbin = gst_element_factory_make ("playbin", "playbin");
2517 if (!self->playbin) {
2518 g_error ("GstPlay: 'playbin' element not found, please check your setup");
2519 g_assert_not_reached ();
2522 gst_object_ref_sink (self->playbin);
2524 if (self->video_renderer) {
2525 gst_play_set_playbin_video_sink (self);
2528 scaletempo = gst_element_factory_make ("scaletempo", NULL);
2530 g_object_set (self->playbin, "audio-filter", scaletempo, NULL);
2532 g_warning ("GstPlay: scaletempo element not available. Audio pitch "
2533 "will not be preserved during trick modes");
2536 self->bus = bus = gst_element_get_bus (self->playbin);
2537 gst_bus_add_signal_watch (bus);
2539 g_signal_connect (G_OBJECT (bus), "message::error", G_CALLBACK (error_cb),
2541 g_signal_connect (G_OBJECT (bus), "message::warning", G_CALLBACK (warning_cb),
2543 g_signal_connect (G_OBJECT (bus), "message::eos", G_CALLBACK (eos_cb), self);
2544 g_signal_connect (G_OBJECT (bus), "message::state-changed",
2545 G_CALLBACK (state_changed_cb), self);
2546 g_signal_connect (G_OBJECT (bus), "message::buffering",
2547 G_CALLBACK (buffering_cb), self);
2548 g_signal_connect (G_OBJECT (bus), "message::clock-lost",
2549 G_CALLBACK (clock_lost_cb), self);
2550 g_signal_connect (G_OBJECT (bus), "message::duration-changed",
2551 G_CALLBACK (duration_changed_cb), self);
2552 g_signal_connect (G_OBJECT (bus), "message::latency",
2553 G_CALLBACK (latency_cb), self);
2554 g_signal_connect (G_OBJECT (bus), "message::request-state",
2555 G_CALLBACK (request_state_cb), self);
2556 g_signal_connect (G_OBJECT (bus), "message::element",
2557 G_CALLBACK (element_cb), self);
2558 g_signal_connect (G_OBJECT (bus), "message::tag", G_CALLBACK (tags_cb), self);
2560 if (self->use_playbin3) {
2561 g_signal_connect (G_OBJECT (bus), "message::stream-collection",
2562 G_CALLBACK (stream_collection_cb), self);
2563 g_signal_connect (G_OBJECT (bus), "message::streams-selected",
2564 G_CALLBACK (streams_selected_cb), self);
2566 g_signal_connect (self->playbin, "video-changed",
2567 G_CALLBACK (video_changed_cb), self);
2568 g_signal_connect (self->playbin, "audio-changed",
2569 G_CALLBACK (audio_changed_cb), self);
2570 g_signal_connect (self->playbin, "text-changed",
2571 G_CALLBACK (subtitle_changed_cb), self);
2573 g_signal_connect (self->playbin, "video-tags-changed",
2574 G_CALLBACK (video_tags_changed_cb), self);
2575 g_signal_connect (self->playbin, "audio-tags-changed",
2576 G_CALLBACK (audio_tags_changed_cb), self);
2577 g_signal_connect (self->playbin, "text-tags-changed",
2578 G_CALLBACK (subtitle_tags_changed_cb), self);
2581 g_signal_connect (self->playbin, "notify::volume",
2582 G_CALLBACK (volume_notify_cb), self);
2583 g_signal_connect (self->playbin, "notify::mute",
2584 G_CALLBACK (mute_notify_cb), self);
2585 g_signal_connect (self->playbin, "source-setup",
2586 G_CALLBACK (source_setup_cb), self);
2588 self->target_state = GST_STATE_NULL;
2589 self->current_state = GST_STATE_NULL;
2590 change_state (self, GST_PLAY_STATE_STOPPED);
2591 self->buffering_percent = 100;
2592 self->is_eos = FALSE;
2593 self->is_live = FALSE;
2596 GST_TRACE_OBJECT (self, "Starting main loop");
2597 g_main_loop_run (self->loop);
2598 GST_TRACE_OBJECT (self, "Stopped main loop");
2600 gst_bus_remove_signal_watch (bus);
2601 gst_object_unref (bus);
2603 remove_tick_source (self);
2604 remove_ready_timeout_source (self);
2606 g_mutex_lock (&self->lock);
2607 if (self->media_info) {
2608 g_object_unref (self->media_info);
2609 self->media_info = NULL;
2612 remove_seek_source (self);
2613 g_mutex_unlock (&self->lock);
2615 g_main_context_pop_thread_default (self->context);
2617 self->target_state = GST_STATE_NULL;
2618 self->current_state = GST_STATE_NULL;
2619 if (self->playbin) {
2620 gst_element_set_state (self->playbin, GST_STATE_NULL);
2621 gst_object_unref (self->playbin);
2622 self->playbin = NULL;
2625 GST_TRACE_OBJECT (self, "Stopped main thread");
2631 gst_play_init_once (G_GNUC_UNUSED gpointer user_data)
2633 gst_init (NULL, NULL);
2635 GST_DEBUG_CATEGORY_INIT (gst_play_debug, "gst-play", 0, "GstPlay");
2636 gst_play_error_quark ();
2643 * @video_renderer: (transfer full) (allow-none): GstPlayVideoRenderer to use
2645 * Creates a new #GstPlay instance.
2647 * Video is going to be rendered by @video_renderer, or if %NULL is provided
2648 * no special video set up will be done and some default handling will be
2651 * Returns: (transfer full): a new #GstPlay instance
2655 gst_play_new (GstPlayVideoRenderer * video_renderer)
2657 static GOnce once = G_ONCE_INIT;
2660 g_once (&once, gst_play_init_once, NULL);
2662 self = g_object_new (GST_TYPE_PLAY, "video-renderer", video_renderer, NULL);
2664 gst_object_ref_sink (self);
2667 g_object_unref (video_renderer);
2673 * gst_play_get_message_bus:
2674 * @play: #GstPlay instance
2676 * GstPlay API exposes a #GstBus instance which purpose is to provide data
2677 * structures representing play-internal events in form of #GstMessage<!-- -->s of
2678 * type GST_MESSAGE_APPLICATION.
2680 * Each message carries a "play-message" field of type #GstPlayMessage.
2681 * Further fields of the message data are specific to each possible value of
2684 * Applications can consume the messages asynchronously within their own
2685 * event-loop / UI-thread etc. Note that in case the application does not
2686 * consume the messages, the bus will accumulate these internally and eventually
2687 * fill memory. To avoid that, the bus has to be set "flushing".
2689 * Returns: (transfer full): The play message bus instance
2694 gst_play_get_message_bus (GstPlay * self)
2696 return g_object_ref (self->api_bus);
2700 gst_play_play_internal (gpointer user_data)
2702 GstPlay *self = GST_PLAY (user_data);
2703 GstStateChangeReturn state_ret;
2705 GST_DEBUG_OBJECT (self, "Play");
2707 g_mutex_lock (&self->lock);
2709 g_mutex_unlock (&self->lock);
2710 return G_SOURCE_REMOVE;
2712 g_mutex_unlock (&self->lock);
2714 remove_ready_timeout_source (self);
2715 self->target_state = GST_STATE_PLAYING;
2717 if (self->current_state < GST_STATE_PAUSED)
2718 change_state (self, GST_PLAY_STATE_BUFFERING);
2720 if (self->current_state >= GST_STATE_PAUSED && !self->is_eos
2721 && self->buffering_percent >= 100
2722 && !(self->seek_position != GST_CLOCK_TIME_NONE || self->seek_pending)) {
2723 state_ret = gst_element_set_state (self->playbin, GST_STATE_PLAYING);
2725 state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED);
2728 if (state_ret == GST_STATE_CHANGE_FAILURE) {
2729 on_error (self, g_error_new (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED,
2730 "Failed to play"), NULL);
2731 return G_SOURCE_REMOVE;
2732 } else if (state_ret == GST_STATE_CHANGE_NO_PREROLL) {
2733 self->is_live = TRUE;
2734 GST_DEBUG_OBJECT (self, "Pipeline is live");
2740 GST_DEBUG_OBJECT (self, "Was EOS, seeking to beginning");
2741 self->is_eos = FALSE;
2743 gst_element_seek_simple (self->playbin, GST_FORMAT_TIME,
2744 GST_SEEK_FLAG_FLUSH, 0);
2746 GST_ERROR_OBJECT (self, "Seek to beginning failed");
2747 gst_play_stop_internal (self, TRUE);
2748 gst_play_play_internal (self);
2752 return G_SOURCE_REMOVE;
2757 * @play: #GstPlay instance
2759 * Request to play the loaded stream.
2763 gst_play_play (GstPlay * self)
2765 g_return_if_fail (GST_IS_PLAY (self));
2767 g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT,
2768 gst_play_play_internal, self, NULL);
2772 gst_play_pause_internal (gpointer user_data)
2774 GstPlay *self = GST_PLAY (user_data);
2775 GstStateChangeReturn state_ret;
2777 GST_DEBUG_OBJECT (self, "Pause");
2779 g_mutex_lock (&self->lock);
2781 g_mutex_unlock (&self->lock);
2782 return G_SOURCE_REMOVE;
2784 g_mutex_unlock (&self->lock);
2787 remove_tick_source (self);
2788 remove_ready_timeout_source (self);
2790 self->target_state = GST_STATE_PAUSED;
2792 if (self->current_state < GST_STATE_PAUSED)
2793 change_state (self, GST_PLAY_STATE_BUFFERING);
2795 state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED);
2796 if (state_ret == GST_STATE_CHANGE_FAILURE) {
2797 on_error (self, g_error_new (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED,
2798 "Failed to pause"), NULL);
2799 return G_SOURCE_REMOVE;
2800 } else if (state_ret == GST_STATE_CHANGE_NO_PREROLL) {
2801 self->is_live = TRUE;
2802 GST_DEBUG_OBJECT (self, "Pipeline is live");
2808 GST_DEBUG_OBJECT (self, "Was EOS, seeking to beginning");
2809 self->is_eos = FALSE;
2811 gst_element_seek_simple (self->playbin, GST_FORMAT_TIME,
2812 GST_SEEK_FLAG_FLUSH, 0);
2814 GST_ERROR_OBJECT (self, "Seek to beginning failed");
2815 gst_play_stop_internal (self, TRUE);
2816 gst_play_pause_internal (self);
2820 return G_SOURCE_REMOVE;
2825 * @play: #GstPlay instance
2827 * Pauses the current stream.
2831 gst_play_pause (GstPlay * self)
2833 g_return_if_fail (GST_IS_PLAY (self));
2835 g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT,
2836 gst_play_pause_internal, self, NULL);
2840 gst_play_stop_internal (GstPlay * self, gboolean transient)
2842 /* directly return if we're already stopped */
2843 if (self->current_state <= GST_STATE_READY &&
2844 self->target_state <= GST_STATE_READY)
2847 GST_DEBUG_OBJECT (self, "Stop (transient %d)", transient);
2850 remove_tick_source (self);
2852 add_ready_timeout_source (self);
2854 self->target_state = GST_STATE_NULL;
2855 self->current_state = GST_STATE_READY;
2856 self->is_live = FALSE;
2857 self->is_eos = FALSE;
2858 gst_bus_set_flushing (self->bus, TRUE);
2859 gst_element_set_state (self->playbin, GST_STATE_READY);
2860 gst_bus_set_flushing (self->bus, FALSE);
2861 change_state (self, transient
2862 && self->app_state !=
2863 GST_PLAY_STATE_STOPPED ? GST_PLAY_STATE_BUFFERING :
2864 GST_PLAY_STATE_STOPPED);
2865 self->buffering_percent = 100;
2866 self->cached_duration = GST_CLOCK_TIME_NONE;
2867 g_mutex_lock (&self->lock);
2868 if (self->media_info) {
2869 g_object_unref (self->media_info);
2870 self->media_info = NULL;
2872 if (self->global_tags) {
2873 gst_tag_list_unref (self->global_tags);
2874 self->global_tags = NULL;
2876 self->seek_pending = FALSE;
2877 remove_seek_source (self);
2878 self->seek_position = GST_CLOCK_TIME_NONE;
2879 self->last_seek_time = GST_CLOCK_TIME_NONE;
2881 if (self->collection) {
2882 if (self->stream_notify_id)
2883 g_signal_handler_disconnect (self->collection, self->stream_notify_id);
2884 self->stream_notify_id = 0;
2885 gst_object_unref (self->collection);
2886 self->collection = NULL;
2888 g_free (self->video_sid);
2889 g_free (self->audio_sid);
2890 g_free (self->subtitle_sid);
2891 self->video_sid = NULL;
2892 self->audio_sid = NULL;
2893 self->subtitle_sid = NULL;
2894 g_mutex_unlock (&self->lock);
2898 gst_play_stop_internal_dispatch (gpointer user_data)
2900 GstPlay *self = GST_PLAY (user_data);
2902 gst_play_stop_internal (self, FALSE);
2904 return G_SOURCE_REMOVE;
2910 * @play: #GstPlay instance
2912 * Stops playing the current stream and resets to the first position
2917 gst_play_stop (GstPlay * self)
2919 g_return_if_fail (GST_IS_PLAY (self));
2921 g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT,
2922 gst_play_stop_internal_dispatch, self, NULL);
2925 /* Must be called with lock from main context, releases lock! */
2927 gst_play_seek_internal_locked (GstPlay * self)
2930 GstClockTime position;
2932 GstStateChangeReturn state_ret;
2934 GstSeekFlags flags = 0;
2935 gboolean accurate = FALSE;
2937 remove_seek_source (self);
2939 /* Only seek in PAUSED */
2940 if (self->current_state < GST_STATE_PAUSED) {
2942 } else if (self->current_state != GST_STATE_PAUSED) {
2943 g_mutex_unlock (&self->lock);
2944 state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED);
2945 if (state_ret == GST_STATE_CHANGE_FAILURE) {
2946 on_error (self, g_error_new (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED,
2947 "Failed to seek"), NULL);
2948 g_mutex_lock (&self->lock);
2951 g_mutex_lock (&self->lock);
2955 self->last_seek_time = gst_util_get_timestamp ();
2956 position = self->seek_position;
2957 self->seek_position = GST_CLOCK_TIME_NONE;
2958 self->seek_pending = TRUE;
2960 g_mutex_unlock (&self->lock);
2962 remove_tick_source (self);
2963 self->is_eos = FALSE;
2965 flags |= GST_SEEK_FLAG_FLUSH;
2967 accurate = gst_play_config_get_seek_accurate (self->config);
2970 flags |= GST_SEEK_FLAG_ACCURATE;
2972 flags &= ~GST_SEEK_FLAG_ACCURATE;
2976 flags |= GST_SEEK_FLAG_TRICKMODE;
2980 s_event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags,
2981 GST_SEEK_TYPE_SET, position, GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE);
2983 s_event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags,
2984 GST_SEEK_TYPE_SET, G_GINT64_CONSTANT (0), GST_SEEK_TYPE_SET, position);
2987 GST_DEBUG_OBJECT (self, "Seek with rate %.2lf to %" GST_TIME_FORMAT,
2988 rate, GST_TIME_ARGS (position));
2990 ret = gst_element_send_event (self->playbin, s_event);
2992 on_error (self, g_error_new (GST_PLAY_ERROR, GST_PLAY_ERROR_FAILED,
2993 "Failed to seek to %" GST_TIME_FORMAT, GST_TIME_ARGS (position)),
2996 g_mutex_lock (&self->lock);
3000 gst_play_seek_internal (gpointer user_data)
3002 GstPlay *self = GST_PLAY (user_data);
3004 g_mutex_lock (&self->lock);
3005 gst_play_seek_internal_locked (self);
3006 g_mutex_unlock (&self->lock);
3008 return G_SOURCE_REMOVE;
3012 * gst_play_set_rate:
3013 * @play: #GstPlay instance
3014 * @rate: playback rate
3016 * Playback at specified rate
3020 gst_play_set_rate (GstPlay * self, gdouble rate)
3022 g_return_if_fail (GST_IS_PLAY (self));
3023 g_return_if_fail (rate != 0.0);
3025 g_object_set (self, "rate", rate, NULL);
3029 * gst_play_get_rate:
3030 * @play: #GstPlay instance
3032 * Returns: current playback rate
3036 gst_play_get_rate (GstPlay * self)
3040 g_return_val_if_fail (GST_IS_PLAY (self), DEFAULT_RATE);
3042 g_object_get (self, "rate", &val, NULL);
3049 * @play: #GstPlay instance
3050 * @position: position to seek in nanoseconds
3052 * Seeks the currently-playing stream to the absolute @position time
3057 gst_play_seek (GstPlay * self, GstClockTime position)
3059 g_return_if_fail (GST_IS_PLAY (self));
3060 g_return_if_fail (GST_CLOCK_TIME_IS_VALID (position));
3062 g_mutex_lock (&self->lock);
3063 if (self->media_info && !self->media_info->seekable) {
3064 GST_DEBUG_OBJECT (self, "Media is not seekable");
3065 g_mutex_unlock (&self->lock);
3069 self->seek_position = position;
3071 /* If there is no seek being dispatch to the main context currently do that,
3072 * otherwise we just updated the seek position so that it will be taken by
3073 * the seek handler from the main context instead of the old one.
3075 if (!self->seek_source) {
3076 GstClockTime now = gst_util_get_timestamp ();
3078 /* If no seek is pending or it was started more than 250 mseconds ago seek
3079 * immediately, otherwise wait until the 250 mseconds have passed */
3080 if (!self->seek_pending || (now - self->last_seek_time > 250 * GST_MSECOND)) {
3081 self->seek_source = g_idle_source_new ();
3082 g_source_set_callback (self->seek_source,
3083 (GSourceFunc) gst_play_seek_internal, self, NULL);
3084 GST_TRACE_OBJECT (self, "Dispatching seek to position %" GST_TIME_FORMAT,
3085 GST_TIME_ARGS (position));
3086 g_source_attach (self->seek_source, self->context);
3088 guint delay = 250000 - (now - self->last_seek_time) / 1000;
3090 /* Note that last_seek_time must be set to something at this point and
3091 * it must be smaller than 250 mseconds */
3092 self->seek_source = g_timeout_source_new (delay);
3093 g_source_set_callback (self->seek_source,
3094 (GSourceFunc) gst_play_seek_internal, self, NULL);
3096 GST_TRACE_OBJECT (self,
3097 "Delaying seek to position %" GST_TIME_FORMAT " by %u us",
3098 GST_TIME_ARGS (position), delay);
3099 g_source_attach (self->seek_source, self->context);
3102 g_mutex_unlock (&self->lock);
3106 remove_seek_source (GstPlay * self)
3108 if (!self->seek_source)
3111 g_source_destroy (self->seek_source);
3112 g_source_unref (self->seek_source);
3113 self->seek_source = NULL;
3118 * @play: #GstPlay instance
3120 * Gets the URI of the currently-playing stream.
3122 * Returns: (transfer full) (nullable): a string containing the URI of the
3123 * currently-playing stream. g_free() after usage.
3127 gst_play_get_uri (GstPlay * self)
3131 g_return_val_if_fail (GST_IS_PLAY (self), DEFAULT_URI);
3133 g_object_get (self, "uri", &val, NULL);
3140 * @play: #GstPlay instance
3141 * @uri: (nullable): next URI to play.
3143 * Sets the next URI to play.
3147 gst_play_set_uri (GstPlay * self, const gchar * val)
3149 g_return_if_fail (GST_IS_PLAY (self));
3151 g_object_set (self, "uri", val, NULL);
3155 * gst_play_set_subtitle_uri:
3156 * @play: #GstPlay instance
3157 * @uri: (nullable): subtitle URI
3159 * Sets the external subtitle URI. This should be combined with a call to
3160 * gst_play_set_subtitle_track_enabled(@play, TRUE) so the subtitles are actually
3165 gst_play_set_subtitle_uri (GstPlay * self, const gchar * suburi)
3167 g_return_if_fail (GST_IS_PLAY (self));
3169 g_object_set (self, "suburi", suburi, NULL);
3173 * gst_play_get_subtitle_uri:
3174 * @play: #GstPlay instance
3176 * current subtitle URI
3178 * Returns: (transfer full) (nullable): URI of the current external subtitle.
3179 * g_free() after usage.
3183 gst_play_get_subtitle_uri (GstPlay * self)
3187 g_return_val_if_fail (GST_IS_PLAY (self), NULL);
3189 g_object_get (self, "suburi", &val, NULL);
3195 * gst_play_get_position:
3196 * @play: #GstPlay instance
3198 * Returns: the absolute position time, in nanoseconds, of the
3199 * currently-playing stream.
3203 gst_play_get_position (GstPlay * self)
3207 g_return_val_if_fail (GST_IS_PLAY (self), DEFAULT_POSITION);
3209 g_object_get (self, "position", &val, NULL);
3215 * gst_play_get_duration:
3216 * @play: #GstPlay instance
3218 * Retrieves the duration of the media stream that self represents.
3220 * Returns: the duration of the currently-playing media stream, in
3225 gst_play_get_duration (GstPlay * self)
3229 g_return_val_if_fail (GST_IS_PLAY (self), DEFAULT_DURATION);
3231 g_object_get (self, "duration", &val, NULL);
3237 * gst_play_get_volume:
3238 * @play: #GstPlay instance
3240 * Returns the current volume level, as a percentage between 0 and 1.
3242 * Returns: the volume as percentage between 0 and 1.
3246 gst_play_get_volume (GstPlay * self)
3250 g_return_val_if_fail (GST_IS_PLAY (self), DEFAULT_VOLUME);
3252 g_object_get (self, "volume", &val, NULL);
3258 * gst_play_set_volume:
3259 * @play: #GstPlay instance
3260 * @val: the new volume level, as a percentage between 0 and 1
3262 * Sets the volume level of the stream as a percentage between 0 and 1.
3266 gst_play_set_volume (GstPlay * self, gdouble val)
3268 g_return_if_fail (GST_IS_PLAY (self));
3270 g_object_set (self, "volume", val, NULL);
3274 * gst_play_get_mute:
3275 * @play: #GstPlay instance
3277 * Returns: %TRUE if the currently-playing stream is muted.
3281 gst_play_get_mute (GstPlay * self)
3285 g_return_val_if_fail (GST_IS_PLAY (self), DEFAULT_MUTE);
3287 g_object_get (self, "mute", &val, NULL);
3293 * gst_play_set_mute:
3294 * @play: #GstPlay instance
3295 * @val: Mute state the should be set
3297 * %TRUE if the currently-playing stream should be muted.
3301 gst_play_set_mute (GstPlay * self, gboolean val)
3303 g_return_if_fail (GST_IS_PLAY (self));
3305 g_object_set (self, "mute", val, NULL);
3309 * gst_play_get_pipeline:
3310 * @play: #GstPlay instance
3312 * Returns: (transfer full): The internal playbin instance.
3314 * The caller should free it with g_object_unref()
3318 gst_play_get_pipeline (GstPlay * self)
3322 g_return_val_if_fail (GST_IS_PLAY (self), NULL);
3324 g_object_get (self, "pipeline", &val, NULL);
3330 * gst_play_get_media_info:
3331 * @play: #GstPlay instance
3333 * A Function to get the current media info #GstPlayMediaInfo instance.
3335 * Returns: (transfer full) (nullable): media info instance.
3337 * The caller should free it with g_object_unref()
3341 gst_play_get_media_info (GstPlay * self)
3343 GstPlayMediaInfo *info;
3345 g_return_val_if_fail (GST_IS_PLAY (self), NULL);
3347 if (!self->media_info)
3350 g_mutex_lock (&self->lock);
3351 info = gst_play_media_info_copy (self->media_info);
3352 g_mutex_unlock (&self->lock);
3358 * gst_play_get_current_audio_track:
3359 * @play: #GstPlay instance
3361 * A Function to get current audio #GstPlayAudioInfo instance.
3363 * Returns: (transfer full) (nullable): current audio track.
3365 * The caller should free it with g_object_unref()
3369 gst_play_get_current_audio_track (GstPlay * self)
3371 GstPlayAudioInfo *info;
3373 g_return_val_if_fail (GST_IS_PLAY (self), NULL);
3375 if (!is_track_enabled (self, GST_PLAY_FLAG_AUDIO))
3378 if (self->use_playbin3) {
3379 info = (GstPlayAudioInfo *)
3380 gst_play_stream_info_get_current_from_stream_id (self,
3381 self->audio_sid, GST_TYPE_PLAY_AUDIO_INFO);
3383 info = (GstPlayAudioInfo *) gst_play_stream_info_get_current (self,
3384 "current-audio", GST_TYPE_PLAY_AUDIO_INFO);
3391 * gst_play_get_current_video_track:
3392 * @play: #GstPlay instance
3394 * A Function to get current video #GstPlayVideoInfo instance.
3396 * Returns: (transfer full) (nullable): current video track.
3398 * The caller should free it with g_object_unref()
3402 gst_play_get_current_video_track (GstPlay * self)
3404 GstPlayVideoInfo *info;
3406 g_return_val_if_fail (GST_IS_PLAY (self), NULL);
3408 if (!is_track_enabled (self, GST_PLAY_FLAG_VIDEO))
3411 if (self->use_playbin3) {
3412 info = (GstPlayVideoInfo *)
3413 gst_play_stream_info_get_current_from_stream_id (self,
3414 self->video_sid, GST_TYPE_PLAY_VIDEO_INFO);
3416 info = (GstPlayVideoInfo *) gst_play_stream_info_get_current (self,
3417 "current-video", GST_TYPE_PLAY_VIDEO_INFO);
3424 * gst_play_get_current_subtitle_track:
3425 * @play: #GstPlay instance
3427 * A Function to get current subtitle #GstPlaySubtitleInfo instance.
3429 * Returns: (transfer full) (nullable): current subtitle track.
3431 * The caller should free it with g_object_unref()
3434 GstPlaySubtitleInfo *
3435 gst_play_get_current_subtitle_track (GstPlay * self)
3437 GstPlaySubtitleInfo *info;
3439 g_return_val_if_fail (GST_IS_PLAY (self), NULL);
3441 if (!is_track_enabled (self, GST_PLAY_FLAG_SUBTITLE))
3444 if (self->use_playbin3) {
3445 info = (GstPlaySubtitleInfo *)
3446 gst_play_stream_info_get_current_from_stream_id (self,
3447 self->subtitle_sid, GST_TYPE_PLAY_SUBTITLE_INFO);
3449 info = (GstPlaySubtitleInfo *) gst_play_stream_info_get_current (self,
3450 "current-text", GST_TYPE_PLAY_SUBTITLE_INFO);
3456 /* Must be called with lock */
3458 gst_play_select_streams (GstPlay * self)
3460 GList *stream_list = NULL;
3461 gboolean ret = FALSE;
3463 if (self->audio_sid)
3464 stream_list = g_list_append (stream_list, g_strdup (self->audio_sid));
3465 if (self->video_sid)
3466 stream_list = g_list_append (stream_list, g_strdup (self->video_sid));
3467 if (self->subtitle_sid)
3468 stream_list = g_list_append (stream_list, g_strdup (self->subtitle_sid));
3470 g_mutex_unlock (&self->lock);
3472 ret = gst_element_send_event (self->playbin,
3473 gst_event_new_select_streams (stream_list));
3474 g_list_free_full (stream_list, g_free);
3476 GST_ERROR_OBJECT (self, "No available streams for select-streams");
3478 g_mutex_lock (&self->lock);
3484 * gst_play_set_audio_track:
3485 * @play: #GstPlay instance
3486 * @stream_index: stream index
3488 * Returns: %TRUE or %FALSE
3490 * Sets the audio track @stream_index.
3494 gst_play_set_audio_track (GstPlay * self, gint stream_index)
3496 GstPlayStreamInfo *info;
3497 gboolean ret = TRUE;
3499 g_return_val_if_fail (GST_IS_PLAY (self), 0);
3501 g_mutex_lock (&self->lock);
3502 info = gst_play_stream_info_find (self->media_info,
3503 GST_TYPE_PLAY_AUDIO_INFO, stream_index);
3504 g_mutex_unlock (&self->lock);
3506 GST_ERROR_OBJECT (self, "invalid audio stream index %d", stream_index);
3510 if (self->use_playbin3) {
3511 g_mutex_lock (&self->lock);
3512 g_free (self->audio_sid);
3513 self->audio_sid = g_strdup (info->stream_id);
3514 ret = gst_play_select_streams (self);
3515 g_mutex_unlock (&self->lock);
3517 g_object_set (G_OBJECT (self->playbin), "current-audio", stream_index,
3521 GST_DEBUG_OBJECT (self, "set stream index '%d'", stream_index);
3526 * gst_play_set_video_track:
3527 * @play: #GstPlay instance
3528 * @stream_index: stream index
3530 * Returns: %TRUE or %FALSE
3532 * Sets the video track @stream_index.
3536 gst_play_set_video_track (GstPlay * self, gint stream_index)
3538 GstPlayStreamInfo *info;
3539 gboolean ret = TRUE;
3541 g_return_val_if_fail (GST_IS_PLAY (self), 0);
3543 /* check if stream_index exist in our internal media_info list */
3544 g_mutex_lock (&self->lock);
3545 info = gst_play_stream_info_find (self->media_info,
3546 GST_TYPE_PLAY_VIDEO_INFO, stream_index);
3547 g_mutex_unlock (&self->lock);
3549 GST_ERROR_OBJECT (self, "invalid video stream index %d", stream_index);
3553 if (self->use_playbin3) {
3554 g_mutex_lock (&self->lock);
3555 g_free (self->video_sid);
3556 self->video_sid = g_strdup (info->stream_id);
3557 ret = gst_play_select_streams (self);
3558 g_mutex_unlock (&self->lock);
3560 g_object_set (G_OBJECT (self->playbin), "current-video", stream_index,
3564 GST_DEBUG_OBJECT (self, "set stream index '%d'", stream_index);
3569 * gst_play_set_subtitle_track:
3570 * @play: #GstPlay instance
3571 * @stream_index: stream index
3573 * Returns: %TRUE or %FALSE
3575 * Sets the subtitle stack @stream_index.
3579 gst_play_set_subtitle_track (GstPlay * self, gint stream_index)
3581 GstPlayStreamInfo *info;
3582 gboolean ret = TRUE;
3584 g_return_val_if_fail (GST_IS_PLAY (self), 0);
3586 g_mutex_lock (&self->lock);
3587 info = gst_play_stream_info_find (self->media_info,
3588 GST_TYPE_PLAY_SUBTITLE_INFO, stream_index);
3589 g_mutex_unlock (&self->lock);
3591 GST_ERROR_OBJECT (self, "invalid subtitle stream index %d", stream_index);
3595 if (self->use_playbin3) {
3596 g_mutex_lock (&self->lock);
3597 g_free (self->subtitle_sid);
3598 self->subtitle_sid = g_strdup (info->stream_id);
3599 ret = gst_play_select_streams (self);
3600 g_mutex_unlock (&self->lock);
3602 g_object_set (G_OBJECT (self->playbin), "current-text", stream_index, NULL);
3605 GST_DEBUG_OBJECT (self, "set stream index '%d'", stream_index);
3610 * gst_play_set_audio_track_enabled:
3611 * @play: #GstPlay instance
3612 * @enabled: TRUE or FALSE
3614 * Enable or disable the current audio track.
3618 gst_play_set_audio_track_enabled (GstPlay * self, gboolean enabled)
3620 g_return_if_fail (GST_IS_PLAY (self));
3623 play_set_flag (self, GST_PLAY_FLAG_AUDIO);
3625 play_clear_flag (self, GST_PLAY_FLAG_AUDIO);
3627 GST_DEBUG_OBJECT (self, "track is '%s'", enabled ? "Enabled" : "Disabled");
3631 * gst_play_set_video_track_enabled:
3632 * @play: #GstPlay instance
3633 * @enabled: TRUE or FALSE
3635 * Enable or disable the current video track.
3639 gst_play_set_video_track_enabled (GstPlay * self, gboolean enabled)
3641 g_return_if_fail (GST_IS_PLAY (self));
3644 play_set_flag (self, GST_PLAY_FLAG_VIDEO);
3646 play_clear_flag (self, GST_PLAY_FLAG_VIDEO);
3648 GST_DEBUG_OBJECT (self, "track is '%s'", enabled ? "Enabled" : "Disabled");
3652 * gst_play_set_subtitle_track_enabled:
3653 * @play: #GstPlay instance
3654 * @enabled: TRUE or FALSE
3656 * Enable or disable the current subtitle track.
3660 gst_play_set_subtitle_track_enabled (GstPlay * self, gboolean enabled)
3662 g_return_if_fail (GST_IS_PLAY (self));
3665 play_set_flag (self, GST_PLAY_FLAG_SUBTITLE);
3667 play_clear_flag (self, GST_PLAY_FLAG_SUBTITLE);
3669 GST_DEBUG_OBJECT (self, "track is '%s'", enabled ? "Enabled" : "Disabled");
3673 * gst_play_set_visualization:
3674 * @play: #GstPlay instance
3675 * @name: (nullable): visualization element obtained from
3676 * #gst_play_visualizations_get()
3678 * Returns: %TRUE if the visualizations was set correctly. Otherwise,
3683 gst_play_set_visualization (GstPlay * self, const gchar * name)
3685 g_return_val_if_fail (GST_IS_PLAY (self), FALSE);
3687 g_mutex_lock (&self->lock);
3688 if (self->current_vis_element) {
3689 gst_object_unref (self->current_vis_element);
3690 self->current_vis_element = NULL;
3694 self->current_vis_element = gst_element_factory_make (name, NULL);
3695 if (!self->current_vis_element)
3696 goto error_no_element;
3697 gst_object_ref_sink (self->current_vis_element);
3699 g_object_set (self->playbin, "vis-plugin", self->current_vis_element, NULL);
3701 g_mutex_unlock (&self->lock);
3702 GST_DEBUG_OBJECT (self, "set vis-plugin to '%s'", name);
3707 g_mutex_unlock (&self->lock);
3708 GST_WARNING_OBJECT (self, "could not find visualization '%s'", name);
3713 * gst_play_get_current_visualization:
3714 * @play: #GstPlay instance
3716 * Returns: (transfer full) (nullable): Name of the currently enabled
3718 * g_free() after usage.
3722 gst_play_get_current_visualization (GstPlay * self)
3725 GstElement *vis_plugin = NULL;
3727 g_return_val_if_fail (GST_IS_PLAY (self), NULL);
3729 if (!is_track_enabled (self, GST_PLAY_FLAG_VIS))
3732 g_object_get (self->playbin, "vis-plugin", &vis_plugin, NULL);
3735 GstElementFactory *factory = gst_element_get_factory (vis_plugin);
3737 name = g_strdup (gst_plugin_feature_get_name (factory));
3738 gst_object_unref (vis_plugin);
3741 GST_DEBUG_OBJECT (self, "vis-plugin '%s' %p", name, vis_plugin);
3747 * gst_play_set_visualization_enabled:
3748 * @play: #GstPlay instance
3749 * @enabled: TRUE or FALSE
3751 * Enable or disable the visualization.
3755 gst_play_set_visualization_enabled (GstPlay * self, gboolean enabled)
3757 g_return_if_fail (GST_IS_PLAY (self));
3760 play_set_flag (self, GST_PLAY_FLAG_VIS);
3762 play_clear_flag (self, GST_PLAY_FLAG_VIS);
3764 GST_DEBUG_OBJECT (self, "visualization is '%s'",
3765 enabled ? "Enabled" : "Disabled");
3770 const gchar *label; /* channel label name */
3771 const gchar *name; /* get_name () */
3774 static const struct CBChannelMap cb_channel_map[] = {
3775 /* GST_PLAY_COLOR_BALANCE_BRIGHTNESS */ {"BRIGHTNESS", "brightness"},
3776 /* GST_PLAY_COLOR_BALANCE_CONTRAST */ {"CONTRAST", "contrast"},
3777 /* GST_PLAY_COLOR_BALANCE_SATURATION */ {"SATURATION", "saturation"},
3778 /* GST_PLAY_COLOR_BALANCE_HUE */ {"HUE", "hue"},
3781 static GstColorBalanceChannel *
3782 gst_play_color_balance_find_channel (GstPlay * self,
3783 GstPlayColorBalanceType type)
3785 GstColorBalanceChannel *channel;
3786 const GList *l, *channels;
3788 if (type < GST_PLAY_COLOR_BALANCE_BRIGHTNESS ||
3789 type > GST_PLAY_COLOR_BALANCE_HUE)
3793 gst_color_balance_list_channels (GST_COLOR_BALANCE (self->playbin));
3794 for (l = channels; l; l = l->next) {
3796 if (g_strrstr (channel->label, cb_channel_map[type].label))
3804 * gst_play_has_color_balance:
3805 * @play:#GstPlay instance
3807 * Checks whether the @play has color balance support available.
3809 * Returns: %TRUE if @play has color balance support. Otherwise,
3814 gst_play_has_color_balance (GstPlay * self)
3816 const GList *channels;
3818 g_return_val_if_fail (GST_IS_PLAY (self), FALSE);
3820 if (!GST_IS_COLOR_BALANCE (self->playbin))
3824 gst_color_balance_list_channels (GST_COLOR_BALANCE (self->playbin));
3825 return (channels != NULL);
3829 * gst_play_set_color_balance:
3830 * @play: #GstPlay instance
3831 * @type: #GstPlayColorBalanceType
3832 * @value: The new value for the @type, ranged [0,1]
3834 * Sets the current value of the indicated channel @type to the passed
3839 gst_play_set_color_balance (GstPlay * self, GstPlayColorBalanceType type,
3842 GstColorBalanceChannel *channel;
3845 g_return_if_fail (GST_IS_PLAY (self));
3846 g_return_if_fail (value >= 0.0 && value <= 1.0);
3848 if (!GST_IS_COLOR_BALANCE (self->playbin))
3851 channel = gst_play_color_balance_find_channel (self, type);
3855 value = CLAMP (value, 0.0, 1.0);
3857 /* Convert to channel range */
3858 new_val = channel->min_value + value * ((gdouble) channel->max_value -
3859 (gdouble) channel->min_value);
3861 gst_color_balance_set_value (GST_COLOR_BALANCE (self->playbin), channel,
3866 * gst_play_get_color_balance:
3867 * @play: #GstPlay instance
3868 * @type: #GstPlayColorBalanceType
3870 * Retrieve the current value of the indicated @type.
3872 * Returns: The current value of @type, between [0,1]. In case of
3873 * error -1 is returned.
3877 gst_play_get_color_balance (GstPlay * self, GstPlayColorBalanceType type)
3879 GstColorBalanceChannel *channel;
3882 g_return_val_if_fail (GST_IS_PLAY (self), -1);
3884 if (!GST_IS_COLOR_BALANCE (self->playbin))
3887 channel = gst_play_color_balance_find_channel (self, type);
3891 value = gst_color_balance_get_value (GST_COLOR_BALANCE (self->playbin),
3894 return ((gdouble) value -
3895 (gdouble) channel->min_value) / ((gdouble) channel->max_value -
3896 (gdouble) channel->min_value);
3900 * gst_play_get_multiview_mode:
3901 * @play: #GstPlay instance
3903 * Retrieve the current value of the indicated @type.
3905 * Returns: The current value of @type, Default: -1 "none"
3909 GstVideoMultiviewFramePacking
3910 gst_play_get_multiview_mode (GstPlay * self)
3912 GstVideoMultiviewFramePacking val = GST_VIDEO_MULTIVIEW_FRAME_PACKING_NONE;
3914 g_return_val_if_fail (GST_IS_PLAY (self),
3915 GST_VIDEO_MULTIVIEW_FRAME_PACKING_NONE);
3917 g_object_get (self, "video-multiview-mode", &val, NULL);
3923 * gst_play_set_multiview_mode:
3924 * @play: #GstPlay instance
3925 * @mode: The new value for the @type
3927 * Sets the current value of the indicated mode @type to the passed
3933 gst_play_set_multiview_mode (GstPlay * self, GstVideoMultiviewFramePacking mode)
3935 g_return_if_fail (GST_IS_PLAY (self));
3937 g_object_set (self, "video-multiview-mode", mode, NULL);
3941 * gst_play_get_multiview_flags:
3942 * @play: #GstPlay instance
3944 * Retrieve the current value of the indicated @type.
3946 * Returns: The current value of @type, Default: 0x00000000 "none
3950 GstVideoMultiviewFlags
3951 gst_play_get_multiview_flags (GstPlay * self)
3953 GstVideoMultiviewFlags val = GST_VIDEO_MULTIVIEW_FLAGS_NONE;
3955 g_return_val_if_fail (GST_IS_PLAY (self), val);
3957 g_object_get (self, "video-multiview-flags", &val, NULL);
3963 * gst_play_set_multiview_flags:
3964 * @play: #GstPlay instance
3965 * @flags: The new value for the @type
3967 * Sets the current value of the indicated mode @type to the passed
3973 gst_play_set_multiview_flags (GstPlay * self, GstVideoMultiviewFlags flags)
3975 g_return_if_fail (GST_IS_PLAY (self));
3977 g_object_set (self, "video-multiview-flags", flags, NULL);
3981 * gst_play_get_audio_video_offset:
3982 * @play: #GstPlay instance
3984 * Retrieve the current value of audio-video-offset property
3986 * Returns: The current value of audio-video-offset in nanoseconds
3991 gst_play_get_audio_video_offset (GstPlay * self)
3995 g_return_val_if_fail (GST_IS_PLAY (self), DEFAULT_AUDIO_VIDEO_OFFSET);
3997 g_object_get (self, "audio-video-offset", &val, NULL);
4003 * gst_play_set_audio_video_offset:
4004 * @play: #GstPlay instance
4005 * @offset: #gint64 in nanoseconds
4007 * Sets audio-video-offset property by value of @offset
4012 gst_play_set_audio_video_offset (GstPlay * self, gint64 offset)
4014 g_return_if_fail (GST_IS_PLAY (self));
4016 g_object_set (self, "audio-video-offset", offset, NULL);
4020 * gst_play_get_subtitle_video_offset:
4021 * @play: #GstPlay instance
4023 * Retrieve the current value of subtitle-video-offset property
4025 * Returns: The current value of subtitle-video-offset in nanoseconds
4030 gst_play_get_subtitle_video_offset (GstPlay * self)
4034 g_return_val_if_fail (GST_IS_PLAY (self), DEFAULT_SUBTITLE_VIDEO_OFFSET);
4036 g_object_get (self, "subtitle-video-offset", &val, NULL);
4042 * gst_play_set_subtitle_video_offset:
4043 * @play: #GstPlay instance
4044 * @offset: #gint64 in nanoseconds
4046 * Sets subtitle-video-offset property by value of @offset
4051 gst_play_set_subtitle_video_offset (GstPlay * self, gint64 offset)
4053 g_return_if_fail (GST_IS_PLAY (self));
4055 g_object_set (self, "subtitle-video-offset", offset, NULL);
4059 #define C_ENUM(v) ((gint) v)
4060 #define C_FLAGS(v) ((guint) v)
4063 gst_play_color_balance_type_get_type (void)
4065 static gsize id = 0;
4066 static const GEnumValue values[] = {
4067 {C_ENUM (GST_PLAY_COLOR_BALANCE_HUE), "GST_PLAY_COLOR_BALANCE_HUE",
4069 {C_ENUM (GST_PLAY_COLOR_BALANCE_BRIGHTNESS),
4070 "GST_PLAY_COLOR_BALANCE_BRIGHTNESS", "brightness"},
4071 {C_ENUM (GST_PLAY_COLOR_BALANCE_SATURATION),
4072 "GST_PLAY_COLOR_BALANCE_SATURATION", "saturation"},
4073 {C_ENUM (GST_PLAY_COLOR_BALANCE_CONTRAST),
4074 "GST_PLAY_COLOR_BALANCE_CONTRAST", "contrast"},
4078 if (g_once_init_enter (&id)) {
4079 GType tmp = g_enum_register_static ("GstPlayColorBalanceType", values);
4080 g_once_init_leave (&id, tmp);
4087 * gst_play_color_balance_type_get_name:
4088 * @type: a #GstPlayColorBalanceType
4090 * Gets a string representing the given color balance type.
4092 * Returns: (transfer none): a string with the name of the color
4097 gst_play_color_balance_type_get_name (GstPlayColorBalanceType type)
4099 g_return_val_if_fail (type >= GST_PLAY_COLOR_BALANCE_BRIGHTNESS &&
4100 type <= GST_PLAY_COLOR_BALANCE_HUE, NULL);
4102 return cb_channel_map[type].name;
4106 gst_play_state_get_type (void)
4108 static gsize id = 0;
4109 static const GEnumValue values[] = {
4110 {C_ENUM (GST_PLAY_STATE_STOPPED), "GST_PLAY_STATE_STOPPED", "stopped"},
4111 {C_ENUM (GST_PLAY_STATE_BUFFERING), "GST_PLAY_STATE_BUFFERING",
4113 {C_ENUM (GST_PLAY_STATE_PAUSED), "GST_PLAY_STATE_PAUSED", "paused"},
4114 {C_ENUM (GST_PLAY_STATE_PLAYING), "GST_PLAY_STATE_PLAYING", "playing"},
4118 if (g_once_init_enter (&id)) {
4119 GType tmp = g_enum_register_static ("GstPlayState", values);
4120 g_once_init_leave (&id, tmp);
4127 gst_play_message_get_type (void)
4129 static gsize id = 0;
4130 static const GEnumValue values[] = {
4131 {C_ENUM (GST_PLAY_MESSAGE_URI_LOADED), "GST_PLAY_MESSAGE_URI_LOADED",
4133 {C_ENUM (GST_PLAY_MESSAGE_POSITION_UPDATED),
4134 "GST_PLAY_MESSAGE_POSITION_UPDATED", "position-updated"},
4135 {C_ENUM (GST_PLAY_MESSAGE_DURATION_CHANGED),
4136 "GST_PLAY_MESSAGE_DURATION_CHANGED", "duration-changed"},
4137 {C_ENUM (GST_PLAY_MESSAGE_STATE_CHANGED),
4138 "GST_PLAY_MESSAGE_STATE_CHANGED", "state-changed"},
4139 {C_ENUM (GST_PLAY_MESSAGE_BUFFERING), "GST_PLAY_MESSAGE_BUFFERING",
4141 {C_ENUM (GST_PLAY_MESSAGE_END_OF_STREAM),
4142 "GST_PLAY_MESSAGE_END_OF_STREAM", "end-of-stream"},
4143 {C_ENUM (GST_PLAY_MESSAGE_ERROR), "GST_PLAY_MESSAGE_ERROR", "error"},
4144 {C_ENUM (GST_PLAY_MESSAGE_WARNING), "GST_PLAY_MESSAGE_WARNING",
4146 {C_ENUM (GST_PLAY_MESSAGE_VIDEO_DIMENSIONS_CHANGED),
4147 "GST_PLAY_MESSAGE_VIDEO_DIMENSIONS_CHANGED",
4148 "video-dimensions-changed"},
4149 {C_ENUM (GST_PLAY_MESSAGE_MEDIA_INFO_UPDATED),
4150 "GST_PLAY_MESSAGE_MEDIA_INFO_UPDATED", "media-info-updated"},
4151 {C_ENUM (GST_PLAY_MESSAGE_VOLUME_CHANGED),
4152 "GST_PLAY_MESSAGE_VOLUME_CHANGED", "volume-changed"},
4153 {C_ENUM (GST_PLAY_MESSAGE_MUTE_CHANGED),
4154 "GST_PLAY_MESSAGE_MUTE_CHANGED", "mute-changed"},
4155 {C_ENUM (GST_PLAY_MESSAGE_SEEK_DONE), "GST_PLAY_MESSAGE_SEEK_DONE",
4160 if (g_once_init_enter (&id)) {
4161 GType tmp = g_enum_register_static ("GstPlayMessage", values);
4162 g_once_init_leave (&id, tmp);
4169 * gst_play_state_get_name:
4170 * @state: a #GstPlayState
4172 * Gets a string representing the given state.
4174 * Returns: (transfer none): a string with the name of the state.
4178 gst_play_state_get_name (GstPlayState state)
4181 case GST_PLAY_STATE_STOPPED:
4183 case GST_PLAY_STATE_BUFFERING:
4185 case GST_PLAY_STATE_PAUSED:
4187 case GST_PLAY_STATE_PLAYING:
4191 g_assert_not_reached ();
4196 * gst_play_message_get_name:
4197 * @message_type: a #GstPlayMessage
4199 * Returns: (transfer none): a string with the name of the message.
4203 gst_play_message_get_name (GstPlayMessage message_type)
4205 GEnumClass *enum_class;
4206 GEnumValue *enum_value;
4207 enum_class = g_type_class_ref (GST_TYPE_PLAY_MESSAGE);
4208 enum_value = g_enum_get_value (enum_class, message_type);
4209 g_assert (enum_value != NULL);
4210 g_type_class_unref (enum_class);
4211 return enum_value->value_name;
4215 gst_play_error_get_type (void)
4217 static gsize id = 0;
4218 static const GEnumValue values[] = {
4219 {C_ENUM (GST_PLAY_ERROR_FAILED), "GST_PLAY_ERROR_FAILED", "failed"},
4223 if (g_once_init_enter (&id)) {
4224 GType tmp = g_enum_register_static ("GstPlayError", values);
4225 g_once_init_leave (&id, tmp);
4232 * gst_play_error_get_name:
4233 * @error: a #GstPlayError
4235 * Gets a string representing the given error.
4237 * Returns: (transfer none): a string with the given error.
4241 gst_play_error_get_name (GstPlayError error)
4244 case GST_PLAY_ERROR_FAILED:
4248 g_assert_not_reached ();
4253 * gst_play_set_config:
4254 * @play: #GstPlay instance
4255 * @config: (transfer full): a #GstStructure
4257 * Set the configuration of the play. If the play is already configured, and
4258 * the configuration haven't change, this function will return %TRUE. If the
4259 * play is not in the GST_PLAY_STATE_STOPPED, this method will return %FALSE
4260 * and active configuration will remain.
4262 * @config is a #GstStructure that contains the configuration parameters for
4265 * This function takes ownership of @config.
4267 * Returns: %TRUE when the configuration could be set.
4271 gst_play_set_config (GstPlay * self, GstStructure * config)
4273 g_return_val_if_fail (GST_IS_PLAY (self), FALSE);
4274 g_return_val_if_fail (config != NULL, FALSE);
4276 g_mutex_lock (&self->lock);
4278 if (self->app_state != GST_PLAY_STATE_STOPPED) {
4279 GST_INFO_OBJECT (self, "can't change config while play is %s",
4280 gst_play_state_get_name (self->app_state));
4281 g_mutex_unlock (&self->lock);
4286 gst_structure_free (self->config);
4287 self->config = config;
4288 g_mutex_unlock (&self->lock);
4294 * gst_play_get_config:
4295 * @play: #GstPlay instance
4297 * Get a copy of the current configuration of the play. This configuration
4298 * can either be modified and used for the gst_play_set_config() call
4299 * or it must be freed after usage.
4301 * Returns: (transfer full): a copy of the current configuration of @play. Use
4302 * gst_structure_free() after usage or gst_play_set_config().
4307 gst_play_get_config (GstPlay * self)
4311 g_return_val_if_fail (GST_IS_PLAY (self), NULL);
4313 g_mutex_lock (&self->lock);
4314 ret = gst_structure_copy (self->config);
4315 g_mutex_unlock (&self->lock);
4321 * gst_play_config_set_user_agent:
4322 * @config: a #GstPlay configuration
4323 * @agent: (nullable): the string to use as user agent
4325 * Set the user agent to pass to the server if @play needs to connect
4326 * to a server during playback. This is typically used when playing HTTP
4332 gst_play_config_set_user_agent (GstStructure * config, const gchar * agent)
4334 g_return_if_fail (config != NULL);
4335 g_return_if_fail (agent != NULL);
4337 gst_structure_id_set (config,
4338 CONFIG_QUARK (USER_AGENT), G_TYPE_STRING, agent, NULL);
4342 * gst_play_config_get_user_agent:
4343 * @config: a #GstPlay configuration
4345 * Return the user agent which has been configured using
4346 * gst_play_config_set_user_agent() if any.
4348 * Returns: (transfer full) (nullable): the configured agent, or %NULL
4352 gst_play_config_get_user_agent (const GstStructure * config)
4354 gchar *agent = NULL;
4356 g_return_val_if_fail (config != NULL, NULL);
4358 gst_structure_id_get (config,
4359 CONFIG_QUARK (USER_AGENT), G_TYPE_STRING, &agent, NULL);
4365 * gst_play_config_set_position_update_interval:
4366 * @config: a #GstPlay configuration
4367 * @interval: interval in ms
4369 * set desired interval in milliseconds between two position-updated messages.
4370 * pass 0 to stop updating the position.
4374 gst_play_config_set_position_update_interval (GstStructure * config,
4377 g_return_if_fail (config != NULL);
4378 g_return_if_fail (interval <= 10000);
4380 gst_structure_id_set (config,
4381 CONFIG_QUARK (POSITION_INTERVAL_UPDATE), G_TYPE_UINT, interval, NULL);
4385 * gst_play_config_get_position_update_interval:
4386 * @config: a #GstPlay configuration
4388 * Returns: current position update interval in milliseconds
4393 gst_play_config_get_position_update_interval (const GstStructure * config)
4395 guint interval = DEFAULT_POSITION_UPDATE_INTERVAL_MS;
4397 g_return_val_if_fail (config != NULL, DEFAULT_POSITION_UPDATE_INTERVAL_MS);
4399 gst_structure_id_get (config,
4400 CONFIG_QUARK (POSITION_INTERVAL_UPDATE), G_TYPE_UINT, &interval, NULL);
4406 * gst_play_config_set_seek_accurate:
4407 * @config: a #GstPlay configuration
4408 * @accurate: accurate seek or not
4410 * Enable or disable accurate seeking. When enabled, elements will try harder
4411 * to seek as accurately as possible to the requested seek position. Generally
4412 * it will be slower especially for formats that don't have any indexes or
4413 * timestamp markers in the stream.
4415 * If accurate seeking is disabled, elements will seek as close as the request
4416 * position without slowing down seeking too much.
4418 * Accurate seeking is disabled by default.
4423 gst_play_config_set_seek_accurate (GstStructure * config, gboolean accurate)
4425 g_return_if_fail (config != NULL);
4427 gst_structure_id_set (config,
4428 CONFIG_QUARK (ACCURATE_SEEK), G_TYPE_BOOLEAN, accurate, NULL);
4432 * gst_play_config_get_seek_accurate:
4433 * @config: a #GstPlay configuration
4435 * Returns: %TRUE if accurate seeking is enabled
4440 gst_play_config_get_seek_accurate (const GstStructure * config)
4442 gboolean accurate = FALSE;
4444 g_return_val_if_fail (config != NULL, FALSE);
4446 gst_structure_id_get (config,
4447 CONFIG_QUARK (ACCURATE_SEEK), G_TYPE_BOOLEAN, &accurate, NULL);
4453 * gst_play_get_video_snapshot:
4454 * @play: #GstPlay instance
4455 * @format: output format of the video snapshot
4456 * @config: (allow-none): Additional configuration
4458 * Get a snapshot of the currently selected video stream, if any. The format can be
4459 * selected with @format and optional configuration is possible with @config
4460 * Currently supported settings are:
4461 * - width, height of type G_TYPE_INT
4462 * - pixel-aspect-ratio of type GST_TYPE_FRACTION
4463 * Except for GST_PLAY_THUMBNAIL_RAW_NATIVE format, if no config is set, pixel-aspect-ratio would be 1/1
4465 * Returns: (transfer full) (nullable): Current video snapshot sample or %NULL on failure
4470 gst_play_get_video_snapshot (GstPlay * self,
4471 GstPlaySnapshotFormat format, const GstStructure * config)
4473 gint video_tracks = 0;
4474 GstSample *sample = NULL;
4475 GstCaps *caps = NULL;
4480 g_return_val_if_fail (GST_IS_PLAY (self), NULL);
4482 g_object_get (self->playbin, "n-video", &video_tracks, NULL);
4483 if (video_tracks == 0) {
4484 GST_DEBUG_OBJECT (self, "total video track num is 0");
4489 case GST_PLAY_THUMBNAIL_RAW_xRGB:
4490 caps = gst_caps_new_simple ("video/x-raw",
4491 "format", G_TYPE_STRING, "xRGB", NULL);
4493 case GST_PLAY_THUMBNAIL_RAW_BGRx:
4494 caps = gst_caps_new_simple ("video/x-raw",
4495 "format", G_TYPE_STRING, "BGRx", NULL);
4497 case GST_PLAY_THUMBNAIL_JPG:
4498 caps = gst_caps_new_empty_simple ("image/jpeg");
4500 case GST_PLAY_THUMBNAIL_PNG:
4501 caps = gst_caps_new_empty_simple ("image/png");
4503 case GST_PLAY_THUMBNAIL_RAW_NATIVE:
4505 caps = gst_caps_new_empty_simple ("video/x-raw");
4509 if (NULL != config) {
4510 if (!gst_structure_get_int (config, "width", &width))
4512 if (!gst_structure_get_int (config, "height", &height))
4514 if (!gst_structure_get_fraction (config, "pixel-aspect-ratio", &par_n,
4516 if (format != GST_PLAY_THUMBNAIL_RAW_NATIVE) {
4526 if (width > 0 && height > 0) {
4527 gst_caps_set_simple (caps, "width", G_TYPE_INT, width,
4528 "height", G_TYPE_INT, height, NULL);
4531 if (format != GST_PLAY_THUMBNAIL_RAW_NATIVE) {
4532 gst_caps_set_simple (caps, "pixel-aspect-ratio", GST_TYPE_FRACTION,
4533 par_n, par_d, NULL);
4534 } else if (NULL != config && par_n != 0 && par_d != 0) {
4535 gst_caps_set_simple (caps, "pixel-aspect-ratio", GST_TYPE_FRACTION,
4536 par_n, par_d, NULL);
4539 g_signal_emit_by_name (self->playbin, "convert-sample", caps, &sample);
4540 gst_caps_unref (caps);
4542 GST_WARNING_OBJECT (self, "Failed to retrieve or convert video frame");
4550 * gst_play_is_play_message:
4551 * @msg: A #GstMessage
4553 * Returns: A #gboolean indicating wheter the passes message represents a #GstPlay message or not.
4558 gst_play_is_play_message (GstMessage * msg)
4560 const GstStructure *data = NULL;
4561 g_return_val_if_fail (GST_IS_MESSAGE (msg), FALSE);
4563 data = gst_message_get_structure (msg);
4564 g_return_val_if_fail (data, FALSE);
4566 return g_str_equal (gst_structure_get_name (data), GST_PLAY_MESSAGE_DATA);
4569 #define PARSE_MESSAGE_FIELD(msg, field, value_type, value) G_STMT_START { \
4570 const GstStructure *data = NULL; \
4571 g_return_if_fail (gst_play_is_play_message (msg)); \
4572 data = gst_message_get_structure (msg); \
4573 gst_structure_get (data, field, value_type, value, NULL); \
4577 * gst_play_message_parse_type:
4578 * @msg: A #GstMessage
4579 * @type: (out) (optional): the resulting message type
4581 * Parse the given @msg and extract its #GstPlayMessage type.
4586 gst_play_message_parse_type (GstMessage * msg, GstPlayMessage * type)
4588 PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_TYPE,
4589 GST_TYPE_PLAY_MESSAGE, type);
4593 * gst_play_message_parse_duration_updated:
4594 * @msg: A #GstMessage
4595 * @duration: (out) (optional): the resulting duration
4597 * Parse the given duration @msg and extract the corresponding #GstClockTime
4602 gst_play_message_parse_duration_updated (GstMessage * msg,
4603 GstClockTime * duration)
4605 PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_DURATION,
4606 GST_TYPE_CLOCK_TIME, duration);
4610 * gst_play_message_parse_position_updated:
4611 * @msg: A #GstMessage
4612 * @position: (out) (optional): the resulting position
4614 * Parse the given position @msg and extract the corresponding #GstClockTime
4619 gst_play_message_parse_position_updated (GstMessage * msg,
4620 GstClockTime * position)
4622 PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_POSITION,
4623 GST_TYPE_CLOCK_TIME, position);
4627 * gst_play_message_parse_state_changed:
4628 * @msg: A #GstMessage
4629 * @state: (out) (optional): the resulting play state
4631 * Parse the given state @msg and extract the corresponding #GstPlayState
4636 gst_play_message_parse_state_changed (GstMessage * msg, GstPlayState * state)
4638 PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_PLAY_STATE,
4639 GST_TYPE_PLAY_STATE, state);
4643 * gst_play_message_parse_buffering_percent:
4644 * @msg: A #GstMessage
4645 * @percent: (out) (optional): the resulting buffering percent
4647 * Parse the given buffering-percent @msg and extract the corresponding value
4652 gst_play_message_parse_buffering_percent (GstMessage * msg, guint * percent)
4654 PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_BUFFERING_PERCENT,
4655 G_TYPE_UINT, percent);
4659 * gst_play_message_parse_error:
4660 * @msg: A #GstMessage
4661 * @error: (out) (optional) (transfer full): the resulting error
4662 * @details: (out) (optional) (nullable) (transfer full): A #GstStructure containing additional details about the error
4664 * Parse the given error @msg and extract the corresponding #GError
4669 gst_play_message_parse_error (GstMessage * msg, GError ** error,
4670 GstStructure ** details)
4672 PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_ERROR, G_TYPE_ERROR, error);
4673 PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_ERROR, GST_TYPE_STRUCTURE,
4678 * gst_play_message_parse_warning:
4679 * @msg: A #GstMessage
4680 * @error: (out) (optional) (transfer full): the resulting warning
4681 * @details: (out) (optional) (nullable) (transfer full): A #GstStructure containing additional details about the warning
4683 * Parse the given error @msg and extract the corresponding #GError warning
4688 gst_play_message_parse_warning (GstMessage * msg, GError ** error,
4689 GstStructure ** details)
4691 PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_WARNING, G_TYPE_ERROR, error);
4692 PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_WARNING, GST_TYPE_STRUCTURE,
4697 * gst_play_message_parse_video_dimensions_changed:
4698 * @msg: A #GstMessage
4699 * @width: (out) (optional): the resulting video width
4700 * @height: (out) (optional): the resulting video height
4702 * Parse the given @msg and extract the corresponding video dimensions
4707 gst_play_message_parse_video_dimensions_changed (GstMessage * msg,
4708 guint * width, guint * height)
4710 PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_VIDEO_WIDTH,
4711 G_TYPE_UINT, width);
4712 PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_VIDEO_HEIGHT,
4713 G_TYPE_UINT, height);
4717 * gst_play_message_parse_media_info_updated:
4718 * @msg: A #GstMessage
4719 * @info: (out) (optional) (transfer full): the resulting media info
4721 * Parse the given @msg and extract the corresponding media information
4726 gst_play_message_parse_media_info_updated (GstMessage * msg,
4727 GstPlayMediaInfo ** info)
4729 PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_MEDIA_INFO,
4730 GST_TYPE_PLAY_MEDIA_INFO, info);
4734 * gst_play_message_parse_volume_changed:
4735 * @msg: A #GstMessage
4736 * @volume: (out) (optional): the resulting audio volume
4738 * Parse the given @msg and extract the corresponding audio volume
4743 gst_play_message_parse_volume_changed (GstMessage * msg, gdouble * volume)
4745 PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_VOLUME, G_TYPE_DOUBLE,
4750 * gst_play_message_parse_muted_changed:
4751 * @msg: A #GstMessage
4752 * @muted: (out) (optional): the resulting audio muted state
4754 * Parse the given @msg and extract the corresponding audio muted state
4759 gst_play_message_parse_muted_changed (GstMessage * msg, gboolean * muted)
4761 PARSE_MESSAGE_FIELD (msg, GST_PLAY_MESSAGE_DATA_IS_MUTED, G_TYPE_BOOLEAN,