GESTimeline: Fix reference counting of tracks, add docs.
[platform/upstream/gstreamer.git] / ges / ges-timeline.c
1 /* GStreamer Editing Services
2  * Copyright (C) 2009 Edward Hervey <bilboed@bilboed.com>
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 /**
21  * SECTION:ges-timeline
22  * @short_description: Multimedia timeline
23  *
24  * #GESTimeline is the central object for any multimedia timeline.
25  * 
26  * Contains a list of #GESTimelineLayer which users should use to arrange the
27  * various timeline objects through time.
28  *
29  * The output type is determined by the #GESTimelineTrack that are set on
30  * the #GESTimeline.
31  */
32
33 #include "gesmarshal.h"
34 #include "ges-internal.h"
35 #include "ges-timeline.h"
36 #include "ges-track.h"
37 #include "ges-timeline-layer.h"
38 #include "ges.h"
39
40
41 G_DEFINE_TYPE (GESTimeline, ges_timeline, GST_TYPE_BIN);
42
43 /* private structure to contain our track-related information */
44
45 typedef struct
46 {
47   GESTimeline *timeline;
48   GESTrack *track;
49   GstPad *pad;                  /* Pad from the track */
50   GstPad *ghostpad;
51 } TrackPrivate;
52
53 enum
54 {
55   TRACK_ADDED,
56   TRACK_REMOVED,
57   LAYER_ADDED,
58   LAYER_REMOVED,
59   LAST_SIGNAL
60 };
61
62 static guint ges_timeline_signals[LAST_SIGNAL] = { 0 };
63
64 gint custom_find_track (TrackPrivate * priv, GESTrack * track);
65 static void
66 ges_timeline_get_property (GObject * object, guint property_id,
67     GValue * value, GParamSpec * pspec)
68 {
69   switch (property_id) {
70     default:
71       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
72   }
73 }
74
75 static void
76 ges_timeline_set_property (GObject * object, guint property_id,
77     const GValue * value, GParamSpec * pspec)
78 {
79   switch (property_id) {
80     default:
81       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
82   }
83 }
84
85 static void
86 ges_timeline_dispose (GObject * object)
87 {
88   G_OBJECT_CLASS (ges_timeline_parent_class)->dispose (object);
89 }
90
91 static void
92 ges_timeline_finalize (GObject * object)
93 {
94   G_OBJECT_CLASS (ges_timeline_parent_class)->finalize (object);
95 }
96
97 static void
98 ges_timeline_class_init (GESTimelineClass * klass)
99 {
100   GObjectClass *object_class = G_OBJECT_CLASS (klass);
101
102   object_class->get_property = ges_timeline_get_property;
103   object_class->set_property = ges_timeline_set_property;
104   object_class->dispose = ges_timeline_dispose;
105   object_class->finalize = ges_timeline_finalize;
106
107   /* Signals
108    * 'track-added'
109    * 'track-removed'
110    * 'layer-added'
111    * 'layer-removed'
112    */
113
114   ges_timeline_signals[TRACK_ADDED] =
115       g_signal_new ("track-added", G_TYPE_FROM_CLASS (klass),
116       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, track_added), NULL,
117       NULL, ges_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GES_TYPE_TRACK);
118
119   ges_timeline_signals[TRACK_REMOVED] =
120       g_signal_new ("track-removed", G_TYPE_FROM_CLASS (klass),
121       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, track_removed),
122       NULL, NULL, ges_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GES_TYPE_TRACK);
123
124   ges_timeline_signals[LAYER_ADDED] =
125       g_signal_new ("layer-added", G_TYPE_FROM_CLASS (klass),
126       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, layer_added), NULL,
127       NULL, ges_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GES_TYPE_TIMELINE_LAYER);
128
129   ges_timeline_signals[LAYER_REMOVED] =
130       g_signal_new ("layer-removed", G_TYPE_FROM_CLASS (klass),
131       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, layer_removed),
132       NULL, NULL, ges_marshal_VOID__OBJECT, G_TYPE_NONE, 1,
133       GES_TYPE_TIMELINE_LAYER);
134 }
135
136 static void
137 ges_timeline_init (GESTimeline * self)
138 {
139   self->layers = NULL;
140   self->tracks = NULL;
141 }
142
143 GESTimeline *
144 ges_timeline_new (void)
145 {
146   return g_object_new (GES_TYPE_TIMELINE, NULL);
147 }
148
149 GESTimeline *
150 ges_timeline_load_from_uri (gchar * uri)
151 {
152   /* FIXME : IMPLEMENT */
153   return NULL;
154 }
155
156 gboolean
157 ges_timeline_save (GESTimeline * timeline, gchar * uri)
158 {
159   /* FIXME : IMPLEMENT */
160   return FALSE;
161 }
162
163 static void
164 layer_object_added_cb (GESTimelineLayer * layer, GESTimelineObject * object,
165     GESTimeline * timeline)
166 {
167   GList *tmp;
168
169   GST_DEBUG ("New TimelineObject %p added to layer %p", object, layer);
170
171   for (tmp = timeline->tracks; tmp; tmp = g_list_next (tmp)) {
172     TrackPrivate *priv = (TrackPrivate *) tmp->data;
173     GESTrack *track = priv->track;
174     GESTrackObject *trobj;
175
176     GST_LOG ("Trying with track %p", track);
177
178     if (G_UNLIKELY (!(trobj =
179                 ges_timeline_object_create_track_object (object, track)))) {
180       GST_WARNING ("Couldn't create TrackObject for TimelineObject");
181       continue;
182     }
183
184     GST_LOG ("Got new TrackObject %p, adding it to track", trobj);
185     ges_track_add_object (track, trobj);
186   }
187
188   GST_DEBUG ("done");
189 }
190
191
192 static void
193 layer_object_removed_cb (GESTimelineLayer * layer, GESTimelineObject * object,
194     GESTimeline * timeline)
195 {
196   GList *tmp;
197
198   GST_DEBUG ("TimelineObject %p removed from layer %p", object, layer);
199
200   /* Go over the object's track objects and figure out which one belongs to
201    * the list of tracks we control */
202
203   for (tmp = object->trackobjects; tmp; tmp = g_list_next (tmp)) {
204     GESTrackObject *trobj = (GESTrackObject *) tmp->data;
205
206     GST_DEBUG ("Trying to remove TrackObject %p", trobj);
207     if (G_LIKELY (g_list_find_custom (timeline->tracks,
208                 (gconstpointer) trobj->track,
209                 (GCompareFunc) custom_find_track))) {
210       GST_DEBUG ("Belongs to one of the tracks we control");
211       ges_track_remove_object (trobj->track, trobj);
212
213       ges_timeline_object_release_track_object (object, trobj);
214     }
215   }
216
217   GST_DEBUG ("Done");
218 }
219
220 /**
221  * ges_timeline_add_layer:
222  * @timeline: a #GESTimeline
223  * @layer: the #GESTimelineLayer to add
224  *
225  * Add the layer to the timeline. The reference to the @layer will be stolen
226  * by the @timeline.
227  *
228  * Returns: TRUE if the layer was properly added, else FALSE.
229  */
230 gboolean
231 ges_timeline_add_layer (GESTimeline * timeline, GESTimelineLayer * layer)
232 {
233   GST_DEBUG ("timeline:%p, layer:%p", timeline, layer);
234
235   /* We can only add a layer that doesn't already belong to another timeline */
236   if (G_UNLIKELY (layer->timeline)) {
237     GST_WARNING ("Layer belongs to another timeline, can't add it");
238     return FALSE;
239   }
240
241   /* Add to the list of layers, make sure we don't already control it */
242   if (G_UNLIKELY (g_list_find (timeline->layers, (gconstpointer) layer))) {
243     GST_WARNING ("Layer is already controlled by this timeline");
244     return FALSE;
245   }
246
247   /* Reference is stolen */
248   timeline->layers = g_list_append (timeline->layers, layer);
249
250   /* Inform the layer that it belongs to a new timeline */
251   ges_timeline_layer_set_timeline (layer, timeline);
252
253   /* FIXME : GO OVER THE LIST OF ALREADY EXISTING TIMELINE OBJECTS IN THAT
254    * LAYER AND ADD THEM !!! */
255
256   /* Connect to 'object-added'/'object-removed' signal from the new layer */
257   g_signal_connect (layer, "object-added", G_CALLBACK (layer_object_added_cb),
258       timeline);
259   g_signal_connect (layer, "object-removed",
260       G_CALLBACK (layer_object_removed_cb), timeline);
261
262   GST_DEBUG ("Done adding layer, emitting 'layer-added' signal");
263   g_signal_emit (timeline, ges_timeline_signals[LAYER_ADDED], 0, layer);
264
265   return TRUE;
266 }
267
268 gboolean
269 ges_timeline_remove_layer (GESTimeline * timeline, GESTimelineLayer * layer)
270 {
271   GST_DEBUG ("timeline:%p, layer:%p", timeline, layer);
272
273   if (G_UNLIKELY (!g_list_find (timeline->layers, layer))) {
274     GST_WARNING ("Layer doesn't belong to this timeline");
275     return FALSE;
276   }
277
278   /* Disconnect signals */
279   GST_DEBUG ("Disconnecting signal callbacks");
280   g_signal_handlers_disconnect_by_func (layer, layer_object_added_cb, timeline);
281   g_signal_handlers_disconnect_by_func (layer, layer_object_removed_cb,
282       timeline);
283
284   timeline->layers = g_list_remove (timeline->layers, layer);
285
286   ges_timeline_layer_set_timeline (layer, NULL);
287
288   g_signal_emit (timeline, ges_timeline_signals[LAYER_REMOVED], 0, layer);
289
290   g_object_unref (layer);
291
292   return TRUE;
293 }
294
295 static void
296 pad_added_cb (GESTrack * track, GstPad * pad, TrackPrivate * priv)
297 {
298   gchar *padname;
299
300   GST_DEBUG ("track:%p, pad:%s:%s", track, GST_DEBUG_PAD_NAME (pad));
301
302   if (G_UNLIKELY (priv->pad)) {
303     GST_WARNING ("We are already controlling a pad for this track");
304     return;
305   }
306
307   /* Remember the pad */
308   priv->pad = pad;
309
310   /* ghost it ! */
311   GST_DEBUG ("Ghosting pad and adding it to ourself");
312   padname = g_strdup_printf ("track_%p_src", track);
313   priv->ghostpad = gst_ghost_pad_new (padname, pad);
314   g_free (padname);
315   gst_pad_set_active (priv->ghostpad, TRUE);
316   gst_element_add_pad (GST_ELEMENT (priv->timeline), priv->ghostpad);
317 }
318
319 static void
320 pad_removed_cb (GESTrack * track, GstPad * pad, TrackPrivate * priv)
321 {
322   GST_DEBUG ("track:%p, pad:%s:%s", track, GST_DEBUG_PAD_NAME (pad));
323
324   if (G_UNLIKELY (priv->pad != pad)) {
325     GST_WARNING ("Not the pad we're controlling");
326     return;
327   }
328
329   if (G_UNLIKELY (priv->ghostpad == NULL)) {
330     GST_WARNING ("We don't have a ghostpad for this pad !");
331     return;
332   }
333
334   GST_DEBUG ("Removing ghostpad");
335   gst_pad_set_active (priv->ghostpad, FALSE);
336   gst_element_remove_pad (GST_ELEMENT (priv->timeline), priv->ghostpad);
337   gst_object_unref (priv->ghostpad);
338   priv->ghostpad = NULL;
339   priv->pad = NULL;
340 }
341
342 gint
343 custom_find_track (TrackPrivate * priv, GESTrack * track)
344 {
345   if (priv->track == track)
346     return 0;
347   return -1;
348 }
349
350 /**
351  * ges_timeline_add_track:
352  * @timeline: a #GESTimeline
353  * @track: the #GESTrack to add
354  *
355  * Add a track to the timeline. The reference to the track will be stolen by the
356  * pipeline.
357  *
358  * Returns: TRUE if the track was properly added, else FALSE.
359  */
360
361 gboolean
362 ges_timeline_add_track (GESTimeline * timeline, GESTrack * track)
363 {
364   GList *tmp;
365   TrackPrivate *priv;
366
367   GST_DEBUG ("timeline:%p, track:%p", timeline, track);
368
369   /* make sure we don't already control it */
370   if (G_UNLIKELY ((tmp =
371               g_list_find_custom (timeline->tracks, (gconstpointer) track,
372                   (GCompareFunc) custom_find_track)))) {
373     GST_WARNING ("Track is already controlled by this timeline");
374     return FALSE;
375   }
376
377   /* Add the track to ourself (as a GstBin) 
378    * Reference is stolen ! */
379   if (G_UNLIKELY (!gst_bin_add (GST_BIN (timeline), GST_ELEMENT (track)))) {
380     GST_WARNING ("Couldn't add track to ourself (GST)");
381     return FALSE;
382   }
383
384   priv = g_new0 (TrackPrivate, 1);
385   priv->timeline = timeline;
386   priv->track = track;
387
388   /* Add the track to the list of tracks we track */
389   timeline->tracks = g_list_append (timeline->tracks, priv);
390
391   /* Listen to pad-added/-removed */
392   g_signal_connect (track, "pad-added", (GCallback) pad_added_cb, priv);
393   g_signal_connect (track, "pad-removed", (GCallback) pad_removed_cb, priv);
394
395   /* Inform the track that it's currently being used by ourself */
396   ges_track_set_timeline (track, timeline);
397
398   GST_DEBUG ("Done adding track, emitting 'track-added' signal");
399
400   /* emit 'track-added' */
401   g_signal_emit (timeline, ges_timeline_signals[TRACK_ADDED], 0, track);
402
403   return TRUE;
404 }
405
406 /**
407  * ges_timeline_remove_track:
408  * @timeline: a #GESTimeline
409  * @track: the #GESTrack to remove
410  *
411  * Remove the @track from the @timeline. The reference stolen when adding the
412  * @track will be removed. If you wish to use the @track after calling this
413  * function you must ensure that you have a reference to it.
414  *
415  * Returns: TRUE if the @track was properly removed, else FALSE.
416  */
417 gboolean
418 ges_timeline_remove_track (GESTimeline * timeline, GESTrack * track)
419 {
420   GList *tmp;
421   TrackPrivate *priv;
422
423   GST_DEBUG ("timeline:%p, track:%p", timeline, track);
424
425   if (G_UNLIKELY (!(tmp =
426               g_list_find_custom (timeline->tracks, (gconstpointer) track,
427                   (GCompareFunc) custom_find_track)))) {
428     GST_WARNING ("Track doesn't belong to this timeline");
429     return FALSE;
430   }
431
432   priv = tmp->data;
433   timeline->tracks = g_list_remove (timeline->tracks, priv);
434
435   ges_track_set_timeline (track, NULL);
436
437   /* Remove ghost pad */
438   if (priv->ghostpad) {
439     GST_DEBUG ("Removing ghostpad");
440     gst_pad_set_active (priv->ghostpad, FALSE);
441     gst_ghost_pad_set_target ((GstGhostPad *) priv->ghostpad, NULL);
442     gst_element_remove_pad (GST_ELEMENT (timeline), priv->ghostpad);
443   }
444
445   /* Remove pad-added/-removed handlers */
446   g_signal_handlers_disconnect_by_func (track, pad_added_cb, priv);
447   g_signal_handlers_disconnect_by_func (track, pad_removed_cb, priv);
448
449   /* Signal track removal to all layers/objects */
450   g_signal_emit (timeline, ges_timeline_signals[TRACK_REMOVED], 0, track);
451
452   /* remove track from our bin */
453   if (G_UNLIKELY (!gst_bin_remove (GST_BIN (timeline), GST_ELEMENT (track)))) {
454     GST_WARNING ("Couldn't remove track to ourself (GST)");
455     return FALSE;
456   }
457
458   g_free (priv);
459
460   return TRUE;
461 }
462
463 /**
464  * ges_timeline_get_track_for_pad:
465  * @timeline: The #GESTimeline
466  * @pad: The #GstPad
467  *
468  * Search the #GESTrack corresponding to the given @timeline's @pad.
469  *
470  * Returns: The corresponding #GESTrack if it is found, or #NULL if there is
471  * an error.
472  */
473
474 GESTrack *
475 ges_timeline_get_track_for_pad (GESTimeline * timeline, GstPad * pad)
476 {
477   GList *tmp;
478
479   for (tmp = timeline->tracks; tmp; tmp = g_list_next (tmp)) {
480     TrackPrivate *priv = (TrackPrivate *) tmp->data;
481     if (pad == priv->ghostpad)
482       return priv->track;
483   }
484
485   return NULL;
486 }