Port from GstData to GstMiniObject.
[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 *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 (GST_OBJECT (play_bin->audio_sink));
227     play_bin->audio_sink = NULL;
228   }
229   if (play_bin->video_sink != NULL) {
230     gst_object_unref (GST_OBJECT (play_bin->video_sink));
231     play_bin->video_sink = NULL;
232   }
233   if (play_bin->visualisation != NULL) {
234     gst_object_unref (GST_OBJECT (play_bin->visualisation));
235     play_bin->visualisation = NULL;
236   }
237   g_free (play_bin->font_desc);
238   play_bin->font_desc = NULL;
239
240   if (G_OBJECT_CLASS (parent_class)->dispose) {
241     G_OBJECT_CLASS (parent_class)->dispose (object);
242   }
243 }
244
245
246 static void
247 gst_play_bin_set_property (GObject * object, guint prop_id,
248     const GValue * value, GParamSpec * pspec)
249 {
250   GstPlayBin *play_bin;
251
252   g_return_if_fail (GST_IS_PLAY_BIN (object));
253
254   play_bin = GST_PLAY_BIN (object);
255
256   switch (prop_id) {
257     case ARG_VIDEO_SINK:
258       if (play_bin->video_sink != NULL) {
259         gst_object_unref (GST_OBJECT (play_bin->video_sink));
260       }
261       play_bin->video_sink = g_value_get_object (value);
262       if (play_bin->video_sink != NULL) {
263         gst_object_ref (GST_OBJECT (play_bin->video_sink));
264         gst_object_sink (GST_OBJECT (play_bin->video_sink));
265       }
266       /* when changing the videosink, we just remove the
267        * video pipeline from the cache so that it will be 
268        * regenerated with the new sink element */
269       g_hash_table_remove (play_bin->cache, "vbin");
270       break;
271     case ARG_AUDIO_SINK:
272       if (play_bin->audio_sink != NULL) {
273         gst_object_unref (GST_OBJECT (play_bin->audio_sink));
274       }
275       play_bin->audio_sink = g_value_get_object (value);
276       if (play_bin->audio_sink != NULL) {
277         gst_object_ref (GST_OBJECT (play_bin->audio_sink));
278         gst_object_sink (GST_OBJECT (play_bin->audio_sink));
279       }
280       g_hash_table_remove (play_bin->cache, "abin");
281       break;
282     case ARG_VIS_PLUGIN:
283       if (play_bin->visualisation != NULL) {
284         gst_object_unref (GST_OBJECT (play_bin->visualisation));
285       }
286       play_bin->visualisation = g_value_get_object (value);
287       if (play_bin->visualisation != NULL) {
288         gst_object_ref (GST_OBJECT (play_bin->visualisation));
289         gst_object_sink (GST_OBJECT (play_bin->visualisation));
290       }
291       break;
292     case ARG_VOLUME:
293       play_bin->volume = g_value_get_double (value);
294       if (play_bin->volume_element) {
295         g_object_set (G_OBJECT (play_bin->volume_element), "volume",
296             play_bin->volume, NULL);
297       }
298       break;
299     case ARG_FONT_DESC:
300       g_free (play_bin->font_desc);
301       play_bin->font_desc = g_strdup (g_value_get_string (value));
302       if (play_bin->textoverlay_element) {
303         g_object_set (G_OBJECT (play_bin->textoverlay_element),
304             "font-desc", g_value_get_string (value), NULL);
305       }
306       break;
307     default:
308       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
309       break;
310   }
311 }
312
313 static void
314 gst_play_bin_get_property (GObject * object, guint prop_id, GValue * value,
315     GParamSpec * pspec)
316 {
317   GstPlayBin *play_bin;
318
319   g_return_if_fail (GST_IS_PLAY_BIN (object));
320
321   play_bin = GST_PLAY_BIN (object);
322
323   switch (prop_id) {
324     case ARG_VIDEO_SINK:
325       g_value_set_object (value, play_bin->video_sink);
326       break;
327     case ARG_AUDIO_SINK:
328       g_value_set_object (value, play_bin->audio_sink);
329       break;
330     case ARG_VIS_PLUGIN:
331       g_value_set_object (value, play_bin->visualisation);
332       break;
333     case ARG_VOLUME:
334       g_value_set_double (value, play_bin->volume);
335       break;
336     case ARG_FRAME:
337       g_value_set_boxed (value, play_bin->frame);
338       break;
339     default:
340       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
341       break;
342   }
343 }
344
345 /* signal fired when the identity has received a new buffer. This is used for
346  * making screenshots.
347  */
348 static void
349 handoff (GstElement * identity, GstBuffer * frame, gpointer data)
350 {
351   GstPlayBin *play_bin = GST_PLAY_BIN (data);
352
353   if (play_bin->frame) {
354     gst_buffer_unref (play_bin->frame);
355   }
356   play_bin->frame = gst_buffer_ref (frame);
357 }
358
359 /* make the element (bin) that contains the elements needed to perform
360  * video display. We connect a handoff signal to identity so that we
361  * can grab snapshots. Identity's sinkpad is ghosted to vbin.
362  *
363  *  +-------------------------------------------------------------+
364  *  | vbin                                                        |
365  *  |      +--------+   +----------+   +----------+   +---------+ |
366  *  |      |identity|   |colorspace|   |videoscale|   |videosink| |
367  *  |   +-sink     src-sink       src-sink       src-sink       | |
368  *  |   |  +---+----+   +----------+   +----------+   +---------+ |
369  * sink-+      |                                                  |
370  *  +----------|--------------------------------------------------+
371  *           handoff
372  */
373 static GstElement *
374 gen_video_element (GstPlayBin * play_bin)
375 {
376   GstElement *element;
377   GstElement *conv;
378
379   //GstElement *scale;
380   GstElement *sink;
381   GstElement *identity;
382   GstPad *pad;
383
384   /* first see if we have it in the cache */
385   element = g_hash_table_lookup (play_bin->cache, "vbin");
386   if (element != NULL) {
387     return element;
388   }
389
390   element = gst_bin_new ("vbin");
391   identity = gst_element_factory_make ("identity", "id");
392   g_signal_connect (identity, "handoff", G_CALLBACK (handoff), play_bin);
393   conv = gst_element_factory_make ("ffmpegcolorspace", "vconv");
394   //scale = gst_element_factory_make ("videoscale", "vscale");
395   if (play_bin->video_sink) {
396     sink = play_bin->video_sink;
397   } else {
398     sink = gst_element_factory_make ("xvimagesink", "sink");
399   }
400   gst_object_ref (GST_OBJECT (sink));
401   g_hash_table_insert (play_bin->cache, "video_sink", sink);
402
403   gst_bin_add (GST_BIN (element), identity);
404   gst_bin_add (GST_BIN (element), conv);
405   //gst_bin_add (GST_BIN (element), scale);
406   gst_bin_add (GST_BIN (element), sink);
407   gst_element_link_pads (identity, "src", conv, "sink");
408   gst_element_link_pads (conv, "src",   /*scale, "sink");
409                                            gst_element_link_pads (scale, "src", */ sink, "sink");
410
411   pad = gst_element_get_pad (identity, "sink");
412   gst_element_add_ghost_pad (element, pad, "sink");
413   g_object_unref (G_OBJECT (pad));
414
415   gst_element_set_state (element, GST_STATE_READY);
416
417   /* since we're gonna add it to a bin but don't want to lose it,
418    * we keep a reference. */
419   gst_object_ref (GST_OBJECT (element));
420   g_hash_table_insert (play_bin->cache, "vbin", element);
421
422   return element;
423 }
424
425 /* make an element for playback of video with subtitles embedded.
426  *
427  *  +--------------------------------------------------+
428  *  | tbin                  +-------------+            |
429  *  |          +-----+      | textoverlay |   +------+ |
430  *  |          | csp | +--video_sink      |   | vbin | |
431  * video_sink-sink  src+ +-text_sink     src-sink    | |
432  *  |          +-----+   |  +-------------+   +------+ |
433  * text_sink-------------+                             |
434  *  +--------------------------------------------------+
435  */
436
437 static GstElement *
438 gen_text_element (GstPlayBin * play_bin)
439 {
440   GstElement *element, *csp, *overlay, *vbin;
441   GstPad *pad;
442
443   overlay = gst_element_factory_make ("textoverlay", "overlay");
444   g_object_set (G_OBJECT (overlay),
445       "halign", "center", "valign", "bottom", NULL);
446   play_bin->textoverlay_element = overlay;
447   if (play_bin->font_desc) {
448     g_object_set (G_OBJECT (play_bin->textoverlay_element),
449         "font-desc", play_bin->font_desc, NULL);
450   }
451   vbin = gen_video_element (play_bin);
452   if (!overlay) {
453     g_warning ("No overlay (pango) element, subtitles disabled");
454     return vbin;
455   }
456   csp = gst_element_factory_make ("ffmpegcolorspace", "subtitlecsp");
457   element = gst_bin_new ("textbin");
458   gst_element_link_many (csp, overlay, vbin, NULL);
459   gst_bin_add_many (GST_BIN (element), csp, overlay, vbin, NULL);
460
461   pad = gst_element_get_pad (overlay, "text_sink");
462   gst_element_add_ghost_pad (element, pad, "text_sink");
463   g_object_unref (G_OBJECT (pad));
464
465   pad = gst_element_get_pad (csp, "sink");
466   gst_element_add_ghost_pad (element, pad, "sink");
467   g_object_unref (G_OBJECT (pad));
468
469   return element;
470 }
471
472 /* make the element (bin) that contains the elements needed to perform
473  * audio playback. 
474  *
475  *  +-------------------------------------------------------------+
476  *  | abin                                                        |
477  *  |      +---------+   +----------+   +---------+   +---------+ |
478  *  |      |audioconv|   |audioscale|   | volume  |   |audiosink| |
479  *  |   +-sink      src-sink       src-sink      src-sink       | |
480  *  |   |  +---------+   +----------+   +---------+   +---------+ |
481  * sink-+                                                         |
482  *  +-------------------------------------------------------------+
483  *                  
484  */
485 static GstElement *
486 gen_audio_element (GstPlayBin * play_bin)
487 {
488   GstElement *element;
489   GstElement *conv;
490   GstElement *sink;
491   GstElement *volume;
492   GstElement *scale;
493   GstPad *pad;
494
495   element = g_hash_table_lookup (play_bin->cache, "abin");
496   if (element != NULL) {
497     return element;
498   }
499   element = gst_bin_new ("abin");
500   conv = gst_element_factory_make ("audioconvert", "aconv");
501   scale = gst_element_factory_make ("audioscale", "ascale");
502
503   volume = gst_element_factory_make ("volume", "volume");
504   g_object_set (G_OBJECT (volume), "volume", play_bin->volume, NULL);
505   play_bin->volume_element = volume;
506
507   if (play_bin->audio_sink) {
508     sink = play_bin->audio_sink;
509   } else {
510     sink = gst_element_factory_make ("alsasink", "sink");
511     play_bin->audio_sink = GST_ELEMENT (gst_object_ref (GST_OBJECT (sink)));
512   }
513
514   gst_object_ref (GST_OBJECT (sink));
515   g_hash_table_insert (play_bin->cache, "audio_sink", sink);
516
517   gst_bin_add (GST_BIN (element), conv);
518   //gst_bin_add (GST_BIN (element), scale);
519   //gst_bin_add (GST_BIN (element), volume);
520   gst_bin_add (GST_BIN (element), sink);
521
522   gst_element_link_pads (conv, "src",   /*scale, "sink");
523                                            gst_element_link_pads (scale, "src", volume, "sink");
524                                            gst_element_link_pads (volume, "src", */ sink, "sink");
525
526   pad = gst_element_get_pad (conv, "sink");
527   gst_element_add_ghost_pad (element, pad, "sink");
528   g_object_unref (G_OBJECT (pad));
529
530   gst_element_set_state (element, GST_STATE_READY);
531
532   /* since we're gonna add it to a bin but don't want to lose it,
533    * we keep a reference. */
534   gst_object_ref (GST_OBJECT (element));
535   g_hash_table_insert (play_bin->cache, "abin", element);
536
537   return element;
538 }
539
540 /* make the element (bin) that contains the elements needed to perform
541  * visualisation ouput.  The idea is to split the audio using tee, then 
542  * sending the output to the regular audio bin and the other output to
543  * the vis plugin that transforms it into a video that is rendered with the
544  * normal video bin. The video bin is run in a thread to make sure it does
545  * not block the audio playback pipeline.
546  *
547  *  +--------------------------------------------------------------------------+
548  *  | visbin                                                                   |
549  *  |      +------+   +----------------+                                       |
550  *  |      | tee  |   |   abin ...     |                                       |
551  *  |   +-sink   src-sink              |                                       |
552  *  |   |  |      |   +----------------+                 +-------------------+ |
553  *  |   |  |      |                                      | vthread           | |
554  *  |   |  |      |   +---------+   +------+   +------+  | +--------------+  | |
555  *  |   |  |      |   |audioconv|   | vis  |   |vqueue|  | | vbin ...     |  | |
556  *  |   |  |     src-sink      src-sink   src-sink   src-sink             |  | |
557  *  |   |  |      |   +---------+   +------+   +------+  | +--------------+  | |
558  *  |   |  |      |                                      +-------------------+ |
559  *  |   |  +------+                                                            |
560  * sink-+                                                                      |
561    +--------------------------------------------------------------------------+
562  */
563 static GstElement *
564 gen_vis_element (GstPlayBin * play_bin)
565 {
566   GstElement *element;
567   GstElement *tee;
568   GstElement *asink;
569   GstElement *vsink;
570   GstElement *conv;
571   GstElement *vis;
572   GstElement *vqueue;
573   GstElement *vthread;
574   GstPad *pad;
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   vthread = gst_element_factory_make ("thread", "vthread");
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 (vthread), vsink);
588   gst_bin_add (GST_BIN (element), vthread);
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 (GST_OBJECT (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 (asink, "sink");
608   gst_pad_link (gst_element_get_request_pad (tee, "src%d"), pad);
609   g_object_unref (G_OBJECT (pad));
610
611   pad = gst_element_get_pad (conv, "sink");
612   gst_pad_link (gst_element_get_request_pad (tee, "src%d"), pad);
613   g_object_unref (G_OBJECT (pad));
614
615   pad = gst_element_get_pad (tee, "sink");
616   gst_element_add_ghost_pad (element, pad, "sink");
617   g_object_unref (G_OBJECT (pad));
618
619   return element;
620 }
621
622 /* get rid of all installed sinks */
623 static void
624 remove_sinks (GstPlayBin * play_bin)
625 {
626   GList *sinks;
627   GstObject *parent;
628   GstElement *element;
629
630   GST_DEBUG ("removesinks");
631   element = g_hash_table_lookup (play_bin->cache, "abin");
632   if (element != NULL) {
633     parent = gst_element_get_parent (element);
634     if (parent != NULL) {
635       /* we remove the element from the parent so that
636        * there is no unwanted state change when the parent
637        * is disposed */
638       gst_bin_remove (GST_BIN (parent), element);
639       g_object_unref (G_OBJECT (parent));
640     }
641   }
642   element = g_hash_table_lookup (play_bin->cache, "vbin");
643   if (element != NULL) {
644     parent = gst_element_get_parent (element);
645     if (parent != NULL) {
646       gst_bin_remove (GST_BIN (parent), element);
647       g_object_unref (G_OBJECT (parent));
648     }
649   }
650
651   for (sinks = play_bin->sinks; sinks; sinks = g_list_next (sinks)) {
652     GstElement *element = GST_ELEMENT (sinks->data);
653     GstPad *pad = gst_element_get_pad (element, "sink");
654
655     GST_LOG ("removing sink %p", element);
656     if (GST_PAD_PEER (pad))
657       gst_pad_unlink (GST_PAD_PEER (pad), pad);
658     g_object_unref (G_OBJECT (pad));
659     gst_bin_remove (GST_BIN (play_bin), element);
660   }
661   g_list_free (play_bin->sinks);
662   play_bin->sinks = NULL;
663
664   if (play_bin->frame) {
665     gst_buffer_unref (play_bin->frame);
666     play_bin->frame = NULL;
667   }
668
669   play_bin->textoverlay_element = NULL;
670 }
671
672 /* loop over the streams and set up the pipeline to play this
673  * media file. First we count the number of audio and video streams.
674  * If there is no video stream but there exists an audio stream,
675  * we install a visualisation pipeline.
676  * 
677  * Also make sure to only connect the first audio and video pad. FIXME
678  * this should eventually be handled with a tuner interface so that
679  * one can switch the streams.
680  */
681 static gboolean
682 add_sink (GstPlayBin * play_bin, GstElement * sink, GstPad * srcpad)
683 {
684   GstPad *sinkpad;
685   GstPadLinkReturn res;
686   GstElement *parent;
687
688   /* we found a sink for this stream, now try to install it */
689   sinkpad = gst_element_get_pad (sink, "sink");
690   res = gst_pad_link (srcpad, sinkpad);
691   g_object_unref (G_OBJECT (sinkpad));
692
693   parent = gst_pad_get_parent (srcpad);
694   GST_DEBUG ("Adding sink with state %d (parent: %d, peer: %d)\n",
695       GST_STATE (sink), GST_STATE (play_bin), GST_STATE (parent));
696   g_object_unref (G_OBJECT (parent));
697
698   /* try to link the pad of the sink to the stream */
699   if (res < 0) {
700     gchar *capsstr;
701
702     /* could not link this stream */
703     capsstr = gst_caps_to_string (gst_pad_get_caps (srcpad));
704     g_warning ("could not link %s", capsstr);
705     g_free (capsstr);
706   } else {
707     /* we got the sink succesfully linked, now keep the sink
708      * in out internal list */
709     play_bin->sinks = g_list_prepend (play_bin->sinks, sink);
710     gst_element_set_state (sink,
711         (GST_STATE (play_bin) == GST_STATE_PLAYING) ?
712         GST_STATE_PLAYING : GST_STATE_PAUSED);
713     gst_bin_add (GST_BIN (play_bin), sink);
714   }
715
716   return res;
717 }
718
719 static void
720 setup_sinks (GstPlayBaseBin * play_base_bin, GstPlayBaseGroup * group)
721 {
722   GstPlayBin *play_bin = GST_PLAY_BIN (play_base_bin);
723   GList *streaminfo = NULL, *s;
724   gboolean need_vis = FALSE;
725   gboolean need_text = FALSE;
726   GstPad *textsrcpad = NULL, *textsinkpad = NULL, *pad;
727   GstElement *sink;
728
729   /* get rid of existing sinks */
730   if (play_bin->sinks) {
731     remove_sinks (play_bin);
732   }
733   GST_DEBUG ("setupsinks");
734
735   /* find out what to do */
736   if (group->type[GST_STREAM_TYPE_VIDEO - 1].npads > 0 &&
737       group->type[GST_STREAM_TYPE_TEXT - 1].npads > 0) {
738     need_text = TRUE;
739   } else if (group->type[GST_STREAM_TYPE_VIDEO - 1].npads == 0 &&
740       group->type[GST_STREAM_TYPE_AUDIO - 1].npads > 0 &&
741       play_bin->visualisation != NULL) {
742     need_vis = TRUE;
743   }
744
745   /* now actually connect everything */
746   g_object_get (G_OBJECT (play_base_bin), "stream-info", &streaminfo, NULL);
747   for (s = streaminfo; s; s = g_list_next (s)) {
748     GObject *obj = G_OBJECT (s->data);
749     gint type;
750     GstObject *object;
751
752     g_object_get (obj, "type", &type, NULL);
753     g_object_get (obj, "object", &object, NULL);
754   }
755
756   /* link audio */
757   if (group->type[GST_STREAM_TYPE_AUDIO - 1].npads > 0) {
758     if (need_vis) {
759       sink = gen_vis_element (play_bin);
760     } else {
761       sink = gen_audio_element (play_bin);
762     }
763     pad = gst_element_get_pad (group->type[GST_STREAM_TYPE_AUDIO - 1].preroll,
764         "src");
765     add_sink (play_bin, sink, pad);
766     g_object_unref (G_OBJECT (pad));
767   }
768
769   /* link video */
770   if (group->type[GST_STREAM_TYPE_VIDEO - 1].npads > 0) {
771     if (need_text) {
772       sink = gen_text_element (play_bin);
773
774       textsinkpad = gst_element_get_pad (sink, "text_sink");
775       textsrcpad =
776           gst_element_get_pad (group->type[GST_STREAM_TYPE_TEXT - 1].preroll,
777           "src");
778       gst_pad_link (textsrcpad, textsinkpad);
779       g_object_unref (G_OBJECT (textsinkpad));
780       g_object_unref (G_OBJECT (textsrcpad));
781     } else {
782       sink = gen_video_element (play_bin);
783     }
784     pad = gst_element_get_pad (group->type[GST_STREAM_TYPE_VIDEO - 1].preroll,
785         "src");
786     add_sink (play_bin, sink, pad);
787     g_object_unref (G_OBJECT (pad));
788   }
789
790   if (play_bin->fakesink) {
791     gst_bin_remove (GST_BIN (play_bin), play_bin->fakesink);
792     play_bin->fakesink = NULL;
793   }
794 }
795
796 static GstElementStateReturn
797 gst_play_bin_change_state (GstElement * element)
798 {
799   GstElementStateReturn ret;
800   GstPlayBin *play_bin;
801   int transition;
802
803   play_bin = GST_PLAY_BIN (element);
804
805   transition = GST_STATE_TRANSITION (element);
806
807   switch (transition) {
808     case GST_STATE_READY_TO_PAUSED:
809       if (!play_bin->fakesink) {
810         play_bin->fakesink = gst_element_factory_make ("fakesink", "test");
811         gst_bin_add (GST_BIN (play_bin), play_bin->fakesink);
812       }
813       break;
814     default:
815       break;
816   }
817
818   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element);
819   if (ret == GST_STATE_FAILURE)
820     return ret;
821
822   switch (transition) {
823     case GST_STATE_PLAYING_TO_PAUSED:
824       /* Set audio sink state to NULL to release the sound device,
825        * but only if we own it (else we might be in chain-transition). */
826       //if (play_bin->audio_sink != NULL &&
827       //    GST_STATE (play_bin->audio_sink) == GST_STATE_PAUSED) {
828       //  gst_element_set_state (play_bin->audio_sink, GST_STATE_NULL);
829       //}
830       break;
831     case GST_STATE_PAUSED_TO_READY:
832       /* Check for NULL because the state transition may be done by
833        * gst_bin_dispose which is called by gst_play_bin_dispose, and in that
834        * case, we don't want to run remove_sinks.
835        * FIXME: should the NULL test be done in remove_sinks? Should we just
836        * set the state to NULL in gst_play_bin_dispose?
837        */
838       if (play_bin->cache != NULL) {
839         remove_sinks (play_bin);
840       }
841       if (play_bin->fakesink) {
842         gst_bin_remove (GST_BIN (play_bin), play_bin->fakesink);
843         play_bin->fakesink = NULL;
844       }
845       break;
846     default:
847       break;
848   }
849
850   return ret;
851 }
852
853 static gboolean
854 plugin_init (GstPlugin * plugin)
855 {
856   GST_DEBUG_CATEGORY_INIT (gst_play_bin_debug, "playbin", 0, "play bin");
857
858   return gst_element_register (plugin, "playbin", GST_RANK_NONE,
859       GST_TYPE_PLAY_BIN);
860 }
861
862 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
863     GST_VERSION_MINOR,
864     "playbin",
865     "player bin", plugin_init, VERSION, GST_LICENSE, GST_PACKAGE, GST_ORIGIN)