gst/playback/gstplaybin.c: Remove visualization from parent explicitely; works around...
[platform/upstream/gst-plugins-base.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 *fakesink;
49   GstElement *audio_sink;
50   GstElement *video_sink;
51   GstElement *visualisation;
52   GstElement *volume_element;
53   GstElement *textoverlay_element;
54   gfloat volume;
55
56   /* these are the currently active sinks */
57   GList *sinks;
58
59   /* the last captured frame for snapshots */
60   GstBuffer *frame;
61
62   /* our cache for the sinks */
63   GHashTable *cache;
64
65   /* font description */
66   gchar *font_desc;
67 };
68
69 struct _GstPlayBinClass
70 {
71   GstPlayBaseBinClass parent_class;
72 };
73
74 /* props */
75 enum
76 {
77   ARG_0,
78   ARG_AUDIO_SINK,
79   ARG_VIDEO_SINK,
80   ARG_VIS_PLUGIN,
81   ARG_VOLUME,
82   ARG_FRAME,
83   ARG_FONT_DESC
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 setup_sinks (GstPlayBaseBin * play_base_bin,
97     GstPlayBaseGroup * group);
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 GstElementClass *parent_class;
107
108 //static guint gst_play_bin_signals[LAST_SIGNAL] = { 0 };
109
110 static GstElementDetails gst_play_bin_details = {
111   "Player Bin",
112   "Generic/Bin/Player",
113   "Autoplug and play media from an uri",
114   "Wim Taymans <wim@fluendo.com>"
115 };
116
117 static GType
118 gst_play_bin_get_type (void)
119 {
120   static GType gst_play_bin_type = 0;
121
122   if (!gst_play_bin_type) {
123     static const GTypeInfo gst_play_bin_info = {
124       sizeof (GstPlayBinClass),
125       NULL,
126       NULL,
127       (GClassInitFunc) gst_play_bin_class_init,
128       NULL,
129       NULL,
130       sizeof (GstPlayBin),
131       0,
132       (GInstanceInitFunc) gst_play_bin_init,
133       NULL
134     };
135
136     gst_play_bin_type = g_type_register_static (GST_TYPE_PLAY_BASE_BIN,
137         "GstPlayBin", &gst_play_bin_info, 0);
138   }
139
140   return gst_play_bin_type;
141 }
142
143 static void
144 gst_play_bin_class_init (GstPlayBinClass * klass)
145 {
146   GObjectClass *gobject_klass;
147   GstElementClass *gstelement_klass;
148   GstBinClass *gstbin_klass;
149   GstPlayBaseBinClass *playbasebin_klass;
150
151   gobject_klass = (GObjectClass *) klass;
152   gstelement_klass = (GstElementClass *) klass;
153   gstbin_klass = (GstBinClass *) klass;
154   playbasebin_klass = (GstPlayBaseBinClass *) klass;
155
156   parent_class = g_type_class_ref (gst_play_base_bin_get_type ());
157
158   gobject_klass->set_property = gst_play_bin_set_property;
159   gobject_klass->get_property = gst_play_bin_get_property;
160
161   g_object_class_install_property (gobject_klass, ARG_VIDEO_SINK,
162       g_param_spec_object ("video-sink", "Video Sink",
163           "the video output element to use (NULL = default sink)",
164           GST_TYPE_ELEMENT, G_PARAM_READWRITE));
165   g_object_class_install_property (gobject_klass, ARG_AUDIO_SINK,
166       g_param_spec_object ("audio-sink", "Audio Sink",
167           "the audio output element to use (NULL = default sink)",
168           GST_TYPE_ELEMENT, G_PARAM_READWRITE));
169   g_object_class_install_property (gobject_klass, ARG_VIS_PLUGIN,
170       g_param_spec_object ("vis-plugin", "Vis plugin",
171           "the visualization element to use (NULL = none)",
172           GST_TYPE_ELEMENT, G_PARAM_READWRITE));
173   g_object_class_install_property (gobject_klass, ARG_VOLUME,
174       g_param_spec_double ("volume", "volume", "volume",
175           0.0, VOLUME_MAX_DOUBLE, 1.0, G_PARAM_READWRITE));
176   g_object_class_install_property (gobject_klass, ARG_FRAME,
177       gst_param_spec_mini_object ("frame", "Frame",
178           "The last frame (NULL = no video available)",
179           GST_TYPE_BUFFER, G_PARAM_READABLE));
180   g_object_class_install_property (gobject_klass, ARG_FONT_DESC,
181       g_param_spec_string ("subtitle-font-desc",
182           "Subtitle font description",
183           "Pango font description of font "
184           "to be used for subtitle rendering", NULL, G_PARAM_WRITABLE));
185
186   gobject_klass->dispose = GST_DEBUG_FUNCPTR (gst_play_bin_dispose);
187
188   gst_element_class_set_details (gstelement_klass, &gst_play_bin_details);
189
190   gstelement_klass->change_state =
191       GST_DEBUG_FUNCPTR (gst_play_bin_change_state);
192
193   playbasebin_klass->setup_output_pads = setup_sinks;
194 }
195
196 static void
197 gst_play_bin_init (GstPlayBin * play_bin)
198 {
199   play_bin->video_sink = NULL;
200   play_bin->audio_sink = NULL;
201   play_bin->visualisation = NULL;
202   play_bin->volume_element = NULL;
203   play_bin->textoverlay_element = NULL;
204   play_bin->volume = 1.0;
205   play_bin->sinks = NULL;
206   play_bin->frame = NULL;
207   play_bin->font_desc = NULL;
208   play_bin->cache = g_hash_table_new_full (g_str_hash, g_str_equal,
209       NULL, (GDestroyNotify) gst_object_unref);
210 }
211
212 static void
213 gst_play_bin_dispose (GObject * object)
214 {
215   GstPlayBin *play_bin;
216
217   play_bin = GST_PLAY_BIN (object);
218
219   if (play_bin->cache != NULL) {
220     remove_sinks (play_bin);
221     g_hash_table_destroy (play_bin->cache);
222     play_bin->cache = NULL;
223   }
224
225   if (play_bin->audio_sink != NULL) {
226     gst_object_unref (play_bin->audio_sink);
227     play_bin->audio_sink = NULL;
228   }
229   if (play_bin->video_sink != NULL) {
230     gst_object_unref (play_bin->video_sink);
231     play_bin->video_sink = NULL;
232   }
233   if (play_bin->visualisation != NULL) {
234     gst_object_unref (play_bin->visualisation);
235     play_bin->visualisation = NULL;
236   }
237   g_free (play_bin->font_desc);
238   play_bin->font_desc = NULL;
239
240   G_OBJECT_CLASS (parent_class)->dispose (object);
241 }
242
243
244 static void
245 gst_play_bin_set_property (GObject * object, guint prop_id,
246     const GValue * value, GParamSpec * pspec)
247 {
248   GstPlayBin *play_bin;
249
250   g_return_if_fail (GST_IS_PLAY_BIN (object));
251
252   play_bin = GST_PLAY_BIN (object);
253
254   switch (prop_id) {
255     case ARG_VIDEO_SINK:
256       if (play_bin->video_sink != NULL) {
257         gst_object_unref (play_bin->video_sink);
258       }
259       play_bin->video_sink = g_value_get_object (value);
260       if (play_bin->video_sink != NULL) {
261         gst_object_ref (play_bin->video_sink);
262         gst_object_sink (GST_OBJECT (play_bin->video_sink));
263       }
264       /* when changing the videosink, we just remove the
265        * video pipeline from the cache so that it will be 
266        * regenerated with the new sink element */
267       g_hash_table_remove (play_bin->cache, "vbin");
268       break;
269     case ARG_AUDIO_SINK:
270       if (play_bin->audio_sink != NULL) {
271         gst_object_unref (play_bin->audio_sink);
272       }
273       play_bin->audio_sink = g_value_get_object (value);
274       if (play_bin->audio_sink != NULL) {
275         gst_object_ref (play_bin->audio_sink);
276         gst_object_sink (GST_OBJECT (play_bin->audio_sink));
277       }
278       g_hash_table_remove (play_bin->cache, "abin");
279       break;
280     case ARG_VIS_PLUGIN:
281       if (play_bin->visualisation != NULL) {
282         gst_object_unref (play_bin->visualisation);
283       }
284       play_bin->visualisation = g_value_get_object (value);
285       if (play_bin->visualisation != NULL) {
286         gst_object_ref (play_bin->visualisation);
287         gst_object_sink (GST_OBJECT (play_bin->visualisation));
288       }
289       break;
290     case ARG_VOLUME:
291       play_bin->volume = g_value_get_double (value);
292       if (play_bin->volume_element) {
293         g_object_set (G_OBJECT (play_bin->volume_element), "volume",
294             play_bin->volume, NULL);
295       }
296       break;
297     case ARG_FONT_DESC:
298       g_free (play_bin->font_desc);
299       play_bin->font_desc = g_strdup (g_value_get_string (value));
300       if (play_bin->textoverlay_element) {
301         g_object_set (G_OBJECT (play_bin->textoverlay_element),
302             "font-desc", g_value_get_string (value), NULL);
303       }
304       break;
305     default:
306       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
307       break;
308   }
309 }
310
311 static void
312 gst_play_bin_get_property (GObject * object, guint prop_id, GValue * value,
313     GParamSpec * pspec)
314 {
315   GstPlayBin *play_bin;
316
317   g_return_if_fail (GST_IS_PLAY_BIN (object));
318
319   play_bin = GST_PLAY_BIN (object);
320
321   switch (prop_id) {
322     case ARG_VIDEO_SINK:
323       g_value_set_object (value, play_bin->video_sink);
324       break;
325     case ARG_AUDIO_SINK:
326       g_value_set_object (value, play_bin->audio_sink);
327       break;
328     case ARG_VIS_PLUGIN:
329       g_value_set_object (value, play_bin->visualisation);
330       break;
331     case ARG_VOLUME:
332       g_value_set_double (value, play_bin->volume);
333       break;
334     case ARG_FRAME:
335       gst_value_set_mini_object (value, GST_MINI_OBJECT (play_bin->frame));
336       break;
337     default:
338       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
339       break;
340   }
341 }
342
343 /* signal fired when the identity has received a new buffer. This is used for
344  * making screenshots.
345  */
346 static void
347 handoff (GstElement * identity, GstBuffer * frame, gpointer data)
348 {
349   GstPlayBin *play_bin = GST_PLAY_BIN (data);
350
351   if (play_bin->frame) {
352     gst_buffer_unref (play_bin->frame);
353   }
354   play_bin->frame = gst_buffer_ref (frame);
355 }
356
357 /* make the element (bin) that contains the elements needed to perform
358  * video display. We connect a handoff signal to identity so that we
359  * can grab snapshots. Identity's sinkpad is ghosted to vbin.
360  *
361  *  +-------------------------------------------------------------+
362  *  | vbin                                                        |
363  *  |      +--------+   +----------+   +----------+   +---------+ |
364  *  |      |identity|   |colorspace|   |videoscale|   |videosink| |
365  *  |   +-sink     src-sink       src-sink       src-sink       | |
366  *  |   |  +---+----+   +----------+   +----------+   +---------+ |
367  * sink-+      |                                                  |
368  *  +----------|--------------------------------------------------+
369  *           handoff
370  */
371 static GstElement *
372 gen_video_element (GstPlayBin * play_bin)
373 {
374   GstElement *element;
375   GstElement *conv;
376
377   GstElement *scale;
378   GstElement *sink;
379   GstElement *identity;
380   GstPad *pad;
381
382   /* first see if we have it in the cache */
383   element = g_hash_table_lookup (play_bin->cache, "vbin");
384   if (element != NULL) {
385     return element;
386   }
387
388   element = gst_bin_new ("vbin");
389   identity = gst_element_factory_make ("identity", "id");
390   g_object_set (identity, "silent", TRUE, NULL);
391   g_signal_connect (identity, "handoff", G_CALLBACK (handoff), play_bin);
392   conv = gst_element_factory_make ("ffmpegcolorspace", "vconv");
393   scale = gst_element_factory_make ("videoscale", "vscale");
394   if (play_bin->video_sink) {
395     sink = play_bin->video_sink;
396   } else {
397     sink = gst_element_factory_make ("autovideosink", "videosink");
398   }
399   gst_object_ref (sink);
400   g_hash_table_insert (play_bin->cache, "video_sink", sink);
401
402   gst_bin_add (GST_BIN (element), identity);
403   gst_bin_add (GST_BIN (element), conv);
404   gst_bin_add (GST_BIN (element), scale);
405   gst_bin_add (GST_BIN (element), sink);
406   gst_element_link_pads (identity, "src", conv, "sink");
407   gst_element_link_pads (conv, "src", scale, "sink");
408   gst_element_link_pads (scale, "src", sink, "sink");
409
410   pad = gst_element_get_pad (identity, "sink");
411   gst_element_add_pad (element, gst_ghost_pad_new ("sink", pad));
412   gst_object_unref (pad);
413
414   gst_element_set_state (element, GST_STATE_READY);
415
416   /* since we're gonna add it to a bin but don't want to lose it,
417    * we keep a reference. */
418   gst_object_ref (element);
419   g_hash_table_insert (play_bin->cache, "vbin", element);
420
421   return element;
422 }
423
424 /* make an element for playback of video with subtitles embedded.
425  *
426  *  +--------------------------------------------------+
427  *  | tbin                  +-------------+            |
428  *  |          +-----+      | textoverlay |   +------+ |
429  *  |          | csp | +--video_sink      |   | vbin | |
430  * video_sink-sink  src+ +-text_sink     src-sink    | |
431  *  |          +-----+   |  +-------------+   +------+ |
432  * text_sink-------------+                             |
433  *  +--------------------------------------------------+
434  */
435
436 static GstElement *
437 gen_text_element (GstPlayBin * play_bin)
438 {
439   GstElement *element, *csp, *overlay, *vbin;
440   GstPad *pad;
441
442   overlay = gst_element_factory_make ("textoverlay", "overlay");
443   g_object_set (G_OBJECT (overlay),
444       "halign", "center", "valign", "bottom", NULL);
445   play_bin->textoverlay_element = overlay;
446   if (play_bin->font_desc) {
447     g_object_set (G_OBJECT (play_bin->textoverlay_element),
448         "font-desc", play_bin->font_desc, NULL);
449   }
450   vbin = gen_video_element (play_bin);
451   if (!overlay) {
452     g_warning ("No overlay (pango) element, subtitles disabled");
453     return vbin;
454   }
455   csp = gst_element_factory_make ("ffmpegcolorspace", "subtitlecsp");
456   element = gst_bin_new ("textbin");
457   gst_element_link_many (csp, overlay, vbin, NULL);
458   gst_bin_add_many (GST_BIN (element), csp, overlay, vbin, NULL);
459
460   pad = gst_element_get_pad (overlay, "text_sink");
461 #define gst_element_add_ghost_pad(element, pad, name) \
462     gst_element_add_pad (element, gst_ghost_pad_new (name, pad))
463   gst_element_add_ghost_pad (element, pad, "text_sink");
464   gst_object_unref (pad);
465
466   pad = gst_element_get_pad (csp, "sink");
467   gst_element_add_ghost_pad (element, pad, "sink");
468   gst_object_unref (pad);
469
470   return element;
471 }
472
473 /* make the element (bin) that contains the elements needed to perform
474  * audio playback. 
475  *
476  *  +-------------------------------------------------------------+
477  *  | abin                                                        |
478  *  |      +---------+   +----------+   +---------+   +---------+ |
479  *  |      |audioconv|   |audioscale|   | volume  |   |audiosink| |
480  *  |   +-sink      src-sink       src-sink      src-sink       | |
481  *  |   |  +---------+   +----------+   +---------+   +---------+ |
482  * sink-+                                                         |
483  *  +-------------------------------------------------------------+
484  *                  
485  */
486 static GstElement *
487 gen_audio_element (GstPlayBin * play_bin)
488 {
489   GstElement *element;
490   GstElement *conv;
491   GstElement *sink;
492   GstElement *volume;
493   GstElement *scale;
494   GstPad *pad;
495
496   element = g_hash_table_lookup (play_bin->cache, "abin");
497   if (element != NULL) {
498     return element;
499   }
500   element = gst_bin_new ("abin");
501   conv = gst_element_factory_make ("audioconvert", "aconv");
502   scale = gst_element_factory_make ("audioscale", "ascale");
503
504   volume = gst_element_factory_make ("volume", "volume");
505   g_object_set (G_OBJECT (volume), "volume", play_bin->volume, NULL);
506   play_bin->volume_element = volume;
507
508   if (play_bin->audio_sink) {
509     sink = play_bin->audio_sink;
510   } else {
511     sink = gst_element_factory_make ("autoaudiosink", "audiosink");
512     play_bin->audio_sink = GST_ELEMENT (gst_object_ref (sink));
513   }
514
515   gst_object_ref (sink);
516   g_hash_table_insert (play_bin->cache, "audio_sink", sink);
517
518   gst_bin_add (GST_BIN (element), conv);
519   //gst_bin_add (GST_BIN (element), scale);
520   gst_bin_add (GST_BIN (element), volume);
521   gst_bin_add (GST_BIN (element), sink);
522
523   gst_element_link_pads (conv, "src",   /*scale, "sink");
524                                            gst_element_link_pads (scale, "src", */ volume, "sink");
525   gst_element_link_pads (volume, "src", sink, "sink");
526
527   pad = gst_element_get_pad (conv, "sink");
528   gst_element_add_ghost_pad (element, pad, "sink");
529   gst_object_unref (pad);
530
531   gst_element_set_state (element, GST_STATE_READY);
532
533   /* since we're gonna add it to a bin but don't want to lose it,
534    * we keep a reference. */
535   gst_object_ref (element);
536   g_hash_table_insert (play_bin->cache, "abin", element);
537
538   return element;
539 }
540
541 /* make the element (bin) that contains the elements needed to perform
542  * visualisation ouput.  The idea is to split the audio using tee, then 
543  * sending the output to the regular audio bin and the other output to
544  * the vis plugin that transforms it into a video that is rendered with the
545  * normal video bin. The video bin is run in a thread to make sure it does
546  * not block the audio playback pipeline.
547  *
548  *  +--------------------------------------------------------------------------+
549  *  | visbin                                                                   |
550  *  |      +------+   +----------------+                                       |
551  *  |      | tee  |   |   abin ...     |                                       |
552  *  |   +-sink   src-sink              |                                       |
553  *  |   |  |      |   +----------------+                 +-------------------+ |
554  *  |   |  |      |                                      | vthread           | |
555  *  |   |  |      |   +---------+   +------+   +------+  | +--------------+  | |
556  *  |   |  |      |   |audioconv|   | vis  |   |vqueue|  | | vbin ...     |  | |
557  *  |   |  |     src-sink      src-sink   src-sink   src-sink             |  | |
558  *  |   |  |      |   +---------+   +------+   +------+  | +--------------+  | |
559  *  |   |  |      |                                      +-------------------+ |
560  *  |   |  +------+                                                            |
561  * sink-+                                                                      |
562    +--------------------------------------------------------------------------+
563  */
564 static GstElement *
565 gen_vis_element (GstPlayBin * play_bin)
566 {
567   GstElement *element;
568   GstElement *tee;
569   GstElement *asink;
570   GstElement *vsink;
571   GstElement *conv;
572   GstElement *vis;
573   GstElement *vqueue, *aqueue;
574   GstPad *pad, *rpad;
575
576   element = gst_bin_new ("visbin");
577   tee = gst_element_factory_make ("tee", "tee");
578
579   vqueue = gst_element_factory_make ("queue", "vqueue");
580   aqueue = gst_element_factory_make ("queue", "aqueue");
581
582   asink = gen_audio_element (play_bin);
583   vsink = gen_video_element (play_bin);
584
585   gst_bin_add (GST_BIN (element), asink);
586   gst_bin_add (GST_BIN (element), vqueue);
587   gst_bin_add (GST_BIN (element), aqueue);
588   gst_bin_add (GST_BIN (element), vsink);
589   gst_bin_add (GST_BIN (element), tee);
590
591   conv = gst_element_factory_make ("audioconvert", "aconv");
592   if (play_bin->visualisation) {
593     gst_object_ref (play_bin->visualisation);
594     vis = play_bin->visualisation;
595   } else {
596     vis = gst_element_factory_make ("goom", "vis");
597   }
598
599   gst_bin_add (GST_BIN (element), conv);
600   gst_bin_add (GST_BIN (element), vis);
601
602   gst_element_link_pads (conv, "src", vis, "sink");
603   gst_element_link_pads (vis, "src", vqueue, "sink");
604
605   gst_element_link_pads (vqueue, "src", vsink, "sink");
606
607   pad = gst_element_get_pad (aqueue, "sink");
608   rpad = gst_element_get_request_pad (tee, "src%d");
609   gst_pad_link (rpad, pad);
610   gst_object_unref (rpad);
611   gst_object_unref (pad);
612   gst_element_link_pads (aqueue, "src", asink, "sink");
613
614   pad = gst_element_get_pad (conv, "sink");
615   rpad = gst_element_get_request_pad (tee, "src%d");
616   gst_pad_link (rpad, pad);
617   gst_object_unref (rpad);
618   gst_object_unref (pad);
619
620   pad = gst_element_get_pad (tee, "sink");
621   gst_element_add_ghost_pad (element, pad, "sink");
622   gst_object_unref (pad);
623
624   return element;
625 }
626
627 /* get rid of all installed sinks */
628 static void
629 remove_sinks (GstPlayBin * play_bin)
630 {
631   GList *sinks;
632   GstObject *parent;
633   GstElement *element;
634   GstPad *pad, *peer;
635
636   GST_DEBUG ("removesinks");
637   element = g_hash_table_lookup (play_bin->cache, "abin");
638   if (element != NULL) {
639     parent = gst_element_get_parent (element);
640     if (parent != NULL) {
641       /* we remove the element from the parent so that
642        * there is no unwanted state change when the parent
643        * is disposed */
644       play_bin->sinks = g_list_remove (play_bin->sinks, element);
645       gst_bin_remove (GST_BIN (parent), element);
646       gst_object_unref (parent);
647     }
648     pad = gst_element_get_pad (element, "sink");
649     if (pad != NULL) {
650       peer = gst_pad_get_peer (pad);
651       if (peer != NULL) {
652         gst_pad_unlink (peer, pad);
653         gst_object_unref (peer);
654       }
655       gst_object_unref (pad);
656     }
657   }
658   element = g_hash_table_lookup (play_bin->cache, "vbin");
659   if (element != NULL) {
660     parent = gst_element_get_parent (element);
661     if (parent != NULL) {
662       play_bin->sinks = g_list_remove (play_bin->sinks, element);
663       gst_bin_remove (GST_BIN (parent), element);
664       gst_object_unref (parent);
665     }
666     pad = gst_element_get_pad (element, "sink");
667     if (pad != NULL) {
668       peer = gst_pad_get_peer (pad);
669       if (peer != NULL) {
670         gst_pad_unlink (peer, pad);
671         gst_object_unref (peer);
672       }
673       gst_object_unref (pad);
674     }
675   }
676
677   for (sinks = play_bin->sinks; sinks; sinks = g_list_next (sinks)) {
678     GstElement *element = GST_ELEMENT (sinks->data);
679     GstPad *pad;
680     GstPad *peer;
681
682     pad = gst_element_get_pad (element, "sink");
683
684     GST_LOG ("removing sink %p", element);
685
686     peer = gst_pad_get_peer (pad);
687     if (peer) {
688       gst_pad_unlink (peer, pad);
689       gst_object_unref (peer);
690     }
691     gst_object_unref (pad);
692
693     gst_bin_remove (GST_BIN (play_bin), element);
694   }
695   g_list_free (play_bin->sinks);
696   play_bin->sinks = NULL;
697
698   /* FIXME: this is probably some refcounting problem */
699   if (play_bin->visualisation && GST_OBJECT_PARENT (play_bin->visualisation)) {
700     gst_bin_remove (GST_BIN (GST_OBJECT_PARENT (play_bin->visualisation)),
701         play_bin->visualisation);
702     gst_element_set_state (play_bin->visualisation, GST_STATE_NULL);
703   }
704
705   if (play_bin->frame) {
706     gst_buffer_unref (play_bin->frame);
707     play_bin->frame = NULL;
708   }
709
710   play_bin->textoverlay_element = NULL;
711 }
712
713 /* loop over the streams and set up the pipeline to play this
714  * media file. First we count the number of audio and video streams.
715  * If there is no video stream but there exists an audio stream,
716  * we install a visualisation pipeline.
717  * 
718  * Also make sure to only connect the first audio and video pad. FIXME
719  * this should eventually be handled with a tuner interface so that
720  * one can switch the streams.
721  */
722 static gboolean
723 add_sink (GstPlayBin * play_bin, GstElement * sink, GstPad * srcpad)
724 {
725   GstPad *sinkpad;
726   GstPadLinkReturn res;
727   GstElement *parent;
728
729   gst_bin_add (GST_BIN (play_bin), sink);
730
731   /* we found a sink for this stream, now try to install it */
732   sinkpad = gst_element_get_pad (sink, "sink");
733   res = gst_pad_link (srcpad, sinkpad);
734   gst_object_unref (sinkpad);
735
736   /* this is only for debugging */
737   parent = gst_pad_get_parent_element (srcpad);
738   if (parent) {
739     GST_DEBUG ("Adding sink with state %d (parent: %d, peer: %d)",
740         GST_STATE (sink), GST_STATE (play_bin), GST_STATE (parent));
741     gst_object_unref (parent);
742   }
743
744   /* try to link the pad of the sink to the stream */
745   if (res < 0) {
746     gchar *capsstr;
747
748     /* could not link this stream */
749     capsstr = gst_caps_to_string (gst_pad_get_caps (srcpad));
750     g_warning ("could not link %s: %d", capsstr, res);
751     g_free (capsstr);
752
753     gst_bin_remove (GST_BIN (play_bin), sink);
754   } else {
755     /* we got the sink succesfully linked, now keep the sink
756      * in out internal list */
757     play_bin->sinks = g_list_prepend (play_bin->sinks, sink);
758     gst_element_set_state (sink,
759         (GST_STATE (play_bin) == GST_STATE_PLAYING) ?
760         GST_STATE_PLAYING : GST_STATE_PAUSED);
761   }
762
763   return res;
764 }
765
766 static void
767 setup_sinks (GstPlayBaseBin * play_base_bin, GstPlayBaseGroup * group)
768 {
769   GstPlayBin *play_bin = GST_PLAY_BIN (play_base_bin);
770   GList *streaminfo = NULL, *s;
771   gboolean need_vis = FALSE;
772   gboolean need_text = FALSE;
773   GstPad *textsrcpad = NULL, *textsinkpad = NULL, *pad;
774   GstElement *sink;
775
776   /* get rid of existing sinks */
777   if (play_bin->sinks) {
778     remove_sinks (play_bin);
779   }
780   GST_DEBUG ("setupsinks");
781
782   /* find out what to do */
783   if (group->type[GST_STREAM_TYPE_VIDEO - 1].npads > 0 &&
784       group->type[GST_STREAM_TYPE_TEXT - 1].npads > 0) {
785     need_text = TRUE;
786   } else if (group->type[GST_STREAM_TYPE_VIDEO - 1].npads == 0 &&
787       group->type[GST_STREAM_TYPE_AUDIO - 1].npads > 0 &&
788       play_bin->visualisation != NULL) {
789     need_vis = TRUE;
790   }
791
792   /* now actually connect everything */
793   g_object_get (G_OBJECT (play_base_bin), "stream-info", &streaminfo, NULL);
794   for (s = streaminfo; s; s = g_list_next (s)) {
795     GObject *obj = G_OBJECT (s->data);
796     gint type;
797     GstObject *object;
798
799     g_object_get (obj, "type", &type, NULL);
800     g_object_get (obj, "object", &object, NULL);
801   }
802
803   /* link audio */
804   if (group->type[GST_STREAM_TYPE_AUDIO - 1].npads > 0) {
805     if (need_vis) {
806       sink = gen_vis_element (play_bin);
807     } else {
808       sink = gen_audio_element (play_bin);
809     }
810     pad = gst_element_get_pad (group->type[GST_STREAM_TYPE_AUDIO - 1].preroll,
811         "src");
812     add_sink (play_bin, sink, pad);
813     gst_object_unref (pad);
814   }
815
816   /* link video */
817   if (group->type[GST_STREAM_TYPE_VIDEO - 1].npads > 0) {
818     if (need_text) {
819       sink = gen_text_element (play_bin);
820
821       textsinkpad = gst_element_get_pad (sink, "text_sink");
822       textsrcpad =
823           gst_element_get_pad (group->type[GST_STREAM_TYPE_TEXT - 1].preroll,
824           "src");
825       gst_pad_link (textsrcpad, textsinkpad);
826       gst_object_unref (textsinkpad);
827       gst_object_unref (textsrcpad);
828     } else {
829       sink = gen_video_element (play_bin);
830     }
831     pad = gst_element_get_pad (group->type[GST_STREAM_TYPE_VIDEO - 1].preroll,
832         "src");
833     add_sink (play_bin, sink, pad);
834     gst_object_unref (pad);
835   }
836
837   /* remove the sinks now, pipeline get_state will now wait for the
838    * sinks to preroll */
839   if (play_bin->fakesink) {
840     gst_bin_remove (GST_BIN (play_bin), play_bin->fakesink);
841     play_bin->fakesink = NULL;
842   }
843 }
844
845 static GstElementStateReturn
846 gst_play_bin_change_state (GstElement * element)
847 {
848   GstElementStateReturn ret;
849   GstPlayBin *play_bin;
850   int transition;
851
852   play_bin = GST_PLAY_BIN (element);
853
854   transition = GST_STATE_TRANSITION (element);
855
856   switch (transition) {
857     case GST_STATE_READY_TO_PAUSED:
858       /* this really is the easiest way to make the state change return
859        * ASYNC until we added the sinks */
860       if (!play_bin->fakesink) {
861         play_bin->fakesink = gst_element_factory_make ("fakesink", "test");
862         gst_bin_add (GST_BIN (play_bin), play_bin->fakesink);
863       }
864       break;
865     default:
866       break;
867   }
868
869   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element);
870   if (ret == GST_STATE_FAILURE)
871     return ret;
872
873   switch (transition) {
874     case GST_STATE_PLAYING_TO_PAUSED:
875       /* Set audio sink state to NULL to release the sound device,
876        * but only if we own it (else we might be in chain-transition). */
877       //if (play_bin->audio_sink != NULL &&
878       //    GST_STATE (play_bin->audio_sink) == GST_STATE_PAUSED) {
879       //  gst_element_set_state (play_bin->audio_sink, GST_STATE_NULL);
880       //}
881       break;
882     case GST_STATE_PAUSED_TO_READY:
883       /* Check for NULL because the state transition may be done by
884        * gst_bin_dispose which is called by gst_play_bin_dispose, and in that
885        * case, we don't want to run remove_sinks.
886        * FIXME: should the NULL test be done in remove_sinks? Should we just
887        * set the state to NULL in gst_play_bin_dispose?
888        */
889       if (play_bin->cache != NULL) {
890         remove_sinks (play_bin);
891       }
892       if (play_bin->fakesink) {
893         gst_bin_remove (GST_BIN (play_bin), play_bin->fakesink);
894         play_bin->fakesink = NULL;
895       }
896       break;
897     default:
898       break;
899   }
900
901   return ret;
902 }
903
904 static gboolean
905 plugin_init (GstPlugin * plugin)
906 {
907   GST_DEBUG_CATEGORY_INIT (gst_play_bin_debug, "playbin", 0, "play bin");
908
909   return gst_element_register (plugin, "playbin", GST_RANK_NONE,
910       GST_TYPE_PLAY_BIN);
911 }
912
913 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
914     GST_VERSION_MINOR,
915     "playbin",
916     "player bin", plugin_init, VERSION, GST_LICENSE, GST_PACKAGE, GST_ORIGIN)