GESTimeline: add more fixmes
[platform/upstream/gstreamer.git] / ges / ges-timeline.c
1 /* GStreamer Editing Services
2  * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk>
3  *               2009 Nokia Corporation
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 /**
22  * SECTION:ges-timeline
23  * @short_description: Multimedia timeline
24  *
25  * #GESTimeline is the central object for any multimedia timeline.
26  * 
27  * Contains a list of #GESTimelineLayer which users should use to arrange the
28  * various timeline objects through time.
29  *
30  * The output type is determined by the #GESTrack that are set on
31  * the #GESTimeline.
32  */
33
34 #include "gesmarshal.h"
35 #include "ges-internal.h"
36 #include "ges-timeline.h"
37 #include "ges-track.h"
38 #include "ges-timeline-layer.h"
39 #include "ges.h"
40
41
42 G_DEFINE_TYPE (GESTimeline, ges_timeline, GST_TYPE_BIN);
43
44 /* private structure to contain our track-related information */
45
46 typedef struct
47 {
48   GESTimeline *timeline;
49   GESTrack *track;
50   GstPad *pad;                  /* Pad from the track */
51   GstPad *ghostpad;
52 } TrackPrivate;
53
54 enum
55 {
56   TRACK_ADDED,
57   TRACK_REMOVED,
58   LAYER_ADDED,
59   LAYER_REMOVED,
60   LAST_SIGNAL
61 };
62
63 static GstBinClass *parent_class;
64
65 static guint ges_timeline_signals[LAST_SIGNAL] = { 0 };
66
67 gint custom_find_track (TrackPrivate * priv, GESTrack * track);
68 static GstStateChangeReturn
69 ges_timeline_change_state (GstElement * element, GstStateChange transition);
70 static void
71 discoverer_finished_cb (GstDiscoverer * discoverer, GESTimeline * timeline);
72 static void
73 discoverer_discovered_cb (GstDiscoverer * discoverer,
74     GstDiscovererInfo * info, GError * err, GESTimeline * timeline);
75
76 static void
77 ges_timeline_get_property (GObject * object, guint property_id,
78     GValue * value, GParamSpec * pspec)
79 {
80   switch (property_id) {
81     default:
82       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
83   }
84 }
85
86 static void
87 ges_timeline_set_property (GObject * object, guint property_id,
88     const GValue * value, GParamSpec * pspec)
89 {
90   switch (property_id) {
91     default:
92       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
93   }
94 }
95
96 static void
97 ges_timeline_dispose (GObject * object)
98 {
99   GESTimeline *timeline = GES_TIMELINE (object);
100
101   if (timeline->discoverer) {
102     gst_discoverer_stop (timeline->discoverer);
103     g_object_unref (timeline->discoverer);
104     timeline->discoverer = NULL;
105   }
106
107   while (timeline->layers) {
108     GESTimelineLayer *layer = (GESTimelineLayer *) timeline->layers->data;
109     ges_timeline_remove_layer (timeline, layer);
110   }
111
112   /* FIXME: it should be possible to remove tracks before removing
113    * layers, but at the moment this creates a problem because the track
114    * objects aren't notified that their gnlobjects have been destroyed.
115    */
116
117   while (timeline->tracks) {
118     TrackPrivate *priv = (TrackPrivate *) timeline->tracks->data;
119     ges_timeline_remove_track (timeline, priv->track);
120   }
121
122   G_OBJECT_CLASS (ges_timeline_parent_class)->dispose (object);
123 }
124
125 static void
126 ges_timeline_finalize (GObject * object)
127 {
128   G_OBJECT_CLASS (ges_timeline_parent_class)->finalize (object);
129 }
130
131 static void
132 ges_timeline_class_init (GESTimelineClass * klass)
133 {
134   GObjectClass *object_class = G_OBJECT_CLASS (klass);
135   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
136
137   parent_class = g_type_class_peek_parent (klass);
138
139   element_class->change_state = ges_timeline_change_state;
140
141   object_class->get_property = ges_timeline_get_property;
142   object_class->set_property = ges_timeline_set_property;
143   object_class->dispose = ges_timeline_dispose;
144   object_class->finalize = ges_timeline_finalize;
145
146   /**
147    * GESTimeline::track-added
148    * @timeline: the #GESTimeline
149    * @track: the #GESTrack that was added to the timeline
150    *
151    * Will be emitted after the track was added to the timeline.
152    */
153   ges_timeline_signals[TRACK_ADDED] =
154       g_signal_new ("track-added", G_TYPE_FROM_CLASS (klass),
155       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, track_added), NULL,
156       NULL, ges_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GES_TYPE_TRACK);
157
158   /**
159    * GESTimeline::track-removed
160    * @timeline: the #GESTimeline
161    * @track: the #GESTrack that was removed from the timeline
162    *
163    * Will be emitted after the track was removed from the timeline.
164    */
165   ges_timeline_signals[TRACK_REMOVED] =
166       g_signal_new ("track-removed", G_TYPE_FROM_CLASS (klass),
167       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, track_removed),
168       NULL, NULL, ges_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GES_TYPE_TRACK);
169
170   /**
171    * GESTimeline::layer-added
172    * @timeline: the #GESTimeline
173    * @layer: the #GESTimelineLayer that was added to the timeline
174    *
175    * Will be emitted after the layer was added to the timeline.
176    */
177   ges_timeline_signals[LAYER_ADDED] =
178       g_signal_new ("layer-added", G_TYPE_FROM_CLASS (klass),
179       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, layer_added), NULL,
180       NULL, ges_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GES_TYPE_TIMELINE_LAYER);
181
182   /**
183    * GESTimeline::layer-removed
184    * @timeline: the #GESTimeline
185    * @layer: the #GESTimelineLayer that was removed from the timeline
186    *
187    * Will be emitted after the layer was removed from the timeline.
188    */
189   ges_timeline_signals[LAYER_REMOVED] =
190       g_signal_new ("layer-removed", G_TYPE_FROM_CLASS (klass),
191       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, layer_removed),
192       NULL, NULL, ges_marshal_VOID__OBJECT, G_TYPE_NONE, 1,
193       GES_TYPE_TIMELINE_LAYER);
194 }
195
196 static void
197 ges_timeline_init (GESTimeline * self)
198 {
199   self->layers = NULL;
200   self->tracks = NULL;
201
202   /* New discoverer with a 15s timeout */
203   self->discoverer = gst_discoverer_new (15 * GST_SECOND, NULL);
204   g_signal_connect (self->discoverer, "finished",
205       G_CALLBACK (discoverer_finished_cb), self);
206   g_signal_connect (self->discoverer, "discovered",
207       G_CALLBACK (discoverer_discovered_cb), self);
208   gst_discoverer_start (self->discoverer);
209 }
210
211 /**
212  * ges_timeline_new:
213  *
214  * Creates a new empty #GESTimeline.
215  *
216  * Returns: The new timeline.
217  */
218
219 GESTimeline *
220 ges_timeline_new (void)
221 {
222   return g_object_new (GES_TYPE_TIMELINE, NULL);
223 }
224
225 /**
226  * ges_timeline_new_from_uri:
227  * @uri: the URI to load from
228  *
229  * Creates a timeline from the given URI.
230  *
231  * Returns: A new timeline if the uri was loaded successfully, or NULL if the
232  * uri could not be loaded 
233  */
234
235 GESTimeline *
236 ges_timeline_new_from_uri (gchar * uri)
237 {
238   GESTimeline *ret;
239
240   ret = ges_timeline_new ();
241
242   if (!ges_timeline_load_from_uri (ret, uri)) {
243     g_object_unref (ret);
244     return NULL;
245   }
246
247   return ret;
248 }
249
250
251 /**
252  * ges_timeline_load_from_uri:
253  * @timeline: an empty #GESTimeline into which to load the formatter
254  * @uri: The URI to load from
255  *
256  * Loads the contents of URI into the given timeline.
257  *
258  * Returns: TRUE if the timeline was loaded successfully, or FALSE if the uri
259  * could not be loaded.
260  */
261
262 gboolean
263 ges_timeline_load_from_uri (GESTimeline * timeline, gchar * uri)
264 {
265   GESFormatter *p = NULL;
266   gboolean ret = FALSE;
267
268   if (!(p = ges_formatter_new_for_uri (uri))) {
269     GST_ERROR ("unsupported uri '%s'", uri);
270     goto fail;
271   }
272
273   if (!ges_formatter_load_from_uri (p, timeline, uri)) {
274     GST_ERROR ("error deserializing formatter");
275     goto fail;
276   }
277
278   ret = TRUE;
279
280 fail:
281   if (p)
282     g_object_unref (p);
283   return ret;
284 }
285
286 /**
287  * ges_timeline_save_to_uri:
288  * @timeline: a #GESTimeline
289  * @uri: The location to save to
290  *
291  * Saves the timeline to the given location
292  *
293  * Returns: TRUE if the timeline was successfully saved to the given location,
294  * else FALSE.
295  */
296
297 gboolean
298 ges_timeline_save_to_uri (GESTimeline * timeline, gchar * uri)
299 {
300   GESFormatter *p = NULL;
301   gboolean ret = FALSE;
302
303   if (!(p = ges_formatter_new_for_uri (uri))) {
304     GST_ERROR ("unsupported uri '%s'", uri);
305     goto fail;
306   }
307
308   if (!ges_formatter_save_to_uri (p, timeline, uri)) {
309     GST_ERROR ("error serializing formatter");
310     goto fail;
311   }
312
313   ret = TRUE;
314
315 fail:
316   if (p)
317     g_object_unref (p);
318   return ret;
319 }
320
321 static void
322 add_object_to_track (GESTimelineObject * object, GESTrack * track)
323 {
324   if (!ges_timeline_object_create_track_objects (object, track)) {
325     GST_WARNING ("error creating track objects");
326   }
327 }
328
329 static void
330 add_object_to_tracks (GESTimeline * timeline, GESTimelineObject * object)
331 {
332   GList *tmp;
333
334   for (tmp = timeline->tracks; tmp; tmp = g_list_next (tmp)) {
335     TrackPrivate *priv = (TrackPrivate *) tmp->data;
336     GESTrack *track = priv->track;
337
338     GST_LOG ("Trying with track %p", track);
339     add_object_to_track (object, track);
340   }
341 }
342
343
344 static void
345 do_async_start (GESTimeline * timeline)
346 {
347   GstMessage *message;
348   GList *tmp;
349
350   timeline->async_pending = TRUE;
351
352   /* Freeze state of tracks */
353   for (tmp = timeline->tracks; tmp; tmp = tmp->next) {
354     TrackPrivate *priv = (TrackPrivate *) tmp->data;
355     gst_element_set_locked_state ((GstElement *) priv->track, TRUE);
356   }
357
358   message = gst_message_new_async_start (GST_OBJECT_CAST (timeline), FALSE);
359   parent_class->handle_message (GST_BIN_CAST (timeline), message);
360 }
361
362 static void
363 do_async_done (GESTimeline * timeline)
364 {
365   GstMessage *message;
366
367   if (timeline->async_pending) {
368     GList *tmp;
369     /* Unfreeze state of tracks */
370     for (tmp = timeline->tracks; tmp; tmp = tmp->next) {
371       TrackPrivate *priv = (TrackPrivate *) tmp->data;
372       gst_element_set_locked_state ((GstElement *) priv->track, FALSE);
373       gst_element_sync_state_with_parent ((GstElement *) priv->track);
374     }
375
376     GST_DEBUG_OBJECT (timeline, "Emitting async-done");
377     message = gst_message_new_async_done (GST_OBJECT_CAST (timeline));
378     parent_class->handle_message (GST_BIN_CAST (timeline), message);
379
380     timeline->async_pending = FALSE;
381   }
382 }
383
384 static void
385 discoverer_finished_cb (GstDiscoverer * discoverer, GESTimeline * timeline)
386 {
387   do_async_done (timeline);
388 }
389
390 static void
391 discoverer_discovered_cb (GstDiscoverer * discoverer,
392     GstDiscovererInfo * info, GError * err, GESTimeline * timeline)
393 {
394   GList *tmp;
395   gboolean found = FALSE;
396   gboolean is_image = FALSE;
397   GESTimelineFileSource *tfs = NULL;
398   const gchar *uri = gst_discoverer_info_get_uri (info);
399
400   GST_DEBUG ("Discovered uri %s", uri);
401
402   /* Find corresponding TimelineFileSource in the sources */
403   for (tmp = timeline->pendingobjects; tmp; tmp = tmp->next) {
404     tfs = (GESTimelineFileSource *) tmp->data;
405
406     if (!g_strcmp0 (tfs->uri, uri)) {
407       found = TRUE;
408       break;
409     }
410   }
411
412   if (found) {
413     GList *stream_list;
414
415     /* Remove object from list */
416     timeline->pendingobjects =
417         g_list_delete_link (timeline->pendingobjects, tmp);
418
419     /* FIXME : Handle errors in discovery */
420     stream_list = gst_discoverer_info_get_stream_list (info);
421
422     /* Update timelinefilesource properties based on info */
423     for (tmp = stream_list; tmp; tmp = tmp->next) {
424       GstDiscovererStreamInfo *sinf = (GstDiscovererStreamInfo *) tmp->data;
425
426       if (GST_IS_DISCOVERER_AUDIO_INFO (sinf))
427         tfs->supportedformats |= GES_TRACK_TYPE_AUDIO;
428       else if (GST_IS_DISCOVERER_VIDEO_INFO (sinf)) {
429         tfs->supportedformats |= GES_TRACK_TYPE_VIDEO;
430         if (gst_discoverer_video_info_is_image ((GstDiscovererVideoInfo *)
431                 sinf)) {
432           tfs->supportedformats |= GES_TRACK_TYPE_AUDIO;
433           is_image = TRUE;
434         }
435       }
436     }
437
438     if (stream_list)
439       gst_discoverer_stream_info_list_free (stream_list);
440
441     if (is_image) {
442       /* don't set max-duration on still images */
443       g_object_set (tfs, "is_image", (gboolean) TRUE, NULL);
444     }
445
446     else {
447       g_object_set (tfs, "max-duration",
448           gst_discoverer_info_get_duration (info), NULL);
449     }
450
451     /* Continue the processing on tfs */
452     add_object_to_tracks (timeline, GES_TIMELINE_OBJECT (tfs));
453   }
454 }
455
456 static GstStateChangeReturn
457 ges_timeline_change_state (GstElement * element, GstStateChange transition)
458 {
459   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
460   GESTimeline *timeline = GES_TIMELINE (element);
461
462   switch (transition) {
463     case GST_STATE_CHANGE_READY_TO_PAUSED:
464       if (timeline->pendingobjects) {
465         do_async_start (timeline);
466         ret = GST_STATE_CHANGE_ASYNC;
467       }
468       break;
469     default:
470       break;
471   }
472
473   {
474     GstStateChangeReturn bret;
475
476     bret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
477     if (G_UNLIKELY (bret == GST_STATE_CHANGE_NO_PREROLL)) {
478       do_async_done (timeline);
479       ret = bret;
480     }
481   }
482
483   switch (transition) {
484     case GST_STATE_CHANGE_PAUSED_TO_READY:
485       do_async_done (timeline);
486       break;
487     default:
488       break;
489   }
490
491   return ret;
492
493 }
494
495 static void
496 layer_object_added_cb (GESTimelineLayer * layer, GESTimelineObject * object,
497     GESTimeline * timeline)
498 {
499   GST_DEBUG ("New TimelineObject %p added to layer %p", object, layer);
500
501   if (GES_IS_TIMELINE_FILE_SOURCE (object)) {
502     GESTimelineFileSource *tfs = GES_TIMELINE_FILE_SOURCE (object);
503
504     /* Send the filesource to the discoverer if:
505      * * it doesn't have specified supported formats
506      * * OR it doesn't have a specified max-duration
507      * * OR it doesn't have a valid duration  */
508
509     if (tfs->supportedformats == GES_TRACK_TYPE_UNKNOWN ||
510         tfs->maxduration == GST_CLOCK_TIME_NONE || object->duration == 0) {
511       GST_LOG ("Incomplete TimelineFileSource, discovering it");
512       timeline->pendingobjects =
513           g_list_append (timeline->pendingobjects, object);
514       gst_discoverer_discover_uri_async (timeline->discoverer,
515           GES_TIMELINE_FILE_SOURCE (object)->uri);
516     } else
517       add_object_to_tracks (timeline, object);
518   } else {
519     add_object_to_tracks (timeline, object);
520   }
521
522   GST_DEBUG ("done");
523 }
524
525
526 static void
527 layer_object_removed_cb (GESTimelineLayer * layer, GESTimelineObject * object,
528     GESTimeline * timeline)
529 {
530   GList *tmp, *next;
531
532   GST_DEBUG ("TimelineObject %p removed from layer %p", object, layer);
533
534   /* Go over the object's track objects and figure out which one belongs to
535    * the list of tracks we control */
536
537   for (tmp = object->trackobjects; tmp; tmp = next) {
538     GESTrackObject *trobj = (GESTrackObject *) tmp->data;
539
540     next = g_list_next (tmp);
541
542     GST_DEBUG ("Trying to remove TrackObject %p", trobj);
543     if (G_LIKELY (g_list_find_custom (timeline->tracks,
544                 (gconstpointer) trobj->track,
545                 (GCompareFunc) custom_find_track))) {
546       GST_DEBUG ("Belongs to one of the tracks we control");
547       ges_track_remove_object (trobj->track, trobj);
548
549       ges_timeline_object_release_track_object (object, trobj);
550     }
551   }
552
553   GST_DEBUG ("Done");
554 }
555
556 /**
557  * ges_timeline_add_layer:
558  * @timeline: a #GESTimeline
559  * @layer: the #GESTimelineLayer to add
560  *
561  * Add the layer to the timeline. The reference to the @layer will be stolen
562  * by the @timeline.
563  *
564  * Returns: TRUE if the layer was properly added, else FALSE.
565  */
566 gboolean
567 ges_timeline_add_layer (GESTimeline * timeline, GESTimelineLayer * layer)
568 {
569   GList *objects, *tmp;
570
571   GST_DEBUG ("timeline:%p, layer:%p", timeline, layer);
572
573   /* We can only add a layer that doesn't already belong to another timeline */
574   if (G_UNLIKELY (layer->timeline)) {
575     GST_WARNING ("Layer belongs to another timeline, can't add it");
576     return FALSE;
577   }
578
579   /* Add to the list of layers, make sure we don't already control it */
580   if (G_UNLIKELY (g_list_find (timeline->layers, (gconstpointer) layer))) {
581     GST_WARNING ("Layer is already controlled by this timeline");
582     return FALSE;
583   }
584
585   /* Reference is stolen */
586   timeline->layers = g_list_append (timeline->layers, layer);
587
588   /* Inform the layer that it belongs to a new timeline */
589   ges_timeline_layer_set_timeline (layer, timeline);
590
591   /* Connect to 'object-added'/'object-removed' signal from the new layer */
592   g_signal_connect (layer, "object-added", G_CALLBACK (layer_object_added_cb),
593       timeline);
594   g_signal_connect (layer, "object-removed",
595       G_CALLBACK (layer_object_removed_cb), timeline);
596
597   GST_DEBUG ("Done adding layer, emitting 'layer-added' signal");
598   g_signal_emit (timeline, ges_timeline_signals[LAYER_ADDED], 0, layer);
599
600   /* add any existing timeline objects to the timeline */
601   objects = ges_timeline_layer_get_objects (layer);
602   for (tmp = objects; tmp; tmp = tmp->next) {
603     layer_object_added_cb (layer, tmp->data, timeline);
604     g_object_unref (tmp->data);
605     tmp->data = NULL;
606   }
607   g_list_free (objects);
608
609   return TRUE;
610 }
611
612 /**
613  * ges_timeline_remove_layer:
614  * @timeline: a #GESTimeline
615  * @layer: the #GESTimelineLayer to remove
616  *
617  * Removes the layer from the timeline. The reference that the @timeline holds on
618  * the layer will be dropped. If you wish to use the @layer after calling this
619  * method, you need to take a reference before calling.
620  *
621  * Returns: TRUE if the layer was properly removed, else FALSE.
622  */
623
624 gboolean
625 ges_timeline_remove_layer (GESTimeline * timeline, GESTimelineLayer * layer)
626 {
627   GList *layer_objects, *tmp;
628
629   GST_DEBUG ("timeline:%p, layer:%p", timeline, layer);
630
631   if (G_UNLIKELY (!g_list_find (timeline->layers, layer))) {
632     GST_WARNING ("Layer doesn't belong to this timeline");
633     return FALSE;
634   }
635
636   /* remove objects from any private data structures */
637
638   layer_objects = ges_timeline_layer_get_objects (layer);
639   for (tmp = layer_objects; tmp; tmp = tmp->next) {
640     layer_object_removed_cb (layer, GES_TIMELINE_OBJECT (tmp->data), timeline);
641     g_object_unref (G_OBJECT (tmp->data));
642     tmp->data = NULL;
643   }
644   g_list_free (layer_objects);
645
646   /* Disconnect signals */
647   GST_DEBUG ("Disconnecting signal callbacks");
648   g_signal_handlers_disconnect_by_func (layer, layer_object_added_cb, timeline);
649   g_signal_handlers_disconnect_by_func (layer, layer_object_removed_cb,
650       timeline);
651
652   timeline->layers = g_list_remove (timeline->layers, layer);
653
654   ges_timeline_layer_set_timeline (layer, NULL);
655
656   g_signal_emit (timeline, ges_timeline_signals[LAYER_REMOVED], 0, layer);
657
658   g_object_unref (layer);
659
660   return TRUE;
661 }
662
663 static void
664 pad_added_cb (GESTrack * track, GstPad * pad, TrackPrivate * priv)
665 {
666   gchar *padname;
667
668
669   GST_DEBUG ("track:%p, pad:%s:%s", track, GST_DEBUG_PAD_NAME (pad));
670
671   if (G_UNLIKELY (priv->pad)) {
672     GST_WARNING ("We are already controlling a pad for this track");
673     return;
674   }
675
676   /* Remember the pad */
677   priv->pad = pad;
678
679   /* ghost it ! */
680   GST_DEBUG ("Ghosting pad and adding it to ourself");
681   padname = g_strdup_printf ("track_%p_src", track);
682   priv->ghostpad = gst_ghost_pad_new (padname, pad);
683   g_free (padname);
684   gst_pad_set_active (priv->ghostpad, TRUE);
685   gst_element_add_pad (GST_ELEMENT (priv->timeline), priv->ghostpad);
686 }
687
688 static void
689 pad_removed_cb (GESTrack * track, GstPad * pad, TrackPrivate * priv)
690 {
691   GST_DEBUG ("track:%p, pad:%s:%s", track, GST_DEBUG_PAD_NAME (pad));
692
693   if (G_UNLIKELY (priv->pad != pad)) {
694     GST_WARNING ("Not the pad we're controlling");
695     return;
696   }
697
698   if (G_UNLIKELY (priv->ghostpad == NULL)) {
699     GST_WARNING ("We don't have a ghostpad for this pad !");
700     return;
701   }
702
703   GST_DEBUG ("Removing ghostpad");
704   gst_pad_set_active (priv->ghostpad, FALSE);
705   gst_element_remove_pad (GST_ELEMENT (priv->timeline), priv->ghostpad);
706   priv->ghostpad = NULL;
707   priv->pad = NULL;
708 }
709
710 gint
711 custom_find_track (TrackPrivate * priv, GESTrack * track)
712 {
713   if (priv->track == track)
714     return 0;
715   return -1;
716 }
717
718 /**
719  * ges_timeline_add_track:
720  * @timeline: a #GESTimeline
721  * @track: the #GESTrack to add
722  *
723  * Add a track to the timeline. The reference to the track will be stolen by the
724  * pipeline.
725  *
726  * Returns: TRUE if the track was properly added, else FALSE.
727  */
728
729 /* FIXME: create track objects for timeline objects which have already been
730  * added to existing layers.
731  */
732
733 gboolean
734 ges_timeline_add_track (GESTimeline * timeline, GESTrack * track)
735 {
736   TrackPrivate *priv;
737   GList *tmp;
738
739   GST_DEBUG ("timeline:%p, track:%p", timeline, track);
740
741   /* make sure we don't already control it */
742   if (G_UNLIKELY (g_list_find_custom (timeline->tracks, (gconstpointer) track,
743               (GCompareFunc) custom_find_track))) {
744     GST_WARNING ("Track is already controlled by this timeline");
745     return FALSE;
746   }
747
748   /* Add the track to ourself (as a GstBin) 
749    * Reference is stolen ! */
750   if (G_UNLIKELY (!gst_bin_add (GST_BIN (timeline), GST_ELEMENT (track)))) {
751     GST_WARNING ("Couldn't add track to ourself (GST)");
752     return FALSE;
753   }
754
755   priv = g_new0 (TrackPrivate, 1);
756   priv->timeline = timeline;
757   priv->track = track;
758
759   /* Add the track to the list of tracks we track */
760   timeline->tracks = g_list_append (timeline->tracks, priv);
761
762   /* Listen to pad-added/-removed */
763   g_signal_connect (track, "pad-added", (GCallback) pad_added_cb, priv);
764   g_signal_connect (track, "pad-removed", (GCallback) pad_removed_cb, priv);
765
766   /* Inform the track that it's currently being used by ourself */
767   ges_track_set_timeline (track, timeline);
768
769   GST_DEBUG ("Done adding track, emitting 'track-added' signal");
770
771   /* emit 'track-added' */
772   g_signal_emit (timeline, ges_timeline_signals[TRACK_ADDED], 0, track);
773
774   /* ensure that each existing timeline object has the opportunity to create a
775    * track object for this track*/
776
777   for (tmp = timeline->layers; tmp; tmp = tmp->next) {
778     GList *objects, *obj;
779     objects = ges_timeline_layer_get_objects (tmp->data);
780
781     for (obj = objects; obj; obj = obj->next) {
782       add_object_to_track (obj->data, track);
783       g_object_unref (obj->data);
784       obj->data = NULL;
785     }
786     g_list_free (objects);
787   }
788
789   return TRUE;
790 }
791
792 /**
793  * ges_timeline_remove_track:
794  * @timeline: a #GESTimeline
795  * @track: the #GESTrack to remove
796  *
797  * Remove the @track from the @timeline. The reference stolen when adding the
798  * @track will be removed. If you wish to use the @track after calling this
799  * function you must ensure that you have a reference to it.
800  *
801  * Returns: TRUE if the @track was properly removed, else FALSE.
802  */
803
804 /* FIXME: release any track objects associated with this layer. currenly this
805  * will not happen if you remove the track before removing *all*
806  * timelineobjects which have a track object in this track.
807  */
808
809 gboolean
810 ges_timeline_remove_track (GESTimeline * timeline, GESTrack * track)
811 {
812   GList *tmp;
813   TrackPrivate *priv;
814
815   GST_DEBUG ("timeline:%p, track:%p", timeline, track);
816
817   if (G_UNLIKELY (!(tmp =
818               g_list_find_custom (timeline->tracks, (gconstpointer) track,
819                   (GCompareFunc) custom_find_track)))) {
820     GST_WARNING ("Track doesn't belong to this timeline");
821     return FALSE;
822   }
823
824   priv = tmp->data;
825   timeline->tracks = g_list_remove (timeline->tracks, priv);
826
827   ges_track_set_timeline (track, NULL);
828
829   /* Remove ghost pad */
830   if (priv->ghostpad) {
831     GST_DEBUG ("Removing ghostpad");
832     gst_pad_set_active (priv->ghostpad, FALSE);
833     gst_ghost_pad_set_target ((GstGhostPad *) priv->ghostpad, NULL);
834     gst_element_remove_pad (GST_ELEMENT (timeline), priv->ghostpad);
835   }
836
837   /* Remove pad-added/-removed handlers */
838   g_signal_handlers_disconnect_by_func (track, pad_added_cb, priv);
839   g_signal_handlers_disconnect_by_func (track, pad_removed_cb, priv);
840
841   /* Signal track removal to all layers/objects */
842   g_signal_emit (timeline, ges_timeline_signals[TRACK_REMOVED], 0, track);
843
844   /* remove track from our bin */
845   gst_object_ref (track);
846   if (G_UNLIKELY (!gst_bin_remove (GST_BIN (timeline), GST_ELEMENT (track)))) {
847     GST_WARNING ("Couldn't remove track to ourself (GST)");
848     gst_object_unref (track);
849     return FALSE;
850   }
851
852   /* set track state to NULL */
853
854   gst_element_set_state (GST_ELEMENT (track), GST_STATE_NULL);
855
856   gst_object_unref (track);
857
858   g_free (priv);
859
860   return TRUE;
861 }
862
863 /**
864  * ges_timeline_get_track_for_pad:
865  * @timeline: The #GESTimeline
866  * @pad: The #GstPad
867  *
868  * Search the #GESTrack corresponding to the given @timeline's @pad.
869  *
870  * Returns: The corresponding #GESTrack if it is found, or #NULL if there is
871  * an error.
872  */
873
874 GESTrack *
875 ges_timeline_get_track_for_pad (GESTimeline * timeline, GstPad * pad)
876 {
877   GList *tmp;
878
879   for (tmp = timeline->tracks; tmp; tmp = g_list_next (tmp)) {
880     TrackPrivate *priv = (TrackPrivate *) tmp->data;
881     if (pad == priv->ghostpad)
882       return priv->track;
883   }
884
885   return NULL;
886 }
887
888 /**
889  * ges_timeline_get_tracks:
890  * @timeline: a #GESTimeline
891  *
892  * Returns the list of #GESTrack used by the Timeline.
893  *
894  * Returns: A list of #GESTrack. The caller should unref each track
895  * once he is done with them. */
896 GList *
897 ges_timeline_get_tracks (GESTimeline * timeline)
898 {
899   GList *tmp, *res = NULL;
900
901   for (tmp = timeline->tracks; tmp; tmp = g_list_next (tmp)) {
902     TrackPrivate *priv = (TrackPrivate *) tmp->data;
903     res = g_list_append (res, g_object_ref (priv->track));
904   }
905
906   return res;
907 }