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, &current_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, &current_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", &current_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