gst/playback/gstplaybasebin.c: Fix missing unlock.
[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", "videosink");
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", "audiosink");
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, *aqueue;
573   GstPad *pad, *rpad;
574
575   element = gst_bin_new ("visbin");
576   tee = gst_element_factory_make ("tee", "tee");
577
578   vqueue = gst_element_factory_make ("queue", "vqueue");
579   aqueue = gst_element_factory_make ("queue", "aqueue");
580
581   asink = gen_audio_element (play_bin);
582   vsink = gen_video_element (play_bin);
583
584   gst_bin_add (GST_BIN (element), asink);
585   gst_bin_add (GST_BIN (element), vqueue);
586   gst_bin_add (GST_BIN (element), aqueue);
587   gst_bin_add (GST_BIN (element), vsink);
588   gst_bin_add (GST_BIN (element), tee);
589
590   conv = gst_element_factory_make ("audioconvert", "aconv");
591   if (play_bin->visualisation) {
592     gst_object_ref (GST_OBJECT (play_bin->visualisation));
593     vis = play_bin->visualisation;
594   } else {
595     vis = gst_element_factory_make ("goom", "vis");
596   }
597
598   gst_bin_add (GST_BIN (element), conv);
599   gst_bin_add (GST_BIN (element), vis);
600
601   gst_element_link_pads (conv, "src", vis, "sink");
602   gst_element_link_pads (vis, "src", vqueue, "sink");
603
604   gst_element_link_pads (vqueue, "src", vsink, "sink");
605
606   pad = gst_element_get_pad (aqueue, "sink");
607   rpad = gst_element_get_request_pad (tee, "src%d");
608   gst_pad_link (rpad, pad);
609   g_object_unref (G_OBJECT (rpad));
610   g_object_unref (G_OBJECT (pad));
611   gst_element_link_pads (aqueue, "src", asink, "sink");
612
613   pad = gst_element_get_pad (conv, "sink");
614   rpad = gst_element_get_request_pad (tee, "src%d");
615   gst_pad_link (rpad, pad);
616   g_object_unref (G_OBJECT (rpad));
617   g_object_unref (G_OBJECT (pad));
618
619   pad = gst_element_get_pad (tee, "sink");
620   gst_element_add_ghost_pad (element, pad, "sink");
621   g_object_unref (G_OBJECT (pad));
622
623   return element;
624 }
625
626 /* get rid of all installed sinks */
627 static void
628 remove_sinks (GstPlayBin * play_bin)
629 {
630   GList *sinks;
631   GstObject *parent;
632   GstElement *element;
633
634   GST_DEBUG ("removesinks");
635   element = g_hash_table_lookup (play_bin->cache, "abin");
636   if (element != NULL) {
637     parent = gst_element_get_parent (element);
638     if (parent != NULL) {
639       /* we remove the element from the parent so that
640        * there is no unwanted state change when the parent
641        * is disposed */
642       gst_bin_remove (GST_BIN (parent), element);
643       g_object_unref (G_OBJECT (parent));
644     }
645   }
646   element = g_hash_table_lookup (play_bin->cache, "vbin");
647   if (element != NULL) {
648     parent = gst_element_get_parent (element);
649     if (parent != NULL) {
650       gst_bin_remove (GST_BIN (parent), element);
651       g_object_unref (G_OBJECT (parent));
652     }
653   }
654
655   for (sinks = play_bin->sinks; sinks; sinks = g_list_next (sinks)) {
656     GstElement *element = GST_ELEMENT (sinks->data);
657     GstPad *pad = gst_element_get_pad (element, "sink");
658
659     GST_LOG ("removing sink %p", element);
660     if (GST_PAD_PEER (pad))
661       gst_pad_unlink (GST_PAD_PEER (pad), pad);
662     g_object_unref (G_OBJECT (pad));
663     gst_bin_remove (GST_BIN (play_bin), element);
664   }
665   g_list_free (play_bin->sinks);
666   play_bin->sinks = NULL;
667
668   if (play_bin->frame) {
669     gst_buffer_unref (play_bin->frame);
670     play_bin->frame = NULL;
671   }
672
673   play_bin->textoverlay_element = NULL;
674 }
675
676 /* loop over the streams and set up the pipeline to play this
677  * media file. First we count the number of audio and video streams.
678  * If there is no video stream but there exists an audio stream,
679  * we install a visualisation pipeline.
680  * 
681  * Also make sure to only connect the first audio and video pad. FIXME
682  * this should eventually be handled with a tuner interface so that
683  * one can switch the streams.
684  */
685 static gboolean
686 add_sink (GstPlayBin * play_bin, GstElement * sink, GstPad * srcpad)
687 {
688   GstPad *sinkpad;
689   GstPadLinkReturn res;
690   GstElement *parent;
691
692   gst_bin_add (GST_BIN (play_bin), sink);
693
694   /* we found a sink for this stream, now try to install it */
695   sinkpad = gst_element_get_pad (sink, "sink");
696   res = gst_pad_link (srcpad, sinkpad);
697   g_object_unref (G_OBJECT (sinkpad));
698
699   parent = gst_pad_get_parent (srcpad);
700   GST_DEBUG ("Adding sink with state %d (parent: %d, peer: %d)\n",
701       GST_STATE (sink), GST_STATE (play_bin), GST_STATE (parent));
702   g_object_unref (G_OBJECT (parent));
703
704   /* try to link the pad of the sink to the stream */
705   if (res < 0) {
706     gchar *capsstr;
707
708     /* could not link this stream */
709     capsstr = gst_caps_to_string (gst_pad_get_caps (srcpad));
710     g_warning ("could not link %s", capsstr);
711     g_free (capsstr);
712
713     gst_bin_remove (GST_BIN (play_bin), sink);
714   } else {
715     /* we got the sink succesfully linked, now keep the sink
716      * in out internal list */
717     play_bin->sinks = g_list_prepend (play_bin->sinks, sink);
718     gst_element_set_state (sink,
719         (GST_STATE (play_bin) == GST_STATE_PLAYING) ?
720         GST_STATE_PLAYING : GST_STATE_PAUSED);
721   }
722
723   return res;
724 }
725
726 static void
727 setup_sinks (GstPlayBaseBin * play_base_bin, GstPlayBaseGroup * group)
728 {
729   GstPlayBin *play_bin = GST_PLAY_BIN (play_base_bin);
730   GList *streaminfo = NULL, *s;
731   gboolean need_vis = FALSE;
732   gboolean need_text = FALSE;
733   GstPad *textsrcpad = NULL, *textsinkpad = NULL, *pad;
734   GstElement *sink;
735
736   /* get rid of existing sinks */
737   if (play_bin->sinks) {
738     remove_sinks (play_bin);
739   }
740   GST_DEBUG ("setupsinks");
741
742   /* find out what to do */
743   if (group->type[GST_STREAM_TYPE_VIDEO - 1].npads > 0 &&
744       group->type[GST_STREAM_TYPE_TEXT - 1].npads > 0) {
745     need_text = TRUE;
746   } else if (group->type[GST_STREAM_TYPE_VIDEO - 1].npads == 0 &&
747       group->type[GST_STREAM_TYPE_AUDIO - 1].npads > 0 &&
748       play_bin->visualisation != NULL) {
749     need_vis = TRUE;
750   }
751
752   /* now actually connect everything */
753   g_object_get (G_OBJECT (play_base_bin), "stream-info", &streaminfo, NULL);
754   for (s = streaminfo; s; s = g_list_next (s)) {
755     GObject *obj = G_OBJECT (s->data);
756     gint type;
757     GstObject *object;
758
759     g_object_get (obj, "type", &type, NULL);
760     g_object_get (obj, "object", &object, NULL);
761   }
762
763   /* link audio */
764   if (group->type[GST_STREAM_TYPE_AUDIO - 1].npads > 0) {
765     if (need_vis) {
766       sink = gen_vis_element (play_bin);
767     } else {
768       sink = gen_audio_element (play_bin);
769     }
770     pad = gst_element_get_pad (group->type[GST_STREAM_TYPE_AUDIO - 1].preroll,
771         "src");
772     add_sink (play_bin, sink, pad);
773     g_object_unref (G_OBJECT (pad));
774   }
775
776   /* link video */
777   if (group->type[GST_STREAM_TYPE_VIDEO - 1].npads > 0) {
778     if (need_text) {
779       sink = gen_text_element (play_bin);
780
781       textsinkpad = gst_element_get_pad (sink, "text_sink");
782       textsrcpad =
783           gst_element_get_pad (group->type[GST_STREAM_TYPE_TEXT - 1].preroll,
784           "src");
785       gst_pad_link (textsrcpad, textsinkpad);
786       g_object_unref (G_OBJECT (textsinkpad));
787       g_object_unref (G_OBJECT (textsrcpad));
788     } else {
789       sink = gen_video_element (play_bin);
790     }
791     pad = gst_element_get_pad (group->type[GST_STREAM_TYPE_VIDEO - 1].preroll,
792         "src");
793     add_sink (play_bin, sink, pad);
794     g_object_unref (G_OBJECT (pad));
795   }
796
797   if (play_bin->fakesink) {
798     gst_bin_remove (GST_BIN (play_bin), play_bin->fakesink);
799     play_bin->fakesink = NULL;
800   }
801 }
802
803 static GstElementStateReturn
804 gst_play_bin_change_state (GstElement * element)
805 {
806   GstElementStateReturn ret;
807   GstPlayBin *play_bin;
808   int transition;
809
810   play_bin = GST_PLAY_BIN (element);
811
812   transition = GST_STATE_TRANSITION (element);
813
814   switch (transition) {
815     case GST_STATE_READY_TO_PAUSED:
816       if (!play_bin->fakesink) {
817         play_bin->fakesink = gst_element_factory_make ("fakesink", "test");
818         gst_bin_add (GST_BIN (play_bin), play_bin->fakesink);
819       }
820       break;
821     default:
822       break;
823   }
824
825   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element);
826   if (ret == GST_STATE_FAILURE)
827     return ret;
828
829   switch (transition) {
830     case GST_STATE_PLAYING_TO_PAUSED:
831       /* Set audio sink state to NULL to release the sound device,
832        * but only if we own it (else we might be in chain-transition). */
833       //if (play_bin->audio_sink != NULL &&
834       //    GST_STATE (play_bin->audio_sink) == GST_STATE_PAUSED) {
835       //  gst_element_set_state (play_bin->audio_sink, GST_STATE_NULL);
836       //}
837       break;
838     case GST_STATE_PAUSED_TO_READY:
839       /* Check for NULL because the state transition may be done by
840        * gst_bin_dispose which is called by gst_play_bin_dispose, and in that
841        * case, we don't want to run remove_sinks.
842        * FIXME: should the NULL test be done in remove_sinks? Should we just
843        * set the state to NULL in gst_play_bin_dispose?
844        */
845       if (play_bin->cache != NULL) {
846         remove_sinks (play_bin);
847       }
848       if (play_bin->fakesink) {
849         gst_bin_remove (GST_BIN (play_bin), play_bin->fakesink);
850         play_bin->fakesink = NULL;
851       }
852       break;
853     default:
854       break;
855   }
856
857   return ret;
858 }
859
860 static gboolean
861 plugin_init (GstPlugin * plugin)
862 {
863   GST_DEBUG_CATEGORY_INIT (gst_play_bin_debug, "playbin", 0, "play bin");
864
865   return gst_element_register (plugin, "playbin", GST_RANK_NONE,
866       GST_TYPE_PLAY_BIN);
867 }
868
869 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
870     GST_VERSION_MINOR,
871     "playbin",
872     "player bin", plugin_init, VERSION, GST_LICENSE, GST_PACKAGE, GST_ORIGIN)