gst/playback/: Implement group-switch signal for use in apps to clear metadata cache...
[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 "gstplaybasebin.h"
28
29 GST_DEBUG_CATEGORY_STATIC (gst_play_bin_debug);
30 #define GST_CAT_DEFAULT gst_play_bin_debug
31
32 #define GST_TYPE_PLAY_BIN               (gst_play_bin_get_type())
33 #define GST_PLAY_BIN(obj)               (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PLAY_BIN,GstPlayBin))
34 #define GST_PLAY_BIN_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PLAY_BIN,GstPlayBinClass))
35 #define GST_IS_PLAY_BIN(obj)            (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PLAY_BIN))
36 #define GST_IS_PLAY_BIN_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PLAY_BIN))
37
38 #define VOLUME_MAX_DOUBLE 4.0
39
40 typedef struct _GstPlayBin GstPlayBin;
41 typedef struct _GstPlayBinClass GstPlayBinClass;
42
43 struct _GstPlayBin
44 {
45   GstPlayBaseBin parent;
46
47   /* the configurable elements */
48   GstElement *audio_sink;
49   GstElement *video_sink;
50   GstElement *visualisation;
51   GstElement *volume_element;
52   gfloat volume;
53
54   /* these are the currently active sinks */
55   GList *sinks;
56
57   /* these are the sink elements used for seeking/query etc.. */
58   GList *seekables;
59
60   /* the last captured frame for snapshots */
61   GstBuffer *frame;
62
63   /* our cache for the sinks */
64   GHashTable *cache;
65
66   /* boolean to see if we're currently switching groups */
67   gboolean group_switch;
68 };
69
70 struct _GstPlayBinClass
71 {
72   GstPlayBaseBinClass parent_class;
73 };
74
75 /* props */
76 enum
77 {
78   ARG_0,
79   ARG_AUDIO_SINK,
80   ARG_VIDEO_SINK,
81   ARG_VIS_PLUGIN,
82   ARG_VOLUME,
83   ARG_FRAME
84 };
85
86 /* signals */
87 enum
88 {
89   LAST_SIGNAL
90 };
91
92 static void gst_play_bin_class_init (GstPlayBinClass * klass);
93 static void gst_play_bin_init (GstPlayBin * play_bin);
94 static void gst_play_bin_dispose (GObject * object);
95
96 static void group_switch (GstPlayBaseBin * play_base_bin);
97 static void setup_sinks (GstPlayBaseBin * play_base_bin);
98 static void remove_sinks (GstPlayBin * play_bin);
99
100 static void gst_play_bin_set_property (GObject * object, guint prop_id,
101     const GValue * value, GParamSpec * spec);
102 static void gst_play_bin_get_property (GObject * object, guint prop_id,
103     GValue * value, GParamSpec * spec);
104 static GstElementStateReturn gst_play_bin_change_state (GstElement * element);
105
106 static const GstEventMask *gst_play_bin_get_event_masks (GstElement * element);
107 static gboolean gst_play_bin_send_event (GstElement * element,
108     GstEvent * event);
109 static const GstFormat *gst_play_bin_get_formats (GstElement * element);
110 static gboolean gst_play_bin_convert (GstElement * element,
111     GstFormat src_format, gint64 src_value,
112     GstFormat * dest_format, gint64 * dest_value);
113 static const GstQueryType *gst_play_bin_get_query_types (GstElement * element);
114 static gboolean gst_play_bin_query (GstElement * element, GstQueryType type,
115     GstFormat * format, gint64 * value);
116
117
118 static GstElementClass *parent_class;
119
120 //static guint gst_play_bin_signals[LAST_SIGNAL] = { 0 };
121
122 static GstElementDetails gst_play_bin_details = {
123   "Player Bin",
124   "Generic/Bin/Player",
125   "Autoplug and play media from an uri",
126   "Wim Taymans <wim@fluendo.com>"
127 };
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_ref (gst_play_base_bin_get_type ());
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 (G_OBJECT_CLASS (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 (G_OBJECT_CLASS (gobject_klass), ARG_FRAME,
189       g_param_spec_boxed ("frame", "Frame",
190           "The last frame (NULL = no video available)",
191           GST_TYPE_BUFFER, G_PARAM_READABLE));
192
193   gobject_klass->dispose = GST_DEBUG_FUNCPTR (gst_play_bin_dispose);
194
195   gst_element_class_set_details (gstelement_klass, &gst_play_bin_details);
196
197   gstelement_klass->change_state =
198       GST_DEBUG_FUNCPTR (gst_play_bin_change_state);
199   gstelement_klass->get_event_masks =
200       GST_DEBUG_FUNCPTR (gst_play_bin_get_event_masks);
201   gstelement_klass->send_event = GST_DEBUG_FUNCPTR (gst_play_bin_send_event);
202   gstelement_klass->get_formats = GST_DEBUG_FUNCPTR (gst_play_bin_get_formats);
203   gstelement_klass->convert = GST_DEBUG_FUNCPTR (gst_play_bin_convert);
204   gstelement_klass->get_query_types =
205       GST_DEBUG_FUNCPTR (gst_play_bin_get_query_types);
206   gstelement_klass->query = GST_DEBUG_FUNCPTR (gst_play_bin_query);
207
208   playbasebin_klass->setup_output_pads = setup_sinks;
209   playbasebin_klass->group_switch = group_switch;
210 }
211
212 static void
213 gst_play_bin_init (GstPlayBin * play_bin)
214 {
215   play_bin->video_sink = NULL;
216   play_bin->audio_sink = NULL;
217   play_bin->visualisation = NULL;
218   play_bin->volume_element = NULL;
219   play_bin->volume = 1.0;
220   play_bin->seekables = NULL;
221   play_bin->sinks = NULL;
222   play_bin->frame = NULL;
223   play_bin->cache = g_hash_table_new_full (g_str_hash, g_str_equal,
224       NULL, (GDestroyNotify) gst_object_unref);
225   play_bin->group_switch = FALSE;
226
227   /* no iterate is needed */
228   GST_FLAG_SET (play_bin, GST_BIN_SELF_SCHEDULABLE);
229 }
230
231 static void
232 gst_play_bin_dispose (GObject * object)
233 {
234   GstPlayBin *play_bin;
235
236   play_bin = GST_PLAY_BIN (object);
237
238   if (play_bin->cache != NULL) {
239     remove_sinks (play_bin);
240     g_hash_table_destroy (play_bin->cache);
241     play_bin->cache = NULL;
242   }
243
244   if (play_bin->audio_sink != NULL) {
245     gst_object_unref (GST_OBJECT (play_bin->audio_sink));
246     play_bin->audio_sink = NULL;
247   }
248   if (play_bin->video_sink != NULL) {
249     gst_object_unref (GST_OBJECT (play_bin->video_sink));
250     play_bin->video_sink = NULL;
251   }
252   if (play_bin->visualisation != NULL) {
253     gst_object_unref (GST_OBJECT (play_bin->visualisation));
254     play_bin->visualisation = NULL;
255   }
256
257
258   if (G_OBJECT_CLASS (parent_class)->dispose) {
259     G_OBJECT_CLASS (parent_class)->dispose (object);
260   }
261 }
262
263
264 static void
265 gst_play_bin_set_property (GObject * object, guint prop_id,
266     const GValue * value, GParamSpec * pspec)
267 {
268   GstPlayBin *play_bin;
269
270   g_return_if_fail (GST_IS_PLAY_BIN (object));
271
272   play_bin = GST_PLAY_BIN (object);
273
274   switch (prop_id) {
275     case ARG_VIDEO_SINK:
276       if (play_bin->video_sink != NULL) {
277         gst_object_unref (GST_OBJECT (play_bin->video_sink));
278       }
279       play_bin->video_sink = g_value_get_object (value);
280       if (play_bin->video_sink != NULL) {
281         gst_object_ref (GST_OBJECT (play_bin->video_sink));
282         gst_object_sink (GST_OBJECT (play_bin->video_sink));
283       }
284       /* when changing the videosink, we just remove the
285        * video pipeline from the cache so that it will be 
286        * regenerated with the new sink element */
287       g_hash_table_remove (play_bin->cache, "vbin");
288       break;
289     case ARG_AUDIO_SINK:
290       if (play_bin->audio_sink != NULL) {
291         gst_object_unref (GST_OBJECT (play_bin->audio_sink));
292       }
293       play_bin->audio_sink = g_value_get_object (value);
294       if (play_bin->audio_sink != NULL) {
295         gst_object_ref (GST_OBJECT (play_bin->audio_sink));
296         gst_object_sink (GST_OBJECT (play_bin->audio_sink));
297       }
298       g_hash_table_remove (play_bin->cache, "abin");
299       break;
300     case ARG_VIS_PLUGIN:
301       if (play_bin->visualisation != NULL) {
302         gst_object_unref (GST_OBJECT (play_bin->visualisation));
303       }
304       play_bin->visualisation = g_value_get_object (value);
305       if (play_bin->visualisation != NULL) {
306         gst_object_ref (GST_OBJECT (play_bin->visualisation));
307         gst_object_sink (GST_OBJECT (play_bin->visualisation));
308       }
309       break;
310     case ARG_VOLUME:
311       if (play_bin->volume_element) {
312         play_bin->volume = g_value_get_double (value);
313         g_object_set (G_OBJECT (play_bin->volume_element), "volume",
314             play_bin->volume, NULL);
315       }
316       break;
317     default:
318       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
319       break;
320   }
321 }
322
323 static void
324 gst_play_bin_get_property (GObject * object, guint prop_id, GValue * value,
325     GParamSpec * pspec)
326 {
327   GstPlayBin *play_bin;
328
329   g_return_if_fail (GST_IS_PLAY_BIN (object));
330
331   play_bin = GST_PLAY_BIN (object);
332
333   switch (prop_id) {
334     case ARG_VIDEO_SINK:
335       g_value_set_object (value, play_bin->video_sink);
336       break;
337     case ARG_AUDIO_SINK:
338       g_value_set_object (value, play_bin->audio_sink);
339       break;
340     case ARG_VIS_PLUGIN:
341       g_value_set_object (value, play_bin->visualisation);
342       break;
343     case ARG_VOLUME:
344       g_value_set_double (value, play_bin->volume);
345       break;
346     case ARG_FRAME:
347       g_value_set_boxed (value, play_bin->frame);
348       break;
349     default:
350       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
351       break;
352   }
353 }
354
355 /* signal fired when the identity has received a new buffer. This is used for
356  * making screenshots.
357  */
358 static void
359 handoff (GstElement * identity, GstBuffer * frame, gpointer data)
360 {
361   GstPlayBin *play_bin = GST_PLAY_BIN (data);
362
363   if (play_bin->frame) {
364     gst_buffer_unref (play_bin->frame);
365   }
366   play_bin->frame = gst_buffer_ref (frame);
367 }
368
369 /* make the element (bin) that contains the elements needed to perform
370  * video display. We connect a handoff signal to identity so that we
371  * can grab snapshots. Identity's sinkpad is ghosted to vbin.
372  *
373  *  +-------------------------------------------------------------+
374  *  | vbin                                                        |
375  *  |      +--------+   +----------+   +----------+   +---------+ |
376  *  |      |identity|   |colorspace|   |videoscale|   |videosink| |
377  *  |   +-sink     src-sink       src-sink       src-sink       | |
378  *  |   |  +---+----+   +----------+   +----------+   +---------+ |
379  * sink-+      |                                                  |
380  *  +----------|--------------------------------------------------+
381  *           handoff
382  */
383 static GstElement *
384 gen_video_element (GstPlayBin * play_bin)
385 {
386   GstElement *element;
387   GstElement *conv;
388   GstElement *scale;
389   GstElement *sink;
390   GstElement *identity;
391
392   /* first see if we have it in the cache */
393   element = g_hash_table_lookup (play_bin->cache, "vbin");
394   if (element != NULL) {
395     /* need to get the video sink element as we need to add it to the
396      * list of seekable elements */
397     sink = g_hash_table_lookup (play_bin->cache, "video_sink");
398     goto done;
399   }
400
401   element = gst_bin_new ("vbin");
402   identity = gst_element_factory_make ("identity", "id");
403   g_signal_connect (identity, "handoff", G_CALLBACK (handoff), play_bin);
404   conv = gst_element_factory_make ("ffmpegcolorspace", "vconv");
405   scale = gst_element_factory_make ("videoscale", "vscale");
406   if (play_bin->video_sink) {
407     sink = play_bin->video_sink;
408   } else {
409     sink = gst_element_factory_make ("xvimagesink", "sink");
410   }
411   gst_object_ref (GST_OBJECT (sink));
412   g_hash_table_insert (play_bin->cache, "video_sink", sink);
413
414   gst_bin_add (GST_BIN (element), identity);
415   gst_bin_add (GST_BIN (element), conv);
416   gst_bin_add (GST_BIN (element), scale);
417   gst_bin_add (GST_BIN (element), sink);
418   gst_element_link_pads (identity, "src", conv, "sink");
419   gst_element_link_pads (conv, "src", scale, "sink");
420   gst_element_link_pads (scale, "src", sink, "sink");
421
422   gst_element_add_ghost_pad (element, gst_element_get_pad (identity, "sink"),
423       "sink");
424
425   gst_element_set_state (element, GST_STATE_READY);
426
427   /* since we're gonna add it to a bin but don't want to lose it,
428    * we keep a reference. */
429   gst_object_ref (GST_OBJECT (element));
430   g_hash_table_insert (play_bin->cache, "vbin", element);
431
432 done:
433   play_bin->seekables = g_list_append (play_bin->seekables, sink);
434
435   return element;
436 }
437
438 /* make an element for playback of video with subtitles embedded.
439  *
440  *  +--------------------------------------------------+
441  *  | tbin                  +-------------+            |
442  *  |          +-----+      | textoverlay |   +------+ |
443  *  |          | csp | +--video_sink      |   | vbin | |
444  * video_sink-sink  src+ +-text_sink     src-sink    | |
445  *  |          +-----+   |  +-------------+   +------+ |
446  * text_sink-------------+                             |
447  *  +--------------------------------------------------+
448  */
449
450 static GstElement *
451 gen_text_element (GstPlayBin * play_bin)
452 {
453   GstElement *element, *csp, *overlay, *vbin;
454
455   overlay = gst_element_factory_make ("textoverlay", "overlay");
456   g_object_set (G_OBJECT (overlay),
457       "halign", "center", "valign", "bottom", NULL);
458   vbin = gen_video_element (play_bin);
459   if (!overlay) {
460     g_warning ("No overlay (pango) element, subtitles disabled");
461     return vbin;
462   }
463   csp = gst_element_factory_make ("ffmpegcolorspace", "subtitlecsp");
464   element = gst_bin_new ("textbin");
465   gst_element_link_many (csp, overlay, vbin, NULL);
466   gst_bin_add_many (GST_BIN (element), csp, overlay, vbin, NULL);
467
468   gst_element_add_ghost_pad (element,
469       gst_element_get_pad (overlay, "text_sink"), "text_sink");
470   gst_element_add_ghost_pad (element,
471       gst_element_get_pad (csp, "sink"), "sink");
472
473   return element;
474 }
475
476 /* make the element (bin) that contains the elements needed to perform
477  * audio playback. 
478  *
479  *  +-------------------------------------------------------------+
480  *  | abin                                                        |
481  *  |      +---------+   +----------+   +---------+   +---------+ |
482  *  |      |audioconv|   |audioscale|   | volume  |   |audiosink| |
483  *  |   +-sink      src-sink       src-sink      src-sink       | |
484  *  |   |  +---------+   +----------+   +---------+   +---------+ |
485  * sink-+                                                         |
486  *  +-------------------------------------------------------------+
487  *                  
488  */
489 static GstElement *
490 gen_audio_element (GstPlayBin * play_bin)
491 {
492   GstElement *element;
493   GstElement *conv;
494   GstElement *sink;
495   GstElement *volume;
496   GstElement *scale;
497
498   element = g_hash_table_lookup (play_bin->cache, "abin");
499   if (element != NULL) {
500     sink = g_hash_table_lookup (play_bin->cache, "audio_sink");
501     goto done;
502   }
503   element = gst_bin_new ("abin");
504   conv = gst_element_factory_make ("audioconvert", "aconv");
505   scale = gst_element_factory_make ("audioscale", "ascale");
506
507   volume = gst_element_factory_make ("volume", "volume");
508   g_object_set (G_OBJECT (volume), "volume", play_bin->volume, NULL);
509   play_bin->volume_element = volume;
510
511   if (play_bin->audio_sink) {
512     sink = play_bin->audio_sink;
513   } else {
514     sink = gst_element_factory_make ("osssink", "sink");
515     play_bin->audio_sink = GST_ELEMENT (gst_object_ref (GST_OBJECT (sink)));
516   }
517
518   gst_object_ref (GST_OBJECT (sink));
519   g_hash_table_insert (play_bin->cache, "audio_sink", sink);
520
521   gst_bin_add (GST_BIN (element), conv);
522   gst_bin_add (GST_BIN (element), scale);
523   gst_bin_add (GST_BIN (element), volume);
524   gst_bin_add (GST_BIN (element), sink);
525
526   gst_element_link_pads (conv, "src", scale, "sink");
527   gst_element_link_pads (scale, "src", volume, "sink");
528   gst_element_link_pads (volume, "src", sink, "sink");
529
530   gst_element_add_ghost_pad (element,
531       gst_element_get_pad (conv, "sink"), "sink");
532
533   gst_element_set_state (element, GST_STATE_READY);
534
535   /* since we're gonna add it to a bin but don't want to lose it,
536    * we keep a reference. */
537   gst_object_ref (GST_OBJECT (element));
538   g_hash_table_insert (play_bin->cache, "abin", element);
539
540 done:
541   play_bin->seekables = g_list_prepend (play_bin->seekables, sink);
542
543   return element;
544 }
545
546 /* make the element (bin) that contains the elements needed to perform
547  * visualisation ouput.  The idea is to split the audio using tee, then 
548  * sending the output to the regular audio bin and the other output to
549  * the vis plugin that transforms it into a video that is rendered with the
550  * normal video bin. The video bin is run in a thread to make sure it does
551  * not block the audio playback pipeline.
552  *
553  *  +--------------------------------------------------------------------------+
554  *  | visbin                                                                   |
555  *  |      +------+   +----------------+                                       |
556  *  |      | tee  |   |   abin ...     |                                       |
557  *  |   +-sink   src-sink              |                                       |
558  *  |   |  |      |   +----------------+                 +-------------------+ |
559  *  |   |  |      |                                      | vthread           | |
560  *  |   |  |      |   +---------+   +------+   +------+  | +--------------+  | |
561  *  |   |  |      |   |audioconv|   | vis  |   |vqueue|  | | vbin ...     |  | |
562  *  |   |  |     src-sink      src-sink   src-sink   src-sink             |  | |
563  *  |   |  |      |   +---------+   +------+   +------+  | +--------------+  | |
564  *  |   |  |      |                                      +-------------------+ |
565  *  |   |  +------+                                                            |
566  * sink-+                                                                      |
567    +--------------------------------------------------------------------------+
568  */
569 static GstElement *
570 gen_vis_element (GstPlayBin * play_bin)
571 {
572   GstElement *element;
573   GstElement *tee;
574   GstElement *asink;
575   GstElement *vsink;
576   GstElement *conv;
577   GstElement *vis;
578   GstElement *vqueue;
579   GstElement *vthread;
580
581   element = gst_bin_new ("visbin");
582   tee = gst_element_factory_make ("tee", "tee");
583
584   vqueue = gst_element_factory_make ("queue", "vqueue");
585   vthread = gst_element_factory_make ("thread", "vthread");
586
587   asink = gen_audio_element (play_bin);
588   vsink = gen_video_element (play_bin);
589
590   gst_bin_add (GST_BIN (element), asink);
591   gst_bin_add (GST_BIN (element), vqueue);
592   gst_bin_add (GST_BIN (vthread), vsink);
593   gst_bin_add (GST_BIN (element), vthread);
594   gst_bin_add (GST_BIN (element), tee);
595
596   conv = gst_element_factory_make ("audioconvert", "aconv");
597   if (play_bin->visualisation) {
598     gst_object_ref (GST_OBJECT (play_bin->visualisation));
599     vis = play_bin->visualisation;
600   } else {
601     vis = gst_element_factory_make ("goom", "vis");
602   }
603
604   gst_bin_add (GST_BIN (element), conv);
605   gst_bin_add (GST_BIN (element), vis);
606
607   gst_element_link_pads (conv, "src", vis, "sink");
608   gst_element_link_pads (vis, "src", vqueue, "sink");
609
610   gst_element_link_pads (vqueue, "src", vsink, "sink");
611
612   gst_pad_link (gst_element_get_request_pad (tee, "src%d"),
613       gst_element_get_pad (asink, "sink"));
614
615   gst_pad_link (gst_element_get_request_pad (tee, "src%d"),
616       gst_element_get_pad (conv, "sink"));
617
618   gst_element_add_ghost_pad (element,
619       gst_element_get_pad (tee, "sink"), "sink");
620
621   return element;
622 }
623
624 /* set for group switch */
625 static void
626 group_switch (GstPlayBaseBin * play_base_bin)
627 {
628   GST_PLAY_BIN (play_base_bin)->group_switch = TRUE;
629 }
630
631 /* get rid of all installed sinks */
632 static void
633 remove_sinks (GstPlayBin * play_bin)
634 {
635   GList *sinks;
636   GstObject *parent;
637   GstElement *element;
638
639   GST_DEBUG ("removesinks");
640   play_bin->group_switch = FALSE;
641   element = g_hash_table_lookup (play_bin->cache, "abin");
642   if (element != NULL) {
643     parent = gst_element_get_parent (element);
644     if (parent != NULL) {
645       /* we remove the element from the parent so that
646        * there is no unwanted state change when the parent
647        * is disposed */
648       gst_bin_remove (GST_BIN (parent), element);
649     }
650   }
651   element = g_hash_table_lookup (play_bin->cache, "vbin");
652   if (element != NULL) {
653     parent = gst_element_get_parent (element);
654     if (parent != NULL) {
655       gst_bin_remove (GST_BIN (parent), element);
656     }
657   }
658
659   for (sinks = play_bin->sinks; sinks; sinks = g_list_next (sinks)) {
660     GstElement *element = GST_ELEMENT (sinks->data);
661     GstPad *pad = gst_element_get_pad (element, "sink");
662
663     GST_LOG ("removing sink %p", element);
664     if (GST_PAD_PEER (pad))
665       gst_pad_unlink (GST_PAD_PEER (pad), pad);
666     gst_bin_remove (GST_BIN (play_bin), element);
667   }
668   g_list_free (play_bin->sinks);
669   g_list_free (play_bin->seekables);
670   play_bin->sinks = NULL;
671   play_bin->seekables = NULL;
672
673   if (play_bin->frame) {
674     gst_buffer_unref (play_bin->frame);
675     play_bin->frame = NULL;
676   }
677 }
678
679 /* loop over the streams and set up the pipeline to play this
680  * media file. First we count the number of audio and video streams.
681  * If there is no video stream but there exists an audio stream,
682  * we install a visualisation pipeline.
683  * 
684  * Also make sure to only connect the first audio and video pad. FIXME
685  * this should eventually be handled with a tuner interface so that
686  * one can switch the streams.
687  */
688 static gboolean
689 add_sink (GstPlayBin * play_bin, GstElement * sink, GstPad * srcpad)
690 {
691   GstPad *sinkpad;
692   gboolean res;
693
694   /* we found a sink for this stream, now try to install it */
695   gst_bin_add (GST_BIN (play_bin), sink);
696   GST_DEBUG ("Adding sink with state %d (parent: %d, peer: %d)\n",
697       GST_STATE (sink), GST_STATE (play_bin),
698       GST_STATE (gst_pad_get_parent (srcpad)));
699   sinkpad = gst_element_get_pad (sink, "sink");
700
701   /* try to link the pad of the sink to the stream */
702   res = gst_pad_link (srcpad, sinkpad);
703   if (!res) {
704     gchar *capsstr;
705
706     /* could not link this stream */
707     capsstr = gst_caps_to_string (gst_pad_get_caps (srcpad));
708     g_warning ("could not link %s", capsstr);
709     g_free (capsstr);
710     GST_LOG ("removing sink %p", sink);
711     gst_bin_remove (GST_BIN (play_bin), sink);
712   } else {
713     /* we got the sink succesfully linked, now keep the sink
714      * in out internal list */
715     play_bin->sinks = g_list_prepend (play_bin->sinks, sink);
716   }
717
718   return res;
719 }
720
721 static void
722 setup_sinks (GstPlayBaseBin * play_base_bin)
723 {
724   GstPlayBin *play_bin = GST_PLAY_BIN (play_base_bin);
725   GstPlayBaseGroup *group;
726   GList *streaminfo = NULL, *s;
727   gboolean need_vis = FALSE;
728   gboolean need_text = FALSE;
729   GstPad *textsrcpad = NULL, *textsinkpad = NULL;
730   GstElement *sink;
731
732   /* get rid of existing sinks */
733   if (play_bin->sinks) {
734     remove_sinks (play_bin);
735   }
736   GST_DEBUG ("setupsinks");
737
738   /* find out what to do */
739   group = play_base_bin->queued_groups->data;
740   if (group->type[GST_STREAM_TYPE_VIDEO - 1].npads > 0 &&
741       group->type[GST_STREAM_TYPE_TEXT - 1].npads > 0) {
742     need_text = TRUE;
743   } else if (group->type[GST_STREAM_TYPE_VIDEO - 1].npads == 0 &&
744       group->type[GST_STREAM_TYPE_AUDIO - 1].npads > 0 &&
745       play_bin->visualisation != NULL) {
746     need_vis = TRUE;
747   }
748
749   /* now actually connect everything */
750   g_object_get (G_OBJECT (play_base_bin), "stream-info", &streaminfo, NULL);
751   for (s = streaminfo; s; s = g_list_next (s)) {
752     GObject *obj = G_OBJECT (s->data);
753     gint type;
754     GstObject *object;
755
756     g_object_get (obj, "type", &type, NULL);
757     g_object_get (obj, "object", &object, NULL);
758
759     /* use the sink elements as seek entry point */
760     if (type == 4) {
761       play_bin->seekables = g_list_prepend (play_bin->seekables, object);
762     }
763   }
764
765   /* link audio */
766   if (group->type[GST_STREAM_TYPE_AUDIO - 1].npads > 0) {
767     if (need_vis) {
768       sink = gen_vis_element (play_bin);
769     } else {
770       sink = gen_audio_element (play_bin);
771     }
772     add_sink (play_bin, sink,
773         gst_element_get_pad (group->type[GST_STREAM_TYPE_AUDIO - 1].preroll,
774             "src"));
775   }
776
777   /* link video */
778   if (group->type[GST_STREAM_TYPE_VIDEO - 1].npads > 0) {
779     if (need_text) {
780       sink = gen_text_element (play_bin);
781
782       textsinkpad = gst_element_get_pad (sink, "text_sink");
783       textsrcpad =
784           gst_element_get_pad (group->type[GST_STREAM_TYPE_TEXT - 1].preroll,
785           "src");
786       if (textsinkpad && textsrcpad) {
787         gst_pad_link (textsrcpad, textsinkpad);
788       }
789     } else {
790       sink = gen_video_element (play_bin);
791     }
792     add_sink (play_bin, sink,
793         gst_element_get_pad (group->type[GST_STREAM_TYPE_VIDEO - 1].preroll,
794             "src"));
795   }
796 }
797
798 static GstElementStateReturn
799 gst_play_bin_change_state (GstElement * element)
800 {
801   GstElementStateReturn ret;
802   GstPlayBin *play_bin;
803   int transition;
804
805   play_bin = GST_PLAY_BIN (element);
806
807   transition = GST_STATE_TRANSITION (element);
808
809   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element);
810   if (ret == GST_STATE_FAILURE)
811     return ret;
812
813   switch (transition) {
814     case GST_STATE_PLAYING_TO_PAUSED:
815       /* Set audio sink state to NULL to release the sound device,
816        * but only if we own it (else we might be in chain-transition). */
817       if (play_bin->audio_sink != NULL && !play_bin->group_switch &&
818           GST_STATE (play_bin->audio_sink) == GST_STATE_PAUSED) {
819         gst_element_set_state (play_bin->audio_sink, GST_STATE_NULL);
820       }
821       break;
822     case GST_STATE_PAUSED_TO_READY:
823       /* Check for NULL because the state transition may be done by
824        * gst_bin_dispose which is called by gst_play_bin_dispose, and in that
825        * case, we don't want to run remove_sinks.
826        * FIXME: should the NULL test be done in remove_sinks? Should we just
827        * set the state to NULL in gst_play_bin_dispose?
828        */
829       if (play_bin->cache != NULL) {
830         remove_sinks (play_bin);
831       }
832       break;
833     default:
834       break;
835   }
836
837   return ret;
838 }
839
840
841 static const GstEventMask *
842 gst_play_bin_get_event_masks (GstElement * element)
843 {
844   /* FIXME, get the list from the number of installed sinks */
845   return NULL;
846 }
847
848 /* send an event to all the sinks */
849 static gboolean
850 gst_play_bin_send_event (GstElement * element, GstEvent * event)
851 {
852   gboolean res = FALSE;
853   GList *s;
854   GstPlayBin *play_bin;
855   GstElementState state;
856   gboolean need_pause = FALSE;
857
858   play_bin = GST_PLAY_BIN (element);
859
860   state = gst_element_get_state (element);
861   /* we pause the pipeline first before sending the event. We only
862    * do this if the pipeline was playing. */
863   if (state == GST_STATE_PLAYING) {
864     need_pause = TRUE;
865     gst_element_set_state (element, GST_STATE_PAUSED);
866   }
867
868   /* loop over all seekables and send the event to them */
869   for (s = play_bin->seekables; s; s = g_list_next (s)) {
870     GstElement *element = GST_ELEMENT (s->data);
871     gboolean ok;
872
873     /* ref each event before sending it */
874     gst_event_ref (event);
875     ok = gst_element_send_event (element, event);
876     res |= ok;
877   }
878   gst_event_unref (event);
879
880   /* and restart the pipeline if we paused it */
881   if (need_pause)
882     gst_element_set_state (element, GST_STATE_PLAYING);
883
884   return res;
885 }
886
887 static const GstFormat *
888 gst_play_bin_get_formats (GstElement * element)
889 {
890   /* FIXME, compile this list from the installed sinks */
891   static GstFormat formats[] = {
892     GST_FORMAT_TIME,
893     0,
894   };
895
896   return formats;
897 }
898
899 static gboolean
900 gst_play_bin_convert (GstElement * element,
901     GstFormat src_format, gint64 src_value,
902     GstFormat * dest_format, gint64 * dest_value)
903 {
904   gboolean res = FALSE;
905   GList *s;
906   GstPlayBin *play_bin;
907
908   play_bin = GST_PLAY_BIN (element);
909
910   /* do a conversion, loop over all sinks, stop as soon as one of the
911    * sinks returns a successful result */
912   for (s = play_bin->seekables; s; s = g_list_next (s)) {
913     GstElement *element = GST_ELEMENT (s->data);
914
915     res = gst_element_convert (element, src_format, src_value,
916         dest_format, dest_value);
917     if (res)
918       break;
919   }
920   return res;
921 }
922
923 static const GstQueryType *
924 gst_play_bin_get_query_types (GstElement * element)
925 {
926   /* FIXME, compile from the installed sinks */
927   static const GstQueryType query_types[] = {
928     GST_QUERY_TOTAL,
929     GST_QUERY_POSITION,
930     0
931   };
932
933   return query_types;
934 }
935
936 static gboolean
937 gst_play_bin_query (GstElement * element, GstQueryType type,
938     GstFormat * format, gint64 * value)
939 {
940   gboolean res = FALSE;
941   GList *s;
942   GstPlayBin *play_bin;
943
944   play_bin = GST_PLAY_BIN (element);
945
946   for (s = play_bin->seekables; s; s = g_list_next (s)) {
947     GstElement *element = GST_ELEMENT (s->data);
948
949     res = gst_element_query (element, type, format, value);
950     if (res)
951       break;
952   }
953   return res;
954 }
955
956 static gboolean
957 plugin_init (GstPlugin * plugin)
958 {
959   GST_DEBUG_CATEGORY_INIT (gst_play_bin_debug, "playbin", 0, "play bin");
960
961   return gst_element_register (plugin, "playbin", GST_RANK_NONE,
962       GST_TYPE_PLAY_BIN);
963 }
964
965 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
966     GST_VERSION_MINOR,
967     "playbin",
968     "player bin", plugin_init, VERSION, GST_LICENSE, GST_PACKAGE, GST_ORIGIN)