1 /*
2 * Clutter-GStreamer.
3 *
4 * GStreamer integration library for Clutter.
5 *
6 * clutter-gst-player.c - Wrap some convenience functions around playbin
7 *
8 * Authored By Damien Lespiau <damien.lespiau@intel.com>
9 * Lionel Landwerlin <lionel.g.landwerlin@linux.intel.com>
10 *
11 * Copyright (C) 2011 Intel Corporation
12 *
13 * This library is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU Lesser General Public
15 * License as published by the Free Software Foundation; either
16 * version 2 of the License, or (at your option) any later version.
17 *
18 * This library is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 * Lesser General Public License for more details.
22 *
23 * You should have received a copy of the GNU Lesser General Public
24 * License along with this library; if not, write to the
25 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
26 * Boston, MA 02111-1307, USA.
27 */
28
29 /**
30 * SECTION:clutter-gst-player
31 * @short_description: An interface for controlling playback of media data
32 *
33 * #ClutterGstPlayer is an interface for controlling playback of media
34 * sources. Contrary to most interfaces, you don't need to implement
35 * #ClutterGstPlayer. It already provides an implementation/logic
36 * leaving you only tweak a few properties to get the desired behavior.
37 *
38 * #ClutterGstPlayer extends and implements #ClutterMedia to create
39 * enhanced player.
40 *
41 * #ClutterMedia is available since Clutter 0.2
42 */
43
44 #ifdef HAVE_CONFIG_H
45 #include "config.h"
46 #endif
47
48 #include <string.h>
49
50 #include <gst/video/video.h>
51 #include <gst/tag/tag.h>
52 #include <gst/audio/streamvolume.h>
53
54 #include "clutter-gst-debug.h"
55 #include "clutter-gst-enum-types.h"
56 #include "clutter-gst-marshal.h"
57 #include "clutter-gst-player.h"
58 #include "clutter-gst-private.h"
59
60 #if defined (CLUTTER_WINDOWING_X11) && defined (HAVE_HW_DECODER_SUPPORT)
61 #define GST_USE_UNSTABLE_API 1
62 #include <gst/video/videocontext.h>
63 #include <clutter/x11/clutter-x11.h>
64 #endif
65
66
67 typedef ClutterGstPlayerIface ClutterGstPlayerInterface;
68
69 G_DEFINE_INTERFACE_WITH_CODE (ClutterGstPlayer, clutter_gst_player, G_TYPE_OBJECT,
70 g_type_interface_add_prerequisite (g_define_type_id,
71 CLUTTER_TYPE_MEDIA))
72
73 #define PLAYER_GET_PRIVATE(player) \
74 (g_object_get_qdata (G_OBJECT (player), \
75 clutter_gst_player_private_quark))
76 #define PLAYER_SET_PRIVATE(player,private) \
77 (g_object_set_qdata (G_OBJECT (player), \
78 clutter_gst_player_private_quark, \
79 private))
80
81 /* idle timeouts (in ms) */
82 #define TICK_TIMEOUT 500
83 #define BUFFERING_TIMEOUT 250
84
85 enum
86 {
87 DOWNLOAD_BUFFERING,
88
89 LAST_SIGNAL
90 };
91
92 enum
93 {
94 PROP_0,
95
96 /* ClutterMedia properties */
97 PROP_URI,
98 PROP_PLAYING,
99 PROP_PROGRESS,
100 PROP_SUBTITLE_URI,
101 PROP_SUBTITLE_FONT_NAME,
102 PROP_AUDIO_VOLUME,
103 PROP_CAN_SEEK,
104 PROP_BUFFER_FILL,
105 PROP_DURATION,
106
107 /* ClutterGstPlayer properties */
108 PROP_IDLE,
109 PROP_USER_AGENT,
110 PROP_SEEK_FLAGS,
111 PROP_AUDIO_STREAMS,
112 PROP_AUDIO_STREAM,
113 PROP_SUBTITLE_TRACKS,
114 PROP_SUBTITLE_TRACK,
115 PROP_IN_SEEK
116 };
117
118 struct _ClutterGstPlayerIfacePrivate
119 {
120 void (*set_property) (GObject *object,
121 guint property_id,
122 const GValue *value,
123 GParamSpec *pspec);
124 void (*get_property) (GObject *object,
125 guint property_id,
126 GValue *value,
127 GParamSpec *pspec);
128 };
129
130 typedef struct _ClutterGstPlayerPrivate ClutterGstPlayerPrivate;
131
132 /* Elements don't expose header files */
133 typedef enum {
134 GST_PLAY_FLAG_VIDEO = (1 << 0),
135 GST_PLAY_FLAG_AUDIO = (1 << 1),
136 GST_PLAY_FLAG_TEXT = (1 << 2),
137 GST_PLAY_FLAG_VIS = (1 << 3),
138 GST_PLAY_FLAG_SOFT_VOLUME = (1 << 4),
139 GST_PLAY_FLAG_NATIVE_AUDIO = (1 << 5),
140 GST_PLAY_FLAG_NATIVE_VIDEO = (1 << 6),
141 GST_PLAY_FLAG_DOWNLOAD = (1 << 7),
142 GST_PLAY_FLAG_BUFFERING = (1 << 8),
143 GST_PLAY_FLAG_DEINTERLACE = (1 << 9)
144 } GstPlayFlags;
145
146 struct _ClutterGstPlayerPrivate
147 {
148 GObject parent;
149
150 GstElement *pipeline;
151 GstBus *bus;
152
153 gchar *uri;
154
155 guint is_idle : 1;
156 guint can_seek : 1;
157 guint in_seek : 1;
158 guint is_changing_uri : 1;
159 guint in_error : 1;
160 guint in_eos : 1;
161 guint in_download_buffering : 1;
162 /* when in progressive download, we use the buffer-fill property to signal
163 * that we have enough data to play the stream. This flag allows to send
164 * the notify that buffer-fill is 1.0 only once */
165 guint virtual_stream_buffer_signalled : 1;
166
167 gdouble stacked_progress;
168
169 gdouble target_progress;
170 GstState target_state;
171
172 guint tick_timeout_id;
173 guint buffering_timeout_id;
174
175 /* This is a cubic volume, suitable for use in a UI cf. StreamVolume doc */
176 gdouble volume;
177
178 gdouble buffer_fill;
179 gdouble duration;
180 gchar *font_name;
181 gchar *user_agent;
182
183 GstSeekFlags seek_flags; /* flags for the seek in set_progress(); */
184
185 GstElement *download_buffering_element;
186
187 GList *audio_streams;
188 GList *subtitle_tracks;
189 };
190
191 static GQuark clutter_gst_player_private_quark = 0;
192 static GQuark clutter_gst_player_class_quark = 0;
193
194 static guint signals[LAST_SIGNAL] = { 0, };
195
196 static gboolean player_buffering_timeout (gpointer data);
197
198 /* Logic */
199 static ClutterGstPlayerIfacePrivate *
clutter_gst_player_get_class_iface_priv(GObject * object)200 clutter_gst_player_get_class_iface_priv (GObject *object)
201 {
202 GType k = G_OBJECT_TYPE (object);
203 ClutterGstPlayerIfacePrivate *ret = NULL;
204 while (k != 0 && ret == NULL)
205 {
206 ret = g_type_get_qdata (k, clutter_gst_player_class_quark);
207 k = g_type_parent (k);
208 }
209 return ret;
210 }
211
212 #ifdef CLUTTER_GST_ENABLE_DEBUG
213 static gchar *
get_stream_description(GstTagList * tags,gint track_num)214 get_stream_description (GstTagList *tags,
215 gint track_num)
216 {
217 gchar *description = NULL;
218
219 if (tags)
220 {
221
222 gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &description);
223
224 if (description)
225 {
226 const gchar *language = gst_tag_get_language_name (description);
227
228 if (language)
229 {
230 g_free (description);
231 description = g_strdup (language);
232 }
233 }
234
235 if (!description)
236 gst_tag_list_get_string (tags, GST_TAG_CODEC, &description);
237 }
238
239 if (!description)
240 description = g_strdup_printf ("Track %d", track_num);
241
242 return description;
243 }
244
245 gchar *
list_to_string(GList * list)246 list_to_string (GList *list)
247 {
248 GstTagList *tags;
249 gchar *description;
250 GString *string;
251 GList *l;
252 gint n, i;
253
254 if (!list)
255 return g_strdup ("<empty list>");
256
257 string = g_string_new (NULL);
258 n = g_list_length (list);
259 for (i = 0, l = list; i < n - 1; i++, l = g_list_next (l))
260 {
261 tags = l->data;
262 description = get_stream_description (tags, i);
263 g_string_append_printf (string, "%s, ", description);
264 g_free (description);
265 }
266
267 tags = l->data;
268 description = get_stream_description (tags, i);
269 g_string_append_printf (string, "%s", (gchar *) description);
270 g_free (description);
271
272 return g_string_free (string, FALSE);
273 }
274 #endif
275
276 static const gchar *
gst_state_to_string(GstState state)277 gst_state_to_string (GstState state)
278 {
279 switch (state)
280 {
281 case GST_STATE_VOID_PENDING:
282 return "pending";
283 case GST_STATE_NULL:
284 return "null";
285 case GST_STATE_READY:
286 return "ready";
287 case GST_STATE_PAUSED:
288 return "paused";
289 case GST_STATE_PLAYING:
290 return "playing";
291 }
292
293 return "Unknown state";
294 }
295
296 static void
free_tags_list(GList ** listp)297 free_tags_list (GList **listp)
298 {
299 GList *l;
300
301 l = *listp;
302 while (l)
303 {
304 if (l->data)
305 gst_tag_list_unref (l->data);
306 l = g_list_delete_link (l, l);
307 }
308
309 *listp = NULL;
310 }
311
312 static gboolean
tick_timeout(gpointer data)313 tick_timeout (gpointer data)
314 {
315 GObject *player = data;
316
317 g_object_notify (player, "progress");
318
319 return TRUE;
320 }
321
322 static void
player_set_user_agent(ClutterGstPlayer * player,const gchar * user_agent)323 player_set_user_agent (ClutterGstPlayer *player,
324 const gchar *user_agent)
325 {
326 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
327 GstElement *source;
328 GParamSpec *pspec;
329
330 if (user_agent == NULL)
331 return;
332
333 g_object_get (priv->pipeline, "source", &source, NULL);
334 if (source == NULL)
335 return;
336
337 pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (source),
338 "user-agent");
339 if (pspec == NULL)
340 return;
341
342 CLUTTER_GST_NOTE (MEDIA, "setting user agent: %s", user_agent);
343
344 g_object_set (source, "user-agent", user_agent, NULL);
345 }
346
347 static void
autoload_subtitle(ClutterGstPlayer * player,const gchar * uri)348 autoload_subtitle (ClutterGstPlayer *player,
349 const gchar *uri)
350 {
351 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
352 gchar *path, *dot, *subtitle_path;
353 GFile *video;
354 guint i;
355
356 static const char subtitles_extensions[][4] =
357 {
358 "sub", "SUB",
359 "srt", "SRT",
360 "smi", "SMI",
361 "ssa", "SSA",
362 "ass", "ASS",
363 "asc", "ASC"
364 };
365
366 /* do not try to look for subtitle files if the video file is not mounted
367 * locally */
368 if (!g_str_has_prefix (uri, "file://"))
369 return;
370
371 /* Retrieve the absolute path of the video file */
372 video = g_file_new_for_uri (uri);
373 path = g_file_get_path (video);
374 g_object_unref (video);
375 if (path == NULL)
376 return;
377
378 /* Put a '\0' after the dot of the extension */
379 dot = strrchr (path, '.');
380 if (dot == NULL) {
381 g_free (path);
382 return;
383 }
384 *++dot = '\0';
385
386 /* we can't use path as the temporary buffer for the paths of the potential
387 * subtitle files as we may not have enough room there */
388 subtitle_path = g_malloc (strlen (path) + 1 + 4);
389 strcpy (subtitle_path, path);
390
391 /* reuse dot to point to the first byte of the extension of subtitle_path */
392 dot = subtitle_path + (dot - path);
393
394 for (i = 0; i < G_N_ELEMENTS (subtitles_extensions); i++)
395 {
396 GFile *candidate;
397
398 memcpy (dot, subtitles_extensions[i], 4);
399 candidate = g_file_new_for_path (subtitle_path);
400 if (g_file_query_exists (candidate, NULL))
401 {
402 gchar *suburi;
403
404 suburi = g_file_get_uri (candidate);
405
406 CLUTTER_GST_NOTE (MEDIA, "found subtitle: %s", suburi);
407
408 g_object_set (priv->pipeline, "suburi", suburi, NULL);
409 g_free (suburi);
410
411 g_object_unref (candidate);
412 break;
413 }
414
415 g_object_unref (candidate);
416 }
417
418 g_free (path);
419 g_free (subtitle_path);
420 }
421
422 static void
set_subtitle_uri(ClutterGstPlayer * player,const gchar * uri)423 set_subtitle_uri (ClutterGstPlayer *player,
424 const gchar *uri)
425 {
426 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
427 GstPlayFlags flags;
428
429 if (!priv->pipeline)
430 return;
431
432 CLUTTER_GST_NOTE (MEDIA, "setting subtitle URI: %s", uri);
433
434 g_object_get (priv->pipeline, "flags", &flags, NULL);
435
436 g_object_set (priv->pipeline, "suburi", uri, NULL);
437
438 g_object_set (priv->pipeline, "flags", flags, NULL);
439 }
440
441 static void
player_configure_buffering_timeout(ClutterGstPlayer * player,guint ms)442 player_configure_buffering_timeout (ClutterGstPlayer *player,
443 guint ms)
444 {
445 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
446
447 if (priv->buffering_timeout_id)
448 {
449 g_source_remove (priv->buffering_timeout_id);
450 priv->buffering_timeout_id = 0;
451 }
452
453 if (ms)
454 {
455 priv->buffering_timeout_id =
456 g_timeout_add (ms, player_buffering_timeout, player);
457 }
458 }
459
460 static void
player_clear_download_buffering(ClutterGstPlayer * player)461 player_clear_download_buffering (ClutterGstPlayer *player)
462 {
463 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
464
465 if (priv->download_buffering_element)
466 {
467 g_object_unref (priv->download_buffering_element);
468 priv->download_buffering_element = NULL;
469 }
470 player_configure_buffering_timeout (player, 0);
471 priv->in_download_buffering = FALSE;
472 priv->virtual_stream_buffer_signalled = 0;
473 }
474
475 static void
set_uri(ClutterGstPlayer * player,const gchar * uri)476 set_uri (ClutterGstPlayer *player,
477 const gchar *uri)
478 {
479 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
480 GObject *self = G_OBJECT (player);
481 GstState state, pending;
482
483 CLUTTER_GST_NOTE (MEDIA, "setting uri %s", uri);
484
485 if (!priv->pipeline)
486 return;
487
488 g_free (priv->uri);
489
490 priv->in_eos = FALSE;
491 priv->in_error = FALSE;
492
493 if (uri)
494 {
495 priv->uri = g_strdup (uri);
496
497 /* Ensure the tick timeout is installed.
498 *
499 * We also have it installed in PAUSED state, because
500 * seeks etc may have a delayed effect on the position.
501 */
502 if (priv->tick_timeout_id == 0)
503 {
504 priv->tick_timeout_id =
505 g_timeout_add (TICK_TIMEOUT, tick_timeout, self);
506 }
507
508 /* try to load subtitles based on the uri of the file */
509 set_subtitle_uri (player, NULL);
510
511 /* reset the states of download buffering */
512 player_clear_download_buffering (player);
513 }
514 else
515 {
516 priv->uri = NULL;
517
518 if (priv->tick_timeout_id)
519 {
520 g_source_remove (priv->tick_timeout_id);
521 priv->tick_timeout_id = 0;
522 }
523
524 if (priv->buffering_timeout_id)
525 {
526 g_source_remove (priv->buffering_timeout_id);
527 priv->buffering_timeout_id = 0;
528 }
529
530 if (priv->download_buffering_element)
531 {
532 g_object_unref (priv->download_buffering_element);
533 priv->download_buffering_element = NULL;
534 }
535
536 }
537
538 priv->can_seek = FALSE;
539 priv->duration = 0.0;
540 priv->stacked_progress = 0.0;
541 priv->target_progress = 0.0;
542
543 CLUTTER_GST_NOTE (MEDIA, "setting URI: %s", uri);
544
545 if (uri)
546 {
547 gst_element_get_state (priv->pipeline, &state, &pending, 0);
548 if (pending)
549 state = pending;
550
551 gst_element_set_state (priv->pipeline, GST_STATE_NULL);
552
553 g_object_set (priv->pipeline, "uri", uri, NULL);
554 set_subtitle_uri (player, NULL);
555 autoload_subtitle (player, uri);
556
557 gst_element_set_state (priv->pipeline, state);
558
559 priv->is_changing_uri = TRUE;
560 }
561 else
562 {
563 priv->is_idle = TRUE;
564 set_subtitle_uri (player, NULL);
565 gst_element_set_state (priv->pipeline, GST_STATE_NULL);
566 g_object_notify (G_OBJECT (player), "idle");
567 }
568
569 /*
570 * Emit notifications for all these to make sure UI is not showing
571 * any properties of the old URI.
572 */
573 g_object_notify (self, "uri");
574 g_object_notify (self, "can-seek");
575 g_object_notify (self, "duration");
576 g_object_notify (self, "progress");
577
578 free_tags_list (&priv->audio_streams);
579 CLUTTER_GST_NOTE (AUDIO_STREAM, "audio-streams changed");
580 g_object_notify (self, "audio-streams");
581
582 free_tags_list (&priv->subtitle_tracks);
583 CLUTTER_GST_NOTE (SUBTITLES, "subtitle-tracks changed");
584 g_object_notify (self, "subtitle-tracks");
585 }
586
587 static void
set_in_seek(ClutterGstPlayer * player,gboolean seeking)588 set_in_seek (ClutterGstPlayer *player,
589 gboolean seeking)
590 {
591 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
592
593 priv->in_seek = seeking;
594 g_object_notify (G_OBJECT (player), "in-seek");
595 }
596
597
598 static void
set_playing(ClutterGstPlayer * player,gboolean playing)599 set_playing (ClutterGstPlayer *player,
600 gboolean playing)
601 {
602 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
603
604 if (!priv->pipeline)
605 return;
606
607 CLUTTER_GST_NOTE (MEDIA, "set playing: %d", playing);
608
609 priv->in_error = FALSE;
610 priv->in_eos = FALSE;
611
612 priv->target_state = playing ? GST_STATE_PLAYING : GST_STATE_PAUSED;
613
614 if (priv->uri)
615 {
616 set_in_seek (player, FALSE);
617
618 gst_element_set_state (priv->pipeline, priv->target_state);
619 }
620 else
621 {
622 if (playing)
623 g_warning ("Unable to start playing: no URI is set");
624 }
625
626 g_object_notify (G_OBJECT (player), "playing");
627 g_object_notify (G_OBJECT (player), "progress");
628 }
629
630 static gboolean
get_playing(ClutterGstPlayer * player)631 get_playing (ClutterGstPlayer *player)
632 {
633 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
634 GstState state, pending;
635 gboolean playing;
636
637 if (!priv->pipeline)
638 return FALSE;
639
640 gst_element_get_state (priv->pipeline, &state, &pending, 0);
641
642 if (pending)
643 playing = (pending == GST_STATE_PLAYING);
644 else
645 playing = (state == GST_STATE_PLAYING);
646
647 CLUTTER_GST_NOTE (MEDIA, "get playing: %d", playing);
648
649 return playing;
650 }
651
652 static void
set_progress(ClutterGstPlayer * player,gdouble progress)653 set_progress (ClutterGstPlayer *player,
654 gdouble progress)
655 {
656 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
657 GstQuery *duration_q;
658 gint64 position;
659
660 if (!priv->pipeline)
661 return;
662
663 CLUTTER_GST_NOTE (MEDIA, "set progress: %.02f", progress);
664
665 priv->in_eos = FALSE;
666 priv->target_progress = progress;
667
668 if (priv->in_download_buffering)
669 {
670 /* we clear the virtual_stream_buffer_signalled flag as it's likely we
671 * need to buffer again */
672 priv->virtual_stream_buffer_signalled = 0;
673 }
674
675 if (priv->in_seek || priv->is_idle || priv->is_changing_uri)
676 {
677 /* We can't seek right now, let's save the position where we
678 want to seek and do that later. */
679 CLUTTER_GST_NOTE (MEDIA,
680 "already seeking/idleing. stacking progress point.");
681 priv->stacked_progress = progress;
682 return;
683 }
684
685 duration_q = gst_query_new_duration (GST_FORMAT_TIME);
686
687 if (gst_element_query (priv->pipeline, duration_q))
688 {
689 gint64 duration = 0;
690
691 gst_query_parse_duration (duration_q, NULL, &duration);
692
693 position = progress * duration;
694 }
695 else
696 position = 0;
697
698 gst_query_unref (duration_q);
699
700 gst_element_seek (priv->pipeline,
701 1.0,
702 GST_FORMAT_TIME,
703 GST_SEEK_FLAG_FLUSH | priv->seek_flags,
704 GST_SEEK_TYPE_SET,
705 position,
706 GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
707
708 set_in_seek (player, TRUE);
709
710 priv->stacked_progress = 0.0;
711
712 CLUTTER_GST_NOTE (MEDIA, "set progress (seeked): %.02f", progress);
713 }
714
715 static gdouble
get_progress(ClutterGstPlayer * player)716 get_progress (ClutterGstPlayer *player)
717 {
718 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
719 GstQuery *position_q, *duration_q;
720 gdouble progress;
721
722 if (!priv->pipeline)
723 return 0.0;
724
725 /* when hitting an error or after an EOS, playbin has some weird values when
726 * querying the duration and progress. We default to 0.0 on error and 1.0 on
727 * EOS */
728 if (priv->in_error)
729 {
730 CLUTTER_GST_NOTE (MEDIA, "get progress (error): 0.0");
731 return 0.0;
732 }
733
734 if (priv->in_eos)
735 {
736 CLUTTER_GST_NOTE (MEDIA, "get progress (eos): 1.0");
737 return 1.0;
738 }
739
740 /* When seeking, the progress returned by playbin is 0.0. We want that to be
741 * the last known position instead as returning 0.0 will have some ugly
742 * effects, say on a progress bar getting updated from the progress tick. */
743 if (priv->in_seek || priv->is_changing_uri)
744 {
745 CLUTTER_GST_NOTE (MEDIA, "get progress (target): %.02f",
746 priv->target_progress);
747 return priv->target_progress;
748 }
749
750 position_q = gst_query_new_position (GST_FORMAT_TIME);
751 duration_q = gst_query_new_duration (GST_FORMAT_TIME);
752
753 if (gst_element_query (priv->pipeline, position_q) &&
754 gst_element_query (priv->pipeline, duration_q))
755 {
756 gint64 position, duration;
757
758 position = duration = 0;
759
760 gst_query_parse_position (position_q, NULL, &position);
761 gst_query_parse_duration (duration_q, NULL, &duration);
762
763 progress = CLAMP ((gdouble) position / (gdouble) duration, 0.0, 1.0);
764 }
765 else
766 progress = 0.0;
767
768 gst_query_unref (position_q);
769 gst_query_unref (duration_q);
770
771 CLUTTER_GST_NOTE (MEDIA, "get progress (pipeline): %.02f", progress);
772
773 return progress;
774 }
775
776 static void
set_subtitle_font_name(ClutterGstPlayer * player,const gchar * font_name)777 set_subtitle_font_name (ClutterGstPlayer *player,
778 const gchar *font_name)
779 {
780 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
781
782 if (!priv->pipeline)
783 return;
784
785 CLUTTER_GST_NOTE (MEDIA, "setting subtitle font to %s", font_name);
786
787 g_free (priv->font_name);
788 priv->font_name = g_strdup (font_name);
789 g_object_set (priv->pipeline, "subtitle-font-desc", font_name, NULL);
790 }
791
792 static void
set_audio_volume(ClutterGstPlayer * player,gdouble volume)793 set_audio_volume (ClutterGstPlayer *player,
794 gdouble volume)
795 {
796 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
797
798 if (!priv->pipeline)
799 return;
800
801 CLUTTER_GST_NOTE (MEDIA, "set volume: %.02f", volume);
802
803 volume = CLAMP (volume, 0.0, 1.0);
804 gst_stream_volume_set_volume (GST_STREAM_VOLUME (priv->pipeline),
805 GST_STREAM_VOLUME_FORMAT_CUBIC,
806 volume);
807 g_object_notify (G_OBJECT (player), "audio-volume");
808 }
809
810 static gdouble
get_audio_volume(ClutterGstPlayer * player)811 get_audio_volume (ClutterGstPlayer *player)
812 {
813 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
814
815 if (!priv->pipeline)
816 return 0.0;
817
818 CLUTTER_GST_NOTE (MEDIA, "get volume: %.02f", priv->volume);
819
820 return priv->volume;
821 }
822
823 static gboolean
player_buffering_timeout(gpointer data)824 player_buffering_timeout (gpointer data)
825 {
826 ClutterGstPlayer *player = (ClutterGstPlayer *) data;
827 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
828 gdouble start_d, stop_d, seconds_buffered;
829 gint64 start, stop, left;
830 GstState current_state;
831 GstElement *element;
832 GstQuery *query;
833 gboolean res;
834
835 element = priv->download_buffering_element;
836 if (element == NULL)
837 element = priv->pipeline;
838
839 /* queue2 only knows about _PERCENT and _BYTES */
840 query = gst_query_new_buffering (GST_FORMAT_PERCENT);
841 res = gst_element_query (element, query);
842
843 if (res == FALSE)
844 {
845 priv->buffering_timeout_id = 0;
846 player_clear_download_buffering (player);
847 return FALSE;
848 }
849
850 /* signal the current range */
851 gst_query_parse_buffering_stats (query, NULL, NULL, NULL, &left);
852 gst_query_parse_buffering_range (query, NULL, &start, &stop, NULL);
853
854 CLUTTER_GST_NOTE (BUFFERING,
855 "start %" G_GINT64_FORMAT ", stop %" G_GINT64_FORMAT
856 ", buffering left %" G_GINT64_FORMAT, start, stop, left);
857
858 start_d = (gdouble) start / GST_FORMAT_PERCENT_MAX;
859 stop_d = (gdouble) stop / GST_FORMAT_PERCENT_MAX;
860
861 g_signal_emit (player, signals[DOWNLOAD_BUFFERING], 0, start_d, stop_d);
862
863 /* handle the "virtual stream buffer" and the associated pipeline state.
864 * We pause the pipeline until 2s of content is buffered. With the current
865 * implementation of queue2, start is always 0, so even when we seek in
866 * the stream the start position of the download-buffering signal is
867 * always 0.0. FIXME: look at gst_query_parse_nth_buffering_range () */
868 seconds_buffered = priv->duration * (stop_d - start_d);
869 priv->buffer_fill = seconds_buffered / 2.0;
870 priv->buffer_fill = CLAMP (priv->buffer_fill, 0.0, 1.0);
871
872 if (priv->buffer_fill != 1.0 || !priv->virtual_stream_buffer_signalled)
873 {
874 CLUTTER_GST_NOTE (BUFFERING, "buffer holds %0.2fs of data, buffer-fill "
875 "is %.02f", seconds_buffered, priv->buffer_fill);
876
877 g_object_notify (G_OBJECT (player), "buffer-fill");
878
879 if (priv->buffer_fill == 1.0)
880 priv->virtual_stream_buffer_signalled = 1;
881 }
882
883 gst_element_get_state (priv->pipeline, ¤t_state, NULL, 0);
884 if (priv->buffer_fill < 1.0)
885 {
886 if (current_state != GST_STATE_PAUSED)
887 {
888 CLUTTER_GST_NOTE (BUFFERING, "pausing the pipeline");
889 gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
890 }
891 }
892 else
893 {
894 if (current_state != priv->target_state)
895 {
896 CLUTTER_GST_NOTE (BUFFERING, "restoring the pipeline");
897 gst_element_set_state (priv->pipeline, priv->target_state);
898 }
899 }
900
901 /* the file has finished downloading */
902 if (left == G_GINT64_CONSTANT (0))
903 {
904 priv->buffering_timeout_id = 0;
905
906 player_clear_download_buffering (player);
907 gst_query_unref (query);
908 return FALSE;
909 }
910
911 gst_query_unref (query);
912 return TRUE;
913 }
914
915 static void
bus_message_error_cb(GstBus * bus,GstMessage * message,ClutterGstPlayer * player)916 bus_message_error_cb (GstBus *bus,
917 GstMessage *message,
918 ClutterGstPlayer *player)
919 {
920 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
921 GError *error = NULL;
922
923 gst_element_set_state (priv->pipeline, GST_STATE_NULL);
924
925 gst_message_parse_error (message, &error, NULL);
926 g_signal_emit_by_name (player, "error", error);
927 g_error_free (error);
928
929 priv->is_idle = TRUE;
930 g_object_notify (G_OBJECT (player), "idle");
931 }
932
933 /*
934 * This is what's intented in the EOS callback:
935 * - receive EOS from playbin
936 * - fire the EOS signal, the user can install a signal handler to loop the
937 * video for instance.
938 * - after having emitted the signal, check the state of the pipeline
939 * - if the pipeline has been set back to playing or pause, don't touch the
940 * idle state. This will avoid drawing a frame (or more) with the idle
941 * material when looping
942 */
943 static void
bus_message_eos_cb(GstBus * bus,GstMessage * message,ClutterGstPlayer * player)944 bus_message_eos_cb (GstBus *bus,
945 GstMessage *message,
946 ClutterGstPlayer *player)
947 {
948 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
949 GstState state, pending;
950
951 priv->in_eos = TRUE;
952
953 gst_element_set_state (priv->pipeline, GST_STATE_READY);
954
955 g_signal_emit_by_name (player, "eos");
956 g_object_notify (G_OBJECT (player), "progress");
957
958 gst_element_get_state (priv->pipeline, &state, &pending, 0);
959 if (pending)
960 state = pending;
961
962 if (!(state == GST_STATE_PLAYING || state == GST_STATE_PAUSED))
963 {
964 priv->is_idle = TRUE;
965 g_object_notify (G_OBJECT (player), "idle");
966 }
967 }
968
969 static void
bus_message_buffering_cb(GstBus * bus,GstMessage * message,ClutterGstPlayer * player)970 bus_message_buffering_cb (GstBus *bus,
971 GstMessage *message,
972 ClutterGstPlayer *player)
973 {
974 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
975 GstBufferingMode mode;
976 GstState current_state;
977 gint buffer_percent;
978
979 gst_message_parse_buffering_stats (message, &mode, NULL, NULL, NULL);
980
981 if (mode != GST_BUFFERING_DOWNLOAD)
982 priv->in_download_buffering = FALSE;
983
984 switch (mode)
985 {
986 case GST_BUFFERING_STREAM:
987 gst_message_parse_buffering (message, &buffer_percent);
988 priv->buffer_fill = CLAMP ((gdouble) buffer_percent / 100.0, 0.0, 1.0);
989
990 CLUTTER_GST_NOTE (BUFFERING, "buffer-fill: %.02f", priv->buffer_fill);
991
992 /* The playbin documentation says that we need to pause the pipeline
993 * when there's not enough data yet. We try to limit the calls to
994 * gst_element_set_state() */
995 gst_element_get_state (priv->pipeline, ¤t_state, NULL, 0);
996
997 if (priv->buffer_fill < 1.0)
998 {
999 if (current_state != GST_STATE_PAUSED)
1000 {
1001 CLUTTER_GST_NOTE (BUFFERING, "pausing the pipeline");
1002 gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1003 }
1004 }
1005 else
1006 {
1007 if (current_state != priv->target_state)
1008 {
1009 CLUTTER_GST_NOTE (BUFFERING, "restoring the pipeline");
1010 gst_element_set_state (priv->pipeline, priv->target_state);
1011 }
1012 }
1013
1014 g_object_notify (G_OBJECT (player), "buffer-fill");
1015 break;
1016
1017 case GST_BUFFERING_DOWNLOAD:
1018 /* we rate limit the messages from GStreamer for a usage in a UI (we
1019 * don't want *that* many updates). This is done by installing an idle
1020 * handler querying the buffer range and sending a signal from there */
1021
1022 if (priv->in_download_buffering)
1023 break;
1024
1025 /* install the querying idle handler the first time we receive a download
1026 * buffering message */
1027 player_configure_buffering_timeout (player, BUFFERING_TIMEOUT);
1028
1029 /* pause the stream. the idle timeout will set the target state when
1030 * having received enough data. We'll use buffer_fill as a "virtual
1031 * stream buffer" to signal the application we're buffering until we
1032 * can play back from the downloaded stream. */
1033 gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1034 priv->buffer_fill = 0.0;
1035 g_object_notify (G_OBJECT (player), "buffer-fill");
1036
1037 priv->download_buffering_element = g_object_ref (message->src);
1038 priv->in_download_buffering = TRUE;
1039 priv->virtual_stream_buffer_signalled = 0;
1040 break;
1041
1042 case GST_BUFFERING_TIMESHIFT:
1043 case GST_BUFFERING_LIVE:
1044 default:
1045 g_warning ("Buffering mode %d not handled", mode);
1046 break;
1047 }
1048 }
1049
1050 static void
on_source_changed(GstElement * pipeline,GParamSpec * pspec,ClutterGstPlayer * player)1051 on_source_changed (GstElement *pipeline,
1052 GParamSpec *pspec,
1053 ClutterGstPlayer *player)
1054 {
1055 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
1056
1057 player_set_user_agent (player, priv->user_agent);
1058 }
1059
1060 static void
query_duration(ClutterGstPlayer * player)1061 query_duration (ClutterGstPlayer *player)
1062 {
1063 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
1064 gboolean success;
1065 gint64 duration;
1066 gdouble new_duration, difference;
1067
1068 success = gst_element_query_duration (priv->pipeline,
1069 GST_FORMAT_TIME,
1070 &duration);
1071 if (G_UNLIKELY (success != TRUE))
1072 return;
1073
1074 new_duration = (gdouble) duration / GST_SECOND;
1075
1076 /* while we store the new duration if it sligthly changes, the duration
1077 * signal is sent only if the new duration is at least one second different
1078 * from the old one (as the duration signal is mainly used to update the
1079 * time displayed in a UI */
1080 difference = ABS (priv->duration - new_duration);
1081 if (difference > 1e-3)
1082 {
1083 CLUTTER_GST_NOTE (MEDIA, "duration: %.02f", new_duration);
1084 priv->duration = new_duration;
1085
1086 if (difference > 1.0)
1087 g_object_notify (G_OBJECT (player), "duration");
1088 }
1089 }
1090
1091 static void
bus_message_duration_changed_cb(GstBus * bus,GstMessage * message,ClutterGstPlayer * player)1092 bus_message_duration_changed_cb (GstBus *bus,
1093 GstMessage *message,
1094 ClutterGstPlayer *player)
1095 {
1096 /* GstElements send a duration-changed message on the bus to signal
1097 * that the duration has changed and should be re-queried */
1098 query_duration (player);
1099 }
1100
1101 static void
bus_message_state_change_cb(GstBus * bus,GstMessage * message,ClutterGstPlayer * player)1102 bus_message_state_change_cb (GstBus *bus,
1103 GstMessage *message,
1104 ClutterGstPlayer *player)
1105 {
1106 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
1107 GstState old_state, new_state;
1108 gpointer src;
1109
1110 src = GST_MESSAGE_SRC (message);
1111 if (src != priv->pipeline)
1112 return;
1113
1114 gst_message_parse_state_changed (message, &old_state, &new_state, NULL);
1115
1116 CLUTTER_GST_NOTE (MEDIA, "state change: %s -> %s",
1117 gst_state_to_string (old_state),
1118 gst_state_to_string (new_state));
1119
1120 if (old_state == new_state)
1121 return;
1122
1123 if (old_state == GST_STATE_READY &&
1124 new_state == GST_STATE_PAUSED)
1125 {
1126 GstQuery *query;
1127
1128 /* Determine whether we can seek */
1129 query = gst_query_new_seeking (GST_FORMAT_TIME);
1130
1131 if (gst_element_query (priv->pipeline, query))
1132 {
1133 gboolean can_seek = FALSE;
1134
1135 gst_query_parse_seeking (query, NULL, &can_seek,
1136 NULL,
1137 NULL);
1138
1139 priv->can_seek = (can_seek == TRUE) ? TRUE : FALSE;
1140 }
1141 else
1142 {
1143 /* could not query for ability to seek by querying the
1144 * pipeline; let's crudely try by using the URI
1145 */
1146 if (priv->uri && g_str_has_prefix (priv->uri, "http://"))
1147 priv->can_seek = FALSE;
1148 else
1149 priv->can_seek = TRUE;
1150 }
1151
1152 gst_query_unref (query);
1153
1154 CLUTTER_GST_NOTE (MEDIA, "can-seek: %d", priv->can_seek);
1155
1156 g_object_notify (G_OBJECT (player), "can-seek");
1157
1158 query_duration (player);
1159 }
1160
1161 /* is_idle controls the drawing with the idle material */
1162 if (new_state == GST_STATE_NULL)
1163 {
1164 priv->is_idle = TRUE;
1165 g_object_notify (G_OBJECT (player), "idle");
1166 }
1167 else if (new_state == GST_STATE_PLAYING)
1168 {
1169 priv->is_idle = FALSE;
1170 priv->is_changing_uri = FALSE;
1171 g_object_notify (G_OBJECT (player), "idle");
1172 }
1173
1174 if (!priv->is_idle)
1175 {
1176 if (priv->stacked_progress)
1177 {
1178 set_progress (player, priv->stacked_progress);
1179 }
1180 }
1181 }
1182
1183 static void
bus_message_async_done_cb(GstBus * bus,GstMessage * message,ClutterGstPlayer * player)1184 bus_message_async_done_cb (GstBus *bus,
1185 GstMessage *message,
1186 ClutterGstPlayer *player)
1187 {
1188 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
1189
1190 if (priv->in_seek)
1191 {
1192 g_object_notify (G_OBJECT (player), "progress");
1193
1194 set_in_seek (player, FALSE);
1195
1196 if (priv->stacked_progress)
1197 {
1198 set_progress (player, priv->stacked_progress);
1199 }
1200 }
1201 }
1202
1203 static gboolean
on_volume_changed_main_context(gpointer data)1204 on_volume_changed_main_context (gpointer data)
1205 {
1206 ClutterGstPlayer *player = CLUTTER_GST_PLAYER (data);
1207 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
1208
1209 if (priv)
1210 {
1211 gdouble volume =
1212 gst_stream_volume_get_volume (GST_STREAM_VOLUME (priv->pipeline),
1213 GST_STREAM_VOLUME_FORMAT_CUBIC);
1214 priv->volume = volume;
1215
1216 g_object_notify (G_OBJECT (player), "audio-volume");
1217 }
1218
1219 g_object_unref (player);
1220
1221 return FALSE;
1222 }
1223
1224 /* playbin proxies the volume property change notification directly from
1225 * the element having the "volume" property. This means this callback is
1226 * called from the thread that runs the element, potentially different from
1227 * the main thread */
1228 static void
on_volume_changed(GstElement * pipeline,GParamSpec * pspec,ClutterGstPlayer * player)1229 on_volume_changed (GstElement *pipeline,
1230 GParamSpec *pspec,
1231 ClutterGstPlayer *player)
1232 {
1233 g_idle_add (on_volume_changed_main_context, g_object_ref (player));
1234 }
1235
1236 static GList *
get_tags(GstElement * pipeline,const gchar * property_name,const gchar * action_signal)1237 get_tags (GstElement *pipeline,
1238 const gchar *property_name,
1239 const gchar *action_signal)
1240 {
1241 GList *ret = NULL;
1242 gint i, n;
1243
1244 g_object_get (G_OBJECT (pipeline), property_name, &n, NULL);
1245 if (n == 0)
1246 return NULL;
1247
1248 for (i = 0; i < n; i++)
1249 {
1250 GstTagList *tags = NULL;
1251
1252 g_signal_emit_by_name (G_OBJECT (pipeline), action_signal, i, &tags);
1253
1254 ret = g_list_prepend (ret, tags);
1255 }
1256
1257 return g_list_reverse (ret);
1258 }
1259
1260 static gboolean
on_audio_changed_main_context(gpointer data)1261 on_audio_changed_main_context (gpointer data)
1262 {
1263 ClutterGstPlayer *player = CLUTTER_GST_PLAYER (data);
1264 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
1265
1266 if (priv)
1267 {
1268 free_tags_list (&priv->audio_streams);
1269 priv->audio_streams = get_tags (priv->pipeline, "n-audio", "get-audio-tags");
1270
1271 CLUTTER_GST_NOTE (AUDIO_STREAM, "audio-streams changed");
1272
1273 g_object_notify (G_OBJECT (player), "audio-streams");
1274 }
1275
1276 g_object_unref (player);
1277
1278 return FALSE;
1279 }
1280
1281 /* same explanation as for notify::volume's usage of g_idle_add() */
1282 static void
on_audio_changed(GstElement * pipeline,ClutterGstPlayer * player)1283 on_audio_changed (GstElement *pipeline,
1284 ClutterGstPlayer *player)
1285 {
1286 g_idle_add (on_audio_changed_main_context, g_object_ref (player));
1287 }
1288
1289 static void
on_audio_tags_changed(GstElement * pipeline,gint stream,ClutterGstPlayer * player)1290 on_audio_tags_changed (GstElement *pipeline,
1291 gint stream,
1292 ClutterGstPlayer *player)
1293 {
1294 gint current_stream;
1295
1296 g_object_get (G_OBJECT (pipeline), "current-audio", ¤t_stream, NULL);
1297
1298 if (current_stream != stream)
1299 return;
1300
1301 g_idle_add (on_audio_changed_main_context, g_object_ref (player));
1302 }
1303
1304 static gboolean
on_current_audio_changed_main_context(gpointer data)1305 on_current_audio_changed_main_context (gpointer data)
1306 {
1307 ClutterGstPlayer *player = CLUTTER_GST_PLAYER (data);
1308
1309 CLUTTER_GST_NOTE (AUDIO_STREAM, "audio stream changed");
1310 g_object_notify (G_OBJECT (player), "audio-stream");
1311
1312 g_object_unref (player);
1313
1314 return FALSE;
1315 }
1316
1317 static void
on_current_audio_changed(GstElement * pipeline,GParamSpec * pspec,ClutterGstPlayer * player)1318 on_current_audio_changed (GstElement *pipeline,
1319 GParamSpec *pspec,
1320 ClutterGstPlayer *player)
1321 {
1322 g_idle_add (on_current_audio_changed_main_context, g_object_ref (player));
1323 }
1324
1325 static gboolean
on_text_changed_main_context(gpointer data)1326 on_text_changed_main_context (gpointer data)
1327 {
1328 ClutterGstPlayer *player = CLUTTER_GST_PLAYER (data);
1329 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
1330
1331 if (priv)
1332 {
1333 free_tags_list (&priv->subtitle_tracks);
1334 priv->subtitle_tracks = get_tags (priv->pipeline, "n-text", "get-text-tags");
1335
1336 CLUTTER_GST_NOTE (AUDIO_STREAM, "subtitle-tracks changed");
1337
1338 g_object_notify (G_OBJECT (player), "subtitle-tracks");
1339 }
1340
1341 g_object_unref (player);
1342
1343 return FALSE;
1344 }
1345
1346 /* same explanation as for notify::volume's usage of g_idle_add() */
1347 static void
on_text_changed(GstElement * pipeline,ClutterGstPlayer * player)1348 on_text_changed (GstElement *pipeline,
1349 ClutterGstPlayer *player)
1350 {
1351 g_idle_add (on_text_changed_main_context, g_object_ref (player));
1352 }
1353
1354 static void
on_text_tags_changed(GstElement * pipeline,gint stream,ClutterGstPlayer * player)1355 on_text_tags_changed (GstElement *pipeline,
1356 gint stream,
1357 ClutterGstPlayer *player)
1358 {
1359 g_idle_add (on_text_changed_main_context, g_object_ref (player));
1360 }
1361
1362 static gboolean
on_current_text_changed_main_context(gpointer data)1363 on_current_text_changed_main_context (gpointer data)
1364 {
1365 ClutterGstPlayer *player = CLUTTER_GST_PLAYER (data);
1366
1367 CLUTTER_GST_NOTE (AUDIO_STREAM, "text stream changed");
1368 g_object_notify (G_OBJECT (player), "subtitle-track");
1369
1370 g_object_unref (player);
1371
1372 return FALSE;
1373 }
1374
1375 static void
on_current_text_changed(GstElement * pipeline,GParamSpec * pspec,ClutterGstPlayer * player)1376 on_current_text_changed (GstElement *pipeline,
1377 GParamSpec *pspec,
1378 ClutterGstPlayer *player)
1379 {
1380 g_idle_add (on_current_text_changed_main_context, g_object_ref (player));
1381 }
1382
1383 /* GObject's magic/madness */
1384
1385 static void
clutter_gst_player_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)1386 clutter_gst_player_set_property (GObject *object,
1387 guint property_id,
1388 const GValue *value,
1389 GParamSpec *pspec)
1390 {
1391 ClutterGstPlayer *player = CLUTTER_GST_PLAYER (object);
1392 ClutterGstPlayerIfacePrivate *iface_priv;
1393
1394 switch (property_id)
1395 {
1396 case PROP_URI:
1397 set_uri (player, g_value_get_string (value));
1398 break;
1399
1400 case PROP_PLAYING:
1401 set_playing (player, g_value_get_boolean (value));
1402 break;
1403
1404 case PROP_PROGRESS:
1405 set_progress (player, g_value_get_double (value));
1406 break;
1407
1408 case PROP_SUBTITLE_URI:
1409 set_subtitle_uri (player, g_value_get_string (value));
1410 break;
1411
1412 case PROP_SUBTITLE_FONT_NAME:
1413 set_subtitle_font_name (player, g_value_get_string (value));
1414 break;
1415
1416 case PROP_AUDIO_VOLUME:
1417 set_audio_volume (player, g_value_get_double (value));
1418 break;
1419
1420 case PROP_USER_AGENT:
1421 clutter_gst_player_set_user_agent (player,
1422 g_value_get_string (value));
1423 break;
1424
1425 case PROP_SEEK_FLAGS:
1426 clutter_gst_player_set_seek_flags (player,
1427 g_value_get_flags (value));
1428 break;
1429
1430 case PROP_AUDIO_STREAM:
1431 clutter_gst_player_set_audio_stream (player,
1432 g_value_get_int (value));
1433 break;
1434
1435 case PROP_SUBTITLE_TRACK:
1436 clutter_gst_player_set_subtitle_track (player,
1437 g_value_get_int (value));
1438 break;
1439
1440 default:
1441 iface_priv = clutter_gst_player_get_class_iface_priv (object);
1442 g_assert (iface_priv != NULL);
1443 iface_priv->set_property (object, property_id, value, pspec);
1444 }
1445 }
1446
1447
1448 static void
clutter_gst_player_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)1449 clutter_gst_player_get_property (GObject *object,
1450 guint property_id,
1451 GValue *value,
1452 GParamSpec *pspec)
1453 {
1454 ClutterGstPlayer *player = CLUTTER_GST_PLAYER (object);
1455 ClutterGstPlayerPrivate *priv = PLAYER_GET_PRIVATE (player);
1456 ClutterGstPlayerIfacePrivate *iface_priv;
1457 gchar *str;
1458
1459 switch (property_id)
1460 {
1461 case PROP_URI:
1462 g_value_set_string (value, priv->uri);
1463 break;
1464
1465 case PROP_PLAYING:
1466 g_value_set_boolean (value, get_playing (player));
1467 break;
1468
1469 case PROP_PROGRESS:
1470 g_value_set_double (value, get_progress (player));
1471 break;
1472
1473 case PROP_SUBTITLE_URI:
1474 g_object_get (priv->pipeline, "suburi", &str, NULL);
1475 g_value_take_string (value, str);
1476 break;
1477
1478 case PROP_SUBTITLE_FONT_NAME:
1479 g_value_set_string (value, priv->font_name);
1480 break;
1481
1482 case PROP_AUDIO_VOLUME:
1483 g_value_set_double (value, get_audio_volume (player));
1484 break;
1485
1486 case PROP_CAN_SEEK:
1487 g_value_set_boolean (value, priv->can_seek);
1488 break;
1489
1490 case PROP_BUFFER_FILL:
1491 g_value_set_double (value, priv->buffer_fill);
1492 break;
1493
1494 case PROP_DURATION:
1495 g_value_set_double (value, priv->duration);
1496 break;
1497
1498 case PROP_IDLE:
1499 g_value_set_boolean (value, priv->is_idle);
1500 break;
1501
1502 case PROP_USER_AGENT:
1503 {
1504 gchar *user_agent;
1505
1506 user_agent = clutter_gst_player_get_user_agent (player);
1507 g_value_take_string (value, user_agent);
1508 }
1509 break;
1510
1511 case PROP_SEEK_FLAGS:
1512 {
1513 ClutterGstSeekFlags seek_flags;
1514
1515 seek_flags = clutter_gst_player_get_seek_flags (player);
1516 g_value_set_flags (value, seek_flags);
1517 }
1518 break;
1519
1520 case PROP_AUDIO_STREAMS:
1521 g_value_set_pointer (value, priv->audio_streams);
1522 break;
1523
1524 case PROP_AUDIO_STREAM:
1525 {
1526 gint index_;
1527
1528 index_ = clutter_gst_player_get_audio_stream (player);
1529 g_value_set_int (value, index_);
1530 }
1531 break;
1532
1533 case PROP_SUBTITLE_TRACKS:
1534 g_value_set_pointer (value, priv->subtitle_tracks);
1535 break;
1536
1537 case PROP_SUBTITLE_TRACK:
1538 {
1539 gint index_;
1540
1541 index_ = clutter_gst_player_get_subtitle_track (player);
1542 g_value_set_int (value, index_);
1543 }
1544 break;
1545
1546 case PROP_IN_SEEK:
1547 g_value_set_boolean (value, priv->in_seek);
1548 break;
1549
1550 default:
1551 iface_priv = clutter_gst_player_get_class_iface_priv (object);
1552 iface_priv->get_property (object, property_id, value, pspec);
1553 }
1554 }
1555
1556 /**
1557 * clutter_gst_player_class_init:
1558 * @object_class: a #GObjectClass
1559 *
1560 * Adds the #ClutterGstPlayer properties to a class and surchages the
1561 * set/get_property of #GObjectClass. You should call this
1562 * function at the end of the class_init method of the class
1563 * implementing #ClutterGstPlayer.
1564 *
1565 * Since: 1.4
1566 */
1567 void
clutter_gst_player_class_init(GObjectClass * object_class)1568 clutter_gst_player_class_init (GObjectClass *object_class)
1569 {
1570 ClutterGstPlayerIfacePrivate *priv;
1571
1572 priv = g_new0 (ClutterGstPlayerIfacePrivate, 1);
1573 g_type_set_qdata (G_OBJECT_CLASS_TYPE (object_class),
1574 clutter_gst_player_class_quark,
1575 priv);
1576
1577 /* Save object's methods we want to override */
1578 priv->set_property = object_class->set_property;
1579 priv->get_property = object_class->get_property;
1580
1581 /* Replace by our methods */
1582 object_class->set_property = clutter_gst_player_set_property;
1583 object_class->get_property = clutter_gst_player_get_property;
1584
1585 /* Override ClutterMedia's properties */
1586 g_object_class_override_property (object_class,
1587 PROP_URI, "uri");
1588 g_object_class_override_property (object_class,
1589 PROP_PLAYING, "playing");
1590 g_object_class_override_property (object_class,
1591 PROP_PROGRESS, "progress");
1592 g_object_class_override_property (object_class,
1593 PROP_SUBTITLE_URI, "subtitle-uri");
1594 g_object_class_override_property (object_class,
1595 PROP_SUBTITLE_FONT_NAME,
1596 "subtitle-font-name");
1597 g_object_class_override_property (object_class,
1598 PROP_AUDIO_VOLUME, "audio-volume");
1599 g_object_class_override_property (object_class,
1600 PROP_CAN_SEEK, "can-seek");
1601 g_object_class_override_property (object_class,
1602 PROP_DURATION, "duration");
1603 g_object_class_override_property (object_class,
1604 PROP_BUFFER_FILL, "buffer-fill");
1605
1606 /* Override ClutterGstPlayer's properties */
1607 g_object_class_override_property (object_class,
1608 PROP_IDLE, "idle");
1609 g_object_class_override_property (object_class,
1610 PROP_USER_AGENT, "user-agent");
1611 g_object_class_override_property (object_class,
1612 PROP_SEEK_FLAGS, "seek-flags");
1613
1614 g_object_class_override_property (object_class,
1615 PROP_AUDIO_STREAMS, "audio-streams");
1616 g_object_class_override_property (object_class,
1617 PROP_AUDIO_STREAM, "audio-stream");
1618
1619 g_object_class_override_property (object_class,
1620 PROP_SUBTITLE_TRACKS, "subtitle-tracks");
1621 g_object_class_override_property (object_class,
1622 PROP_SUBTITLE_TRACK, "subtitle-track");
1623 g_object_class_override_property (object_class,
1624 PROP_IN_SEEK, "in-seek");
1625 }
1626
1627 static GstElement *
get_pipeline(void)1628 get_pipeline (void)
1629 {
1630 GstElement *pipeline, *audio_sink;
1631
1632 pipeline = gst_element_factory_make ("playbin", "pipeline");
1633 if (!pipeline)
1634 {
1635 g_critical ("Unable to create playbin element");
1636 return NULL;
1637 }
1638
1639 audio_sink = gst_element_factory_make ("gconfaudiosink", "audio-sink");
1640 if (!audio_sink)
1641 {
1642 audio_sink = gst_element_factory_make ("autoaudiosink", "audio-sink");
1643 if (!audio_sink)
1644 {
1645 audio_sink = gst_element_factory_make ("alsasink", "audio-sink");
1646 g_warning ("Could not create a GST audio_sink. "
1647 "Audio unavailable.");
1648
1649 /* do we even need to bother? */
1650 if (!audio_sink)
1651 audio_sink = gst_element_factory_make ("fakesink", "audio-sink");
1652 }
1653 }
1654
1655 g_object_set (G_OBJECT (pipeline),
1656 "audio-sink", audio_sink,
1657 "subtitle-font-desc", "Sans 16",
1658 NULL);
1659
1660 return pipeline;
1661 }
1662
1663 /* ClutterGstPlayerIface implementation */
1664
1665 static GstElement *
clutter_gst_player_get_pipeline_impl(ClutterGstPlayer * player)1666 clutter_gst_player_get_pipeline_impl (ClutterGstPlayer *player)
1667 {
1668 ClutterGstPlayerPrivate *priv;
1669
1670 priv = PLAYER_GET_PRIVATE (player);
1671
1672 return priv->pipeline;
1673 }
1674
1675 static gchar *
clutter_gst_player_get_user_agent_impl(ClutterGstPlayer * player)1676 clutter_gst_player_get_user_agent_impl (ClutterGstPlayer *player)
1677 {
1678 ClutterGstPlayerPrivate *priv;
1679 GstElement *source;
1680 GParamSpec *pspec;
1681 gchar *user_agent;
1682
1683 priv = PLAYER_GET_PRIVATE (player);
1684
1685 /* If the user has set a custom user agent, we just return it even if it is
1686 * not used by the current source element of the pipeline */
1687 if (priv->user_agent)
1688 return g_strdup (priv->user_agent);
1689
1690 /* If not, we try to retrieve the user agent used by the current source */
1691 g_object_get (priv->pipeline, "source", &source, NULL);
1692 if (source == NULL)
1693 return NULL;
1694
1695 pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (source),
1696 "user-agent");
1697 if (pspec == NULL)
1698 return NULL;
1699
1700 g_object_get (source, "user-agent", &user_agent, NULL);
1701
1702 return user_agent;
1703 }
1704
1705 static void
clutter_gst_player_set_user_agent_impl(ClutterGstPlayer * player,const gchar * user_agent)1706 clutter_gst_player_set_user_agent_impl (ClutterGstPlayer *player,
1707 const gchar *user_agent)
1708 {
1709 ClutterGstPlayerPrivate *priv;
1710
1711 priv = PLAYER_GET_PRIVATE (player);
1712
1713 g_free (priv->user_agent);
1714 if (user_agent)
1715 priv->user_agent = g_strdup (user_agent);
1716 else
1717 priv->user_agent = NULL;
1718
1719 player_set_user_agent (player, user_agent);
1720 }
1721
1722 static ClutterGstSeekFlags
clutter_gst_player_get_seek_flags_impl(ClutterGstPlayer * player)1723 clutter_gst_player_get_seek_flags_impl (ClutterGstPlayer *player)
1724 {
1725 ClutterGstPlayerPrivate *priv;
1726
1727 priv = PLAYER_GET_PRIVATE (player);
1728
1729 if (priv->seek_flags == GST_SEEK_FLAG_ACCURATE)
1730 return CLUTTER_GST_SEEK_FLAG_ACCURATE;
1731 else
1732 return CLUTTER_GST_SEEK_FLAG_NONE;
1733 }
1734
1735 static void
clutter_gst_player_set_seek_flags_impl(ClutterGstPlayer * player,ClutterGstSeekFlags flags)1736 clutter_gst_player_set_seek_flags_impl (ClutterGstPlayer *player,
1737 ClutterGstSeekFlags flags)
1738 {
1739 ClutterGstPlayerPrivate *priv;
1740
1741 priv = PLAYER_GET_PRIVATE (player);
1742
1743 if (flags == CLUTTER_GST_SEEK_FLAG_NONE)
1744 priv->seek_flags = GST_SEEK_FLAG_KEY_UNIT;
1745 else if (flags & CLUTTER_GST_SEEK_FLAG_ACCURATE)
1746 priv->seek_flags = GST_SEEK_FLAG_ACCURATE;
1747 }
1748
1749 static ClutterGstBufferingMode
clutter_gst_player_get_buffering_mode_impl(ClutterGstPlayer * player)1750 clutter_gst_player_get_buffering_mode_impl (ClutterGstPlayer *player)
1751 {
1752 ClutterGstPlayerPrivate *priv;
1753 GstPlayFlags flags;
1754
1755 priv = PLAYER_GET_PRIVATE (player);
1756
1757 g_object_get (G_OBJECT (priv->pipeline), "flags", &flags, NULL);
1758
1759 if (flags & GST_PLAY_FLAG_DOWNLOAD)
1760 return CLUTTER_GST_BUFFERING_MODE_DOWNLOAD;
1761
1762 return CLUTTER_GST_BUFFERING_MODE_STREAM;
1763 }
1764
1765 static void
clutter_gst_player_set_buffering_mode_impl(ClutterGstPlayer * player,ClutterGstBufferingMode mode)1766 clutter_gst_player_set_buffering_mode_impl (ClutterGstPlayer *player,
1767 ClutterGstBufferingMode mode)
1768 {
1769 ClutterGstPlayerPrivate *priv;
1770 GstPlayFlags flags;
1771
1772 priv = PLAYER_GET_PRIVATE (player);
1773
1774 g_object_get (G_OBJECT (priv->pipeline), "flags", &flags, NULL);
1775
1776 switch (mode)
1777 {
1778 case CLUTTER_GST_BUFFERING_MODE_STREAM:
1779 flags &= ~GST_PLAY_FLAG_DOWNLOAD;
1780 break;
1781
1782 case CLUTTER_GST_BUFFERING_MODE_DOWNLOAD:
1783 flags |= GST_PLAY_FLAG_DOWNLOAD;
1784 break;
1785
1786 default:
1787 g_warning ("Unexpected buffering mode %d", mode);
1788 break;
1789 }
1790
1791 g_object_set (G_OBJECT (priv->pipeline), "flags", flags, NULL);
1792 }
1793
1794 static GList *
clutter_gst_player_get_audio_streams_impl(ClutterGstPlayer * player)1795 clutter_gst_player_get_audio_streams_impl (ClutterGstPlayer *player)
1796 {
1797 ClutterGstPlayerPrivate *priv;
1798
1799 priv = PLAYER_GET_PRIVATE (player);
1800
1801 if (CLUTTER_GST_DEBUG_ENABLED (AUDIO_STREAM))
1802 {
1803 gchar *streams;
1804
1805 streams = list_to_string (priv->audio_streams);
1806 CLUTTER_GST_NOTE (AUDIO_STREAM, "audio streams: %s", streams);
1807 g_free (streams);
1808 }
1809
1810 return priv->audio_streams;
1811 }
1812
1813 static gint
clutter_gst_player_get_audio_stream_impl(ClutterGstPlayer * player)1814 clutter_gst_player_get_audio_stream_impl (ClutterGstPlayer *player)
1815 {
1816 ClutterGstPlayerPrivate *priv;
1817 gint index_ = -1;
1818
1819 priv = PLAYER_GET_PRIVATE (player);
1820
1821 g_object_get (G_OBJECT (priv->pipeline),
1822 "current-audio", &index_,
1823 NULL);
1824
1825 CLUTTER_GST_NOTE (AUDIO_STREAM, "audio stream is #%d", index_);
1826
1827 return index_;
1828 }
1829
1830 static void
clutter_gst_player_set_audio_stream_impl(ClutterGstPlayer * player,gint index_)1831 clutter_gst_player_set_audio_stream_impl (ClutterGstPlayer *player,
1832 gint index_)
1833 {
1834 ClutterGstPlayerPrivate *priv;
1835
1836 priv = PLAYER_GET_PRIVATE (player);
1837
1838 g_return_if_fail (index_ >= 0 &&
1839 index_ < (gint) g_list_length (priv->audio_streams));
1840
1841 CLUTTER_GST_NOTE (AUDIO_STREAM, "set audio audio stream to #%d", index_);
1842
1843 g_object_set (G_OBJECT (priv->pipeline),
1844 "current-audio", index_,
1845 NULL);
1846 }
1847
1848 static GList *
clutter_gst_player_get_subtitle_tracks_impl(ClutterGstPlayer * player)1849 clutter_gst_player_get_subtitle_tracks_impl (ClutterGstPlayer *player)
1850 {
1851 ClutterGstPlayerPrivate *priv;
1852
1853 priv = PLAYER_GET_PRIVATE (player);
1854
1855 if (CLUTTER_GST_DEBUG_ENABLED (SUBTITLES))
1856 {
1857 gchar *tracks;
1858
1859 tracks = list_to_string (priv->subtitle_tracks);
1860 CLUTTER_GST_NOTE (SUBTITLES, "subtitle tracks: %s", tracks);
1861 g_free (tracks);
1862 }
1863
1864 return priv->subtitle_tracks;
1865 }
1866
1867 static gint
clutter_gst_player_get_subtitle_track_impl(ClutterGstPlayer * player)1868 clutter_gst_player_get_subtitle_track_impl (ClutterGstPlayer *player)
1869 {
1870 ClutterGstPlayerPrivate *priv;
1871 gint index_ = -1;
1872
1873 priv = PLAYER_GET_PRIVATE (player);
1874
1875 g_object_get (G_OBJECT (priv->pipeline),
1876 "current-text", &index_,
1877 NULL);
1878
1879 CLUTTER_GST_NOTE (SUBTITLES, "text track is #%d", index_);
1880
1881 return index_;
1882 }
1883
1884 static void
clutter_gst_player_set_subtitle_track_impl(ClutterGstPlayer * player,gint index_)1885 clutter_gst_player_set_subtitle_track_impl (ClutterGstPlayer *player,
1886 gint index_)
1887 {
1888 ClutterGstPlayerPrivate *priv;
1889 GstPlayFlags flags;
1890
1891 priv = PLAYER_GET_PRIVATE (player);
1892
1893 g_return_if_fail (index_ >= -1 &&
1894 index_ < (gint) g_list_length (priv->subtitle_tracks));
1895
1896 CLUTTER_GST_NOTE (SUBTITLES, "set subtitle track to #%d", index_);
1897
1898 g_object_get (priv->pipeline, "flags", &flags, NULL);
1899 flags &= ~GST_PLAY_FLAG_TEXT;
1900 g_object_set (priv->pipeline, "flags", flags, NULL);
1901
1902 if (index_ >= 0)
1903 {
1904 g_object_set (G_OBJECT (priv->pipeline),
1905 "current-text", index_,
1906 NULL);
1907
1908 flags |= GST_PLAY_FLAG_TEXT;
1909 g_object_set (priv->pipeline, "flags", flags, NULL);
1910 }
1911 }
1912
1913 static gboolean
clutter_gst_player_get_idle_impl(ClutterGstPlayer * player)1914 clutter_gst_player_get_idle_impl (ClutterGstPlayer *player)
1915 {
1916 ClutterGstPlayerPrivate *priv;
1917
1918 priv = PLAYER_GET_PRIVATE (player);
1919
1920 return priv->is_idle;
1921 }
1922
1923 static gboolean
clutter_gst_player_get_in_seek_impl(ClutterGstPlayer * player)1924 clutter_gst_player_get_in_seek_impl (ClutterGstPlayer *player)
1925 {
1926 ClutterGstPlayerPrivate *priv;
1927
1928 priv = PLAYER_GET_PRIVATE (player);
1929
1930 return priv->in_seek;
1931 }
1932
1933
1934 /**/
1935
1936 #if defined (CLUTTER_WINDOWING_X11) && defined (HAVE_HW_DECODER_SUPPORT)
1937 static GstBusSyncReply
on_sync_message(GstBus * bus,GstMessage * message,gpointer user_data)1938 on_sync_message (GstBus * bus, GstMessage * message, gpointer user_data)
1939 {
1940 Display *display = user_data;
1941 GstVideoContext *context;
1942 const gchar **types;
1943
1944 if (gst_video_context_message_parse_prepare (message, &types, &context)) {
1945 gint i;
1946
1947 for (i = 0; types[i]; i++) {
1948
1949 if (!strcmp(types[i], "x11-display")) {
1950 gst_video_context_set_context_pointer (context, "x11-display", display);
1951 }
1952 else if (!strcmp(types[i], "x11-display-name")) {
1953 gst_video_context_set_context_string (context, "x11-display-name",
1954 DisplayString (display));
1955 } else {
1956 continue;
1957 }
1958
1959 gst_message_unref (message);
1960 return GST_BUS_DROP;
1961 }
1962 }
1963
1964 return GST_BUS_PASS;
1965 }
1966 #endif
1967
1968 /**
1969 * clutter_gst_player_init:
1970 * @player: a #ClutterGstPlayer
1971 *
1972 * Initialize a #ClutterGstPlayer instance. You should call this
1973 * function at the beginning of the init method of the class
1974 * implementing #ClutterGstPlayer.
1975 *
1976 * When you're finished with the ClutterGstPlayer mixin features (usually in
1977 * the dispose or finalize vfuncs), call clutter_gst_player_deinit() to
1978 * desallocate the resources created by clutter_gst_player_init().
1979 *
1980 * Return value: TRUE if the initialization was successfull, FALSE otherwise.
1981 *
1982 * Since: 1.4
1983 */
1984 gboolean
clutter_gst_player_init(ClutterGstPlayer * player)1985 clutter_gst_player_init (ClutterGstPlayer *player)
1986 {
1987 ClutterGstPlayerPrivate *priv;
1988 ClutterGstPlayerIface *iface;
1989
1990 g_return_val_if_fail (CLUTTER_GST_IS_PLAYER (player), FALSE);
1991
1992 priv = PLAYER_GET_PRIVATE (player);
1993 if (priv)
1994 return TRUE;
1995
1996 iface = CLUTTER_GST_PLAYER_GET_INTERFACE (player);
1997
1998 iface->get_pipeline = clutter_gst_player_get_pipeline_impl;
1999 iface->get_user_agent = clutter_gst_player_get_user_agent_impl;
2000 iface->set_user_agent = clutter_gst_player_set_user_agent_impl;
2001 iface->get_seek_flags = clutter_gst_player_get_seek_flags_impl;
2002 iface->set_seek_flags = clutter_gst_player_set_seek_flags_impl;
2003 iface->get_buffering_mode = clutter_gst_player_get_buffering_mode_impl;
2004 iface->set_buffering_mode = clutter_gst_player_set_buffering_mode_impl;
2005 iface->get_audio_streams = clutter_gst_player_get_audio_streams_impl;
2006 iface->get_audio_stream = clutter_gst_player_get_audio_stream_impl;
2007 iface->set_audio_stream = clutter_gst_player_set_audio_stream_impl;
2008 iface->get_subtitle_tracks = clutter_gst_player_get_subtitle_tracks_impl;
2009 iface->get_subtitle_track = clutter_gst_player_get_subtitle_track_impl;
2010 iface->set_subtitle_track = clutter_gst_player_set_subtitle_track_impl;
2011 iface->get_idle = clutter_gst_player_get_idle_impl;
2012 iface->get_in_seek = clutter_gst_player_get_in_seek_impl;
2013
2014 priv = g_slice_new0 (ClutterGstPlayerPrivate);
2015 PLAYER_SET_PRIVATE (player, priv);
2016
2017 priv->is_idle = TRUE;
2018 priv->in_seek = FALSE;
2019 priv->is_changing_uri = FALSE;
2020 priv->in_download_buffering = FALSE;
2021
2022 priv->pipeline = get_pipeline ();
2023 if (!priv->pipeline)
2024 {
2025 g_critical ("Unable to create pipeline");
2026 return FALSE;
2027 }
2028
2029 g_signal_connect (priv->pipeline, "notify::source",
2030 G_CALLBACK (on_source_changed), player);
2031
2032 /* We default to not playing until someone calls set_playing(TRUE) */
2033 priv->target_state = GST_STATE_PAUSED;
2034
2035 /* Default to a fast seek, ie. same effect than set_seek_flags (NONE); */
2036 priv->seek_flags = GST_SEEK_FLAG_KEY_UNIT;
2037
2038 priv->bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
2039
2040 gst_bus_add_signal_watch (priv->bus);
2041
2042 g_signal_connect_object (priv->bus, "message::error",
2043 G_CALLBACK (bus_message_error_cb),
2044 player, 0);
2045 g_signal_connect_object (priv->bus, "message::eos",
2046 G_CALLBACK (bus_message_eos_cb),
2047 player, 0);
2048 g_signal_connect_object (priv->bus, "message::buffering",
2049 G_CALLBACK (bus_message_buffering_cb),
2050 player, 0);
2051 g_signal_connect_object (priv->bus, "message::duration-changed",
2052 G_CALLBACK (bus_message_duration_changed_cb),
2053 player, 0);
2054 g_signal_connect_object (priv->bus, "message::state-changed",
2055 G_CALLBACK (bus_message_state_change_cb),
2056 player, 0);
2057 g_signal_connect_object (priv->bus, "message::async-done",
2058 G_CALLBACK (bus_message_async_done_cb),
2059 player, 0);
2060
2061 g_signal_connect (priv->pipeline, "notify::volume",
2062 G_CALLBACK (on_volume_changed),
2063 player);
2064
2065 g_signal_connect (priv->pipeline, "audio-changed",
2066 G_CALLBACK (on_audio_changed),
2067 player);
2068 g_signal_connect (priv->pipeline, "audio-tags-changed",
2069 G_CALLBACK (on_audio_tags_changed),
2070 player);
2071 g_signal_connect (priv->pipeline, "notify::current-audio",
2072 G_CALLBACK (on_current_audio_changed),
2073 player);
2074
2075 g_signal_connect (priv->pipeline, "text-changed",
2076 G_CALLBACK (on_text_changed),
2077 player);
2078 g_signal_connect (priv->pipeline, "text-tags-changed",
2079 G_CALLBACK (on_text_tags_changed),
2080 player);
2081 g_signal_connect (priv->pipeline, "notify::current-text",
2082 G_CALLBACK (on_current_text_changed),
2083 player);
2084
2085 #if defined(CLUTTER_WINDOWING_X11) && defined (HAVE_HW_DECODER_SUPPORT)
2086 if (clutter_check_windowing_backend (CLUTTER_WINDOWING_X11))
2087 gst_bus_set_sync_handler (priv->bus, on_sync_message,
2088 clutter_x11_get_default_display (), NULL);
2089 #endif
2090
2091 gst_object_unref (GST_OBJECT (priv->bus));
2092
2093 return TRUE;
2094 }
2095
2096 /**
2097 * clutter_gst_player_deinit:
2098 * @player: a #ClutterGstPlayer
2099 *
2100 * Frees the resources created by clutter_gst_player_init(). After
2101 * clutter_gst_player_deinit() has been called, no other player method can be
2102 * called on the instance.
2103 *
2104 * Since: 1.4
2105 */
2106 void
clutter_gst_player_deinit(ClutterGstPlayer * player)2107 clutter_gst_player_deinit (ClutterGstPlayer *player)
2108 {
2109 ClutterGstPlayerPrivate *priv;
2110
2111 g_return_if_fail (CLUTTER_GST_IS_PLAYER (player));
2112
2113 priv = PLAYER_GET_PRIVATE (player);
2114
2115 if (priv == NULL)
2116 return;
2117
2118 PLAYER_SET_PRIVATE (player, NULL);
2119
2120 if (priv->tick_timeout_id)
2121 {
2122 g_source_remove (priv->tick_timeout_id);
2123 priv->tick_timeout_id = 0;
2124 }
2125
2126 if (priv->buffering_timeout_id)
2127 {
2128 g_source_remove (priv->buffering_timeout_id);
2129 priv->buffering_timeout_id = 0;
2130 }
2131
2132 if (priv->download_buffering_element)
2133 {
2134 g_object_unref (priv->download_buffering_element);
2135 priv->download_buffering_element = NULL;
2136 }
2137
2138 gst_element_set_state (priv->pipeline, GST_STATE_NULL);
2139
2140 if (priv->bus)
2141 {
2142 gst_bus_remove_signal_watch (priv->bus);
2143 priv->bus = NULL;
2144 }
2145
2146 if (priv->pipeline)
2147 {
2148 gst_object_unref (GST_OBJECT (priv->pipeline));
2149 priv->pipeline = NULL;
2150 }
2151
2152 g_free (priv->uri);
2153 g_free (priv->font_name);
2154 g_free (priv->user_agent);
2155 free_tags_list (&priv->audio_streams);
2156 free_tags_list (&priv->subtitle_tracks);
2157
2158 g_slice_free (ClutterGstPlayerPrivate, priv);
2159 }
2160
2161 static void
clutter_gst_player_default_init(ClutterGstPlayerIface * iface)2162 clutter_gst_player_default_init (ClutterGstPlayerIface *iface)
2163 {
2164 GParamSpec *pspec;
2165
2166 /**
2167 * ClutterGstPlayer:idle:
2168 *
2169 * Whether the #ClutterGstPlayer is in idle mode.
2170 *
2171 * Since: 1.4
2172 */
2173 pspec = g_param_spec_boolean ("idle",
2174 "Idle",
2175 "Idle state of the player's pipeline",
2176 TRUE,
2177 CLUTTER_GST_PARAM_READABLE);
2178 g_object_interface_install_property (iface, pspec);
2179
2180 /**
2181 * ClutterGstPlayer:user-agent:
2182 *
2183 * The User Agent used by #ClutterGstPlayer with network protocols.
2184 *
2185 * Since: 1.4
2186 */
2187 pspec = g_param_spec_string ("user-agent",
2188 "User Agent",
2189 "User Agent used with network protocols",
2190 NULL,
2191 CLUTTER_GST_PARAM_READWRITE);
2192 g_object_interface_install_property (iface, pspec);
2193
2194 /**
2195 * ClutterGstPlayer:seek-flags:
2196 *
2197 * Flags to use when seeking.
2198 *
2199 * Since: 1.4
2200 */
2201 pspec = g_param_spec_flags ("seek-flags",
2202 "Seek Flags",
2203 "Flags to use when seeking",
2204 CLUTTER_GST_TYPE_SEEK_FLAGS,
2205 CLUTTER_GST_SEEK_FLAG_NONE,
2206 CLUTTER_GST_PARAM_READWRITE);
2207 g_object_interface_install_property (iface, pspec);
2208
2209 /**
2210 * ClutterGstPlayer:audio-streams:
2211 *
2212 * List of audio streams available on the current media.
2213 *
2214 * Since: 1.4
2215 */
2216 pspec = g_param_spec_pointer ("audio-streams",
2217 "Audio Streams",
2218 "List of the audio streams of the media",
2219 CLUTTER_GST_PARAM_READABLE);
2220 g_object_interface_install_property (iface, pspec);
2221
2222 /**
2223 * ClutterGstPlayer:audio-stream:
2224 *
2225 * Index of the current audio stream.
2226 *
2227 * Since: 1.4
2228 */
2229 pspec = g_param_spec_int ("audio-stream",
2230 "Audio Stream",
2231 "Index of the current audio stream",
2232 -1, G_MAXINT, -1,
2233 CLUTTER_GST_PARAM_READWRITE);
2234 g_object_interface_install_property (iface, pspec);
2235
2236 pspec = g_param_spec_pointer ("subtitle-tracks",
2237 "Subtitles Tracks",
2238 "List of the subtitles tracks of the media",
2239 CLUTTER_GST_PARAM_READABLE);
2240 g_object_interface_install_property (iface, pspec);
2241
2242 pspec = g_param_spec_int ("subtitle-track",
2243 "Subtitles Track",
2244 "Index of the current subtitles track",
2245 -1, G_MAXINT, -1,
2246 CLUTTER_GST_PARAM_READWRITE);
2247 g_object_interface_install_property (iface, pspec);
2248
2249
2250 /**
2251 * ClutterGstPlayer:in-seek:
2252 *
2253 * Whether or not the stream is being seeked.
2254 *
2255 * Since: 1.6
2256 */
2257 pspec = g_param_spec_boolean ("in-seek",
2258 "In seek mode",
2259 "If currently seeking",
2260 FALSE,
2261 CLUTTER_GST_PARAM_READABLE);
2262 g_object_interface_install_property (iface, pspec);
2263
2264
2265 /* Signals */
2266
2267 /**
2268 * ClutterGstPlayer::download-buffering:
2269 * @player: the #ClutterGstPlayer instance that received the signal
2270 * @start: start position of the buffering
2271 * @stop: start position of the buffering
2272 *
2273 * The ::download-buffering signal is emitted each time their an
2274 * update about the buffering of the current media.
2275 *
2276 * Since: 1.4
2277 */
2278 signals[DOWNLOAD_BUFFERING] =
2279 g_signal_new ("download-buffering",
2280 CLUTTER_GST_TYPE_PLAYER,
2281 G_SIGNAL_RUN_LAST,
2282 G_STRUCT_OFFSET (ClutterGstPlayerIface,
2283 download_buffering),
2284 NULL, NULL,
2285 _clutter_gst_marshal_VOID__DOUBLE_DOUBLE,
2286 G_TYPE_NONE, 2, G_TYPE_DOUBLE, G_TYPE_DOUBLE);
2287
2288 /* Setup a quark for per instance private data */
2289 if (!clutter_gst_player_private_quark)
2290 {
2291 clutter_gst_player_private_quark =
2292 g_quark_from_static_string ("clutter-gst-player-private-quark");
2293 clutter_gst_player_class_quark =
2294 g_quark_from_static_string ("clutter-gst-player-class-quark");
2295 }
2296 }
2297
2298 /* ClutterGstIface */
2299
2300 /**
2301 * clutter_gst_player_get_pipeline:
2302 * @player: a #ClutterGstPlayer
2303 *
2304 * Retrieves the #GstPipeline used by the @player, for direct use with
2305 * GStreamer API.
2306 *
2307 * Return value: (transfer none): the #GstPipeline element used by the player
2308 *
2309 * Since: 1.4
2310 */
2311 GstElement *
clutter_gst_player_get_pipeline(ClutterGstPlayer * player)2312 clutter_gst_player_get_pipeline (ClutterGstPlayer *player)
2313 {
2314 ClutterGstPlayerIface *iface;
2315
2316 g_return_val_if_fail (CLUTTER_GST_IS_PLAYER (player), NULL);
2317
2318 iface = CLUTTER_GST_PLAYER_GET_INTERFACE (player);
2319
2320 return iface->get_pipeline (player);
2321 }
2322
2323 /**
2324 * clutter_gst_player_get_user_agent:
2325 * @player: a #ClutterGstPlayer
2326 *
2327 * Retrieves the user agent used when streaming.
2328 *
2329 * Return value: the user agent used. The returned string has to be freed with
2330 * g_free()
2331 *
2332 * Since: 1.4
2333 */
2334 gchar *
clutter_gst_player_get_user_agent(ClutterGstPlayer * player)2335 clutter_gst_player_get_user_agent (ClutterGstPlayer *player)
2336 {
2337 ClutterGstPlayerIface *iface;
2338
2339 g_return_val_if_fail (CLUTTER_GST_IS_PLAYER (player), NULL);
2340
2341 iface = CLUTTER_GST_PLAYER_GET_INTERFACE (player);
2342
2343 return iface->get_user_agent (player);
2344 }
2345
2346 /**
2347 * clutter_gst_player_set_user_agent:
2348 * @player: a #ClutterGstPlayer
2349 * @user_agent: the user agent
2350 *
2351 * Sets the user agent to use when streaming.
2352 *
2353 * When streaming content, you might want to set a custom user agent, eg. to
2354 * promote your software, make it appear in statistics or because the server
2355 * requires a special user agent you want to impersonate.
2356 *
2357 * Since: 1.4
2358 */
2359 void
clutter_gst_player_set_user_agent(ClutterGstPlayer * player,const gchar * user_agent)2360 clutter_gst_player_set_user_agent (ClutterGstPlayer *player,
2361 const gchar *user_agent)
2362 {
2363 ClutterGstPlayerIface *iface;
2364
2365 g_return_if_fail (CLUTTER_GST_IS_PLAYER (player));
2366
2367 iface = CLUTTER_GST_PLAYER_GET_INTERFACE (player);
2368
2369 iface->set_user_agent (player, user_agent);
2370 }
2371
2372 /**
2373 * clutter_gst_player_get_seek_flags:
2374 * @player: a #ClutterGstPlayer
2375 *
2376 * Get the current value of the seek-flags property.
2377 *
2378 * Return value: a combination of #ClutterGstSeekFlags
2379 *
2380 * Since: 1.4
2381 */
2382 ClutterGstSeekFlags
clutter_gst_player_get_seek_flags(ClutterGstPlayer * player)2383 clutter_gst_player_get_seek_flags (ClutterGstPlayer *player)
2384 {
2385 ClutterGstPlayerIface *iface;
2386
2387 g_return_val_if_fail (CLUTTER_GST_IS_PLAYER (player),
2388 CLUTTER_GST_SEEK_FLAG_NONE);
2389
2390 iface = CLUTTER_GST_PLAYER_GET_INTERFACE (player);
2391
2392 return iface->get_seek_flags (player);
2393 }
2394
2395 /**
2396 * clutter_gst_player_set_seek_flags:
2397 * @player: a #ClutterGstPlayer
2398 * @flags: a combination of #ClutterGstSeekFlags
2399 *
2400 * Seeking can be done with several trade-offs. Clutter-gst defaults
2401 * to %CLUTTER_GST_SEEK_FLAG_NONE.
2402 *
2403 * Since: 1.4
2404 */
2405 void
clutter_gst_player_set_seek_flags(ClutterGstPlayer * player,ClutterGstSeekFlags flags)2406 clutter_gst_player_set_seek_flags (ClutterGstPlayer *player,
2407 ClutterGstSeekFlags flags)
2408 {
2409 ClutterGstPlayerIface *iface;
2410
2411 g_return_if_fail (CLUTTER_GST_IS_PLAYER (player));
2412
2413 iface = CLUTTER_GST_PLAYER_GET_INTERFACE (player);
2414
2415 iface->set_seek_flags (player, flags);
2416 }
2417
2418 /**
2419 * clutter_gst_player_get_buffering_mode:
2420 * @player: a #ClutterGstPlayer
2421 *
2422 * Return value: a #ClutterGstBufferingMode
2423 *
2424 * Since: 1.4
2425 */
2426 ClutterGstBufferingMode
clutter_gst_player_get_buffering_mode(ClutterGstPlayer * player)2427 clutter_gst_player_get_buffering_mode (ClutterGstPlayer *player)
2428 {
2429 ClutterGstPlayerIface *iface;
2430
2431 g_return_val_if_fail (CLUTTER_GST_IS_PLAYER (player),
2432 CLUTTER_GST_BUFFERING_MODE_STREAM);
2433
2434 iface = CLUTTER_GST_PLAYER_GET_INTERFACE (player);
2435
2436 return iface->get_buffering_mode (player);
2437 }
2438
2439 /**
2440 * clutter_gst_player_set_buffering_mode:
2441 * @player: a #ClutterGstPlayer
2442 * @mode: a #ClutterGstBufferingMode
2443 *
2444 * Since: 1.4
2445 */
2446 void
clutter_gst_player_set_buffering_mode(ClutterGstPlayer * player,ClutterGstBufferingMode mode)2447 clutter_gst_player_set_buffering_mode (ClutterGstPlayer *player,
2448 ClutterGstBufferingMode mode)
2449 {
2450 ClutterGstPlayerIface *iface;
2451
2452 g_return_if_fail (CLUTTER_GST_IS_PLAYER (player));
2453
2454 iface = CLUTTER_GST_PLAYER_GET_INTERFACE (player);
2455
2456 iface->set_buffering_mode (player, mode);
2457 }
2458
2459 /**
2460 * clutter_gst_player_get_audio_streams:
2461 * @player: a #ClutterGstPlayer
2462 *
2463 * Get the list of audio streams of the current media.
2464 *
2465 * Return value: (transfer none) (element-type utf8): a list of
2466 * strings describing the available audio streams
2467 *
2468 * Since: 1.4
2469 */
2470 GList *
clutter_gst_player_get_audio_streams(ClutterGstPlayer * player)2471 clutter_gst_player_get_audio_streams (ClutterGstPlayer *player)
2472 {
2473 ClutterGstPlayerIface *iface;
2474
2475 g_return_val_if_fail (CLUTTER_GST_IS_PLAYER (player), NULL);
2476
2477 iface = CLUTTER_GST_PLAYER_GET_INTERFACE (player);
2478
2479 return iface->get_audio_streams (player);
2480 }
2481
2482 /**
2483 * clutter_gst_player_get_audio_stream:
2484 * @player: a #ClutterGstPlayer
2485 *
2486 * Get the current audio stream. The number returned in the index of the
2487 * audio stream playing in the list returned by
2488 * clutter_gst_player_get_audio_streams().
2489 *
2490 * Return value: the index of the current audio stream, -1 if the media has no
2491 * audio stream
2492 *
2493 * Since: 1.4
2494 */
2495 gint
clutter_gst_player_get_audio_stream(ClutterGstPlayer * player)2496 clutter_gst_player_get_audio_stream (ClutterGstPlayer *player)
2497 {
2498 ClutterGstPlayerIface *iface;
2499
2500 g_return_val_if_fail (CLUTTER_GST_IS_PLAYER (player), -1);
2501
2502 iface = CLUTTER_GST_PLAYER_GET_INTERFACE (player);
2503
2504 return iface->get_audio_stream (player);
2505 }
2506
2507 /**
2508 * clutter_gst_player_set_audio_stream:
2509 * @player: a #ClutterGstPlayer
2510 * @index_: the index of the audio stream
2511 *
2512 * Set the audio stream to play. @index_ is the index of the stream
2513 * in the list returned by clutter_gst_player_get_audio_streams().
2514 *
2515 * Since: 1.4
2516 */
2517 void
clutter_gst_player_set_audio_stream(ClutterGstPlayer * player,gint index_)2518 clutter_gst_player_set_audio_stream (ClutterGstPlayer *player,
2519 gint index_)
2520 {
2521 ClutterGstPlayerIface *iface;
2522
2523 g_return_if_fail (CLUTTER_GST_IS_PLAYER (player));
2524
2525 iface = CLUTTER_GST_PLAYER_GET_INTERFACE (player);
2526
2527 iface->set_audio_stream (player, index_);
2528 }
2529
2530 /**
2531 * clutter_gst_player_get_subtitle_tracks:
2532 * @player: a #ClutterGstPlayer
2533 *
2534 * Get the list of subtitles tracks of the current media.
2535 *
2536 * Return value: (transfer none) (element-type utf8): a list of
2537 * strings describing the available subtitles tracks
2538 *
2539 * Since: 1.4
2540 */
2541 GList *
clutter_gst_player_get_subtitle_tracks(ClutterGstPlayer * player)2542 clutter_gst_player_get_subtitle_tracks (ClutterGstPlayer *player)
2543 {
2544 ClutterGstPlayerIface *iface;
2545
2546 g_return_val_if_fail (CLUTTER_GST_IS_PLAYER (player), NULL);
2547
2548 iface = CLUTTER_GST_PLAYER_GET_INTERFACE (player);
2549
2550 return iface->get_subtitle_tracks (player);
2551 }
2552
2553 /**
2554 * clutter_gst_player_get_subtitle_track:
2555 * @player: a #ClutterGstPlayer
2556 *
2557 * Get the current subtitles track. The number returned is the index of the
2558 * subtiles track in the list returned by
2559 * clutter_gst_player_get_subtitle_tracks().
2560 *
2561 * Return value: the index of the current subtitlest track, -1 if the media has
2562 * no subtitles track or if the subtitles have been turned off
2563 *
2564 * Since: 1.4
2565 */
2566 gint
clutter_gst_player_get_subtitle_track(ClutterGstPlayer * player)2567 clutter_gst_player_get_subtitle_track (ClutterGstPlayer *player)
2568 {
2569 ClutterGstPlayerIface *iface;
2570
2571 g_return_val_if_fail (CLUTTER_GST_IS_PLAYER (player), -1);
2572
2573 iface = CLUTTER_GST_PLAYER_GET_INTERFACE (player);
2574
2575 return iface->get_subtitle_track (player);
2576 }
2577
2578 /**
2579 * clutter_gst_player_set_subtitle_track:
2580 * @player: a #ClutterGstPlayer
2581 * @index_: the index of the subtitles track
2582 *
2583 * Set the subtitles track to play. @index_ is the index of the stream
2584 * in the list returned by clutter_gst_player_get_subtitle_tracks().
2585 *
2586 * If @index_ is -1, the subtitles are turned off.
2587 *
2588 * Since: 1.4
2589 */
2590 void
clutter_gst_player_set_subtitle_track(ClutterGstPlayer * player,gint index_)2591 clutter_gst_player_set_subtitle_track (ClutterGstPlayer *player,
2592 gint index_)
2593 {
2594 ClutterGstPlayerIface *iface;
2595
2596 g_return_if_fail (CLUTTER_GST_IS_PLAYER (player));
2597
2598 iface = CLUTTER_GST_PLAYER_GET_INTERFACE (player);
2599
2600 iface->set_subtitle_track (player, index_);
2601 }
2602
2603 /**
2604 * clutter_gst_player_get_idle:
2605 * @player: a #ClutterGstPlayer
2606 *
2607 * Get the idle state of the pipeline.
2608 *
2609 * Return value: TRUE if the pipline is in idle mode, FALSE otherwise.
2610 *
2611 * Since: 1.4
2612 */
2613 gboolean
clutter_gst_player_get_idle(ClutterGstPlayer * player)2614 clutter_gst_player_get_idle (ClutterGstPlayer *player)
2615 {
2616 ClutterGstPlayerIface *iface;
2617
2618 g_return_val_if_fail (CLUTTER_GST_IS_PLAYER (player), TRUE);
2619
2620 iface = CLUTTER_GST_PLAYER_GET_INTERFACE (player);
2621
2622 return iface->get_idle (player);
2623 }
2624
2625 /**
2626 * clutter_gst_player_get_in_seek:
2627 * @player: a #ClutterGstPlayer
2628 *
2629 * Whether the player is seeking.
2630 *
2631 * Return value: TRUE if the player is seeking, FALSE otherwise.
2632 *
2633 * Since: 1.6
2634 */
2635 gboolean
clutter_gst_player_get_in_seek(ClutterGstPlayer * player)2636 clutter_gst_player_get_in_seek (ClutterGstPlayer *player)
2637 {
2638 ClutterGstPlayerIface *iface;
2639
2640 g_return_val_if_fail (CLUTTER_GST_IS_PLAYER (player), FALSE);
2641
2642 iface = CLUTTER_GST_PLAYER_GET_INTERFACE (player);
2643
2644 return iface->get_in_seek (player);
2645 }
2646