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