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