gst/playback/gstplaybin.c: Add "connection-speed" property; re-order redirect message...
[platform/upstream/gstreamer.git] / gst / playback / gstplaybin.c
1 /* GStreamer
2  * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include <string.h>
25 #include <gst/gst.h>
26
27 #include <gst/gst-i18n-plugin.h>
28
29 #include "gstplaybasebin.h"
30
31 GST_DEBUG_CATEGORY_STATIC (gst_play_bin_debug);
32 #define GST_CAT_DEFAULT gst_play_bin_debug
33
34 #define GST_TYPE_PLAY_BIN               (gst_play_bin_get_type())
35 #define GST_PLAY_BIN(obj)               (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PLAY_BIN,GstPlayBin))
36 #define GST_PLAY_BIN_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PLAY_BIN,GstPlayBinClass))
37 #define GST_IS_PLAY_BIN(obj)            (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PLAY_BIN))
38 #define GST_IS_PLAY_BIN_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PLAY_BIN))
39
40 #define VOLUME_MAX_DOUBLE 10.0
41 #define CONNECTION_SPEED_DEFAULT 0
42
43 typedef struct _GstPlayBin GstPlayBin;
44 typedef struct _GstPlayBinClass GstPlayBinClass;
45
46 struct _GstPlayBin
47 {
48   GstPlayBaseBin parent;
49
50   /* the configurable elements */
51   GstElement *fakesink;
52   GstElement *audio_sink;
53   GstElement *video_sink;
54   GstElement *visualisation;
55   GstElement *pending_visualisation;
56   GstElement *volume_element;
57   GstElement *textoverlay_element;
58   gfloat volume;
59
60   /* these are the currently active sinks */
61   GList *sinks;
62
63   /* the last captured frame for snapshots */
64   GstBuffer *frame;
65
66   /* our cache for the sinks */
67   GHashTable *cache;
68
69   /* font description */
70   gchar *font_desc;
71
72   /* connection speed in bits/sec (0 = unknown) */
73   guint connection_speed;
74 };
75
76 struct _GstPlayBinClass
77 {
78   GstPlayBaseBinClass parent_class;
79 };
80
81 /* props */
82 enum
83 {
84   ARG_0,
85   ARG_AUDIO_SINK,
86   ARG_VIDEO_SINK,
87   ARG_VIS_PLUGIN,
88   ARG_VOLUME,
89   ARG_FRAME,
90   ARG_FONT_DESC,
91   ARG_CONNECTION_SPEED
92 };
93
94 /* signals */
95 enum
96 {
97   LAST_SIGNAL
98 };
99
100 static void gst_play_bin_class_init (GstPlayBinClass * klass);
101 static void gst_play_bin_init (GstPlayBin * play_bin);
102 static void gst_play_bin_dispose (GObject * object);
103
104 static gboolean setup_sinks (GstPlayBaseBin * play_base_bin,
105     GstPlayBaseGroup * group);
106 static void remove_sinks (GstPlayBin * play_bin);
107
108 static void gst_play_bin_set_property (GObject * object, guint prop_id,
109     const GValue * value, GParamSpec * spec);
110 static void gst_play_bin_get_property (GObject * object, guint prop_id,
111     GValue * value, GParamSpec * spec);
112
113 static gboolean gst_play_bin_send_event (GstElement * element,
114     GstEvent * event);
115 static GstStateChangeReturn gst_play_bin_change_state (GstElement * element,
116     GstStateChange transition);
117 static void gst_play_bin_handle_message (GstBin * bin, GstMessage * message);
118
119 static GstElementClass *parent_class;
120
121 //static guint gst_play_bin_signals[LAST_SIGNAL] = { 0 };
122
123 static const GstElementDetails gst_play_bin_details =
124 GST_ELEMENT_DETAILS ("Player Bin",
125     "Generic/Bin/Player",
126     "Autoplug and play media from an uri",
127     "Wim Taymans <wim@fluendo.com>");
128
129 static GType
130 gst_play_bin_get_type (void)
131 {
132   static GType gst_play_bin_type = 0;
133
134   if (!gst_play_bin_type) {
135     static const GTypeInfo gst_play_bin_info = {
136       sizeof (GstPlayBinClass),
137       NULL,
138       NULL,
139       (GClassInitFunc) gst_play_bin_class_init,
140       NULL,
141       NULL,
142       sizeof (GstPlayBin),
143       0,
144       (GInstanceInitFunc) gst_play_bin_init,
145       NULL
146     };
147
148     gst_play_bin_type = g_type_register_static (GST_TYPE_PLAY_BASE_BIN,
149         "GstPlayBin", &gst_play_bin_info, 0);
150   }
151
152   return gst_play_bin_type;
153 }
154
155 static void
156 gst_play_bin_class_init (GstPlayBinClass * klass)
157 {
158   GObjectClass *gobject_klass;
159   GstElementClass *gstelement_klass;
160   GstBinClass *gstbin_klass;
161   GstPlayBaseBinClass *playbasebin_klass;
162
163   gobject_klass = (GObjectClass *) klass;
164   gstelement_klass = (GstElementClass *) klass;
165   gstbin_klass = (GstBinClass *) klass;
166   playbasebin_klass = (GstPlayBaseBinClass *) klass;
167
168   parent_class = g_type_class_peek_parent (klass);
169
170   gobject_klass->set_property = gst_play_bin_set_property;
171   gobject_klass->get_property = gst_play_bin_get_property;
172
173   g_object_class_install_property (gobject_klass, ARG_VIDEO_SINK,
174       g_param_spec_object ("video-sink", "Video Sink",
175           "the video output element to use (NULL = default sink)",
176           GST_TYPE_ELEMENT, G_PARAM_READWRITE));
177   g_object_class_install_property (gobject_klass, ARG_AUDIO_SINK,
178       g_param_spec_object ("audio-sink", "Audio Sink",
179           "the audio output element to use (NULL = default sink)",
180           GST_TYPE_ELEMENT, G_PARAM_READWRITE));
181   g_object_class_install_property (gobject_klass, ARG_VIS_PLUGIN,
182       g_param_spec_object ("vis-plugin", "Vis plugin",
183           "the visualization element to use (NULL = none)",
184           GST_TYPE_ELEMENT, G_PARAM_READWRITE));
185   g_object_class_install_property (gobject_klass, ARG_VOLUME,
186       g_param_spec_double ("volume", "volume", "volume",
187           0.0, VOLUME_MAX_DOUBLE, 1.0, G_PARAM_READWRITE));
188   g_object_class_install_property (gobject_klass, ARG_FRAME,
189       gst_param_spec_mini_object ("frame", "Frame",
190           "The last frame (NULL = no video available)",
191           GST_TYPE_BUFFER, G_PARAM_READABLE));
192   g_object_class_install_property (gobject_klass, ARG_FONT_DESC,
193       g_param_spec_string ("subtitle-font-desc",
194           "Subtitle font description",
195           "Pango font description of font "
196           "to be used for subtitle rendering", NULL, G_PARAM_WRITABLE));
197   /**
198    * GstPlayBin:connection-speed
199    *
200    * Network connection speed in kbps (0 = unknown)
201    *
202    * Since: 0.10.10
203    **/
204   g_object_class_install_property (gobject_klass, ARG_CONNECTION_SPEED,
205       g_param_spec_uint ("connection-speed", "Connection Speed",
206           "Network connection speed in kbps (0 = unknown)",
207           0, G_MAXUINT, CONNECTION_SPEED_DEFAULT, G_PARAM_READWRITE));
208
209   gobject_klass->dispose = GST_DEBUG_FUNCPTR (gst_play_bin_dispose);
210
211   gst_element_class_set_details (gstelement_klass, &gst_play_bin_details);
212
213   gstelement_klass->change_state =
214       GST_DEBUG_FUNCPTR (gst_play_bin_change_state);
215   gstelement_klass->send_event = GST_DEBUG_FUNCPTR (gst_play_bin_send_event);
216
217   gstbin_klass->handle_message =
218       GST_DEBUG_FUNCPTR (gst_play_bin_handle_message);
219
220   playbasebin_klass->setup_output_pads = setup_sinks;
221 }
222
223 static void
224 gst_play_bin_init (GstPlayBin * play_bin)
225 {
226   play_bin->video_sink = NULL;
227   play_bin->audio_sink = NULL;
228   play_bin->visualisation = NULL;
229   play_bin->pending_visualisation = NULL;
230   play_bin->volume_element = NULL;
231   play_bin->textoverlay_element = NULL;
232   play_bin->volume = 1.0;
233   play_bin->sinks = NULL;
234   play_bin->frame = NULL;
235   play_bin->font_desc = NULL;
236   play_bin->cache = g_hash_table_new_full (g_str_hash, g_str_equal,
237       NULL, (GDestroyNotify) gst_object_unref);
238 }
239
240 static void
241 gst_play_bin_dispose (GObject * object)
242 {
243   GstPlayBin *play_bin;
244
245   play_bin = GST_PLAY_BIN (object);
246
247   if (play_bin->cache != NULL) {
248     remove_sinks (play_bin);
249     g_hash_table_destroy (play_bin->cache);
250     play_bin->cache = NULL;
251   }
252
253   if (play_bin->audio_sink != NULL) {
254     gst_element_set_state (play_bin->audio_sink, GST_STATE_NULL);
255     gst_object_unref (play_bin->audio_sink);
256     play_bin->audio_sink = NULL;
257   }
258   if (play_bin->video_sink != NULL) {
259     gst_element_set_state (play_bin->video_sink, GST_STATE_NULL);
260     gst_object_unref (play_bin->video_sink);
261     play_bin->video_sink = NULL;
262   }
263   if (play_bin->visualisation != NULL) {
264     gst_element_set_state (play_bin->visualisation, GST_STATE_NULL);
265     gst_object_unref (play_bin->visualisation);
266     play_bin->visualisation = NULL;
267   }
268   if (play_bin->pending_visualisation != NULL) {
269     gst_element_set_state (play_bin->pending_visualisation, GST_STATE_NULL);
270     gst_object_unref (play_bin->pending_visualisation);
271     play_bin->pending_visualisation = NULL;
272   }
273   if (play_bin->textoverlay_element != NULL) {
274     gst_object_unref (play_bin->textoverlay_element);
275     play_bin->textoverlay_element = NULL;
276   }
277   g_free (play_bin->font_desc);
278   play_bin->font_desc = NULL;
279
280   G_OBJECT_CLASS (parent_class)->dispose (object);
281 }
282
283 static void
284 gst_play_bin_vis_unblocked (GstPad * tee_pad, gboolean blocked,
285     gpointer user_data)
286 {
287   /* Unblocked */
288 }
289
290 static void
291 gst_play_bin_vis_blocked (GstPad * tee_pad, gboolean blocked,
292     gpointer user_data)
293 {
294   GstPlayBin *play_bin = GST_PLAY_BIN (user_data);
295   GstBin *vis_bin = NULL;
296   GstPad *vis_sink_pad = NULL, *vis_src_pad = NULL, *vqueue_pad = NULL;
297   GstState bin_state;
298
299   /* We want to disable visualisation */
300   if (!GST_IS_ELEMENT (play_bin->pending_visualisation)) {
301     /* Set visualisation element to READY */
302     gst_element_set_state (play_bin->visualisation, GST_STATE_READY);
303     goto beach;
304   }
305
306   vis_bin =
307       GST_BIN (gst_object_get_parent (GST_OBJECT (play_bin->visualisation)));
308
309   if (!GST_IS_BIN (vis_bin) || !GST_IS_PAD (tee_pad)) {
310     goto beach;
311   }
312
313   vis_src_pad = gst_element_get_pad (play_bin->visualisation, "src");
314   vis_sink_pad = gst_pad_get_peer (tee_pad);
315
316   /* Can be fakesink */
317   if (GST_IS_PAD (vis_src_pad)) {
318     vqueue_pad = gst_pad_get_peer (vis_src_pad);
319   }
320
321   if (!GST_IS_PAD (vis_sink_pad)) {
322     goto beach;
323   }
324
325   /* Check the bin's state */
326   GST_OBJECT_LOCK (vis_bin);
327   bin_state = GST_STATE (vis_bin);
328   GST_OBJECT_UNLOCK (vis_bin);
329
330   /* Unlink */
331   gst_pad_unlink (tee_pad, vis_sink_pad);
332   gst_object_unref (vis_sink_pad);
333   vis_sink_pad = NULL;
334
335   if (GST_IS_PAD (vqueue_pad)) {
336     gst_pad_unlink (vis_src_pad, vqueue_pad);
337     gst_object_unref (vis_src_pad);
338     vis_src_pad = NULL;
339   }
340
341   /* Remove from vis_bin */
342   gst_bin_remove (vis_bin, play_bin->visualisation);
343   /* Set state to NULL */
344   gst_element_set_state (play_bin->visualisation, GST_STATE_NULL);
345   /* And loose our ref */
346   gst_object_unref (play_bin->visualisation);
347
348   if (play_bin->pending_visualisation) {
349     /* Ref this new visualisation element before adding to the bin */
350     gst_object_ref (play_bin->pending_visualisation);
351     /* Add the new one */
352     gst_bin_add (vis_bin, play_bin->pending_visualisation);
353     /* Synchronizing state */
354     gst_element_set_state (play_bin->pending_visualisation, bin_state);
355
356     vis_sink_pad = gst_element_get_pad (play_bin->pending_visualisation,
357         "sink");
358     vis_src_pad = gst_element_get_pad (play_bin->pending_visualisation, "src");
359
360     if (!GST_IS_PAD (vis_sink_pad) || !GST_IS_PAD (vis_src_pad)) {
361       goto beach;
362     }
363
364     /* Link */
365     gst_pad_link (tee_pad, vis_sink_pad);
366     gst_pad_link (vis_src_pad, vqueue_pad);
367   }
368
369   /* We are done */
370   gst_object_unref (play_bin->visualisation);
371   play_bin->visualisation = play_bin->pending_visualisation;
372   play_bin->pending_visualisation = NULL;
373
374 beach:
375   if (vis_sink_pad) {
376     gst_object_unref (vis_sink_pad);
377   }
378   if (vis_src_pad) {
379     gst_object_unref (vis_src_pad);
380   }
381   if (vqueue_pad) {
382     gst_object_unref (vqueue_pad);
383   }
384   if (vis_bin) {
385     gst_object_unref (vis_bin);
386   }
387
388   /* Unblock the pad */
389   gst_pad_set_blocked_async (tee_pad, FALSE, gst_play_bin_vis_unblocked,
390       play_bin);
391 }
392
393 static void
394 gst_play_bin_set_property (GObject * object, guint prop_id,
395     const GValue * value, GParamSpec * pspec)
396 {
397   GstPlayBin *play_bin;
398
399   play_bin = GST_PLAY_BIN (object);
400
401   switch (prop_id) {
402     case ARG_VIDEO_SINK:
403       if (play_bin->video_sink != NULL) {
404         gst_object_unref (play_bin->video_sink);
405       }
406       play_bin->video_sink = g_value_get_object (value);
407       if (play_bin->video_sink != NULL) {
408         gst_object_ref (play_bin->video_sink);
409         gst_object_sink (GST_OBJECT (play_bin->video_sink));
410       }
411       /* when changing the videosink, we just remove the
412        * video pipeline from the cache so that it will be 
413        * regenerated with the new sink element */
414       g_hash_table_remove (play_bin->cache, "vbin");
415       break;
416     case ARG_AUDIO_SINK:
417       if (play_bin->audio_sink != NULL) {
418         gst_object_unref (play_bin->audio_sink);
419       }
420       play_bin->audio_sink = g_value_get_object (value);
421       if (play_bin->audio_sink != NULL) {
422         gst_object_ref (play_bin->audio_sink);
423         gst_object_sink (GST_OBJECT (play_bin->audio_sink));
424       }
425       g_hash_table_remove (play_bin->cache, "abin");
426       break;
427     case ARG_VIS_PLUGIN:
428     {
429       /* Do we already have a visualisation change pending ? */
430       if (play_bin->pending_visualisation) {
431         gst_object_unref (play_bin->pending_visualisation);
432         play_bin->pending_visualisation = g_value_get_object (value);
433         /* Take ownership */
434         if (play_bin->pending_visualisation) {
435           gst_object_ref (play_bin->pending_visualisation);
436           gst_object_sink (GST_OBJECT (play_bin->pending_visualisation));
437         }
438       } else {
439         play_bin->pending_visualisation = g_value_get_object (value);
440
441         /* Take ownership */
442         if (play_bin->pending_visualisation) {
443           gst_object_ref (play_bin->pending_visualisation);
444           gst_object_sink (GST_OBJECT (play_bin->pending_visualisation));
445         }
446
447         /* Was there a visualisation already set ? */
448         if (play_bin->visualisation != NULL) {
449           GstBin *vis_bin = NULL;
450
451           vis_bin =
452               GST_BIN (gst_object_get_parent (GST_OBJECT (play_bin->
453                       visualisation)));
454
455           /* Check if the visualisation is already in a bin */
456           if (GST_IS_BIN (vis_bin)) {
457             GstPad *vis_sink_pad = NULL, *tee_pad = NULL;
458
459             /* Now get tee pad and block it async */
460             vis_sink_pad = gst_element_get_pad (play_bin->visualisation,
461                 "sink");
462             if (!GST_IS_PAD (vis_sink_pad)) {
463               goto beach;
464             }
465             tee_pad = gst_pad_get_peer (vis_sink_pad);
466             if (!GST_IS_PAD (tee_pad)) {
467               goto beach;
468             }
469
470             /* Block with callback */
471             gst_pad_set_blocked_async (tee_pad, TRUE, gst_play_bin_vis_blocked,
472                 play_bin);
473           beach:
474             if (vis_sink_pad) {
475               gst_object_unref (vis_sink_pad);
476             }
477             if (tee_pad) {
478               gst_object_unref (tee_pad);
479             }
480             gst_object_unref (vis_bin);
481           } else {
482             play_bin->visualisation = play_bin->pending_visualisation;
483             play_bin->pending_visualisation = NULL;
484           }
485         } else {
486           play_bin->visualisation = play_bin->pending_visualisation;
487           play_bin->pending_visualisation = NULL;
488         }
489       }
490       break;
491     }
492     case ARG_VOLUME:
493       play_bin->volume = g_value_get_double (value);
494       if (play_bin->volume_element) {
495         g_object_set (G_OBJECT (play_bin->volume_element), "volume",
496             play_bin->volume, NULL);
497       }
498       break;
499     case ARG_FONT_DESC:
500       g_free (play_bin->font_desc);
501       play_bin->font_desc = g_strdup (g_value_get_string (value));
502       if (play_bin->textoverlay_element) {
503         g_object_set (G_OBJECT (play_bin->textoverlay_element),
504             "font-desc", g_value_get_string (value), NULL);
505       }
506       break;
507     case ARG_CONNECTION_SPEED:
508       play_bin->connection_speed = g_value_get_uint (value) * 1000;
509       break;
510     default:
511       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
512       break;
513   }
514 }
515
516 static void
517 gst_play_bin_get_property (GObject * object, guint prop_id, GValue * value,
518     GParamSpec * pspec)
519 {
520   GstPlayBin *play_bin;
521
522   play_bin = GST_PLAY_BIN (object);
523
524   switch (prop_id) {
525     case ARG_VIDEO_SINK:
526       g_value_set_object (value, play_bin->video_sink);
527       break;
528     case ARG_AUDIO_SINK:
529       g_value_set_object (value, play_bin->audio_sink);
530       break;
531     case ARG_VIS_PLUGIN:
532       g_value_set_object (value, play_bin->visualisation);
533       break;
534     case ARG_VOLUME:
535       g_value_set_double (value, play_bin->volume);
536       break;
537     case ARG_FRAME:
538       gst_value_set_mini_object (value, GST_MINI_OBJECT (play_bin->frame));
539       break;
540     case ARG_CONNECTION_SPEED:
541       g_value_set_uint (value, play_bin->connection_speed / 1000);
542       break;
543     default:
544       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
545       break;
546   }
547 }
548
549 /* signal fired when the identity has received a new buffer. This is used for
550  * making screenshots.
551  */
552 static void
553 handoff (GstElement * identity, GstBuffer * frame, gpointer data)
554 {
555   GstPlayBin *play_bin = GST_PLAY_BIN (data);
556   GstBuffer **frame_p = &play_bin->frame;
557
558   gst_mini_object_replace ((GstMiniObject **) frame_p,
559       GST_MINI_OBJECT_CAST (frame));
560
561   /* applications need to know the buffer caps,
562    * make sure they are always set on the frame */
563   if (GST_BUFFER_CAPS (play_bin->frame) == NULL) {
564     GstPad *pad;
565
566     if ((pad = gst_element_get_pad (identity, "sink"))) {
567       gst_buffer_set_caps (play_bin->frame, GST_PAD_CAPS (pad));
568       gst_object_unref (pad);
569     }
570   }
571 }
572
573 /* make the element (bin) that contains the elements needed to perform
574  * video display. We connect a handoff signal to identity so that we
575  * can grab snapshots. Identity's sinkpad is ghosted to vbin.
576  *
577  *  +-------------------------------------------------------------+
578  *  | vbin                                                        |
579  *  |      +--------+   +----------+   +----------+   +---------+ |
580  *  |      |identity|   |colorspace|   |videoscale|   |videosink| |
581  *  |   +-sink     src-sink       src-sink       src-sink       | |
582  *  |   |  +---+----+   +----------+   +----------+   +---------+ |
583  * sink-+      |                                                  |
584  *  +----------|--------------------------------------------------+
585  *           handoff
586  */
587 /* FIXME: this might return NULL if no videosink was found, handle
588  * this in callers */
589 static GstElement *
590 gen_video_element (GstPlayBin * play_bin)
591 {
592   GstElement *element;
593   GstElement *conv;
594
595   GstElement *scale;
596   GstElement *sink;
597   GstElement *identity;
598   GstPad *pad;
599
600   /* first see if we have it in the cache */
601   element = g_hash_table_lookup (play_bin->cache, "vbin");
602   if (element != NULL) {
603     return element;
604   }
605
606   if (play_bin->video_sink) {
607     sink = play_bin->video_sink;
608   } else {
609     sink = gst_element_factory_make ("autovideosink", "videosink");
610     if (sink == NULL) {
611       sink = gst_element_factory_make ("xvimagesink", "videosink");
612     }
613     /* FIXME: this warrants adding a CORE error category for missing
614      * elements/plugins */
615     if (sink == NULL) {
616       GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN,
617           (_("Both autovideosink and xvimagesink elements are missing.")),
618           (NULL));
619       return NULL;
620     }
621   }
622   gst_object_ref (sink);
623   g_hash_table_insert (play_bin->cache, "video_sink", sink);
624
625
626   element = gst_bin_new ("vbin");
627   identity = gst_element_factory_make ("identity", "id");
628   g_object_set (identity, "silent", TRUE, NULL);
629   g_signal_connect (identity, "handoff", G_CALLBACK (handoff), play_bin);
630   gst_bin_add (GST_BIN (element), identity);
631   conv = gst_element_factory_make ("ffmpegcolorspace", "vconv");
632   if (conv == NULL)
633     goto no_colorspace;
634   scale = gst_element_factory_make ("videoscale", "vscale");
635   if (scale == NULL)
636     goto no_videoscale;
637   gst_bin_add (GST_BIN (element), conv);
638   gst_bin_add (GST_BIN (element), scale);
639   gst_bin_add (GST_BIN (element), sink);
640   gst_element_link_pads (identity, "src", conv, "sink");
641   gst_element_link_pads (conv, "src", scale, "sink");
642   gst_element_link_pads (scale, "src", sink, "sink");
643
644   pad = gst_element_get_pad (identity, "sink");
645   gst_element_add_pad (element, gst_ghost_pad_new ("sink", pad));
646   gst_object_unref (pad);
647
648   gst_element_set_state (element, GST_STATE_READY);
649
650   /* since we're gonna add it to a bin but don't want to lose it,
651    * we keep a reference. */
652   gst_object_ref (element);
653   g_hash_table_insert (play_bin->cache, "vbin", element);
654
655   return element;
656
657 no_colorspace:
658   {
659     GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN,
660         (_("Missing element '%s' - check your GStreamer installation."),
661             "ffmpegcolorspace"), (NULL));
662     gst_object_unref (element);
663     return NULL;
664   }
665
666 no_videoscale:
667   {
668     GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN,
669         (_("Missing element '%s' - check your GStreamer installation."),
670             "videoscale"), ("possibly a liboil version mismatch?"));
671     gst_object_unref (element);
672     return NULL;
673   }
674 }
675
676 /* make an element for playback of video with subtitles embedded.
677  *
678  *  +--------------------------------------------------+
679  *  | tbin                  +-------------+            |
680  *  |          +-----+      | textoverlay |   +------+ |
681  *  |          | csp | +--video_sink      |   | vbin | |
682  * video_sink-sink  src+ +-text_sink     src-sink    | |
683  *  |          +-----+   |  +-------------+   +------+ |
684  * text_sink-------------+                             |
685  *  +--------------------------------------------------+
686  */
687
688 static GstElement *
689 gen_text_element (GstPlayBin * play_bin)
690 {
691   GstElement *element, *csp, *overlay, *vbin;
692   GstPad *pad;
693
694   /* Create our bin */
695   element = gst_bin_new ("textbin");
696
697   /* Text overlay */
698   overlay = gst_element_factory_make ("textoverlay", "overlay");
699
700   /* Create the video rendering bin */
701   vbin = gen_video_element (play_bin);
702
703   /* If no overlay return the video bin */
704   if (!overlay) {
705     GST_WARNING ("No overlay (pango) element, subtitles disabled");
706     return vbin;
707   }
708
709   /* Set some parameters */
710   g_object_set (G_OBJECT (overlay),
711       "halign", "center", "valign", "bottom", NULL);
712   if (play_bin->font_desc) {
713     g_object_set (G_OBJECT (overlay), "font-desc", play_bin->font_desc, NULL);
714   }
715
716   /* Take a ref */
717   play_bin->textoverlay_element = GST_ELEMENT (gst_object_ref (overlay));
718
719   /* we know this will succeed, as the video bin already created one before */
720   csp = gst_element_factory_make ("ffmpegcolorspace", "subtitlecsp");
721
722   /* Add our elements */
723   gst_bin_add_many (GST_BIN (element), csp, overlay, vbin, NULL);
724
725   /* Link */
726   gst_element_link_pads (csp, "src", overlay, "video_sink");
727   gst_element_link_pads (overlay, "src", vbin, "sink");
728
729   /* Add ghost pads on the subtitle bin */
730   pad = gst_element_get_pad (overlay, "text_sink");
731   gst_element_add_pad (element, gst_ghost_pad_new ("text_sink", pad));
732   gst_object_unref (pad);
733
734   pad = gst_element_get_pad (csp, "sink");
735   gst_element_add_pad (element, gst_ghost_pad_new ("sink", pad));
736   gst_object_unref (pad);
737
738   /* Set state to READY */
739   gst_element_set_state (element, GST_STATE_READY);
740
741   return element;
742 }
743
744 /* make the element (bin) that contains the elements needed to perform
745  * audio playback.
746  *
747  *  +-------------------------------------------------------------+
748  *  | abin                                                        |
749  *  |      +---------+   +----------+   +---------+   +---------+ |
750  *  |      |audioconv|   |audioscale|   | volume  |   |audiosink| |
751  *  |   +-sink      src-sink       src-sink      src-sink       | |
752  *  |   |  +---------+   +----------+   +---------+   +---------+ |
753  * sink-+                                                         |
754  *  +-------------------------------------------------------------+
755  *
756  */
757 static GstElement *
758 gen_audio_element (GstPlayBin * play_bin)
759 {
760   GstElement *element;
761   GstElement *conv;
762   GstElement *scale;
763   GstElement *sink;
764   GstElement *volume;
765   GstPad *pad;
766
767   element = g_hash_table_lookup (play_bin->cache, "abin");
768   if (element != NULL) {
769     return element;
770   }
771   element = gst_bin_new ("abin");
772   conv = gst_element_factory_make ("audioconvert", "aconv");
773   if (conv == NULL)
774     goto no_audioconvert;
775
776   scale = gst_element_factory_make ("audioresample", "aresample");
777   if (scale == NULL)
778     goto no_audioresample;
779
780   volume = gst_element_factory_make ("volume", "volume");
781   g_object_set (G_OBJECT (volume), "volume", play_bin->volume, NULL);
782   play_bin->volume_element = volume;
783
784   if (play_bin->audio_sink) {
785     sink = play_bin->audio_sink;
786   } else {
787     sink = gst_element_factory_make ("autoaudiosink", "audiosink");
788     if (sink == NULL) {
789       sink = gst_element_factory_make ("alsasink", "audiosink");
790     }
791     if (sink == NULL) {
792       GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN,
793           (_("Both autoaudiosink and alsasink elements are missing.")), (NULL));
794       return NULL;
795     }
796     play_bin->audio_sink = GST_ELEMENT (gst_object_ref (sink));
797   }
798
799   gst_object_ref (sink);
800   g_hash_table_insert (play_bin->cache, "audio_sink", sink);
801
802   gst_bin_add (GST_BIN (element), conv);
803   gst_bin_add (GST_BIN (element), scale);
804   gst_bin_add (GST_BIN (element), volume);
805   gst_bin_add (GST_BIN (element), sink);
806
807   gst_element_link_pads (conv, "src", scale, "sink");
808   gst_element_link_pads (scale, "src", volume, "sink");
809   gst_element_link_pads (volume, "src", sink, "sink");
810
811   pad = gst_element_get_pad (conv, "sink");
812   gst_element_add_pad (element, gst_ghost_pad_new ("sink", pad));
813   gst_object_unref (pad);
814
815   gst_element_set_state (element, GST_STATE_READY);
816
817   /* since we're gonna add it to a bin but don't want to lose it,
818    * we keep a reference. */
819   gst_object_ref (element);
820   g_hash_table_insert (play_bin->cache, "abin", element);
821
822   return element;
823
824 no_audioconvert:
825   {
826     GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN,
827         (_("Missing element '%s' - check your GStreamer installation."),
828             "audioconvert"), ("possibly a liboil version mismatch?"));
829     gst_object_unref (element);
830     return NULL;
831   }
832
833 no_audioresample:
834   {
835     GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN,
836         (_("Missing element '%s' - check your GStreamer installation."),
837             "audioresample"), ("possibly a liboil version mismatch?"));
838     gst_object_unref (element);
839     return NULL;
840   }
841 }
842
843 /* make the element (bin) that contains the elements needed to perform
844  * visualisation ouput.  The idea is to split the audio using tee, then 
845  * sending the output to the regular audio bin and the other output to
846  * the vis plugin that transforms it into a video that is rendered with the
847  * normal video bin. The video bin is run in a thread to make sure it does
848  * not block the audio playback pipeline.
849  *
850  *  +--------------------------------------------------------------------+
851  *  | visbin                                                             |
852  *  |      +------+   +--------+   +----------------+                    |
853  *  |      | tee  |   | aqueue |   |   abin ...     |                    |
854  *  |   +-sink   src-sink     src-sink              |                    |
855  *  |   |  |      |   +--------+   +----------------+                    |
856  *  |   |  |      |                                                      |
857  *  |   |  |      |   +------+   +---------+   +------+   +-----------+  |
858  *  |   |  |      |   |vqueue|   |audioconv|   | vis  |   | vbin ...  |  |
859  *  |   |  |     src-sink   src-sink      src-sink   src-sink         |  |
860  *  |   |  |      |   +------+   +---------+   +------+   +-----------+  |
861  *  |   |  |      |                                                      |
862  *  |   |  +------+                                                      |
863  * sink-+                                                                |
864    +---------------------------------------------------------------------+
865  */
866 static GstElement *
867 gen_vis_element (GstPlayBin * play_bin)
868 {
869   GstElement *element;
870   GstElement *tee;
871   GstElement *asink;
872   GstElement *vsink;
873   GstElement *conv;
874   GstElement *vis;
875   GstElement *vqueue, *aqueue;
876   GstPad *pad, *rpad;
877
878   asink = gen_audio_element (play_bin);
879   if (!asink)
880     return NULL;
881   vsink = gen_video_element (play_bin);
882   if (!vsink) {
883     gst_object_unref (asink);
884     return NULL;
885   }
886
887   element = gst_bin_new ("visbin");
888   tee = gst_element_factory_make ("tee", "tee");
889
890   vqueue = gst_element_factory_make ("queue", "vqueue");
891   aqueue = gst_element_factory_make ("queue", "aqueue");
892
893   gst_bin_add (GST_BIN (element), asink);
894   gst_bin_add (GST_BIN (element), vqueue);
895   gst_bin_add (GST_BIN (element), aqueue);
896   gst_bin_add (GST_BIN (element), vsink);
897   gst_bin_add (GST_BIN (element), tee);
898
899   conv = gst_element_factory_make ("audioconvert", "aconv");
900   if (conv == NULL)
901     goto no_audioconvert;
902
903   if (play_bin->visualisation) {
904     gst_object_ref (play_bin->visualisation);
905     vis = play_bin->visualisation;
906   } else {
907     vis = gst_element_factory_make ("goom", "vis");
908   }
909
910   gst_bin_add (GST_BIN (element), conv);
911   gst_bin_add (GST_BIN (element), vis);
912
913   gst_element_link_pads (vqueue, "src", conv, "sink");
914   gst_element_link_pads (conv, "src", vis, "sink");
915   gst_element_link_pads (vis, "src", vsink, "sink");
916
917   pad = gst_element_get_pad (aqueue, "sink");
918   rpad = gst_element_get_request_pad (tee, "src%d");
919   gst_pad_link (rpad, pad);
920   gst_object_unref (rpad);
921   gst_object_unref (pad);
922   gst_element_link_pads (aqueue, "src", asink, "sink");
923
924   pad = gst_element_get_pad (vqueue, "sink");
925   rpad = gst_element_get_request_pad (tee, "src%d");
926   gst_pad_link (rpad, pad);
927   gst_object_unref (rpad);
928   gst_object_unref (pad);
929
930   pad = gst_element_get_pad (tee, "sink");
931   gst_element_add_pad (element, gst_ghost_pad_new ("sink", pad));
932   gst_object_unref (pad);
933
934   return element;
935
936 no_audioconvert:
937   {
938     GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN,
939         (_("Missing element '%s' - check your GStreamer installation."),
940             "audioconvert"), ("possibly a liboil version mismatch?"));
941     gst_object_unref (element);
942     return NULL;
943   }
944 }
945
946 /* get rid of all installed sinks */
947 static void
948 remove_sinks (GstPlayBin * play_bin)
949 {
950   GList *sinks;
951   GstObject *parent;
952   GstElement *element;
953   GstPad *pad, *peer;
954
955   GST_DEBUG ("removesinks");
956   element = g_hash_table_lookup (play_bin->cache, "abin");
957   if (element != NULL) {
958     parent = gst_element_get_parent (element);
959     if (parent != NULL) {
960       /* we remove the element from the parent so that
961        * there is no unwanted state change when the parent
962        * is disposed */
963       play_bin->sinks = g_list_remove (play_bin->sinks, element);
964       gst_element_set_state (element, GST_STATE_NULL);
965       gst_bin_remove (GST_BIN (parent), element);
966       gst_object_unref (parent);
967     }
968     pad = gst_element_get_pad (element, "sink");
969     if (pad != NULL) {
970       peer = gst_pad_get_peer (pad);
971       if (peer != NULL) {
972         gst_pad_unlink (peer, pad);
973         gst_object_unref (peer);
974       }
975       gst_object_unref (pad);
976     }
977   }
978   element = g_hash_table_lookup (play_bin->cache, "vbin");
979   if (element != NULL) {
980     parent = gst_element_get_parent (element);
981     if (parent != NULL) {
982       play_bin->sinks = g_list_remove (play_bin->sinks, element);
983       gst_element_set_state (element, GST_STATE_NULL);
984       gst_bin_remove (GST_BIN (parent), element);
985       gst_object_unref (parent);
986     }
987     pad = gst_element_get_pad (element, "sink");
988     if (pad != NULL) {
989       peer = gst_pad_get_peer (pad);
990       if (peer != NULL) {
991         gst_pad_unlink (peer, pad);
992         gst_object_unref (peer);
993       }
994       gst_object_unref (pad);
995     }
996   }
997
998   for (sinks = play_bin->sinks; sinks; sinks = g_list_next (sinks)) {
999     GstElement *element = GST_ELEMENT (sinks->data);
1000     GstPad *pad;
1001     GstPad *peer;
1002
1003     pad = gst_element_get_pad (element, "sink");
1004
1005     GST_LOG ("removing sink %p", element);
1006
1007     peer = gst_pad_get_peer (pad);
1008     if (peer) {
1009       gst_pad_unlink (peer, pad);
1010       gst_object_unref (peer);
1011     }
1012     gst_object_unref (pad);
1013
1014     gst_element_set_state (element, GST_STATE_NULL);
1015     gst_bin_remove (GST_BIN (play_bin), element);
1016   }
1017   g_list_free (play_bin->sinks);
1018   play_bin->sinks = NULL;
1019
1020   /* FIXME: this is probably some refcounting problem */
1021   if (play_bin->visualisation && GST_OBJECT_PARENT (play_bin->visualisation)) {
1022     gst_element_set_state (play_bin->visualisation, GST_STATE_NULL);
1023     gst_bin_remove (GST_BIN (GST_OBJECT_PARENT (play_bin->visualisation)),
1024         play_bin->visualisation);
1025   }
1026
1027   if (play_bin->frame) {
1028     gst_buffer_unref (play_bin->frame);
1029     play_bin->frame = NULL;
1030   }
1031
1032   if (play_bin->textoverlay_element) {
1033     gst_object_unref (play_bin->textoverlay_element);
1034     play_bin->textoverlay_element = NULL;
1035   }
1036 }
1037
1038 /* loop over the streams and set up the pipeline to play this
1039  * media file. First we count the number of audio and video streams.
1040  * If there is no video stream but there exists an audio stream,
1041  * we install a visualisation pipeline.
1042  * 
1043  * Also make sure to only connect the first audio and video pad. FIXME
1044  * this should eventually be handled with a tuner interface so that
1045  * one can switch the streams.
1046  */
1047 static gboolean
1048 add_sink (GstPlayBin * play_bin, GstElement * sink, GstPad * srcpad,
1049     GstPad * subtitle_pad)
1050 {
1051   GstPad *sinkpad;
1052   GstPadLinkReturn linkres;
1053   GstElement *parent;
1054   GstStateChangeReturn stateret;
1055
1056   g_return_val_if_fail (sink != NULL, FALSE);
1057   /* this is only for debugging */
1058   parent = gst_pad_get_parent_element (srcpad);
1059   if (parent) {
1060     GST_DEBUG ("Adding sink with state %d (parent: %d, peer: %d)",
1061         GST_STATE (sink), GST_STATE (play_bin), GST_STATE (parent));
1062     gst_object_unref (parent);
1063   }
1064
1065   /* bring it to the PAUSED state so we can link to the peer without
1066    * breaking the flow */
1067   if ((stateret = gst_element_set_state (sink, GST_STATE_PAUSED)) ==
1068       GST_STATE_CHANGE_FAILURE)
1069     goto state_failed;
1070
1071   gst_bin_add (GST_BIN (play_bin), sink);
1072
1073   /* we found a sink for this stream, now try to install it */
1074   sinkpad = gst_element_get_pad (sink, "sink");
1075   linkres = gst_pad_link (srcpad, sinkpad);
1076   gst_object_unref (sinkpad);
1077
1078   /* try to link the pad of the sink to the stream */
1079   if (GST_PAD_LINK_FAILED (linkres))
1080     goto link_failed;
1081
1082   if (GST_IS_PAD (subtitle_pad)) {
1083     sinkpad = gst_element_get_pad (sink, "text_sink");
1084     linkres = gst_pad_link (subtitle_pad, sinkpad);
1085     gst_object_unref (sinkpad);
1086   }
1087
1088   /* try to link the subtitle pad of the sink to the stream */
1089   if (GST_PAD_LINK_FAILED (linkres)) {
1090     goto subtitle_failed;
1091   }
1092
1093   /* we got the sink succesfully linked, now keep the sink
1094    * in our internal list */
1095   play_bin->sinks = g_list_prepend (play_bin->sinks, sink);
1096
1097   return TRUE;
1098
1099   /* ERRORS */
1100 state_failed:
1101   {
1102     GST_DEBUG_OBJECT (play_bin, "state change failure when adding sink");
1103     return FALSE;
1104   }
1105 link_failed:
1106   {
1107     gchar *capsstr;
1108     GstCaps *caps;
1109
1110     /* could not link this stream */
1111     caps = gst_pad_get_caps (srcpad);
1112     capsstr = gst_caps_to_string (caps);
1113     g_warning ("could not link %s: %d", capsstr, linkres);
1114     GST_DEBUG_OBJECT (play_bin,
1115         "link failed when adding sink, caps %s, reason %d", capsstr, linkres);
1116     g_free (capsstr);
1117     gst_caps_unref (caps);
1118
1119     gst_element_set_state (sink, GST_STATE_NULL);
1120     gst_bin_remove (GST_BIN (play_bin), sink);
1121     return FALSE;
1122   }
1123 subtitle_failed:
1124   {
1125     gchar *capsstr;
1126     GstCaps *caps;
1127
1128     /* could not link this stream */
1129     caps = gst_pad_get_caps (subtitle_pad);
1130     capsstr = gst_caps_to_string (caps);
1131     GST_DEBUG_OBJECT (play_bin,
1132         "subtitle link failed when adding sink, caps %s, reason %d", capsstr,
1133         linkres);
1134     g_free (capsstr);
1135     gst_caps_unref (caps);
1136
1137     return TRUE;
1138   }
1139 }
1140
1141 static gboolean
1142 setup_sinks (GstPlayBaseBin * play_base_bin, GstPlayBaseGroup * group)
1143 {
1144   GstPlayBin *play_bin = GST_PLAY_BIN (play_base_bin);
1145   GList *streaminfo = NULL, *s;
1146   gboolean need_vis = FALSE;
1147   gboolean need_text = FALSE;
1148   GstPad *textsrcpad = NULL, *pad = NULL;
1149   GstElement *sink;
1150   gboolean res = TRUE;
1151
1152   /* get rid of existing sinks */
1153   if (play_bin->sinks) {
1154     remove_sinks (play_bin);
1155   }
1156   GST_DEBUG_OBJECT (play_base_bin, "setupsinks");
1157
1158   /* find out what to do */
1159   if (group->type[GST_STREAM_TYPE_VIDEO - 1].npads > 0 &&
1160       group->type[GST_STREAM_TYPE_TEXT - 1].npads > 0) {
1161     need_text = TRUE;
1162   } else if (group->type[GST_STREAM_TYPE_VIDEO - 1].npads == 0 &&
1163       group->type[GST_STREAM_TYPE_AUDIO - 1].npads > 0 &&
1164       play_bin->visualisation != NULL) {
1165     need_vis = TRUE;
1166   }
1167
1168   /* now actually connect everything */
1169   g_object_get (G_OBJECT (play_base_bin), "stream-info", &streaminfo, NULL);
1170   for (s = streaminfo; s; s = g_list_next (s)) {
1171     GObject *obj = G_OBJECT (s->data);
1172     gint type;
1173     GstObject *object;
1174
1175     g_object_get (obj, "type", &type, NULL);
1176     g_object_get (obj, "object", &object, NULL);
1177   }
1178
1179   /* link audio */
1180   if (group->type[GST_STREAM_TYPE_AUDIO - 1].npads > 0) {
1181     if (need_vis) {
1182       sink = gen_vis_element (play_bin);
1183     } else {
1184       sink = gen_audio_element (play_bin);
1185     }
1186     if (!sink)
1187       return FALSE;
1188     pad = gst_element_get_pad (group->type[GST_STREAM_TYPE_AUDIO - 1].preroll,
1189         "src");
1190     res = add_sink (play_bin, sink, pad, NULL);
1191     gst_object_unref (pad);
1192   }
1193
1194   /* link video */
1195   if (group->type[GST_STREAM_TYPE_VIDEO - 1].npads > 0) {
1196     if (need_text) {
1197       GstObject *parent = NULL, *grandparent = NULL;
1198       GstPad *ghost = NULL;
1199
1200       sink = gen_text_element (play_bin);
1201       textsrcpad =
1202           gst_element_get_pad (group->type[GST_STREAM_TYPE_TEXT - 1].preroll,
1203           "src");
1204       /* This pad is from subtitle-bin, we need to create a ghost pad to have
1205          common grandparents */
1206       parent = gst_object_get_parent (GST_OBJECT (textsrcpad));
1207       if (!parent) {
1208         GST_WARNING_OBJECT (textsrcpad, "subtitle pad has no parent !");
1209         gst_object_unref (textsrcpad);
1210         textsrcpad = NULL;
1211         goto beach;
1212       }
1213
1214       grandparent = gst_object_get_parent (parent);
1215       if (!grandparent) {
1216         GST_WARNING_OBJECT (textsrcpad, "subtitle pad has no grandparent !");
1217         gst_object_unref (parent);
1218         gst_object_unref (textsrcpad);
1219         textsrcpad = NULL;
1220         goto beach;
1221       }
1222
1223       /* We ghost the pad on subtitle_bin only, if the text pad is from the
1224          media demuxer we keep it as it is */
1225       if (!GST_IS_PLAY_BIN (grandparent)) {
1226         GST_DEBUG_OBJECT (textsrcpad, "this subtitle pad is from a subtitle "
1227             "file, ghosting to a suitable hierarchy");
1228         ghost = gst_ghost_pad_new ("text_src", textsrcpad);
1229         if (!GST_IS_PAD (ghost)) {
1230           GST_WARNING_OBJECT (textsrcpad, "failed creating ghost pad for "
1231               "subtitle-bin");
1232           gst_object_unref (parent);
1233           gst_object_unref (grandparent);
1234           gst_object_unref (textsrcpad);
1235           textsrcpad = NULL;
1236           goto beach;
1237         }
1238
1239         if (gst_element_add_pad (GST_ELEMENT (grandparent), ghost)) {
1240           gst_object_unref (textsrcpad);
1241           textsrcpad = gst_object_ref (ghost);
1242         } else {
1243           GST_WARNING_OBJECT (ghost, "failed adding ghost pad on subtitle-bin");
1244           gst_object_unref (ghost);
1245           gst_object_unref (textsrcpad);
1246           textsrcpad = NULL;
1247         }
1248       } else {
1249         GST_DEBUG_OBJECT (textsrcpad, "this subtitle pad is from the demuxer "
1250             "no changes to hierarchy needed");
1251       }
1252
1253       gst_object_unref (parent);
1254       gst_object_unref (grandparent);
1255     } else {
1256       sink = gen_video_element (play_bin);
1257     }
1258   beach:
1259     if (!sink)
1260       return FALSE;
1261     pad = gst_element_get_pad (group->type[GST_STREAM_TYPE_VIDEO - 1].preroll,
1262         "src");
1263     res = add_sink (play_bin, sink, pad, textsrcpad);
1264     gst_object_unref (pad);
1265     if (textsrcpad) {
1266       gst_object_unref (textsrcpad);
1267     }
1268   }
1269
1270   /* remove the sinks now, pipeline get_state will now wait for the
1271    * sinks to preroll */
1272   if (play_bin->fakesink) {
1273     gst_element_set_state (play_bin->fakesink, GST_STATE_NULL);
1274     gst_bin_remove (GST_BIN (play_bin), play_bin->fakesink);
1275     play_bin->fakesink = NULL;
1276   }
1277
1278   return res;
1279 }
1280
1281 /* Send an event to our sinks until one of them works; don't then send to the
1282  * remaining sinks (unlike GstBin)
1283  */
1284 static gboolean
1285 gst_play_bin_send_event_to_sink (GstPlayBin * play_bin, GstEvent * event)
1286 {
1287   GList *sinks = play_bin->sinks;
1288   gboolean res = TRUE;
1289
1290   while (sinks) {
1291     GstElement *sink = GST_ELEMENT_CAST (sinks->data);
1292
1293     gst_event_ref (event);
1294     if ((res = gst_element_send_event (sink, event))) {
1295       GST_DEBUG_OBJECT (play_bin,
1296           "Sent event succesfully to sink %" GST_PTR_FORMAT, sink);
1297       break;
1298     }
1299     GST_DEBUG_OBJECT (play_bin,
1300         "Event failed when sent to sink %" GST_PTR_FORMAT, sink);
1301
1302     sinks = g_list_next (sinks);
1303   }
1304
1305   gst_event_unref (event);
1306
1307   return res;
1308 }
1309
1310 static gboolean
1311 do_playbin_seek (GstElement * element, GstEvent * event)
1312 {
1313   gdouble rate;
1314   GstSeekFlags flags;
1315   gboolean flush;
1316   gboolean was_playing = FALSE;
1317   gboolean res;
1318
1319   gst_event_parse_seek (event, &rate, NULL, &flags, NULL, NULL, NULL, NULL);
1320
1321   flush = flags & GST_SEEK_FLAG_FLUSH;
1322
1323   if (flush) {
1324     GstState state;
1325
1326     /* need to call _get_state() since a bin state is only updated
1327      * with this call. */
1328     gst_element_get_state (element, &state, NULL, 0);
1329     was_playing = state == GST_STATE_PLAYING;
1330
1331     if (was_playing) {
1332       gst_element_set_state (element, GST_STATE_PAUSED);
1333       gst_element_get_state (element, NULL, NULL, 50 * GST_MSECOND);
1334     }
1335   }
1336
1337   GST_DEBUG_OBJECT (element, "Sending seek event to a sink");
1338   res = gst_play_bin_send_event_to_sink (GST_PLAY_BIN (element), event);
1339
1340   if (flush) {
1341     /* need to reset the stream time to 0 after a flushing seek */
1342     if (res)
1343       gst_pipeline_set_new_stream_time (GST_PIPELINE (element), 0);
1344
1345     if (was_playing)
1346       /* and continue playing */
1347       gst_element_set_state (element, GST_STATE_PLAYING);
1348   }
1349   return res;
1350 }
1351
1352 /* We only want to send the event to a single sink (overriding GstBin's 
1353  * behaviour), but we want to keep GstPipeline's behaviour - wrapping seek
1354  * events appropriately. So, this is a messy duplication of code. */
1355 static gboolean
1356 gst_play_bin_send_event (GstElement * element, GstEvent * event)
1357 {
1358   gboolean res = FALSE;
1359   GstEventType event_type = GST_EVENT_TYPE (event);
1360
1361
1362   switch (event_type) {
1363     case GST_EVENT_SEEK:
1364       res = do_playbin_seek (element, event);
1365       break;
1366     default:
1367       res = gst_play_bin_send_event_to_sink (GST_PLAY_BIN (element), event);
1368       break;
1369   }
1370
1371   return res;
1372 }
1373
1374 static void
1375 value_list_append_structure_list (GValue * list_val, GstStructure ** first,
1376     GList * structure_list)
1377 {
1378   GList *l;
1379
1380   for (l = structure_list; l != NULL; l = l->next) {
1381     GValue val = { 0, };
1382
1383     if (*first == NULL)
1384       *first = gst_structure_copy ((GstStructure *) l->data);
1385
1386     g_value_init (&val, GST_TYPE_STRUCTURE);
1387     g_value_take_boxed (&val, gst_structure_copy ((GstStructure *) l->data));
1388     gst_value_list_append_value (list_val, &val);
1389     g_value_unset (&val);
1390   }
1391 }
1392
1393 /* if it's a redirect message with multiple redirect locations we might
1394  * want to pick a different 'best' location depending on the required
1395  * bitrates and the connection speed */
1396 static GstMessage *
1397 gst_play_bin_handle_redirect_message (GstPlayBin * playbin, GstMessage * msg)
1398 {
1399   const GValue *locations_list, *location_val;
1400   GstMessage *new_msg;
1401   GstStructure *new_structure = NULL;
1402   GList *l_good = NULL, *l_neutral = NULL, *l_bad = NULL;
1403   GValue new_list = { 0, };
1404   guint size, i;
1405
1406   GST_DEBUG_OBJECT (playbin, "redirect message: %" GST_PTR_FORMAT, msg);
1407   GST_DEBUG_OBJECT (playbin, "connection speed: %u", playbin->connection_speed);
1408
1409   if (playbin->connection_speed == 0 || msg->structure == NULL)
1410     return msg;
1411
1412   locations_list = gst_structure_get_value (msg->structure, "locations");
1413   if (locations_list == NULL)
1414     return msg;
1415
1416   size = gst_value_list_get_size (locations_list);
1417   if (size < 2)
1418     return msg;
1419
1420   /* maintain existing order as much as possible, just sort references
1421    * with too high a bitrate to the end (the assumption being that if
1422    * bitrates are given they are given for all interesting streams and
1423    * that the you-need-at-least-version-xyz redirect has the same bitrate
1424    * as the lowest referenced redirect alternative) */
1425   for (i = 0; i < size; ++i) {
1426     const GstStructure *s;
1427     gint bitrate = 0;
1428
1429     location_val = gst_value_list_get_value (locations_list, i);
1430     s = (const GstStructure *) g_value_get_boxed (location_val);
1431     if (!gst_structure_get_int (s, "minimum-bitrate", &bitrate) || bitrate <= 0) {
1432       GST_DEBUG_OBJECT (playbin, "no bitrate: %" GST_PTR_FORMAT, s);
1433       l_neutral = g_list_append (l_neutral, (gpointer) s);
1434     } else if (bitrate > playbin->connection_speed) {
1435       GST_DEBUG_OBJECT (playbin, "bitrate too high: %" GST_PTR_FORMAT, s);
1436       l_bad = g_list_append (l_bad, (gpointer) s);
1437     } else if (bitrate <= playbin->connection_speed) {
1438       GST_DEBUG_OBJECT (playbin, "bitrate OK: %" GST_PTR_FORMAT, s);
1439       l_good = g_list_append (l_good, (gpointer) s);
1440     }
1441   }
1442
1443   g_value_init (&new_list, GST_TYPE_LIST);
1444   value_list_append_structure_list (&new_list, &new_structure, l_good);
1445   value_list_append_structure_list (&new_list, &new_structure, l_neutral);
1446   value_list_append_structure_list (&new_list, &new_structure, l_bad);
1447   gst_structure_set_value (new_structure, "locations", &new_list);
1448   g_value_unset (&new_list);
1449
1450   g_list_free (l_good);
1451   g_list_free (l_neutral);
1452   g_list_free (l_bad);
1453
1454   new_msg = gst_message_new_element (msg->src, new_structure);
1455   gst_message_unref (msg);
1456
1457   GST_DEBUG_OBJECT (playbin, "new redirect message: %" GST_PTR_FORMAT, new_msg);
1458   return new_msg;
1459 }
1460
1461 static void
1462 gst_play_bin_handle_message (GstBin * bin, GstMessage * msg)
1463 {
1464   if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ELEMENT && msg->structure != NULL
1465       && gst_structure_has_name (msg->structure, "redirect")) {
1466     msg = gst_play_bin_handle_redirect_message (GST_PLAY_BIN (bin), msg);
1467   }
1468
1469   GST_BIN_CLASS (parent_class)->handle_message (bin, msg);
1470 }
1471
1472 static GstStateChangeReturn
1473 gst_play_bin_change_state (GstElement * element, GstStateChange transition)
1474 {
1475   GstStateChangeReturn ret;
1476   GstPlayBin *play_bin;
1477
1478   play_bin = GST_PLAY_BIN (element);
1479
1480
1481   switch (transition) {
1482     case GST_STATE_CHANGE_READY_TO_PAUSED:
1483       /* this really is the easiest way to make the state change return
1484        * ASYNC until we added the sinks */
1485       if (!play_bin->fakesink) {
1486         play_bin->fakesink = gst_element_factory_make ("fakesink", "test");
1487         gst_bin_add (GST_BIN (play_bin), play_bin->fakesink);
1488       }
1489       break;
1490     default:
1491       break;
1492   }
1493
1494   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1495   if (ret == GST_STATE_CHANGE_FAILURE)
1496     return ret;
1497
1498   switch (transition) {
1499     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1500       /* Set audio sink state to NULL to release the sound device,
1501        * but only if we own it (else we might be in chain-transition). */
1502       //if (play_bin->audio_sink != NULL &&
1503       //    GST_STATE (play_bin->audio_sink) == GST_STATE_PAUSED) {
1504       //  gst_element_set_state (play_bin->audio_sink, GST_STATE_NULL);
1505       //}
1506       break;
1507     case GST_STATE_CHANGE_PAUSED_TO_READY:
1508       /* Check for NULL because the state transition may be done by
1509        * gst_bin_dispose which is called by gst_play_bin_dispose, and in that
1510        * case, we don't want to run remove_sinks.
1511        * FIXME: should the NULL test be done in remove_sinks? Should we just
1512        * set the state to NULL in gst_play_bin_dispose?
1513        */
1514       if (play_bin->cache != NULL) {
1515         remove_sinks (play_bin);
1516       }
1517       if (play_bin->fakesink) {
1518         gst_element_set_state (play_bin->fakesink, GST_STATE_NULL);
1519         gst_bin_remove (GST_BIN (play_bin), play_bin->fakesink);
1520         play_bin->fakesink = NULL;
1521       }
1522       break;
1523     default:
1524       break;
1525   }
1526
1527   return ret;
1528 }
1529
1530 static gboolean
1531 plugin_init (GstPlugin * plugin)
1532 {
1533   GST_DEBUG_CATEGORY_INIT (gst_play_bin_debug, "playbin", 0, "play bin");
1534
1535 #ifdef ENABLE_NLS
1536   GST_DEBUG ("binding text domain %s to locale dir %s", GETTEXT_PACKAGE,
1537       LOCALEDIR);
1538   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
1539 #endif /* ENABLE_NLS */
1540
1541   return gst_element_register (plugin, "playbin", GST_RANK_NONE,
1542       GST_TYPE_PLAY_BIN);
1543 }
1544
1545 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1546     GST_VERSION_MINOR,
1547     "playbin",
1548     "player bin", plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME,
1549     GST_PACKAGE_ORIGIN)