4
* GStreamer integration library for Clutter.
6
* clutter-gst-player.c - Wrap some convenience functions around playbin
8
* Authored By Damien Lespiau <damien.lespiau@intel.com>
9
* Lionel Landwerlin <lionel.g.landwerlin@linux.intel.com>
10
* Matthew Allum <mallum@openedhand.com>
11
* Emmanuele Bassi <ebassi@linux.intel.com>
12
* Andre Moreira Magalhaes <andre.magalhaes@collabora.co.uk>
14
* Copyright (C) 2006 OpenedHand
15
* Copyright (C) 2009-2013 Intel Corporation
16
* Copyright (C) 2012 Collabora Ltd. <http://www.collabora.co.uk/>
18
* This library is free software; you can redistribute it and/or
19
* modify it under the terms of the GNU Lesser General Public
20
* License as published by the Free Software Foundation; either
21
* version 2 of the License, or (at your option) any later version.
23
* This library is distributed in the hope that it will be useful,
24
* but WITHOUT ANY WARRANTY; without even the implied warranty of
25
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
26
* Lesser General Public License for more details.
28
* You should have received a copy of the GNU Lesser General Public
29
* License along with this library; if not, write to the
30
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
31
* Boston, MA 02111-1307, USA.
35
* SECTION:clutter-gst-playback
36
* @short_description: A #ClutterGstPlayback to play media streams
38
* #ClutterGstPlayback implements #ClutterGstPlayer.
45
#include "clutter-gst-debug.h"
46
#include "clutter-gst-enum-types.h"
47
#include "clutter-gst-marshal.h"
48
#include "clutter-gst-playback.h"
49
#include "clutter-gst-player.h"
50
#include "clutter-gst-private.h"
55
#include <gst/video/video.h>
56
#include <gst/tag/tag.h>
57
#include <gst/audio/streamvolume.h>
59
#if defined (CLUTTER_WINDOWING_X11) && defined (HAVE_HW_DECODER_SUPPORT)
60
#define GST_USE_UNSTABLE_API 1
61
#include <gst/video/videocontext.h>
62
#include <clutter/x11/clutter-x11.h>
65
static void player_iface_init (ClutterGstPlayerIface *iface);
67
G_DEFINE_TYPE_WITH_CODE (ClutterGstPlayback, clutter_gst_playback, G_TYPE_OBJECT,
68
G_IMPLEMENT_INTERFACE (CLUTTER_GST_TYPE_PLAYER, player_iface_init))
70
#define GST_PLAYBACK_PRIVATE(o) \
71
(G_TYPE_INSTANCE_GET_PRIVATE ((o), CLUTTER_GST_TYPE_PLAYBACK, ClutterGstPlaybackPrivate))
73
/* idle timeouts (in ms) */
74
#define TICK_TIMEOUT 500
75
#define BUFFERING_TIMEOUT 250
85
PROP_SUBTITLE_FONT_NAME,
107
/* Elements don't expose header files */
109
GST_PLAY_FLAG_VIDEO = (1 << 0),
110
GST_PLAY_FLAG_AUDIO = (1 << 1),
111
GST_PLAY_FLAG_TEXT = (1 << 2),
112
GST_PLAY_FLAG_VIS = (1 << 3),
113
GST_PLAY_FLAG_SOFT_VOLUME = (1 << 4),
114
GST_PLAY_FLAG_NATIVE_AUDIO = (1 << 5),
115
GST_PLAY_FLAG_NATIVE_VIDEO = (1 << 6),
116
GST_PLAY_FLAG_DOWNLOAD = (1 << 7),
117
GST_PLAY_FLAG_BUFFERING = (1 << 8),
118
GST_PLAY_FLAG_DEINTERLACE = (1 << 9)
121
struct _ClutterGstPlaybackPrivate
123
GstElement *pipeline;
125
ClutterGstVideoSink *video_sink;
126
GArray *gst_pipe_sigs;
127
GArray *gst_bus_sigs;
129
ClutterGstFrame *current_frame;
137
guint is_changing_uri : 1;
140
guint in_download_buffering : 1;
142
gdouble stacked_progress;
144
gdouble target_progress;
145
GstState target_state;
146
GstState force_state;
148
guint tick_timeout_id;
149
guint buffering_timeout_id;
151
/* This is a cubic volume, suitable for use in a UI cf. StreamVolume doc */
159
GstSeekFlags seek_flags; /* flags for the seek in set_progress(); */
161
GList *audio_streams;
162
GList *subtitle_tracks;
166
static guint signals[LAST_SIGNAL] = { 0, };
168
static gboolean player_buffering_timeout (gpointer data);
172
#ifdef CLUTTER_GST_ENABLE_DEBUG
174
get_stream_description (GstTagList *tags,
177
gchar *description = NULL;
182
gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &description);
186
const gchar *language = gst_tag_get_language_name (description);
190
g_free (description);
191
description = g_strdup (language);
196
gst_tag_list_get_string (tags, GST_TAG_CODEC, &description);
200
description = g_strdup_printf ("Track %d", track_num);
206
list_to_string (GList *list)
215
return g_strdup ("<empty list>");
217
string = g_string_new (NULL);
218
n = g_list_length (list);
219
for (i = 0, l = list; i < n - 1; i++, l = g_list_next (l))
222
description = get_stream_description (tags, i);
223
g_string_append_printf (string, "%s, ", description);
224
g_free (description);
228
description = get_stream_description (tags, i);
229
g_string_append_printf (string, "%s", (gchar *) description);
230
g_free (description);
232
return g_string_free (string, FALSE);
237
gst_state_to_string (GstState state)
241
case GST_STATE_VOID_PENDING:
245
case GST_STATE_READY:
247
case GST_STATE_PAUSED:
249
case GST_STATE_PLAYING:
253
return "Unknown state";
257
free_tags_list (GList **listp)
265
gst_tag_list_unref (l->data);
266
l = g_list_delete_link (l, l);
272
static GstStateChangeReturn
273
set_pipeline_target_state (ClutterGstPlayback *self, GstState state)
275
ClutterGstPlaybackPrivate *priv = self->priv;
276
GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
278
priv->target_state = state;
279
if (!priv->pipeline || !priv->uri)
282
if (priv->force_state == GST_STATE_VOID_PENDING)
283
ret = gst_element_set_state (priv->pipeline, state);
289
static GstStateChangeReturn
290
force_pipeline_state (ClutterGstPlayback *self, GstState state)
292
ClutterGstPlaybackPrivate *priv = self->priv;
293
GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
295
priv->force_state = state;
299
if (state == GST_STATE_VOID_PENDING)
300
state = priv->target_state;
302
ret = gst_element_set_state (priv->pipeline, state);
310
tick_timeout (gpointer data)
312
GObject *player = data;
314
g_object_notify (player, "progress");
320
player_set_user_agent (ClutterGstPlayback *self,
321
const gchar *user_agent)
323
ClutterGstPlaybackPrivate *priv = self->priv;
327
if (user_agent == NULL)
330
g_object_get (priv->pipeline, "source", &source, NULL);
334
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (source),
339
CLUTTER_GST_NOTE (MEDIA, "setting user agent: %s", user_agent);
341
g_object_set (source, "user-agent", user_agent, NULL);
345
autoload_subtitle (ClutterGstPlayback *self,
348
ClutterGstPlaybackPrivate *priv = self->priv;
349
gchar *path, *dot, *subtitle_path;
353
static const char subtitles_extensions[][4] =
363
/* do not try to look for subtitle files if the video file is not mounted
365
if (!g_str_has_prefix (uri, "file://"))
368
/* Retrieve the absolute path of the video file */
369
video = g_file_new_for_uri (uri);
370
path = g_file_get_path (video);
371
g_object_unref (video);
375
/* Put a '\0' after the dot of the extension */
376
dot = strrchr (path, '.');
383
/* we can't use path as the temporary buffer for the paths of the potential
384
* subtitle files as we may not have enough room there */
385
subtitle_path = g_malloc (strlen (path) + 1 + 4);
386
strcpy (subtitle_path, path);
388
/* reuse dot to point to the first byte of the extension of subtitle_path */
389
dot = subtitle_path + (dot - path);
391
for (i = 0; i < G_N_ELEMENTS (subtitles_extensions); i++)
395
memcpy (dot, subtitles_extensions[i], 4);
396
candidate = g_file_new_for_path (subtitle_path);
397
if (g_file_query_exists (candidate, NULL))
401
suburi = g_file_get_uri (candidate);
403
CLUTTER_GST_NOTE (MEDIA, "found subtitle: %s", suburi);
405
g_object_set (priv->pipeline, "suburi", suburi, NULL);
408
g_object_unref (candidate);
412
g_object_unref (candidate);
416
g_free (subtitle_path);
420
set_subtitle_uri (ClutterGstPlayback *self,
423
ClutterGstPlaybackPrivate *priv = self->priv;
429
CLUTTER_GST_NOTE (MEDIA, "setting subtitle URI: %s", uri);
431
g_object_get (priv->pipeline, "flags", &flags, NULL);
433
g_object_set (priv->pipeline, "suburi", uri, NULL);
435
g_object_set (priv->pipeline, "flags", flags, NULL);
439
player_configure_buffering_timeout (ClutterGstPlayback *self,
442
ClutterGstPlaybackPrivate *priv = self->priv;
444
if (priv->buffering_timeout_id)
446
g_source_remove (priv->buffering_timeout_id);
447
priv->buffering_timeout_id = 0;
452
priv->buffering_timeout_id =
453
g_timeout_add (ms, player_buffering_timeout, self);
454
player_buffering_timeout (self);
460
player_clear_download_buffering (ClutterGstPlayback *self)
462
ClutterGstPlaybackPrivate *priv = self->priv;
464
player_configure_buffering_timeout (self, 0);
466
priv->in_download_buffering = FALSE;
470
is_live_pipeline (GstElement *pipeline)
472
GstState state, pending;
473
GstStateChangeReturn state_change_res;
474
gboolean is_live = FALSE;
476
/* get pipeline current state, we need to change the pipeline state to PAUSED to
477
* see if we are dealing with a live source and we want to restore the pipeline
478
* state afterwards */
479
gst_element_get_state (pipeline, &state, &pending, 0);
481
/* a pipeline with live source should return NO_PREROLL in PAUSE */
482
state_change_res = gst_element_set_state (pipeline, GST_STATE_PAUSED);
483
is_live = (state_change_res == GST_STATE_CHANGE_NO_PREROLL);
485
/* restore pipeline previous state */
486
if (pending == GST_STATE_VOID_PENDING)
487
gst_element_set_state (pipeline, state);
489
gst_element_set_state (pipeline, pending);
495
set_uri (ClutterGstPlayback *self,
498
ClutterGstPlaybackPrivate *priv = self->priv;
500
CLUTTER_GST_NOTE (MEDIA, "setting uri %s", uri);
507
priv->in_eos = FALSE;
508
priv->in_error = FALSE;
512
priv->uri = g_strdup (uri);
514
/* Ensure the tick timeout is installed.
516
* We also have it installed in PAUSED state, because
517
* seeks etc may have a delayed effect on the position.
519
if (priv->tick_timeout_id == 0)
521
priv->tick_timeout_id =
522
g_timeout_add (TICK_TIMEOUT, tick_timeout, self);
525
/* try to load subtitles based on the uri of the file */
526
set_subtitle_uri (self, NULL);
528
/* reset the states of download buffering */
529
player_clear_download_buffering (self);
535
if (priv->tick_timeout_id)
537
g_source_remove (priv->tick_timeout_id);
538
priv->tick_timeout_id = 0;
541
if (priv->buffering_timeout_id)
543
g_source_remove (priv->buffering_timeout_id);
544
priv->buffering_timeout_id = 0;
548
priv->can_seek = FALSE;
549
priv->duration = 0.0;
550
priv->stacked_progress = -1.0;
551
priv->target_progress = 0.0;
553
CLUTTER_GST_NOTE (MEDIA, "setting URI: %s", uri);
557
/* Change uri, force the pipeline to NULL so the uri can be changed, then
558
* set the pipeline to "unforced" mode so it will return to its current
560
force_pipeline_state (self, GST_STATE_NULL);
562
g_object_set (priv->pipeline, "uri", uri, NULL);
564
priv->is_live = is_live_pipeline (priv->pipeline);
566
set_subtitle_uri (self, NULL);
567
autoload_subtitle (self, uri);
569
force_pipeline_state (self, GST_STATE_VOID_PENDING);
571
priv->is_changing_uri = TRUE;
575
priv->is_idle = TRUE;
576
priv->is_live = FALSE;
577
set_subtitle_uri (self, NULL);
578
gst_element_set_state (priv->pipeline, GST_STATE_NULL);
579
g_object_notify (G_OBJECT (self), "idle");
583
* Emit notifications for all these to make sure UI is not showing
584
* any properties of the old URI.
586
g_object_notify (G_OBJECT (self), "uri");
587
g_object_notify (G_OBJECT (self), "can-seek");
588
g_object_notify (G_OBJECT (self), "duration");
589
g_object_notify (G_OBJECT (self), "progress");
591
free_tags_list (&priv->audio_streams);
592
CLUTTER_GST_NOTE (AUDIO_STREAM, "audio-streams changed");
593
g_object_notify (G_OBJECT (self), "audio-streams");
595
free_tags_list (&priv->subtitle_tracks);
596
CLUTTER_GST_NOTE (SUBTITLES, "subtitle-tracks changed");
597
g_object_notify (G_OBJECT (self), "subtitle-tracks");
601
get_audio_volume (ClutterGstPlayback *self)
603
ClutterGstPlaybackPrivate *priv = self->priv;
608
CLUTTER_GST_NOTE (MEDIA, "get volume: %.02f", priv->volume);
614
set_audio_volume (ClutterGstPlayback *self,
617
ClutterGstPlaybackPrivate *priv = self->priv;
622
CLUTTER_GST_NOTE (MEDIA, "set volume: %.02f", volume);
624
volume = CLAMP (volume, 0.0, 1.0);
625
gst_stream_volume_set_volume (GST_STREAM_VOLUME (priv->pipeline),
626
GST_STREAM_VOLUME_FORMAT_CUBIC,
628
g_object_notify (G_OBJECT (self), "audio-volume");
632
set_in_seek (ClutterGstPlayback *self,
635
ClutterGstPlaybackPrivate *priv = self->priv;
637
priv->in_seek = seeking;
638
g_object_notify (G_OBJECT (self), "in-seek");
643
set_playing (ClutterGstPlayback *self,
646
ClutterGstPlaybackPrivate *priv = self->priv;
651
CLUTTER_GST_NOTE (MEDIA, "set playing: %d", playing);
653
priv->in_error = FALSE;
654
priv->in_eos = FALSE;
656
if (!priv->uri && playing)
658
g_warning ("Unable to start playing: no URI is set");
662
set_pipeline_target_state (self,
663
playing ? GST_STATE_PLAYING : GST_STATE_PAUSED);
665
g_object_notify (G_OBJECT (self), "playing");
666
g_object_notify (G_OBJECT (self), "progress");
670
get_playing (ClutterGstPlayback *player)
672
ClutterGstPlaybackPrivate *priv = player->priv;
675
if (!priv->pipeline || !priv->uri)
678
playing = priv->target_state == GST_STATE_PLAYING;
680
CLUTTER_GST_NOTE (MEDIA, "get playing: %d", playing);
686
set_progress (ClutterGstPlayback *self,
689
ClutterGstPlaybackPrivate *priv = self->priv;
690
GstQuery *duration_q;
696
CLUTTER_GST_NOTE (MEDIA, "set progress: %.02f", progress);
698
priv->in_eos = FALSE;
699
priv->target_progress = progress;
701
if (priv->is_changing_uri || priv->in_seek)
703
/* We can't seek right now, let's save the position where we
704
want to seek and do that later. */
705
CLUTTER_GST_NOTE (MEDIA,
706
"already seeking. stacking progress point.");
707
priv->stacked_progress = progress;
711
duration_q = gst_query_new_duration (GST_FORMAT_TIME);
714
if (gst_element_query (priv->pipeline, duration_q))
718
gst_query_parse_duration (duration_q, NULL, &duration);
720
position = progress * duration;
722
else if (progress != 0.0)
724
/* Can't seek into the file if the duration is unknown */
728
gst_element_seek (priv->pipeline,
731
GST_SEEK_FLAG_FLUSH | priv->seek_flags,
734
GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
736
set_in_seek (self, TRUE);
737
CLUTTER_GST_NOTE (MEDIA, "set progress (seeked): %.02f", progress);
738
/* If we seek we want to go and babysit the buffering again in case of
739
* download buffering */
740
if (!priv->is_live && clutter_gst_playback_get_buffering_mode (self) ==
741
CLUTTER_GST_BUFFERING_MODE_DOWNLOAD)
743
force_pipeline_state (self, GST_STATE_PAUSED);
745
priv->stacked_progress = -1.0;
748
gst_query_unref (duration_q);
752
get_progress (ClutterGstPlayback *self)
754
ClutterGstPlaybackPrivate *priv = self->priv;
755
GstQuery *position_q, *duration_q;
761
/* when hitting an error or after an EOS, playbin has some weird values when
762
* querying the duration and progress. We default to 0.0 on error and 1.0 on
766
CLUTTER_GST_NOTE (MEDIA, "get progress (error): 0.0");
772
CLUTTER_GST_NOTE (MEDIA, "get progress (eos): 1.0");
776
/* When seeking, the progress returned by playbin is 0.0. We want that to be
777
* the last known position instead as returning 0.0 will have some ugly
778
* effects, say on a progress bar getting updated from the progress tick. */
779
if (priv->in_seek || priv->is_changing_uri)
781
CLUTTER_GST_NOTE (MEDIA, "get progress (target): %.02f",
782
priv->target_progress);
783
return priv->target_progress;
786
position_q = gst_query_new_position (GST_FORMAT_TIME);
787
duration_q = gst_query_new_duration (GST_FORMAT_TIME);
789
if (gst_element_query (priv->pipeline, position_q) &&
790
gst_element_query (priv->pipeline, duration_q))
792
gint64 position, duration;
794
position = duration = 0;
796
gst_query_parse_position (position_q, NULL, &position);
797
gst_query_parse_duration (duration_q, NULL, &duration);
799
progress = CLAMP ((gdouble) position / (gdouble) duration, 0.0, 1.0);
804
gst_query_unref (position_q);
805
gst_query_unref (duration_q);
807
CLUTTER_GST_NOTE (MEDIA, "get progress (pipeline): %.02f", progress);
813
set_subtitle_font_name (ClutterGstPlayback *self,
814
const gchar *font_name)
816
ClutterGstPlaybackPrivate *priv = self->priv;
821
CLUTTER_GST_NOTE (MEDIA, "setting subtitle font to %s", font_name);
823
g_free (priv->font_name);
824
priv->font_name = g_strdup (font_name);
825
g_object_set (priv->pipeline, "subtitle-font-desc", font_name, NULL);
829
get_position (ClutterGstPlayback *self)
831
ClutterGstPlaybackPrivate *priv = self->priv;
833
GstFormat format = GST_FORMAT_TIME;
836
success = gst_element_query_position (priv->pipeline, format, &position);
837
if (G_UNLIKELY (success != TRUE))
840
return (gdouble) position / GST_SECOND;
844
player_should_buffer (ClutterGstPlayback *self, GstQuery *query)
846
ClutterGstPlaybackPrivate *priv = self->priv;
848
gboolean ret = FALSE;
853
/* Use the estimated total duration left as estimated by queue2
854
* based on the average incoming bitrate, we can stop buffering once
855
* the remaining download takes less time then the remaining play
856
* time (with a 10% safety margin). However regardless of that keep
857
* buffering as long as queue2 indicates that buffering should
858
* happen (based on its high water marks) */
859
gst_query_parse_buffering_range (query, NULL, NULL, NULL, &left);
860
gst_query_parse_buffering_percent (query, &busy, NULL);
862
position = get_position (self);
864
time_left = priv->duration - position;
868
if (left == -1 || (!busy && (((gdouble)left * 1.1) / 1000) < time_left))
881
player_buffering_timeout (gpointer data)
883
ClutterGstPlayback *self = (ClutterGstPlayback *) data;
884
ClutterGstPlaybackPrivate *priv = self->priv;
889
GstBufferingMode mode;
890
gboolean should_buffer;
893
/* currently seeking, wait until it's done to get consistent results */
897
/* queue2 only knows about _PERCENT and _BYTES */
898
query = gst_query_new_buffering (GST_FORMAT_BYTES);
899
res = gst_element_query (priv->pipeline, query);
903
CLUTTER_GST_NOTE (BUFFERING, "Buffer query failed");
907
gst_query_parse_buffering_stats (query, &mode, NULL, NULL, NULL);
909
if (mode != GST_BUFFERING_DOWNLOAD)
911
CLUTTER_GST_NOTE (BUFFERING,
912
"restoring the pipeline as we're not download buffering");
914
force_pipeline_state (self, GST_STATE_VOID_PENDING);
916
player_clear_download_buffering (self);
920
g_signal_emit (self, signals[SHOULD_BUFFER], 0, query, &should_buffer);
923
if (priv->buffer_fill != 0.0)
925
priv->buffer_fill = 0.0;
926
g_object_notify (G_OBJECT (self), "buffer-fill");
928
/* Force to paused if we haven't yet */
929
if (priv->force_state == GST_STATE_VOID_PENDING)
931
/* Starting buffering again */
932
CLUTTER_GST_NOTE (BUFFERING,
933
"pausing the pipeline for buffering: %d", busy);
934
force_pipeline_state (self, GST_STATE_PAUSED);
939
/* Done buffering signal and stop the timeouts */
940
player_clear_download_buffering (self);
941
force_pipeline_state (self, GST_STATE_VOID_PENDING);
942
if (priv->buffer_fill != 1.0)
944
priv->buffer_fill = 1.0;
945
g_object_notify (G_OBJECT (self), "buffer-fill");
950
gst_query_unref (query);
955
bus_message_error_cb (GstBus *bus,
957
ClutterGstPlayback *self)
959
ClutterGstPlaybackPrivate *priv = self->priv;
960
GError *error = NULL;
962
gst_element_set_state (priv->pipeline, GST_STATE_NULL);
964
gst_message_parse_error (message, &error, NULL);
965
g_signal_emit_by_name (self, "error", error);
966
g_error_free (error);
968
priv->is_idle = TRUE;
969
g_object_notify (G_OBJECT (self), "idle");
973
* This is what's intented in the EOS callback:
974
* - receive EOS from playbin
975
* - fire the EOS signal, the user can install a signal handler to loop the
976
* video for instance.
977
* - after having emitted the signal, check the state of the pipeline
978
* - if the pipeline has been set back to playing or pause, don't touch the
979
* idle state. This will avoid drawing a frame (or more) with the idle
980
* material when looping
983
bus_message_eos_cb (GstBus *bus,
985
ClutterGstPlayback *self)
987
ClutterGstPlaybackPrivate *priv = self->priv;
988
GstState state, pending;
992
gst_element_set_state (priv->pipeline, GST_STATE_READY);
994
g_signal_emit_by_name (self, "eos");
995
g_object_notify (G_OBJECT (self), "progress");
997
gst_element_get_state (priv->pipeline, &state, &pending, 0);
1001
if (!(state == GST_STATE_PLAYING || state == GST_STATE_PAUSED))
1003
priv->is_idle = TRUE;
1004
g_object_notify (G_OBJECT (self), "idle");
1009
bus_message_buffering_cb (GstBus *bus,
1010
GstMessage *message,
1011
ClutterGstPlayback *self)
1013
ClutterGstPlaybackPrivate *priv = self->priv;
1014
GstBufferingMode mode;
1015
GstState current_state;
1016
gint buffer_percent;
1018
gst_message_parse_buffering_stats (message, &mode, NULL, NULL, NULL);
1020
if (mode != GST_BUFFERING_DOWNLOAD)
1021
priv->in_download_buffering = FALSE;
1025
case GST_BUFFERING_LIVE:
1026
case GST_BUFFERING_STREAM:
1027
gst_message_parse_buffering (message, &buffer_percent);
1028
priv->buffer_fill = CLAMP ((gdouble) buffer_percent / 100.0, 0.0, 1.0);
1030
CLUTTER_GST_NOTE (BUFFERING, "buffer-fill: %.02f", priv->buffer_fill);
1032
/* no state management needed for live pipelines */
1035
/* The playbin documentation says that we need to pause the pipeline
1036
* when there's not enough data yet. We try to limit the calls to
1037
* gst_element_set_state() */
1038
gst_element_get_state (priv->pipeline, ¤t_state, NULL, 0);
1040
if (priv->buffer_fill < 1.0)
1042
if (priv->force_state != GST_STATE_PAUSED)
1044
CLUTTER_GST_NOTE (BUFFERING, "pausing the pipeline");
1045
force_pipeline_state (self, GST_STATE_PAUSED);
1050
if (priv->force_state != GST_STATE_VOID_PENDING)
1052
CLUTTER_GST_NOTE (BUFFERING, "restoring the pipeline");
1053
force_pipeline_state (self, GST_STATE_VOID_PENDING);
1058
g_object_notify (G_OBJECT (self), "buffer-fill");
1061
case GST_BUFFERING_DOWNLOAD:
1062
if (priv->in_download_buffering)
1065
priv->buffer_fill = 0.0;
1066
g_object_notify (G_OBJECT (self), "buffer-fill");
1067
/* install the querying idle handler the first time we receive a download
1068
* buffering message */
1069
player_configure_buffering_timeout (self, BUFFERING_TIMEOUT);
1071
priv->in_download_buffering = TRUE;
1074
case GST_BUFFERING_TIMESHIFT:
1076
g_warning ("Buffering mode %d not handled", mode);
1082
on_source_changed (GstElement *pipeline,
1084
ClutterGstPlayback *self)
1086
ClutterGstPlaybackPrivate *priv = self->priv;
1088
player_set_user_agent (self, priv->user_agent);
1092
query_duration (ClutterGstPlayback *self)
1094
ClutterGstPlaybackPrivate *priv = self->priv;
1097
gdouble new_duration, difference;
1099
success = gst_element_query_duration (priv->pipeline,
1102
if (G_UNLIKELY (success != TRUE))
1105
new_duration = (gdouble) duration / GST_SECOND;
1107
/* while we store the new duration if it sligthly changes, the duration
1108
* signal is sent only if the new duration is at least one second different
1109
* from the old one (as the duration signal is mainly used to update the
1110
* time displayed in a UI */
1111
difference = ABS (priv->duration - new_duration);
1112
if (difference > 1e-3)
1114
CLUTTER_GST_NOTE (MEDIA, "duration: %.02f", new_duration);
1115
priv->duration = new_duration;
1117
if (difference > 1.0)
1118
g_object_notify (G_OBJECT (self), "duration");
1123
bus_message_duration_changed_cb (GstBus *bus,
1124
GstMessage *message,
1125
ClutterGstPlayback *self)
1127
/* GstElements send a duration-changed message on the bus to signal
1128
* that the duration has changed and should be re-queried */
1129
query_duration (self);
1133
bus_message_state_change_cb (GstBus *bus,
1134
GstMessage *message,
1135
ClutterGstPlayback *self)
1137
ClutterGstPlaybackPrivate *priv = self->priv;
1138
GstState old_state, new_state;
1141
src = GST_MESSAGE_SRC (message);
1142
if (src != priv->pipeline)
1145
gst_message_parse_state_changed (message, &old_state, &new_state, NULL);
1147
CLUTTER_GST_NOTE (MEDIA, "state change: %s -> %s",
1148
gst_state_to_string (old_state),
1149
gst_state_to_string (new_state));
1151
if (old_state == new_state)
1154
if (old_state == GST_STATE_READY &&
1155
new_state == GST_STATE_PAUSED)
1159
/* Determine whether we can seek */
1160
query = gst_query_new_seeking (GST_FORMAT_TIME);
1162
if (gst_element_query (priv->pipeline, query))
1164
gboolean can_seek = FALSE;
1166
gst_query_parse_seeking (query, NULL, &can_seek,
1170
priv->can_seek = (can_seek == TRUE) ? TRUE : FALSE;
1174
/* could not query for ability to seek by querying the
1175
* pipeline; let's crudely try by using the URI
1177
if (priv->uri && g_str_has_prefix (priv->uri, "http://"))
1178
priv->can_seek = FALSE;
1180
priv->can_seek = TRUE;
1183
gst_query_unref (query);
1185
CLUTTER_GST_NOTE (MEDIA, "can-seek: %d", priv->can_seek);
1187
g_object_notify (G_OBJECT (self), "can-seek");
1189
query_duration (self);
1191
priv->is_changing_uri = FALSE;
1192
if (priv->stacked_progress != -1.0 && priv->can_seek)
1194
set_progress (self, priv->stacked_progress);
1198
/* is_idle controls the drawing with the idle material */
1199
if (old_state > GST_STATE_READY && new_state == GST_STATE_READY)
1201
priv->is_idle = TRUE;
1202
g_object_notify (G_OBJECT (self), "idle");
1204
else if (new_state == GST_STATE_PLAYING)
1206
priv->is_idle = FALSE;
1207
g_object_notify (G_OBJECT (self), "idle");
1212
bus_message_async_done_cb (GstBus *bus,
1213
GstMessage *message,
1214
ClutterGstPlayback *self)
1216
ClutterGstPlaybackPrivate *priv = self->priv;
1220
g_object_notify (G_OBJECT (self), "progress");
1222
set_in_seek (self, FALSE);
1223
player_configure_buffering_timeout (self, BUFFERING_TIMEOUT);
1225
if (priv->stacked_progress != -1.0)
1227
set_progress (self, priv->stacked_progress);
1233
on_volume_changed_main_context (gpointer data)
1235
ClutterGstPlayback *self = CLUTTER_GST_PLAYBACK (data);
1236
ClutterGstPlaybackPrivate *priv = self->priv;
1240
gst_stream_volume_get_volume (GST_STREAM_VOLUME (priv->pipeline),
1241
GST_STREAM_VOLUME_FORMAT_CUBIC);
1242
priv->volume = volume;
1244
g_object_notify (G_OBJECT (self), "audio-volume");
1246
g_object_unref (self);
1251
/* playbin proxies the volume property change notification directly from
1252
* the element having the "volume" property. This means this callback is
1253
* called from the thread that runs the element, potentially different from
1254
* the main thread */
1256
on_volume_changed (GstElement *pipeline,
1258
ClutterGstPlayback *self)
1260
g_idle_add (on_volume_changed_main_context, g_object_ref (self));
1264
get_tags (GstElement *pipeline,
1265
const gchar *property_name,
1266
const gchar *action_signal)
1271
g_object_get (G_OBJECT (pipeline), property_name, &n, NULL);
1275
for (i = 0; i < n; i++)
1277
GstTagList *tags = NULL;
1279
g_signal_emit_by_name (G_OBJECT (pipeline), action_signal, i, &tags);
1281
ret = g_list_prepend (ret, tags);
1284
return g_list_reverse (ret);
1288
on_audio_changed_main_context (gpointer data)
1290
ClutterGstPlayback *self = CLUTTER_GST_PLAYBACK (data);
1291
ClutterGstPlaybackPrivate *priv = self->priv;
1293
free_tags_list (&priv->audio_streams);
1294
priv->audio_streams = get_tags (priv->pipeline, "n-audio", "get-audio-tags");
1296
CLUTTER_GST_NOTE (AUDIO_STREAM, "audio-streams changed");
1298
g_object_notify (G_OBJECT (self), "audio-streams");
1300
g_object_unref (self);
1305
/* same explanation as for notify::volume's usage of g_idle_add() */
1307
on_audio_changed (GstElement *pipeline,
1308
ClutterGstPlayback *self)
1310
g_idle_add (on_audio_changed_main_context, g_object_ref (self));
1314
on_audio_tags_changed (GstElement *pipeline,
1316
ClutterGstPlayback *self)
1318
gint current_stream;
1320
g_object_get (G_OBJECT (pipeline), "current-audio", ¤t_stream, NULL);
1322
if (current_stream != stream)
1325
g_idle_add (on_audio_changed_main_context, g_object_ref (self));
1329
on_current_audio_changed_main_context (gpointer data)
1331
ClutterGstPlayback *self = CLUTTER_GST_PLAYBACK (data);
1333
CLUTTER_GST_NOTE (AUDIO_STREAM, "audio stream changed");
1334
g_object_notify (G_OBJECT (self), "audio-stream");
1336
g_object_unref (self);
1342
on_current_audio_changed (GstElement *pipeline,
1344
ClutterGstPlayback *self)
1346
g_idle_add (on_current_audio_changed_main_context, g_object_ref (self));
1350
on_text_changed_main_context (gpointer data)
1352
ClutterGstPlayback *self = CLUTTER_GST_PLAYBACK (data);
1353
ClutterGstPlaybackPrivate *priv = self->priv;
1355
free_tags_list (&priv->subtitle_tracks);
1356
priv->subtitle_tracks = get_tags (priv->pipeline, "n-text", "get-text-tags");
1358
CLUTTER_GST_NOTE (AUDIO_STREAM, "subtitle-tracks changed");
1360
g_object_notify (G_OBJECT (self), "subtitle-tracks");
1362
g_object_unref (self);
1367
/* same explanation as for notify::volume's usage of g_idle_add() */
1369
on_text_changed (GstElement *pipeline,
1370
ClutterGstPlayback *self)
1372
g_idle_add (on_text_changed_main_context, g_object_ref (self));
1376
on_text_tags_changed (GstElement *pipeline,
1378
ClutterGstPlayback *self)
1380
g_idle_add (on_text_changed_main_context, g_object_ref (self));
1384
on_current_text_changed_main_context (gpointer data)
1386
ClutterGstPlayback *self = CLUTTER_GST_PLAYBACK (data);
1388
CLUTTER_GST_NOTE (AUDIO_STREAM, "text stream changed");
1389
g_object_notify (G_OBJECT (self), "subtitle-track");
1391
g_object_unref (self);
1397
on_current_text_changed (GstElement *pipeline,
1399
ClutterGstPlayback *self)
1401
g_idle_add (on_current_text_changed_main_context, g_object_ref (self));
1406
static ClutterGstFrame *
1407
clutter_gst_playback_get_frame (ClutterGstPlayer *self)
1409
ClutterGstPlaybackPrivate *priv = CLUTTER_GST_PLAYBACK (self)->priv;
1411
return priv->current_frame;
1415
clutter_gst_playback_get_pipeline (ClutterGstPlayer *self)
1417
ClutterGstPlaybackPrivate *priv = CLUTTER_GST_PLAYBACK (self)->priv;
1419
return priv->pipeline;
1422
static ClutterGstVideoSink *
1423
clutter_gst_playback_get_video_sink (ClutterGstPlayer *self)
1425
ClutterGstPlaybackPrivate *priv = CLUTTER_GST_PLAYBACK (self)->priv;
1427
return priv->video_sink;
1431
clutter_gst_playback_get_idle (ClutterGstPlayer *self)
1433
ClutterGstPlaybackPrivate *priv = CLUTTER_GST_PLAYBACK (self)->priv;
1435
return priv->is_idle;
1439
clutter_gst_playback_get_audio_volume (ClutterGstPlayer *self)
1441
return get_audio_volume (CLUTTER_GST_PLAYBACK (self));
1445
clutter_gst_playback_set_audio_volume (ClutterGstPlayer *self,
1448
set_audio_volume (CLUTTER_GST_PLAYBACK (self), volume);
1452
clutter_gst_playback_get_playing (ClutterGstPlayer *self)
1454
return get_playing (CLUTTER_GST_PLAYBACK (self));
1458
clutter_gst_playback_set_playing (ClutterGstPlayer *self,
1461
set_playing (CLUTTER_GST_PLAYBACK (self), playing);
1465
player_iface_init (ClutterGstPlayerIface *iface)
1467
iface->get_frame = clutter_gst_playback_get_frame;
1468
iface->get_pipeline = clutter_gst_playback_get_pipeline;
1469
iface->get_video_sink = clutter_gst_playback_get_video_sink;
1471
iface->get_idle = clutter_gst_playback_get_idle;
1473
iface->get_audio_volume = clutter_gst_playback_get_audio_volume;
1474
iface->set_audio_volume = clutter_gst_playback_set_audio_volume;
1476
iface->get_playing = clutter_gst_playback_get_playing;
1477
iface->set_playing = clutter_gst_playback_set_playing;
1483
clutter_gst_playback_get_property (GObject *object,
1488
ClutterGstPlayback *self = CLUTTER_GST_PLAYBACK (object);
1489
ClutterGstPlaybackPrivate *priv = self->priv;
1492
switch (property_id)
1495
g_value_set_string (value, priv->uri);
1499
g_value_set_boolean (value, get_playing (self));
1503
g_value_set_double (value, get_progress (self));
1506
case PROP_SUBTITLE_URI:
1507
g_object_get (priv->pipeline, "suburi", &str, NULL);
1508
g_value_take_string (value, str);
1511
case PROP_SUBTITLE_FONT_NAME:
1512
g_value_set_string (value, priv->font_name);
1515
case PROP_AUDIO_VOLUME:
1516
g_value_set_double (value, get_audio_volume (self));
1520
g_value_set_boolean (value, priv->can_seek);
1523
case PROP_BUFFER_FILL:
1524
g_value_set_double (value, priv->buffer_fill);
1528
g_value_set_double (value, priv->duration);
1532
g_value_set_boolean (value, priv->is_idle);
1535
case PROP_USER_AGENT:
1539
user_agent = clutter_gst_playback_get_user_agent (self);
1540
g_value_take_string (value, user_agent);
1544
case PROP_SEEK_FLAGS:
1546
ClutterGstSeekFlags seek_flags;
1548
seek_flags = clutter_gst_playback_get_seek_flags (self);
1549
g_value_set_flags (value, seek_flags);
1553
case PROP_AUDIO_STREAMS:
1554
g_value_set_pointer (value, priv->audio_streams);
1557
case PROP_AUDIO_STREAM:
1561
index_ = clutter_gst_playback_get_audio_stream (self);
1562
g_value_set_int (value, index_);
1566
case PROP_SUBTITLE_TRACKS:
1567
g_value_set_pointer (value, priv->subtitle_tracks);
1570
case PROP_SUBTITLE_TRACK:
1574
index_ = clutter_gst_playback_get_subtitle_track (self);
1575
g_value_set_int (value, index_);
1580
g_value_set_boolean (value, priv->in_seek);
1584
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1589
clutter_gst_playback_set_property (GObject *object,
1591
const GValue *value,
1594
ClutterGstPlayback *self = CLUTTER_GST_PLAYBACK (object);
1596
switch (property_id)
1599
set_uri (self, g_value_get_string (value));
1603
set_playing (self, g_value_get_boolean (value));
1607
set_progress (self, g_value_get_double (value));
1610
case PROP_SUBTITLE_URI:
1611
set_subtitle_uri (self, g_value_get_string (value));
1614
case PROP_SUBTITLE_FONT_NAME:
1615
set_subtitle_font_name (self, g_value_get_string (value));
1618
case PROP_AUDIO_VOLUME:
1619
set_audio_volume (self, g_value_get_double (value));
1622
case PROP_USER_AGENT:
1623
clutter_gst_playback_set_user_agent (self,
1624
g_value_get_string (value));
1627
case PROP_SEEK_FLAGS:
1628
clutter_gst_playback_set_seek_flags (self,
1629
g_value_get_flags (value));
1632
case PROP_AUDIO_STREAM:
1633
clutter_gst_playback_set_audio_stream (self,
1634
g_value_get_int (value));
1637
case PROP_SUBTITLE_TRACK:
1638
clutter_gst_playback_set_subtitle_track (self,
1639
g_value_get_int (value));
1643
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1648
clutter_gst_playback_dispose (GObject *object)
1650
ClutterGstPlaybackPrivate *priv = CLUTTER_GST_PLAYBACK (object)->priv;
1653
if (priv->tick_timeout_id)
1655
g_source_remove (priv->tick_timeout_id);
1656
priv->tick_timeout_id = 0;
1659
if (priv->buffering_timeout_id)
1661
g_source_remove (priv->buffering_timeout_id);
1662
priv->buffering_timeout_id = 0;
1667
for (i = 0; i < priv->gst_bus_sigs->len; i++)
1668
g_signal_handler_disconnect (priv->bus,
1669
g_array_index (priv->gst_bus_sigs, gulong, i));
1670
gst_bus_remove_signal_watch (priv->bus);
1676
for (i = 0; i < priv->gst_pipe_sigs->len; i++)
1677
g_signal_handler_disconnect (priv->pipeline,
1678
g_array_index (priv->gst_pipe_sigs, gulong, i));
1679
gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1680
g_clear_object (&priv->pipeline);
1683
if (priv->current_frame)
1685
g_boxed_free (CLUTTER_GST_TYPE_FRAME, priv->current_frame);
1686
priv->current_frame = NULL;
1690
g_free (priv->font_name);
1691
g_free (priv->user_agent);
1692
priv->uri = priv->font_name = priv->user_agent = NULL;
1693
free_tags_list (&priv->audio_streams);
1694
free_tags_list (&priv->subtitle_tracks);
1696
G_OBJECT_CLASS (clutter_gst_playback_parent_class)->dispose (object);
1700
clutter_gst_playback_finalize (GObject *object)
1702
G_OBJECT_CLASS (clutter_gst_playback_parent_class)->finalize (object);
1706
clutter_gst_playback_class_init (ClutterGstPlaybackClass *klass)
1708
GObjectClass *object_class = G_OBJECT_CLASS (klass);
1711
g_type_class_add_private (klass, sizeof (ClutterGstPlaybackPrivate));
1713
object_class->get_property = clutter_gst_playback_get_property;
1714
object_class->set_property = clutter_gst_playback_set_property;
1715
object_class->dispose = clutter_gst_playback_dispose;
1716
object_class->finalize = clutter_gst_playback_finalize;
1717
klass->should_buffer = player_should_buffer;
1720
* ClutterGstPlayback:uri:
1722
* The location of a media file, expressed as a valid URI.
1724
pspec = g_param_spec_string ("uri",
1726
"URI of a media file",
1728
CLUTTER_GST_PARAM_READWRITE);
1729
g_object_class_install_property (object_class, PROP_URI, pspec);
1732
* ClutterGstPlayback:progress:
1734
* The current progress of the playback, as a normalized
1735
* value between 0.0 and 1.0.
1737
pspec = g_param_spec_double ("progress",
1739
"Current progress of the playback",
1741
CLUTTER_GST_PARAM_READWRITE);
1742
g_object_class_install_property (object_class, PROP_PROGRESS, pspec);
1745
* ClutterGstPlayback:subtitle-uri:
1747
* The location of a subtitle file, expressed as a valid URI.
1749
pspec = g_param_spec_string ("subtitle-uri",
1751
"URI of a subtitle file",
1753
CLUTTER_GST_PARAM_READWRITE);
1754
g_object_class_install_property (object_class, PROP_SUBTITLE_URI, pspec);
1757
* ClutterGstPlayback:subtitle-font-name:
1759
* The font used to display subtitles. The font description has to
1760
* follow the same grammar as the one recognized by
1761
* pango_font_description_from_string().
1763
pspec = g_param_spec_string ("subtitle-font-name",
1764
"Subtitle Font Name",
1765
"The font used to display subtitles",
1767
CLUTTER_GST_PARAM_READWRITE);
1768
g_object_class_install_property (object_class, PROP_SUBTITLE_FONT_NAME, pspec);
1771
* ClutterGstPlayback:can-seek:
1773
* Whether the current stream is seekable.
1775
pspec = g_param_spec_boolean ("can-seek",
1777
"Whether the current stream is seekable",
1779
CLUTTER_GST_PARAM_READABLE);
1780
g_object_class_install_property (object_class, PROP_CAN_SEEK, pspec);
1783
* ClutterGstPlayback:buffer-fill:
1785
* The fill level of the buffer for the current stream,
1786
* as a value between 0.0 and 1.0.
1788
pspec = g_param_spec_double ("buffer-fill",
1790
"The fill level of the buffer",
1792
CLUTTER_GST_PARAM_READABLE);
1793
g_object_class_install_property (object_class, PROP_BUFFER_FILL, pspec);
1796
* ClutterGstPlayback:duration:
1798
* The duration of the current stream, in seconds
1800
pspec = g_param_spec_double ("duration",
1802
"The duration of the stream, in seconds",
1804
CLUTTER_GST_PARAM_READABLE);
1805
g_object_class_install_property (object_class, PROP_DURATION, pspec);
1808
* ClutterGstPlayback:user-agent:
1810
* The User Agent used by #ClutterGstPlayback with network protocols.
1814
pspec = g_param_spec_string ("user-agent",
1816
"User Agent used with network protocols",
1818
CLUTTER_GST_PARAM_READWRITE);
1819
g_object_class_install_property (object_class, PROP_USER_AGENT, pspec);
1822
* ClutterGstPlayback:seek-flags:
1824
* Flags to use when seeking.
1828
pspec = g_param_spec_flags ("seek-flags",
1830
"Flags to use when seeking",
1831
CLUTTER_GST_TYPE_SEEK_FLAGS,
1832
CLUTTER_GST_SEEK_FLAG_NONE,
1833
CLUTTER_GST_PARAM_READWRITE);
1834
g_object_class_install_property (object_class, PROP_SEEK_FLAGS, pspec);
1837
* ClutterGstPlayback:audio-streams:
1839
* List of audio streams available on the current media.
1843
pspec = g_param_spec_pointer ("audio-streams",
1845
"List of the audio streams of the media",
1846
CLUTTER_GST_PARAM_READABLE);
1847
g_object_class_install_property (object_class, PROP_AUDIO_STREAMS, pspec);
1850
* ClutterGstPlayback:audio-stream:
1852
* Index of the current audio stream.
1856
pspec = g_param_spec_int ("audio-stream",
1858
"Index of the current audio stream",
1860
CLUTTER_GST_PARAM_READWRITE);
1861
g_object_class_install_property (object_class, PROP_AUDIO_STREAM, pspec);
1864
* ClutterGstPlayback:subtitle-tracks:
1866
* List of subtitle tracks available.
1870
pspec = g_param_spec_pointer ("subtitle-tracks",
1872
"List of the subtitles tracks of the media",
1873
CLUTTER_GST_PARAM_READABLE);
1874
g_object_class_install_property (object_class, PROP_SUBTITLE_TRACKS, pspec);
1877
* ClutterGstPlayback:subtitle-track:
1879
* Current subtitle track being displayed.
1883
pspec = g_param_spec_int ("subtitle-track",
1885
"Index of the current subtitles track",
1887
CLUTTER_GST_PARAM_READWRITE);
1888
g_object_class_install_property (object_class, PROP_SUBTITLE_TRACK, pspec);
1891
* ClutterGstPlayback:in-seek:
1893
* Whether or not the stream is being seeked.
1897
pspec = g_param_spec_boolean ("in-seek",
1899
"If currently seeking",
1901
CLUTTER_GST_PARAM_READABLE);
1902
g_object_class_install_property (object_class, PROP_IN_SEEK, pspec);
1905
g_object_class_override_property (object_class,
1907
g_object_class_override_property (object_class,
1908
PROP_PLAYING, "playing");
1909
g_object_class_override_property (object_class,
1910
PROP_AUDIO_VOLUME, "audio-volume");
1916
* ClutterGstPlayback::should-buffer:
1917
* @player: the #ClutterGstPlayback instance that received the signal
1918
* @query: A gst buffering query of format bytes
1920
* The ::should-buffer signal is emitted every time the base class needs to
1921
* decide whether it should continue buffering in download-buffering mode.
1925
signals[SHOULD_BUFFER] =
1926
g_signal_new ("should-buffer",
1927
CLUTTER_GST_TYPE_PLAYBACK,
1929
G_STRUCT_OFFSET (ClutterGstPlaybackClass,
1931
g_signal_accumulator_first_wins, NULL,
1932
_clutter_gst_marshal_BOOL__OBJECT,
1933
G_TYPE_BOOLEAN, 1, GST_TYPE_QUERY);
1937
_new_frame_from_pipeline (ClutterGstVideoSink *sink, ClutterGstPlayback *self)
1939
ClutterGstPlaybackPrivate *priv = self->priv;
1941
clutter_gst_player_update_frame (CLUTTER_GST_PLAYER (self),
1942
&priv->current_frame,
1943
clutter_gst_video_sink_get_frame (sink));
1947
_ready_from_pipeline (ClutterGstVideoSink *sink, ClutterGstPlayback *self)
1949
g_signal_emit_by_name (self, "ready");
1953
_pixel_aspect_ratio_changed (ClutterGstVideoSink *sink,
1955
ClutterGstPlayback *self)
1957
clutter_gst_frame_update_pixel_aspect_ratio (self->priv->current_frame, sink);
1961
get_pipeline (ClutterGstPlayback *self)
1963
ClutterGstPlaybackPrivate *priv = self->priv;
1964
GstElement *pipeline;
1966
pipeline = gst_element_factory_make ("playbin", "pipeline");
1969
g_critical ("Unable to create playbin element");
1973
priv->video_sink = clutter_gst_video_sink_new ();
1975
g_signal_connect (priv->video_sink, "new-frame",
1976
G_CALLBACK (_new_frame_from_pipeline), self);
1977
g_signal_connect (priv->video_sink, "pipeline-ready",
1978
G_CALLBACK (_ready_from_pipeline), self);
1979
g_signal_connect (priv->video_sink, "notify::pixel-aspect-ratio",
1980
G_CALLBACK (_pixel_aspect_ratio_changed), self);
1982
g_object_set (G_OBJECT (pipeline),
1983
"video-sink", priv->video_sink,
1984
"subtitle-font-desc", "Sans 16",
1990
#define connect_signal_custom(store, object, signal, callback, data) \
1992
gulong s = g_signal_connect (object, signal, callback, data); \
1993
g_array_append_val (store, s); \
1996
#define connect_object_custom(store, object, signal, callback, data, flags) \
1998
gulong s = g_signal_connect_object (object, signal, callback, data, flags); \
1999
g_array_append_val (store, s); \
2003
clutter_gst_playback_init (ClutterGstPlayback *self)
2005
ClutterGstPlaybackPrivate *priv;
2007
self->priv = priv = GST_PLAYBACK_PRIVATE (self);
2009
priv->gst_pipe_sigs = g_array_new (FALSE, FALSE, sizeof (gulong));
2010
priv->gst_bus_sigs = g_array_new (FALSE, FALSE, sizeof (gulong));
2012
priv->is_idle = TRUE;
2013
priv->in_seek = FALSE;
2014
priv->is_changing_uri = FALSE;
2015
priv->in_download_buffering = FALSE;
2017
priv->pipeline = get_pipeline (self);
2018
g_assert (priv->pipeline != NULL);
2020
priv->current_frame = clutter_gst_create_blank_frame (NULL);
2022
connect_signal_custom (priv->gst_pipe_sigs, priv->pipeline,
2024
G_CALLBACK (on_source_changed), self);
2026
/* We default to not playing until someone calls set_playing(TRUE) */
2027
priv->target_state = GST_STATE_PAUSED;
2029
/* Default to a fast seek, ie. same effect than set_seek_flags (NONE); */
2030
priv->seek_flags = GST_SEEK_FLAG_KEY_UNIT;
2032
priv->bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
2034
gst_bus_add_signal_watch (priv->bus);
2036
connect_object_custom (priv->gst_bus_sigs,
2037
priv->bus, "message::error",
2038
G_CALLBACK (bus_message_error_cb),
2040
connect_object_custom (priv->gst_bus_sigs,
2041
priv->bus, "message::eos",
2042
G_CALLBACK (bus_message_eos_cb),
2044
connect_object_custom (priv->gst_bus_sigs,
2045
priv->bus, "message::buffering",
2046
G_CALLBACK (bus_message_buffering_cb),
2048
connect_object_custom (priv->gst_bus_sigs,
2049
priv->bus, "message::duration-changed",
2050
G_CALLBACK (bus_message_duration_changed_cb),
2052
connect_object_custom (priv->gst_bus_sigs,
2053
priv->bus, "message::state-changed",
2054
G_CALLBACK (bus_message_state_change_cb),
2056
connect_object_custom (priv->gst_bus_sigs,
2057
priv->bus, "message::async-done",
2058
G_CALLBACK (bus_message_async_done_cb),
2062
connect_signal_custom (priv->gst_pipe_sigs,
2063
priv->pipeline, "notify::volume",
2064
G_CALLBACK (on_volume_changed),
2067
connect_signal_custom (priv->gst_pipe_sigs,
2068
priv->pipeline, "audio-changed",
2069
G_CALLBACK (on_audio_changed),
2071
connect_signal_custom (priv->gst_pipe_sigs,
2072
priv->pipeline, "audio-tags-changed",
2073
G_CALLBACK (on_audio_tags_changed),
2075
connect_signal_custom (priv->gst_pipe_sigs,
2076
priv->pipeline, "notify::current-audio",
2077
G_CALLBACK (on_current_audio_changed),
2080
connect_signal_custom (priv->gst_pipe_sigs,
2081
priv->pipeline, "text-changed",
2082
G_CALLBACK (on_text_changed),
2084
connect_signal_custom (priv->gst_pipe_sigs,
2085
priv->pipeline, "text-tags-changed",
2086
G_CALLBACK (on_text_tags_changed),
2088
connect_signal_custom (priv->gst_pipe_sigs,
2089
priv->pipeline, "notify::current-text",
2090
G_CALLBACK (on_current_text_changed),
2093
#if defined(CLUTTER_WINDOWING_X11) && defined (HAVE_HW_DECODER_SUPPORT)
2094
if (clutter_check_windowing_backend (CLUTTER_WINDOWING_X11))
2095
gst_bus_set_sync_handler (priv->bus, on_sync_message,
2096
clutter_x11_get_default_display (), NULL);
2099
gst_object_unref (GST_OBJECT (priv->bus));
2102
ClutterGstPlayback *
2103
clutter_gst_playback_new (void)
2105
return g_object_new (CLUTTER_GST_TYPE_PLAYBACK, NULL);
2109
* clutter_gst_playback_set_uri:
2110
* @self: a #ClutterGstPlayback
2111
* @uri: the URI of the media stream
2113
* Sets the URI of @self to @uri.
2116
clutter_gst_playback_set_uri (ClutterGstPlayback *self,
2119
g_return_if_fail (CLUTTER_GST_IS_PLAYBACK (self));
2121
g_object_set (G_OBJECT (self), "uri", uri, NULL);
2125
* clutter_gst_playback_get_uri:
2126
* @self: a #ClutterGstPlayback
2128
* Retrieves the URI from @self.
2130
* Return value: the URI of the media stream. Use g_free()
2131
* to free the returned string
2134
clutter_gst_playback_get_uri (ClutterGstPlayback *self)
2136
gchar *retval = NULL;
2138
g_return_val_if_fail (CLUTTER_GST_IS_PLAYBACK (self), NULL);
2140
g_object_get (G_OBJECT (self), "uri", &retval, NULL);
2146
* clutter_gst_playback_set_filename:
2147
* @self: a #ClutterGstPlayback
2148
* @filename: A filename
2150
* Sets the source of @self using a file path.
2153
clutter_gst_playback_set_filename (ClutterGstPlayback *self,
2154
const gchar *filename)
2157
GError *uri_error = NULL;
2159
if (!g_path_is_absolute (filename))
2163
abs_path = g_build_filename (g_get_current_dir (), filename, NULL);
2164
uri = g_filename_to_uri (abs_path, NULL, &uri_error);
2168
uri = g_filename_to_uri (filename, NULL, &uri_error);
2172
g_signal_emit_by_name (self, "error", uri_error);
2173
g_error_free (uri_error);
2177
clutter_gst_playback_set_uri (self, uri);
2183
* clutter_gst_playback_get_user_agent:
2184
* @self: a #ClutterGstPlayback
2186
* Retrieves the user agent used when streaming.
2188
* Return value: the user agent used. The returned string has to be freed with
2194
clutter_gst_playback_get_user_agent (ClutterGstPlayback *self)
2196
ClutterGstPlaybackPrivate *priv;
2201
g_return_val_if_fail (CLUTTER_GST_IS_PLAYBACK (self), NULL);
2205
/* If the user has set a custom user agent, we just return it even if it is
2206
* not used by the current source element of the pipeline */
2207
if (priv->user_agent)
2208
return g_strdup (priv->user_agent);
2210
/* If not, we try to retrieve the user agent used by the current source */
2211
g_object_get (priv->pipeline, "source", &source, NULL);
2215
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (source),
2220
g_object_get (source, "user-agent", &user_agent, NULL);
2226
* clutter_gst_playback_set_user_agent:
2227
* @self: a #ClutterGstPlayback
2228
* @user_agent: the user agent
2230
* Sets the user agent to use when streaming.
2232
* When streaming content, you might want to set a custom user agent, eg. to
2233
* promote your software, make it appear in statistics or because the server
2234
* requires a special user agent you want to impersonate.
2239
clutter_gst_playback_set_user_agent (ClutterGstPlayback *self,
2240
const gchar *user_agent)
2242
ClutterGstPlaybackPrivate *priv;
2244
g_return_if_fail (CLUTTER_GST_IS_PLAYBACK (self));
2248
g_free (priv->user_agent);
2250
priv->user_agent = g_strdup (user_agent);
2252
priv->user_agent = NULL;
2254
player_set_user_agent (self, user_agent);
2258
* clutter_gst_playback_get_seek_flags:
2259
* @self: a #ClutterGstPlayback
2261
* Get the current value of the seek-flags property.
2263
* Return value: a combination of #ClutterGstSeekFlags
2268
clutter_gst_playback_get_seek_flags (ClutterGstPlayback *self)
2270
ClutterGstPlaybackPrivate *priv;
2272
g_return_val_if_fail (CLUTTER_GST_IS_PLAYBACK (self),
2273
CLUTTER_GST_SEEK_FLAG_NONE);
2277
if (priv->seek_flags == GST_SEEK_FLAG_ACCURATE)
2278
return CLUTTER_GST_SEEK_FLAG_ACCURATE;
2280
return CLUTTER_GST_SEEK_FLAG_NONE;
2284
* clutter_gst_playback_set_seek_flags:
2285
* @self: a #ClutterGstPlayback
2286
* @flags: a combination of #ClutterGstSeekFlags
2288
* Seeking can be done with several trade-offs. Clutter-gst defaults
2289
* to %CLUTTER_GST_SEEK_FLAG_NONE.
2294
clutter_gst_playback_set_seek_flags (ClutterGstPlayback *self,
2295
ClutterGstSeekFlags flags)
2297
ClutterGstPlaybackPrivate *priv;
2299
g_return_if_fail (CLUTTER_GST_IS_PLAYBACK (self));
2303
if (flags == CLUTTER_GST_SEEK_FLAG_NONE)
2304
priv->seek_flags = GST_SEEK_FLAG_KEY_UNIT;
2305
else if (flags & CLUTTER_GST_SEEK_FLAG_ACCURATE)
2306
priv->seek_flags = GST_SEEK_FLAG_ACCURATE;
2310
* clutter_gst_playback_get_buffering_mode:
2311
* @self: a #ClutterGstPlayback
2313
* Return value: a #ClutterGstBufferingMode
2317
ClutterGstBufferingMode
2318
clutter_gst_playback_get_buffering_mode (ClutterGstPlayback *self)
2320
ClutterGstPlaybackPrivate *priv;
2323
g_return_val_if_fail (CLUTTER_GST_IS_PLAYBACK (self),
2324
CLUTTER_GST_BUFFERING_MODE_STREAM);
2328
g_object_get (G_OBJECT (priv->pipeline), "flags", &flags, NULL);
2330
if (flags & GST_PLAY_FLAG_DOWNLOAD)
2331
return CLUTTER_GST_BUFFERING_MODE_DOWNLOAD;
2333
return CLUTTER_GST_BUFFERING_MODE_STREAM;
2337
* clutter_gst_playback_set_buffering_mode:
2338
* @self: a #ClutterGstPlayback
2339
* @mode: a #ClutterGstBufferingMode
2344
clutter_gst_playback_set_buffering_mode (ClutterGstPlayback *self,
2345
ClutterGstBufferingMode mode)
2347
ClutterGstPlaybackPrivate *priv;
2350
g_return_if_fail (CLUTTER_GST_IS_PLAYBACK (self));
2354
g_object_get (G_OBJECT (priv->pipeline), "flags", &flags, NULL);
2358
case CLUTTER_GST_BUFFERING_MODE_STREAM:
2359
flags &= ~GST_PLAY_FLAG_DOWNLOAD;
2362
case CLUTTER_GST_BUFFERING_MODE_DOWNLOAD:
2363
flags |= GST_PLAY_FLAG_DOWNLOAD;
2367
g_warning ("Unexpected buffering mode %d", mode);
2371
g_object_set (G_OBJECT (priv->pipeline), "flags", flags, NULL);
2375
* clutter_gst_playback_get_buffer_fill:
2376
* @self: a #ClutterGstPlayback
2378
* Retrieves the amount of the stream that is buffered.
2380
* Return value: the fill level, between 0.0 and 1.0
2383
clutter_gst_playback_get_buffer_fill (ClutterGstPlayback *self)
2385
gdouble retval = 0.0;
2387
g_return_val_if_fail (CLUTTER_GST_IS_PLAYBACK (self), 0);
2389
g_object_get (G_OBJECT (self), "buffer-fill", &retval, NULL);
2395
* clutter_gst_playback_get_buffer_size:
2396
* @self: a #ClutterGstPlayback
2398
* Retrieves the buffer size when buffering network streams.
2400
* Return value: The buffer size
2403
clutter_gst_playback_get_buffer_size (ClutterGstPlayback *self)
2405
ClutterGstPlaybackPrivate *priv;
2408
g_return_val_if_fail (CLUTTER_GST_IS_PLAYBACK (self), 0);
2412
g_object_get (G_OBJECT (priv->pipeline), "buffer-size", &size, NULL);
2418
* clutter_gst_playback_set_buffer_size:
2419
* @self: a #ClutterGstPlayback
2420
* @size: The new size
2422
* Sets the buffer size to be used when buffering network streams.
2425
clutter_gst_playback_set_buffer_size (ClutterGstPlayback *self,
2428
ClutterGstPlaybackPrivate *priv;
2430
g_return_if_fail (CLUTTER_GST_IS_PLAYBACK (self));
2434
g_object_set (G_OBJECT (priv->pipeline), "buffer-size", size, NULL);
2438
* clutter_gst_playback_get_buffer_duration:
2439
* @self: a #ClutterGstPlayback
2441
* Retrieves the buffer duration when buffering network streams.
2443
* Return value: The buffer duration
2446
clutter_gst_playback_get_buffer_duration (ClutterGstPlayback *self)
2448
ClutterGstPlaybackPrivate *priv;
2451
g_return_val_if_fail (CLUTTER_GST_IS_PLAYBACK (self), 0);
2455
g_object_get (G_OBJECT (priv->pipeline), "buffer-duration", &duration, NULL);
2461
* clutter_gst_playback_set_buffer_duration:
2462
* @self: a #ClutterGstPlayback
2463
* @duration: The new duration
2465
* Sets the buffer duration to be used when buffering network streams.
2468
clutter_gst_playback_set_buffer_duration (ClutterGstPlayback *self,
2471
ClutterGstPlaybackPrivate *priv;
2473
g_return_if_fail (CLUTTER_GST_IS_PLAYBACK (self));
2477
g_object_set (G_OBJECT (priv->pipeline), "buffer-duration", duration, NULL);
2481
* clutter_gst_playback_get_audio_streams:
2482
* @self: a #ClutterGstPlayback
2484
* Get the list of audio streams of the current media.
2486
* Return value: (transfer none) (element-type utf8): a list of
2487
* strings describing the available audio streams
2492
clutter_gst_playback_get_audio_streams (ClutterGstPlayback *self)
2494
ClutterGstPlaybackPrivate *priv;
2496
g_return_val_if_fail (CLUTTER_GST_IS_PLAYBACK (self), NULL);
2500
if (CLUTTER_GST_DEBUG_ENABLED (AUDIO_STREAM))
2504
streams = list_to_string (priv->audio_streams);
2505
CLUTTER_GST_NOTE (AUDIO_STREAM, "audio streams: %s", streams);
2509
return priv->audio_streams;
2513
* clutter_gst_playback_get_audio_stream:
2514
* @self: a #ClutterGstPlayback
2516
* Get the current audio stream. The number returned in the index of the
2517
* audio stream playing in the list returned by
2518
* clutter_gst_playback_get_audio_streams().
2520
* Return value: the index of the current audio stream, -1 if the media has no
2526
clutter_gst_playback_get_audio_stream (ClutterGstPlayback *self)
2528
ClutterGstPlaybackPrivate *priv;
2531
g_return_val_if_fail (CLUTTER_GST_IS_PLAYBACK (self), -1);
2535
g_object_get (G_OBJECT (priv->pipeline),
2536
"current-audio", &index_,
2539
CLUTTER_GST_NOTE (AUDIO_STREAM, "audio stream is #%d", index_);
2545
* clutter_gst_playback_set_audio_stream:
2546
* @self: a #ClutterGstPlayback
2547
* @index_: the index of the audio stream
2549
* Set the audio stream to play. @index_ is the index of the stream
2550
* in the list returned by clutter_gst_playback_get_audio_streams().
2555
clutter_gst_playback_set_audio_stream (ClutterGstPlayback *self,
2558
ClutterGstPlaybackPrivate *priv;
2560
g_return_if_fail (CLUTTER_GST_IS_PLAYBACK (self));
2564
g_return_if_fail (index_ >= 0 &&
2565
index_ < (gint) g_list_length (priv->audio_streams));
2567
CLUTTER_GST_NOTE (AUDIO_STREAM, "set audio audio stream to #%d", index_);
2569
g_object_set (G_OBJECT (priv->pipeline),
2570
"current-audio", index_,
2575
* clutter_gst_playback_set_subtitle_uri:
2576
* @self: a #ClutterGstPlayback
2577
* @uri: the URI of a subtitle file
2579
* Sets the location of a subtitle file to display while playing @self.
2582
clutter_gst_playback_set_subtitle_uri (ClutterGstPlayback *self,
2585
g_return_if_fail (CLUTTER_GST_IS_PLAYBACK (self));
2587
g_object_set (G_OBJECT (self), "subtitle-uri", uri, NULL);
2591
* clutter_gst_playback_get_subtitle_uri:
2592
* @self: a #ClutterGstPlayback
2594
* Retrieves the URI of the subtitle file in use.
2596
* Return value: the URI of the subtitle file. Use g_free()
2597
* to free the returned string
2600
clutter_gst_playback_get_subtitle_uri (ClutterGstPlayback *self)
2602
gchar *retval = NULL;
2604
g_return_val_if_fail (CLUTTER_GST_IS_PLAYBACK (self), NULL);
2606
g_object_get (G_OBJECT (self), "subtitle-uri", &retval, NULL);
2612
* clutter_gst_playback_set_subtitle_font_name:
2613
* @self: a #ClutterGstPlayback
2614
* @font_name: a font name, or %NULL to set the default font name
2616
* Sets the font used by the subtitle renderer. The @font_name string must be
2617
* either %NULL, which means that the default font name of the underlying
2618
* implementation will be used; or must follow the grammar recognized by
2619
* pango_font_description_from_string() like:
2622
* clutter_gst_playback_set_subtitle_font_name (player, "Sans 24pt");
2626
clutter_gst_playback_set_subtitle_font_name (ClutterGstPlayback *self,
2627
const char *font_name)
2629
g_return_if_fail (CLUTTER_GST_IS_PLAYBACK (self));
2631
g_object_set (G_OBJECT (self), "subtitle-font-name", font_name, NULL);
2635
* clutter_gst_playback_get_subtitle_font_name:
2636
* @self: a #ClutterGstPlayback
2638
* Retrieves the font name currently used.
2640
* Return value: a string containing the font name. Use g_free()
2641
* to free the returned string
2644
clutter_gst_playback_get_subtitle_font_name (ClutterGstPlayback *self)
2646
gchar *retval = NULL;
2648
g_return_val_if_fail (CLUTTER_GST_IS_PLAYBACK (self), NULL);
2650
g_object_get (G_OBJECT (self), "subtitle-font-name", &retval, NULL);
2656
* clutter_gst_playback_get_subtitle_tracks:
2657
* @self: a #ClutterGstPlayback
2659
* Get the list of subtitles tracks of the current media.
2661
* Return value: (transfer none) (element-type utf8): a list of
2662
* strings describing the available subtitles tracks
2667
clutter_gst_playback_get_subtitle_tracks (ClutterGstPlayback *self)
2669
ClutterGstPlaybackPrivate *priv;
2671
g_return_val_if_fail (CLUTTER_GST_IS_PLAYBACK (self), NULL);
2675
if (CLUTTER_GST_DEBUG_ENABLED (SUBTITLES))
2679
tracks = list_to_string (priv->subtitle_tracks);
2680
CLUTTER_GST_NOTE (SUBTITLES, "subtitle tracks: %s", tracks);
2684
return priv->subtitle_tracks;
2688
* clutter_gst_playback_get_subtitle_track:
2689
* @self: a #ClutterGstPlayback
2691
* Get the current subtitles track. The number returned is the index of the
2692
* subtiles track in the list returned by
2693
* clutter_gst_playback_get_subtitle_tracks().
2695
* Return value: the index of the current subtitlest track, -1 if the media has
2696
* no subtitles track or if the subtitles have been turned off
2701
clutter_gst_playback_get_subtitle_track (ClutterGstPlayback *self)
2703
ClutterGstPlaybackPrivate *priv;
2706
g_return_val_if_fail (CLUTTER_GST_IS_PLAYBACK (self), -1);
2710
g_object_get (G_OBJECT (priv->pipeline),
2711
"current-text", &index_,
2714
CLUTTER_GST_NOTE (SUBTITLES, "text track is #%d", index_);
2720
* clutter_gst_playback_set_subtitle_track:
2721
* @self: a #ClutterGstPlayback
2722
* @index_: the index of the subtitles track
2724
* Set the subtitles track to play. @index_ is the index of the stream
2725
* in the list returned by clutter_gst_playback_get_subtitle_tracks().
2727
* If @index_ is -1, the subtitles are turned off.
2732
clutter_gst_playback_set_subtitle_track (ClutterGstPlayback *self,
2735
ClutterGstPlaybackPrivate *priv;
2738
g_return_if_fail (CLUTTER_GST_IS_PLAYBACK (self));
2742
g_return_if_fail (index_ >= -1 &&
2743
index_ < (gint) g_list_length (priv->subtitle_tracks));
2745
CLUTTER_GST_NOTE (SUBTITLES, "set subtitle track to #%d", index_);
2747
g_object_get (priv->pipeline, "flags", &flags, NULL);
2748
flags &= ~GST_PLAY_FLAG_TEXT;
2749
g_object_set (priv->pipeline, "flags", flags, NULL);
2753
g_object_set (G_OBJECT (priv->pipeline),
2754
"current-text", index_,
2757
flags |= GST_PLAY_FLAG_TEXT;
2758
g_object_set (priv->pipeline, "flags", flags, NULL);
2763
* clutter_gst_playback_get_in_seek:
2764
* @self: a #ClutterGstPlayback
2766
* Whether the player is seeking.
2768
* Return value: TRUE if the player is seeking, FALSE otherwise.
2773
clutter_gst_playback_get_in_seek (ClutterGstPlayback *self)
2775
g_return_val_if_fail (CLUTTER_GST_IS_PLAYBACK (self), FALSE);
2777
return self->priv->in_seek;
2781
* clutter_gst_playback_get_can_seek:
2782
* @self: a #ClutterGstPlayback
2784
* Retrieves whether @self is seekable or not.
2786
* Return value: %TRUE if @self can seek, %FALSE otherwise.
2789
clutter_gst_playback_get_can_seek (ClutterGstPlayback *self)
2791
g_return_val_if_fail (CLUTTER_GST_IS_PLAYBACK (self), FALSE);
2793
return self->priv->can_seek;
2797
* clutter_gst_playback_set_progress:
2798
* @self: a #ClutterGstPlayback
2799
* @progress: the progress of the playback, between 0.0 and 1.0
2801
* Sets the playback progress of @self. The @progress is
2802
* a normalized value between 0.0 (begin) and 1.0 (end).
2805
clutter_gst_playback_set_progress (ClutterGstPlayback *self,
2808
g_return_if_fail (CLUTTER_GST_IS_PLAYBACK (self));
2810
set_progress (self, progress);
2814
* clutter_gst_playback_get_progress:
2815
* @self: a #ClutterGstPlayback
2817
* Retrieves the playback progress of @self.
2819
* Return value: the playback progress, between 0.0 and 1.0
2822
clutter_gst_playback_get_progress (ClutterGstPlayback *self)
2824
g_return_val_if_fail (CLUTTER_GST_IS_PLAYBACK (self), 0);
2826
return get_progress (self);
2829
* clutter_gst_playback_get_position:
2830
* @self: a #ClutterGstPlayback
2832
* Retrieves the position in the media stream that @self represents.
2834
* Return value: the position in the media stream, in seconds
2837
clutter_gst_playback_get_position (ClutterGstPlayback *self)
2839
g_return_val_if_fail (CLUTTER_GST_IS_PLAYBACK (self), 0);
2841
return get_position (self);
2845
* clutter_gst_playback_get_duration:
2846
* @self: a #ClutterGstPlayback
2848
* Retrieves the duration of the media stream that @self represents.
2850
* Return value: the duration of the media stream, in seconds
2853
clutter_gst_playback_get_duration (ClutterGstPlayback *self)
2857
g_return_val_if_fail (CLUTTER_GST_IS_PLAYBACK (self), 0);
2859
g_object_get (G_OBJECT (self), "duration", &retval, NULL);
2865
* clutter_gst_playback_is_live_media:
2866
* @self: a #ClutterGstPlayback
2868
* Whether the player is using a live media.
2870
* Return value: TRUE if the player is using a live media, FALSE otherwise.
2873
clutter_gst_playback_is_live_media (ClutterGstPlayback *self)
2875
g_return_val_if_fail (CLUTTER_GST_IS_PLAYBACK (self), FALSE);
2877
return self->priv->is_live;