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