gst/playback/gstplaybin.c: Update max volume to the same value that the volume elemen...
[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 <gst/gst-i18n-plugin.h>
28
29 #include "gstplaybasebin.h"
30
31 GST_DEBUG_CATEGORY_STATIC (gst_play_bin_debug);
32 #define GST_CAT_DEFAULT gst_play_bin_debug
33
34 #define GST_TYPE_PLAY_BIN               (gst_play_bin_get_type())
35 #define GST_PLAY_BIN(obj)               (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PLAY_BIN,GstPlayBin))
36 #define GST_PLAY_BIN_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PLAY_BIN,GstPlayBinClass))
37 #define GST_IS_PLAY_BIN(obj)            (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PLAY_BIN))
38 #define GST_IS_PLAY_BIN_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PLAY_BIN))
39
40 #define VOLUME_MAX_DOUBLE 10.0
41
42 typedef struct _GstPlayBin GstPlayBin;
43 typedef struct _GstPlayBinClass GstPlayBinClass;
44
45 struct _GstPlayBin
46 {
47   GstPlayBaseBin parent;
48
49   /* the configurable elements */
50   GstElement *fakesink;
51   GstElement *audio_sink;
52   GstElement *video_sink;
53   GstElement *visualisation;
54   GstElement *pending_visualisation;
55   GstElement *volume_element;
56   GstElement *textoverlay_element;
57   gfloat volume;
58
59   /* these are the currently active sinks */
60   GList *sinks;
61
62   /* the last captured frame for snapshots */
63   GstBuffer *frame;
64
65   /* our cache for the sinks */
66   GHashTable *cache;
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 gboolean setup_sinks (GstPlayBaseBin * play_base_bin,
100     GstPlayBaseGroup * group);
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
108 static gboolean gst_play_bin_send_event (GstElement * element,
109     GstEvent * event);
110 static GstStateChangeReturn gst_play_bin_change_state (GstElement * element,
111     GstStateChange transition);
112
113 static GstElementClass *parent_class;
114
115 //static guint gst_play_bin_signals[LAST_SIGNAL] = { 0 };
116
117 static const GstElementDetails gst_play_bin_details =
118 GST_ELEMENT_DETAILS ("Player Bin",
119     "Generic/Bin/Player",
120     "Autoplug and play media from an uri",
121     "Wim Taymans <wim@fluendo.com>");
122
123 static GType
124 gst_play_bin_get_type (void)
125 {
126   static GType gst_play_bin_type = 0;
127
128   if (!gst_play_bin_type) {
129     static const GTypeInfo gst_play_bin_info = {
130       sizeof (GstPlayBinClass),
131       NULL,
132       NULL,
133       (GClassInitFunc) gst_play_bin_class_init,
134       NULL,
135       NULL,
136       sizeof (GstPlayBin),
137       0,
138       (GInstanceInitFunc) gst_play_bin_init,
139       NULL
140     };
141
142     gst_play_bin_type = g_type_register_static (GST_TYPE_PLAY_BASE_BIN,
143         "GstPlayBin", &gst_play_bin_info, 0);
144   }
145
146   return gst_play_bin_type;
147 }
148
149 static void
150 gst_play_bin_class_init (GstPlayBinClass * klass)
151 {
152   GObjectClass *gobject_klass;
153   GstElementClass *gstelement_klass;
154   GstBinClass *gstbin_klass;
155   GstPlayBaseBinClass *playbasebin_klass;
156
157   gobject_klass = (GObjectClass *) klass;
158   gstelement_klass = (GstElementClass *) klass;
159   gstbin_klass = (GstBinClass *) klass;
160   playbasebin_klass = (GstPlayBaseBinClass *) klass;
161
162   parent_class = g_type_class_peek_parent (klass);
163
164   gobject_klass->set_property = gst_play_bin_set_property;
165   gobject_klass->get_property = gst_play_bin_get_property;
166
167   g_object_class_install_property (gobject_klass, ARG_VIDEO_SINK,
168       g_param_spec_object ("video-sink", "Video Sink",
169           "the video output element to use (NULL = default sink)",
170           GST_TYPE_ELEMENT, G_PARAM_READWRITE));
171   g_object_class_install_property (gobject_klass, ARG_AUDIO_SINK,
172       g_param_spec_object ("audio-sink", "Audio Sink",
173           "the audio output element to use (NULL = default sink)",
174           GST_TYPE_ELEMENT, G_PARAM_READWRITE));
175   g_object_class_install_property (gobject_klass, ARG_VIS_PLUGIN,
176       g_param_spec_object ("vis-plugin", "Vis plugin",
177           "the visualization element to use (NULL = none)",
178           GST_TYPE_ELEMENT, G_PARAM_READWRITE));
179   g_object_class_install_property (gobject_klass, ARG_VOLUME,
180       g_param_spec_double ("volume", "volume", "volume",
181           0.0, VOLUME_MAX_DOUBLE, 1.0, G_PARAM_READWRITE));
182   g_object_class_install_property (gobject_klass, ARG_FRAME,
183       gst_param_spec_mini_object ("frame", "Frame",
184           "The last frame (NULL = no video available)",
185           GST_TYPE_BUFFER, G_PARAM_READABLE));
186   g_object_class_install_property (gobject_klass, ARG_FONT_DESC,
187       g_param_spec_string ("subtitle-font-desc",
188           "Subtitle font description",
189           "Pango font description of font "
190           "to be used for subtitle rendering", NULL, G_PARAM_WRITABLE));
191
192   gobject_klass->dispose = GST_DEBUG_FUNCPTR (gst_play_bin_dispose);
193
194   gst_element_class_set_details (gstelement_klass, &gst_play_bin_details);
195
196   gstelement_klass->change_state =
197       GST_DEBUG_FUNCPTR (gst_play_bin_change_state);
198   gstelement_klass->send_event = GST_DEBUG_FUNCPTR (gst_play_bin_send_event);
199
200   playbasebin_klass->setup_output_pads = setup_sinks;
201 }
202
203 static void
204 gst_play_bin_init (GstPlayBin * play_bin)
205 {
206   play_bin->video_sink = NULL;
207   play_bin->audio_sink = NULL;
208   play_bin->visualisation = NULL;
209   play_bin->pending_visualisation = NULL;
210   play_bin->volume_element = NULL;
211   play_bin->textoverlay_element = NULL;
212   play_bin->volume = 1.0;
213   play_bin->sinks = NULL;
214   play_bin->frame = NULL;
215   play_bin->font_desc = NULL;
216   play_bin->cache = g_hash_table_new_full (g_str_hash, g_str_equal,
217       NULL, (GDestroyNotify) gst_object_unref);
218 }
219
220 static void
221 gst_play_bin_dispose (GObject * object)
222 {
223   GstPlayBin *play_bin;
224
225   play_bin = GST_PLAY_BIN (object);
226
227   if (play_bin->cache != NULL) {
228     remove_sinks (play_bin);
229     g_hash_table_destroy (play_bin->cache);
230     play_bin->cache = NULL;
231   }
232
233   if (play_bin->audio_sink != NULL) {
234     gst_element_set_state (play_bin->audio_sink, GST_STATE_NULL);
235     gst_object_unref (play_bin->audio_sink);
236     play_bin->audio_sink = NULL;
237   }
238   if (play_bin->video_sink != NULL) {
239     gst_element_set_state (play_bin->video_sink, GST_STATE_NULL);
240     gst_object_unref (play_bin->video_sink);
241     play_bin->video_sink = NULL;
242   }
243   if (play_bin->visualisation != NULL) {
244     gst_element_set_state (play_bin->visualisation, GST_STATE_NULL);
245     gst_object_unref (play_bin->visualisation);
246     play_bin->visualisation = NULL;
247   }
248   if (play_bin->pending_visualisation != NULL) {
249     gst_element_set_state (play_bin->pending_visualisation, GST_STATE_NULL);
250     gst_object_unref (play_bin->pending_visualisation);
251     play_bin->pending_visualisation = NULL;
252   }
253   if (play_bin->textoverlay_element != NULL) {
254     gst_object_unref (play_bin->textoverlay_element);
255     play_bin->textoverlay_element = NULL;
256   }
257   g_free (play_bin->font_desc);
258   play_bin->font_desc = NULL;
259
260   G_OBJECT_CLASS (parent_class)->dispose (object);
261 }
262
263 static void
264 gst_play_bin_vis_unblocked (GstPad * tee_pad, gboolean blocked,
265     gpointer user_data)
266 {
267   /* Unblocked */
268 }
269
270 static void
271 gst_play_bin_vis_blocked (GstPad * tee_pad, gboolean blocked,
272     gpointer user_data)
273 {
274   GstPlayBin *play_bin = GST_PLAY_BIN (user_data);
275   GstBin *vis_bin = NULL;
276   GstPad *vis_sink_pad = NULL, *vis_src_pad = NULL, *vqueue_pad = NULL;
277   GstState bin_state;
278
279   /* We want to disable visualisation */
280   if (!GST_IS_ELEMENT (play_bin->pending_visualisation)) {
281     /* Set visualisation element to READY */
282     gst_element_set_state (play_bin->visualisation, GST_STATE_READY);
283     goto beach;
284   }
285
286   vis_bin =
287       GST_BIN (gst_object_get_parent (GST_OBJECT (play_bin->visualisation)));
288
289   if (!GST_IS_BIN (vis_bin) || !GST_IS_PAD (tee_pad)) {
290     goto beach;
291   }
292
293   vis_src_pad = gst_element_get_pad (play_bin->visualisation, "src");
294   vis_sink_pad = gst_pad_get_peer (tee_pad);
295
296   /* Can be fakesink */
297   if (GST_IS_PAD (vis_src_pad)) {
298     vqueue_pad = gst_pad_get_peer (vis_src_pad);
299   }
300
301   if (!GST_IS_PAD (vis_sink_pad)) {
302     goto beach;
303   }
304
305   /* Check the bin's state */
306   GST_OBJECT_LOCK (vis_bin);
307   bin_state = GST_STATE (vis_bin);
308   GST_OBJECT_UNLOCK (vis_bin);
309
310   /* Unlink */
311   gst_pad_unlink (tee_pad, vis_sink_pad);
312   gst_object_unref (vis_sink_pad);
313   vis_sink_pad = NULL;
314
315   if (GST_IS_PAD (vqueue_pad)) {
316     gst_pad_unlink (vis_src_pad, vqueue_pad);
317     gst_object_unref (vis_src_pad);
318     vis_src_pad = NULL;
319   }
320
321   /* Remove from vis_bin */
322   gst_bin_remove (vis_bin, play_bin->visualisation);
323   /* Set state to NULL */
324   gst_element_set_state (play_bin->visualisation, GST_STATE_NULL);
325   /* And loose our ref */
326   gst_object_unref (play_bin->visualisation);
327
328   if (play_bin->pending_visualisation) {
329     /* Ref this new visualisation element before adding to the bin */
330     gst_object_ref (play_bin->pending_visualisation);
331     /* Add the new one */
332     gst_bin_add (vis_bin, play_bin->pending_visualisation);
333     /* Synchronizing state */
334     gst_element_set_state (play_bin->pending_visualisation, bin_state);
335
336     vis_sink_pad = gst_element_get_pad (play_bin->pending_visualisation,
337         "sink");
338     vis_src_pad = gst_element_get_pad (play_bin->pending_visualisation, "src");
339
340     if (!GST_IS_PAD (vis_sink_pad) || !GST_IS_PAD (vis_src_pad)) {
341       goto beach;
342     }
343
344     /* Link */
345     gst_pad_link (tee_pad, vis_sink_pad);
346     gst_pad_link (vis_src_pad, vqueue_pad);
347   }
348
349   /* We are done */
350   gst_object_unref (play_bin->visualisation);
351   play_bin->visualisation = play_bin->pending_visualisation;
352   play_bin->pending_visualisation = NULL;
353
354 beach:
355   if (vis_sink_pad) {
356     gst_object_unref (vis_sink_pad);
357   }
358   if (vis_src_pad) {
359     gst_object_unref (vis_src_pad);
360   }
361   if (vqueue_pad) {
362     gst_object_unref (vqueue_pad);
363   }
364   if (vis_bin) {
365     gst_object_unref (vis_bin);
366   }
367
368   /* Unblock the pad */
369   gst_pad_set_blocked_async (tee_pad, FALSE, gst_play_bin_vis_unblocked,
370       play_bin);
371 }
372
373 static void
374 gst_play_bin_set_property (GObject * object, guint prop_id,
375     const GValue * value, GParamSpec * pspec)
376 {
377   GstPlayBin *play_bin;
378
379   g_return_if_fail (GST_IS_PLAY_BIN (object));
380
381   play_bin = GST_PLAY_BIN (object);
382
383   switch (prop_id) {
384     case ARG_VIDEO_SINK:
385       if (play_bin->video_sink != NULL) {
386         gst_object_unref (play_bin->video_sink);
387       }
388       play_bin->video_sink = g_value_get_object (value);
389       if (play_bin->video_sink != NULL) {
390         gst_object_ref (play_bin->video_sink);
391         gst_object_sink (GST_OBJECT (play_bin->video_sink));
392       }
393       /* when changing the videosink, we just remove the
394        * video pipeline from the cache so that it will be 
395        * regenerated with the new sink element */
396       g_hash_table_remove (play_bin->cache, "vbin");
397       break;
398     case ARG_AUDIO_SINK:
399       if (play_bin->audio_sink != NULL) {
400         gst_object_unref (play_bin->audio_sink);
401       }
402       play_bin->audio_sink = g_value_get_object (value);
403       if (play_bin->audio_sink != NULL) {
404         gst_object_ref (play_bin->audio_sink);
405         gst_object_sink (GST_OBJECT (play_bin->audio_sink));
406       }
407       g_hash_table_remove (play_bin->cache, "abin");
408       break;
409     case ARG_VIS_PLUGIN:
410     {
411       /* Do we already have a visualisation change pending ? */
412       if (play_bin->pending_visualisation) {
413         gst_object_unref (play_bin->pending_visualisation);
414         play_bin->pending_visualisation = g_value_get_object (value);
415         /* Take ownership */
416         if (play_bin->pending_visualisation) {
417           gst_object_ref (play_bin->pending_visualisation);
418           gst_object_sink (GST_OBJECT (play_bin->pending_visualisation));
419         }
420       } else {
421         play_bin->pending_visualisation = g_value_get_object (value);
422
423         /* Take ownership */
424         if (play_bin->pending_visualisation) {
425           gst_object_ref (play_bin->pending_visualisation);
426           gst_object_sink (GST_OBJECT (play_bin->pending_visualisation));
427         }
428
429         /* Was there a visualisation already set ? */
430         if (play_bin->visualisation != NULL) {
431           GstBin *vis_bin = NULL;
432
433           vis_bin =
434               GST_BIN (gst_object_get_parent (GST_OBJECT (play_bin->
435                       visualisation)));
436
437           /* Check if the visualisation is already in a bin */
438           if (GST_IS_BIN (vis_bin)) {
439             GstPad *vis_sink_pad = NULL, *tee_pad = NULL;
440
441             /* Now get tee pad and block it async */
442             vis_sink_pad = gst_element_get_pad (play_bin->visualisation,
443                 "sink");
444             if (!GST_IS_PAD (vis_sink_pad)) {
445               goto beach;
446             }
447             tee_pad = gst_pad_get_peer (vis_sink_pad);
448             if (!GST_IS_PAD (tee_pad)) {
449               goto beach;
450             }
451
452             /* Block with callback */
453             gst_pad_set_blocked_async (tee_pad, TRUE, gst_play_bin_vis_blocked,
454                 play_bin);
455           beach:
456             if (vis_sink_pad) {
457               gst_object_unref (vis_sink_pad);
458             }
459             if (tee_pad) {
460               gst_object_unref (tee_pad);
461             }
462             gst_object_unref (vis_bin);
463           } else {
464             play_bin->visualisation = play_bin->pending_visualisation;
465             play_bin->pending_visualisation = NULL;
466           }
467         } else {
468           play_bin->visualisation = play_bin->pending_visualisation;
469           play_bin->pending_visualisation = NULL;
470         }
471       }
472       break;
473     }
474     case ARG_VOLUME:
475       play_bin->volume = g_value_get_double (value);
476       if (play_bin->volume_element) {
477         g_object_set (G_OBJECT (play_bin->volume_element), "volume",
478             play_bin->volume, NULL);
479       }
480       break;
481     case ARG_FONT_DESC:
482       g_free (play_bin->font_desc);
483       play_bin->font_desc = g_strdup (g_value_get_string (value));
484       if (play_bin->textoverlay_element) {
485         g_object_set (G_OBJECT (play_bin->textoverlay_element),
486             "font-desc", g_value_get_string (value), NULL);
487       }
488       break;
489     default:
490       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
491       break;
492   }
493 }
494
495 static void
496 gst_play_bin_get_property (GObject * object, guint prop_id, GValue * value,
497     GParamSpec * pspec)
498 {
499   GstPlayBin *play_bin;
500
501   g_return_if_fail (GST_IS_PLAY_BIN (object));
502
503   play_bin = GST_PLAY_BIN (object);
504
505   switch (prop_id) {
506     case ARG_VIDEO_SINK:
507       g_value_set_object (value, play_bin->video_sink);
508       break;
509     case ARG_AUDIO_SINK:
510       g_value_set_object (value, play_bin->audio_sink);
511       break;
512     case ARG_VIS_PLUGIN:
513       g_value_set_object (value, play_bin->visualisation);
514       break;
515     case ARG_VOLUME:
516       g_value_set_double (value, play_bin->volume);
517       break;
518     case ARG_FRAME:
519       gst_value_set_mini_object (value, GST_MINI_OBJECT (play_bin->frame));
520       break;
521     default:
522       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
523       break;
524   }
525 }
526
527 /* signal fired when the identity has received a new buffer. This is used for
528  * making screenshots.
529  */
530 static void
531 handoff (GstElement * identity, GstBuffer * frame, gpointer data)
532 {
533   GstPlayBin *play_bin = GST_PLAY_BIN (data);
534   GstBuffer **frame_p = &play_bin->frame;
535
536   gst_mini_object_replace ((GstMiniObject **) frame_p,
537       GST_MINI_OBJECT_CAST (frame));
538
539   /* applications need to know the buffer caps,
540    * make sure they are always set on the frame */
541   if (GST_BUFFER_CAPS (play_bin->frame) == NULL) {
542     GstPad *pad;
543
544     if ((pad = gst_element_get_pad (identity, "sink"))) {
545       gst_buffer_set_caps (play_bin->frame, GST_PAD_CAPS (pad));
546       gst_object_unref (pad);
547     }
548   }
549 }
550
551 /* make the element (bin) that contains the elements needed to perform
552  * video display. We connect a handoff signal to identity so that we
553  * can grab snapshots. Identity's sinkpad is ghosted to vbin.
554  *
555  *  +-------------------------------------------------------------+
556  *  | vbin                                                        |
557  *  |      +--------+   +----------+   +----------+   +---------+ |
558  *  |      |identity|   |colorspace|   |videoscale|   |videosink| |
559  *  |   +-sink     src-sink       src-sink       src-sink       | |
560  *  |   |  +---+----+   +----------+   +----------+   +---------+ |
561  * sink-+      |                                                  |
562  *  +----------|--------------------------------------------------+
563  *           handoff
564  */
565 /* FIXME: this might return NULL if no videosink was found, handle
566  * this in callers */
567 static GstElement *
568 gen_video_element (GstPlayBin * play_bin)
569 {
570   GstElement *element;
571   GstElement *conv;
572
573   GstElement *scale;
574   GstElement *sink;
575   GstElement *identity;
576   GstPad *pad;
577
578   /* first see if we have it in the cache */
579   element = g_hash_table_lookup (play_bin->cache, "vbin");
580   if (element != NULL) {
581     return element;
582   }
583
584   if (play_bin->video_sink) {
585     sink = play_bin->video_sink;
586   } else {
587     sink = gst_element_factory_make ("autovideosink", "videosink");
588     if (sink == NULL) {
589       sink = gst_element_factory_make ("xvimagesink", "videosink");
590     }
591     /* FIXME: this warrants adding a CORE error category for missing
592      * elements/plugins */
593     if (sink == NULL) {
594       GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN,
595           (_("Both autovideosink and xvimagesink elements are missing.")),
596           (NULL));
597       return NULL;
598     }
599   }
600   gst_object_ref (sink);
601   g_hash_table_insert (play_bin->cache, "video_sink", sink);
602
603
604   element = gst_bin_new ("vbin");
605   identity = gst_element_factory_make ("identity", "id");
606   g_object_set (identity, "silent", TRUE, NULL);
607   g_signal_connect (identity, "handoff", G_CALLBACK (handoff), play_bin);
608   gst_bin_add (GST_BIN (element), identity);
609   conv = gst_element_factory_make ("ffmpegcolorspace", "vconv");
610   if (conv == NULL)
611     goto no_colorspace;
612   scale = gst_element_factory_make ("videoscale", "vscale");
613   if (scale == NULL)
614     goto no_videoscale;
615   gst_bin_add (GST_BIN (element), conv);
616   gst_bin_add (GST_BIN (element), scale);
617   gst_bin_add (GST_BIN (element), sink);
618   gst_element_link_pads (identity, "src", conv, "sink");
619   gst_element_link_pads (conv, "src", scale, "sink");
620   gst_element_link_pads (scale, "src", sink, "sink");
621
622   pad = gst_element_get_pad (identity, "sink");
623   gst_element_add_pad (element, gst_ghost_pad_new ("sink", pad));
624   gst_object_unref (pad);
625
626   gst_element_set_state (element, GST_STATE_READY);
627
628   /* since we're gonna add it to a bin but don't want to lose it,
629    * we keep a reference. */
630   gst_object_ref (element);
631   g_hash_table_insert (play_bin->cache, "vbin", element);
632
633   return element;
634
635 no_colorspace:
636   {
637     GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN,
638         (_("Missing element '%s' - check your GStreamer installation."),
639             "ffmpegcolorspace"), (NULL));
640     gst_object_unref (element);
641     return NULL;
642   }
643
644 no_videoscale:
645   {
646     GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN,
647         (_("Missing element '%s' - check your GStreamer installation."),
648             "videoscale"), ("possibly a liboil version mismatch?"));
649     gst_object_unref (element);
650     return NULL;
651   }
652 }
653
654 /* make an element for playback of video with subtitles embedded.
655  *
656  *  +--------------------------------------------------+
657  *  | tbin                  +-------------+            |
658  *  |          +-----+      | textoverlay |   +------+ |
659  *  |          | csp | +--video_sink      |   | vbin | |
660  * video_sink-sink  src+ +-text_sink     src-sink    | |
661  *  |          +-----+   |  +-------------+   +------+ |
662  * text_sink-------------+                             |
663  *  +--------------------------------------------------+
664  */
665
666 static GstElement *
667 gen_text_element (GstPlayBin * play_bin)
668 {
669   GstElement *element, *csp, *overlay, *vbin;
670   GstPad *pad;
671
672   /* Create our bin */
673   element = gst_bin_new ("textbin");
674
675   /* Text overlay */
676   overlay = gst_element_factory_make ("textoverlay", "overlay");
677
678   /* Create the video rendering bin */
679   vbin = gen_video_element (play_bin);
680
681   /* If no overlay return the video bin */
682   if (!overlay) {
683     GST_WARNING ("No overlay (pango) element, subtitles disabled");
684     return vbin;
685   }
686
687   /* Set some parameters */
688   g_object_set (G_OBJECT (overlay),
689       "halign", "center", "valign", "bottom", NULL);
690   if (play_bin->font_desc) {
691     g_object_set (G_OBJECT (overlay), "font-desc", play_bin->font_desc, NULL);
692   }
693
694   /* Take a ref */
695   play_bin->textoverlay_element = GST_ELEMENT (gst_object_ref (overlay));
696
697   /* we know this will succeed, as the video bin already created one before */
698   csp = gst_element_factory_make ("ffmpegcolorspace", "subtitlecsp");
699
700   /* Add our elements */
701   gst_bin_add_many (GST_BIN (element), csp, overlay, vbin, NULL);
702
703   /* Link */
704   gst_element_link_pads (csp, "src", overlay, "video_sink");
705   gst_element_link_pads (overlay, "src", vbin, "sink");
706
707   /* Add ghost pads on the subtitle bin */
708   pad = gst_element_get_pad (overlay, "text_sink");
709   gst_element_add_pad (element, gst_ghost_pad_new ("text_sink", pad));
710   gst_object_unref (pad);
711
712   pad = gst_element_get_pad (csp, "sink");
713   gst_element_add_pad (element, gst_ghost_pad_new ("sink", pad));
714   gst_object_unref (pad);
715
716   /* Set state to READY */
717   gst_element_set_state (element, GST_STATE_READY);
718
719   return element;
720 }
721
722 /* make the element (bin) that contains the elements needed to perform
723  * audio playback.
724  *
725  *  +-------------------------------------------------------------+
726  *  | abin                                                        |
727  *  |      +---------+   +----------+   +---------+   +---------+ |
728  *  |      |audioconv|   |audioscale|   | volume  |   |audiosink| |
729  *  |   +-sink      src-sink       src-sink      src-sink       | |
730  *  |   |  +---------+   +----------+   +---------+   +---------+ |
731  * sink-+                                                         |
732  *  +-------------------------------------------------------------+
733  *
734  */
735 static GstElement *
736 gen_audio_element (GstPlayBin * play_bin)
737 {
738   GstElement *element;
739   GstElement *conv;
740   GstElement *scale;
741   GstElement *sink;
742   GstElement *volume;
743   GstPad *pad;
744
745   element = g_hash_table_lookup (play_bin->cache, "abin");
746   if (element != NULL) {
747     return element;
748   }
749   element = gst_bin_new ("abin");
750   conv = gst_element_factory_make ("audioconvert", "aconv");
751   if (conv == NULL)
752     goto no_audioconvert;
753
754   scale = gst_element_factory_make ("audioresample", "aresample");
755   if (scale == NULL)
756     goto no_audioresample;
757
758   volume = gst_element_factory_make ("volume", "volume");
759   g_object_set (G_OBJECT (volume), "volume", play_bin->volume, NULL);
760   play_bin->volume_element = volume;
761
762   if (play_bin->audio_sink) {
763     sink = play_bin->audio_sink;
764   } else {
765     sink = gst_element_factory_make ("autoaudiosink", "audiosink");
766     if (sink == NULL) {
767       sink = gst_element_factory_make ("alsasink", "audiosink");
768     }
769     if (sink == NULL) {
770       GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN,
771           (_("Both autoaudiosink and alsasink elements are missing.")), (NULL));
772       return NULL;
773     }
774     play_bin->audio_sink = GST_ELEMENT (gst_object_ref (sink));
775   }
776
777   gst_object_ref (sink);
778   g_hash_table_insert (play_bin->cache, "audio_sink", sink);
779
780   gst_bin_add (GST_BIN (element), conv);
781   gst_bin_add (GST_BIN (element), scale);
782   gst_bin_add (GST_BIN (element), volume);
783   gst_bin_add (GST_BIN (element), sink);
784
785   gst_element_link_pads (conv, "src", scale, "sink");
786   gst_element_link_pads (scale, "src", volume, "sink");
787   gst_element_link_pads (volume, "src", sink, "sink");
788
789   pad = gst_element_get_pad (conv, "sink");
790   gst_element_add_pad (element, gst_ghost_pad_new ("sink", pad));
791   gst_object_unref (pad);
792
793   gst_element_set_state (element, GST_STATE_READY);
794
795   /* since we're gonna add it to a bin but don't want to lose it,
796    * we keep a reference. */
797   gst_object_ref (element);
798   g_hash_table_insert (play_bin->cache, "abin", element);
799
800   return element;
801
802 no_audioconvert:
803   {
804     GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN,
805         (_("Missing element '%s' - check your GStreamer installation."),
806             "audioconvert"), ("possibly a liboil version mismatch?"));
807     gst_object_unref (element);
808     return NULL;
809   }
810
811 no_audioresample:
812   {
813     GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN,
814         (_("Missing element '%s' - check your GStreamer installation."),
815             "audioresample"), ("possibly a liboil version mismatch?"));
816     gst_object_unref (element);
817     return NULL;
818   }
819 }
820
821 /* make the element (bin) that contains the elements needed to perform
822  * visualisation ouput.  The idea is to split the audio using tee, then 
823  * sending the output to the regular audio bin and the other output to
824  * the vis plugin that transforms it into a video that is rendered with the
825  * normal video bin. The video bin is run in a thread to make sure it does
826  * not block the audio playback pipeline.
827  *
828  *  +--------------------------------------------------------------------+
829  *  | visbin                                                             |
830  *  |      +------+   +--------+   +----------------+                    |
831  *  |      | tee  |   | aqueue |   |   abin ...     |                    |
832  *  |   +-sink   src-sink     src-sink              |                    |
833  *  |   |  |      |   +--------+   +----------------+                    |
834  *  |   |  |      |                                                      |
835  *  |   |  |      |   +------+   +---------+   +------+   +-----------+  |
836  *  |   |  |      |   |vqueue|   |audioconv|   | vis  |   | vbin ...  |  |
837  *  |   |  |     src-sink   src-sink      src-sink   src-sink         |  |
838  *  |   |  |      |   +------+   +---------+   +------+   +-----------+  |
839  *  |   |  |      |                                                      |
840  *  |   |  +------+                                                      |
841  * sink-+                                                                |
842    +---------------------------------------------------------------------+
843  */
844 static GstElement *
845 gen_vis_element (GstPlayBin * play_bin)
846 {
847   GstElement *element;
848   GstElement *tee;
849   GstElement *asink;
850   GstElement *vsink;
851   GstElement *conv;
852   GstElement *vis;
853   GstElement *vqueue, *aqueue;
854   GstPad *pad, *rpad;
855
856   asink = gen_audio_element (play_bin);
857   if (!asink)
858     return NULL;
859   vsink = gen_video_element (play_bin);
860   if (!vsink) {
861     gst_object_unref (asink);
862     return NULL;
863   }
864
865   element = gst_bin_new ("visbin");
866   tee = gst_element_factory_make ("tee", "tee");
867
868   vqueue = gst_element_factory_make ("queue", "vqueue");
869   aqueue = gst_element_factory_make ("queue", "aqueue");
870
871   gst_bin_add (GST_BIN (element), asink);
872   gst_bin_add (GST_BIN (element), vqueue);
873   gst_bin_add (GST_BIN (element), aqueue);
874   gst_bin_add (GST_BIN (element), vsink);
875   gst_bin_add (GST_BIN (element), tee);
876
877   conv = gst_element_factory_make ("audioconvert", "aconv");
878   if (conv == NULL)
879     goto no_audioconvert;
880
881   if (play_bin->visualisation) {
882     gst_object_ref (play_bin->visualisation);
883     vis = play_bin->visualisation;
884   } else {
885     vis = gst_element_factory_make ("goom", "vis");
886   }
887
888   gst_bin_add (GST_BIN (element), conv);
889   gst_bin_add (GST_BIN (element), vis);
890
891   gst_element_link_pads (vqueue, "src", conv, "sink");
892   gst_element_link_pads (conv, "src", vis, "sink");
893   gst_element_link_pads (vis, "src", vsink, "sink");
894
895   pad = gst_element_get_pad (aqueue, "sink");
896   rpad = gst_element_get_request_pad (tee, "src%d");
897   gst_pad_link (rpad, pad);
898   gst_object_unref (rpad);
899   gst_object_unref (pad);
900   gst_element_link_pads (aqueue, "src", asink, "sink");
901
902   pad = gst_element_get_pad (vqueue, "sink");
903   rpad = gst_element_get_request_pad (tee, "src%d");
904   gst_pad_link (rpad, pad);
905   gst_object_unref (rpad);
906   gst_object_unref (pad);
907
908   pad = gst_element_get_pad (tee, "sink");
909   gst_element_add_pad (element, gst_ghost_pad_new ("sink", pad));
910   gst_object_unref (pad);
911
912   return element;
913
914 no_audioconvert:
915   {
916     GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN,
917         (_("Missing element '%s' - check your GStreamer installation."),
918             "audioconvert"), ("possibly a liboil version mismatch?"));
919     gst_object_unref (element);
920     return NULL;
921   }
922 }
923
924 /* get rid of all installed sinks */
925 static void
926 remove_sinks (GstPlayBin * play_bin)
927 {
928   GList *sinks;
929   GstObject *parent;
930   GstElement *element;
931   GstPad *pad, *peer;
932
933   GST_DEBUG ("removesinks");
934   element = g_hash_table_lookup (play_bin->cache, "abin");
935   if (element != NULL) {
936     parent = gst_element_get_parent (element);
937     if (parent != NULL) {
938       /* we remove the element from the parent so that
939        * there is no unwanted state change when the parent
940        * is disposed */
941       play_bin->sinks = g_list_remove (play_bin->sinks, element);
942       gst_element_set_state (element, GST_STATE_NULL);
943       gst_bin_remove (GST_BIN (parent), element);
944       gst_object_unref (parent);
945     }
946     pad = gst_element_get_pad (element, "sink");
947     if (pad != NULL) {
948       peer = gst_pad_get_peer (pad);
949       if (peer != NULL) {
950         gst_pad_unlink (peer, pad);
951         gst_object_unref (peer);
952       }
953       gst_object_unref (pad);
954     }
955   }
956   element = g_hash_table_lookup (play_bin->cache, "vbin");
957   if (element != NULL) {
958     parent = gst_element_get_parent (element);
959     if (parent != NULL) {
960       play_bin->sinks = g_list_remove (play_bin->sinks, element);
961       gst_element_set_state (element, GST_STATE_NULL);
962       gst_bin_remove (GST_BIN (parent), element);
963       gst_object_unref (parent);
964     }
965     pad = gst_element_get_pad (element, "sink");
966     if (pad != NULL) {
967       peer = gst_pad_get_peer (pad);
968       if (peer != NULL) {
969         gst_pad_unlink (peer, pad);
970         gst_object_unref (peer);
971       }
972       gst_object_unref (pad);
973     }
974   }
975
976   for (sinks = play_bin->sinks; sinks; sinks = g_list_next (sinks)) {
977     GstElement *element = GST_ELEMENT (sinks->data);
978     GstPad *pad;
979     GstPad *peer;
980
981     pad = gst_element_get_pad (element, "sink");
982
983     GST_LOG ("removing sink %p", element);
984
985     peer = gst_pad_get_peer (pad);
986     if (peer) {
987       gst_pad_unlink (peer, pad);
988       gst_object_unref (peer);
989     }
990     gst_object_unref (pad);
991
992     gst_element_set_state (element, GST_STATE_NULL);
993     gst_bin_remove (GST_BIN (play_bin), element);
994   }
995   g_list_free (play_bin->sinks);
996   play_bin->sinks = NULL;
997
998   /* FIXME: this is probably some refcounting problem */
999   if (play_bin->visualisation && GST_OBJECT_PARENT (play_bin->visualisation)) {
1000     gst_element_set_state (play_bin->visualisation, GST_STATE_NULL);
1001     gst_bin_remove (GST_BIN (GST_OBJECT_PARENT (play_bin->visualisation)),
1002         play_bin->visualisation);
1003   }
1004
1005   if (play_bin->frame) {
1006     gst_buffer_unref (play_bin->frame);
1007     play_bin->frame = NULL;
1008   }
1009
1010   if (play_bin->textoverlay_element) {
1011     gst_object_unref (play_bin->textoverlay_element);
1012     play_bin->textoverlay_element = NULL;
1013   }
1014 }
1015
1016 /* loop over the streams and set up the pipeline to play this
1017  * media file. First we count the number of audio and video streams.
1018  * If there is no video stream but there exists an audio stream,
1019  * we install a visualisation pipeline.
1020  * 
1021  * Also make sure to only connect the first audio and video pad. FIXME
1022  * this should eventually be handled with a tuner interface so that
1023  * one can switch the streams.
1024  */
1025 static gboolean
1026 add_sink (GstPlayBin * play_bin, GstElement * sink, GstPad * srcpad,
1027     GstPad * subtitle_pad)
1028 {
1029   GstPad *sinkpad;
1030   GstPadLinkReturn linkres;
1031   GstElement *parent;
1032   GstStateChangeReturn stateret;
1033
1034   g_return_val_if_fail (sink != NULL, FALSE);
1035   /* this is only for debugging */
1036   parent = gst_pad_get_parent_element (srcpad);
1037   if (parent) {
1038     GST_DEBUG ("Adding sink with state %d (parent: %d, peer: %d)",
1039         GST_STATE (sink), GST_STATE (play_bin), GST_STATE (parent));
1040     gst_object_unref (parent);
1041   }
1042
1043   /* bring it to the PAUSED state so we can link to the peer without
1044    * breaking the flow */
1045   if ((stateret = gst_element_set_state (sink, GST_STATE_PAUSED)) ==
1046       GST_STATE_CHANGE_FAILURE)
1047     goto state_failed;
1048
1049   gst_bin_add (GST_BIN (play_bin), sink);
1050
1051   /* we found a sink for this stream, now try to install it */
1052   sinkpad = gst_element_get_pad (sink, "sink");
1053   linkres = gst_pad_link (srcpad, sinkpad);
1054   gst_object_unref (sinkpad);
1055
1056   /* try to link the pad of the sink to the stream */
1057   if (GST_PAD_LINK_FAILED (linkres))
1058     goto link_failed;
1059
1060   if (GST_IS_PAD (subtitle_pad)) {
1061     sinkpad = gst_element_get_pad (sink, "text_sink");
1062     linkres = gst_pad_link (subtitle_pad, sinkpad);
1063     gst_object_unref (sinkpad);
1064   }
1065
1066   /* try to link the subtitle pad of the sink to the stream */
1067   if (GST_PAD_LINK_FAILED (linkres)) {
1068     goto subtitle_failed;
1069   }
1070
1071   /* we got the sink succesfully linked, now keep the sink
1072    * in our internal list */
1073   play_bin->sinks = g_list_prepend (play_bin->sinks, sink);
1074
1075   return TRUE;
1076
1077   /* ERRORS */
1078 state_failed:
1079   {
1080     GST_DEBUG_OBJECT (play_bin, "state change failure when adding sink");
1081     return FALSE;
1082   }
1083 link_failed:
1084   {
1085     gchar *capsstr;
1086     GstCaps *caps;
1087
1088     /* could not link this stream */
1089     caps = gst_pad_get_caps (srcpad);
1090     capsstr = gst_caps_to_string (caps);
1091     g_warning ("could not link %s: %d", capsstr, linkres);
1092     GST_DEBUG_OBJECT (play_bin,
1093         "link failed when adding sink, caps %s, reason %d", capsstr, linkres);
1094     g_free (capsstr);
1095     gst_caps_unref (caps);
1096
1097     gst_element_set_state (sink, GST_STATE_NULL);
1098     gst_bin_remove (GST_BIN (play_bin), sink);
1099     return FALSE;
1100   }
1101 subtitle_failed:
1102   {
1103     gchar *capsstr;
1104     GstCaps *caps;
1105
1106     /* could not link this stream */
1107     caps = gst_pad_get_caps (subtitle_pad);
1108     capsstr = gst_caps_to_string (caps);
1109     GST_DEBUG_OBJECT (play_bin,
1110         "subtitle link failed when adding sink, caps %s, reason %d", capsstr,
1111         linkres);
1112     g_free (capsstr);
1113     gst_caps_unref (caps);
1114
1115     return TRUE;
1116   }
1117 }
1118
1119 static gboolean
1120 setup_sinks (GstPlayBaseBin * play_base_bin, GstPlayBaseGroup * group)
1121 {
1122   GstPlayBin *play_bin = GST_PLAY_BIN (play_base_bin);
1123   GList *streaminfo = NULL, *s;
1124   gboolean need_vis = FALSE;
1125   gboolean need_text = FALSE;
1126   GstPad *textsrcpad = NULL, *pad = NULL;
1127   GstElement *sink;
1128   gboolean res = TRUE;
1129
1130   /* get rid of existing sinks */
1131   if (play_bin->sinks) {
1132     remove_sinks (play_bin);
1133   }
1134   GST_DEBUG_OBJECT (play_base_bin, "setupsinks");
1135
1136   /* find out what to do */
1137   if (group->type[GST_STREAM_TYPE_VIDEO - 1].npads > 0 &&
1138       group->type[GST_STREAM_TYPE_TEXT - 1].npads > 0) {
1139     need_text = TRUE;
1140   } else if (group->type[GST_STREAM_TYPE_VIDEO - 1].npads == 0 &&
1141       group->type[GST_STREAM_TYPE_AUDIO - 1].npads > 0 &&
1142       play_bin->visualisation != NULL) {
1143     need_vis = TRUE;
1144   }
1145
1146   /* now actually connect everything */
1147   g_object_get (G_OBJECT (play_base_bin), "stream-info", &streaminfo, NULL);
1148   for (s = streaminfo; s; s = g_list_next (s)) {
1149     GObject *obj = G_OBJECT (s->data);
1150     gint type;
1151     GstObject *object;
1152
1153     g_object_get (obj, "type", &type, NULL);
1154     g_object_get (obj, "object", &object, NULL);
1155   }
1156
1157   /* link audio */
1158   if (group->type[GST_STREAM_TYPE_AUDIO - 1].npads > 0) {
1159     if (need_vis) {
1160       sink = gen_vis_element (play_bin);
1161     } else {
1162       sink = gen_audio_element (play_bin);
1163     }
1164     if (!sink)
1165       return FALSE;
1166     pad = gst_element_get_pad (group->type[GST_STREAM_TYPE_AUDIO - 1].preroll,
1167         "src");
1168     res = add_sink (play_bin, sink, pad, NULL);
1169     gst_object_unref (pad);
1170   }
1171
1172   /* link video */
1173   if (group->type[GST_STREAM_TYPE_VIDEO - 1].npads > 0) {
1174     if (need_text) {
1175       GstObject *parent = NULL, *grandparent = NULL;
1176       GstPad *ghost = NULL;
1177
1178       sink = gen_text_element (play_bin);
1179       textsrcpad =
1180           gst_element_get_pad (group->type[GST_STREAM_TYPE_TEXT - 1].preroll,
1181           "src");
1182       /* This pad is from subtitle-bin, we need to create a ghost pad to have
1183          common grandparents */
1184       parent = gst_object_get_parent (GST_OBJECT (textsrcpad));
1185       if (!parent) {
1186         GST_WARNING_OBJECT (textsrcpad, "subtitle pad has no parent !");
1187         gst_object_unref (textsrcpad);
1188         textsrcpad = NULL;
1189         goto beach;
1190       }
1191
1192       grandparent = gst_object_get_parent (parent);
1193       if (!grandparent) {
1194         GST_WARNING_OBJECT (textsrcpad, "subtitle pad has no grandparent !");
1195         gst_object_unref (parent);
1196         gst_object_unref (textsrcpad);
1197         textsrcpad = NULL;
1198         goto beach;
1199       }
1200
1201       /* We ghost the pad on subtitle_bin only, if the text pad is from the
1202          media demuxer we keep it as it is */
1203       if (!GST_IS_PLAY_BIN (grandparent)) {
1204         GST_DEBUG_OBJECT (textsrcpad, "this subtitle pad is from a subtitle "
1205             "file, ghosting to a suitable hierarchy");
1206         ghost = gst_ghost_pad_new ("text_src", textsrcpad);
1207         if (!GST_IS_PAD (ghost)) {
1208           GST_WARNING_OBJECT (textsrcpad, "failed creating ghost pad for "
1209               "subtitle-bin");
1210           gst_object_unref (parent);
1211           gst_object_unref (grandparent);
1212           gst_object_unref (textsrcpad);
1213           textsrcpad = NULL;
1214           goto beach;
1215         }
1216
1217         if (gst_element_add_pad (GST_ELEMENT (grandparent), ghost)) {
1218           gst_object_unref (textsrcpad);
1219           textsrcpad = gst_object_ref (ghost);
1220         } else {
1221           GST_WARNING_OBJECT (ghost, "failed adding ghost pad on subtitle-bin");
1222           gst_object_unref (ghost);
1223           gst_object_unref (textsrcpad);
1224           textsrcpad = NULL;
1225         }
1226       } else {
1227         GST_DEBUG_OBJECT (textsrcpad, "this subtitle pad is from the demuxer "
1228             "no changes to hierarchy needed");
1229       }
1230
1231       gst_object_unref (parent);
1232       gst_object_unref (grandparent);
1233     } else {
1234       sink = gen_video_element (play_bin);
1235     }
1236   beach:
1237     if (!sink)
1238       return FALSE;
1239     pad = gst_element_get_pad (group->type[GST_STREAM_TYPE_VIDEO - 1].preroll,
1240         "src");
1241     res = add_sink (play_bin, sink, pad, textsrcpad);
1242     gst_object_unref (pad);
1243     if (textsrcpad) {
1244       gst_object_unref (textsrcpad);
1245     }
1246   }
1247
1248   /* remove the sinks now, pipeline get_state will now wait for the
1249    * sinks to preroll */
1250   if (play_bin->fakesink) {
1251     gst_element_set_state (play_bin->fakesink, GST_STATE_NULL);
1252     gst_bin_remove (GST_BIN (play_bin), play_bin->fakesink);
1253     play_bin->fakesink = NULL;
1254   }
1255
1256   return res;
1257 }
1258
1259 /* Send an event to our sinks until one of them works; don't then send to the
1260  * remaining sinks (unlike GstBin)
1261  */
1262 static gboolean
1263 gst_play_bin_send_event_to_sink (GstPlayBin * play_bin, GstEvent * event)
1264 {
1265   GList *sinks = play_bin->sinks;
1266   gboolean res = TRUE;
1267
1268   while (sinks) {
1269     GstElement *sink = GST_ELEMENT_CAST (sinks->data);
1270
1271     gst_event_ref (event);
1272     if ((res = gst_element_send_event (sink, event))) {
1273       GST_DEBUG_OBJECT (play_bin,
1274           "Sent event succesfully to sink %" GST_PTR_FORMAT, sink);
1275       break;
1276     }
1277     GST_DEBUG_OBJECT (play_bin,
1278         "Event failed when sent to sink %" GST_PTR_FORMAT, sink);
1279
1280     sinks = g_list_next (sinks);
1281   }
1282
1283   gst_event_unref (event);
1284
1285   return res;
1286 }
1287
1288 static gboolean
1289 do_playbin_seek (GstElement * element, GstEvent * event)
1290 {
1291   gdouble rate;
1292   GstSeekFlags flags;
1293   gboolean flush;
1294   gboolean was_playing = FALSE;
1295   gboolean res;
1296
1297   gst_event_parse_seek (event, &rate, NULL, &flags, NULL, NULL, NULL, NULL);
1298
1299   flush = flags & GST_SEEK_FLAG_FLUSH;
1300
1301   if (flush) {
1302     GstState state;
1303
1304     /* need to call _get_state() since a bin state is only updated
1305      * with this call. */
1306     gst_element_get_state (element, &state, NULL, 0);
1307     was_playing = state == GST_STATE_PLAYING;
1308
1309     if (was_playing) {
1310       gst_element_set_state (element, GST_STATE_PAUSED);
1311       gst_element_get_state (element, NULL, NULL, 50 * GST_MSECOND);
1312     }
1313   }
1314
1315   GST_DEBUG_OBJECT (element, "Sending seek event to a sink");
1316   res = gst_play_bin_send_event_to_sink (GST_PLAY_BIN (element), event);
1317
1318   if (flush) {
1319     /* need to reset the stream time to 0 after a flushing seek */
1320     if (res)
1321       gst_pipeline_set_new_stream_time (GST_PIPELINE (element), 0);
1322
1323     if (was_playing)
1324       /* and continue playing */
1325       gst_element_set_state (element, GST_STATE_PLAYING);
1326   }
1327   return res;
1328 }
1329
1330 /* We only want to send the event to a single sink (overriding GstBin's 
1331  * behaviour), but we want to keep GstPipeline's behaviour - wrapping seek
1332  * events appropriately. So, this is a messy duplication of code. */
1333 static gboolean
1334 gst_play_bin_send_event (GstElement * element, GstEvent * event)
1335 {
1336   gboolean res = FALSE;
1337   GstEventType event_type = GST_EVENT_TYPE (event);
1338
1339
1340   switch (event_type) {
1341     case GST_EVENT_SEEK:
1342       res = do_playbin_seek (element, event);
1343       break;
1344     default:
1345       res = gst_play_bin_send_event_to_sink (GST_PLAY_BIN (element), event);
1346       break;
1347   }
1348
1349   return res;
1350 }
1351
1352 static GstStateChangeReturn
1353 gst_play_bin_change_state (GstElement * element, GstStateChange transition)
1354 {
1355   GstStateChangeReturn ret;
1356   GstPlayBin *play_bin;
1357
1358   play_bin = GST_PLAY_BIN (element);
1359
1360
1361   switch (transition) {
1362     case GST_STATE_CHANGE_READY_TO_PAUSED:
1363       /* this really is the easiest way to make the state change return
1364        * ASYNC until we added the sinks */
1365       if (!play_bin->fakesink) {
1366         play_bin->fakesink = gst_element_factory_make ("fakesink", "test");
1367         gst_bin_add (GST_BIN (play_bin), play_bin->fakesink);
1368       }
1369       break;
1370     default:
1371       break;
1372   }
1373
1374   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1375   if (ret == GST_STATE_CHANGE_FAILURE)
1376     return ret;
1377
1378   switch (transition) {
1379     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1380       /* Set audio sink state to NULL to release the sound device,
1381        * but only if we own it (else we might be in chain-transition). */
1382       //if (play_bin->audio_sink != NULL &&
1383       //    GST_STATE (play_bin->audio_sink) == GST_STATE_PAUSED) {
1384       //  gst_element_set_state (play_bin->audio_sink, GST_STATE_NULL);
1385       //}
1386       break;
1387     case GST_STATE_CHANGE_PAUSED_TO_READY:
1388       /* Check for NULL because the state transition may be done by
1389        * gst_bin_dispose which is called by gst_play_bin_dispose, and in that
1390        * case, we don't want to run remove_sinks.
1391        * FIXME: should the NULL test be done in remove_sinks? Should we just
1392        * set the state to NULL in gst_play_bin_dispose?
1393        */
1394       if (play_bin->cache != NULL) {
1395         remove_sinks (play_bin);
1396       }
1397       if (play_bin->fakesink) {
1398         gst_element_set_state (play_bin->fakesink, GST_STATE_NULL);
1399         gst_bin_remove (GST_BIN (play_bin), play_bin->fakesink);
1400         play_bin->fakesink = NULL;
1401       }
1402       break;
1403     default:
1404       break;
1405   }
1406
1407   return ret;
1408 }
1409
1410 static gboolean
1411 plugin_init (GstPlugin * plugin)
1412 {
1413   GST_DEBUG_CATEGORY_INIT (gst_play_bin_debug, "playbin", 0, "play bin");
1414
1415 #ifdef ENABLE_NLS
1416   GST_DEBUG ("binding text domain %s to locale dir %s", GETTEXT_PACKAGE,
1417       LOCALEDIR);
1418   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
1419 #endif /* ENABLE_NLS */
1420
1421   return gst_element_register (plugin, "playbin", GST_RANK_NONE,
1422       GST_TYPE_PLAY_BIN);
1423 }
1424
1425 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1426     GST_VERSION_MINOR,
1427     "playbin",
1428     "player bin", plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME,
1429     GST_PACKAGE_ORIGIN)