GESTimeLine(PipeLine): remove additional unref
[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 #GESTimelineTrack 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 guint ges_timeline_signals[LAST_SIGNAL] = { 0 };
64
65 gint custom_find_track (TrackPrivate * priv, GESTrack * track);
66 static void
67 ges_timeline_get_property (GObject * object, guint property_id,
68     GValue * value, GParamSpec * pspec)
69 {
70   switch (property_id) {
71     default:
72       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
73   }
74 }
75
76 static void
77 ges_timeline_set_property (GObject * object, guint property_id,
78     const 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_dispose (GObject * object)
88 {
89   G_OBJECT_CLASS (ges_timeline_parent_class)->dispose (object);
90 }
91
92 static void
93 ges_timeline_finalize (GObject * object)
94 {
95   G_OBJECT_CLASS (ges_timeline_parent_class)->finalize (object);
96 }
97
98 static void
99 ges_timeline_class_init (GESTimelineClass * klass)
100 {
101   GObjectClass *object_class = G_OBJECT_CLASS (klass);
102
103   object_class->get_property = ges_timeline_get_property;
104   object_class->set_property = ges_timeline_set_property;
105   object_class->dispose = ges_timeline_dispose;
106   object_class->finalize = ges_timeline_finalize;
107
108
109   /**
110    * GESTimeline::track-added
111    * @timeline: the #GESTimeline
112    * @track: the #GESTrack that was added to the timeline
113    *
114    * Will be emitted after the track was added to the timeline.
115    */
116   ges_timeline_signals[TRACK_ADDED] =
117       g_signal_new ("track-added", G_TYPE_FROM_CLASS (klass),
118       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, track_added), NULL,
119       NULL, ges_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GES_TYPE_TRACK);
120
121   /**
122    * GESTimeline::track-removed
123    * @timeline: the #GESTimeline
124    * @track: the #GESTrack that was removed from the timeline
125    *
126    * Will be emitted after the track was removed from the timeline.
127    */
128   ges_timeline_signals[TRACK_REMOVED] =
129       g_signal_new ("track-removed", G_TYPE_FROM_CLASS (klass),
130       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, track_removed),
131       NULL, NULL, ges_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GES_TYPE_TRACK);
132
133   /**
134    * GESTimeline::layer-added
135    * @timeline: the #GESTimeline
136    * @layer: the #GESTimelineLayer that was added to the timeline
137    *
138    * Will be emitted after the layer was added to the timeline.
139    */
140   ges_timeline_signals[LAYER_ADDED] =
141       g_signal_new ("layer-added", G_TYPE_FROM_CLASS (klass),
142       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, layer_added), NULL,
143       NULL, ges_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GES_TYPE_TIMELINE_LAYER);
144
145   /**
146    * GESTimeline::layer-removed
147    * @timeline: the #GESTimeline
148    * @layer: the #GESTimelineLayer that was removed from the timeline
149    *
150    * Will be emitted after the layer was removed from the timeline.
151    */
152   ges_timeline_signals[LAYER_REMOVED] =
153       g_signal_new ("layer-removed", G_TYPE_FROM_CLASS (klass),
154       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESTimelineClass, layer_removed),
155       NULL, NULL, ges_marshal_VOID__OBJECT, G_TYPE_NONE, 1,
156       GES_TYPE_TIMELINE_LAYER);
157 }
158
159 static void
160 ges_timeline_init (GESTimeline * self)
161 {
162   self->layers = NULL;
163   self->tracks = NULL;
164 }
165
166 /**
167  * ges_timeline_new:
168  *
169  * Creates a new empty #GESTimeline.
170  *
171  * Returns: The new timeline.
172  */
173
174 GESTimeline *
175 ges_timeline_new (void)
176 {
177   return g_object_new (GES_TYPE_TIMELINE, NULL);
178 }
179
180 /**
181  * ges_timeline_load_from_uri:
182  * @uri: The URI to load from
183  *
184  * Creates a timeline from the contents of given uri.
185  *
186  * NOT_IMPLEMENTED !
187  *
188  * Returns: A new #GESTimeline if loading was successful, else NULL.
189  */
190
191 GESTimeline *
192 ges_timeline_load_from_uri (gchar * uri)
193 {
194   /* FIXME : IMPLEMENT */
195   return NULL;
196 }
197
198 /**
199  * ges_timeline_save:
200  * @timeline: a #GESTimeline
201  * @uri: The location to save to
202  *
203  * Saves the timeline to the given location
204  *
205  * NOT_IMPLEMENTED !
206  *
207  * Returns: TRUE if the timeline was successfully saved to the given location,
208  * else FALSE.
209  */
210
211 gboolean
212 ges_timeline_save (GESTimeline * timeline, gchar * uri)
213 {
214   /* FIXME : IMPLEMENT */
215   return FALSE;
216 }
217
218 static void
219 layer_object_added_cb (GESTimelineLayer * layer, GESTimelineObject * object,
220     GESTimeline * timeline)
221 {
222   GList *tmp;
223
224   GST_DEBUG ("New TimelineObject %p added to layer %p", object, layer);
225
226   for (tmp = timeline->tracks; tmp; tmp = g_list_next (tmp)) {
227     TrackPrivate *priv = (TrackPrivate *) tmp->data;
228     GESTrack *track = priv->track;
229     GESTrackObject *trobj;
230
231     GST_LOG ("Trying with track %p", track);
232
233     if (G_UNLIKELY (!(trobj =
234                 ges_timeline_object_create_track_object (object, track)))) {
235       GST_WARNING ("Couldn't create TrackObject for TimelineObject");
236       continue;
237     }
238
239     GST_LOG ("Got new TrackObject %p, adding it to track", trobj);
240     ges_track_add_object (track, trobj);
241   }
242
243   GST_DEBUG ("done");
244 }
245
246
247 static void
248 layer_object_removed_cb (GESTimelineLayer * layer, GESTimelineObject * object,
249     GESTimeline * timeline)
250 {
251   GList *tmp, *next;
252
253   GST_DEBUG ("TimelineObject %p removed from layer %p", object, layer);
254
255   /* Go over the object's track objects and figure out which one belongs to
256    * the list of tracks we control */
257
258   for (tmp = object->trackobjects; tmp; tmp = next) {
259     GESTrackObject *trobj = (GESTrackObject *) tmp->data;
260
261     next = g_list_next (tmp);
262
263     GST_DEBUG ("Trying to remove TrackObject %p", trobj);
264     if (G_LIKELY (g_list_find_custom (timeline->tracks,
265                 (gconstpointer) trobj->track,
266                 (GCompareFunc) custom_find_track))) {
267       GST_DEBUG ("Belongs to one of the tracks we control");
268       ges_track_remove_object (trobj->track, trobj);
269
270       ges_timeline_object_release_track_object (object, trobj);
271     }
272   }
273
274   GST_DEBUG ("Done");
275 }
276
277 /**
278  * ges_timeline_add_layer:
279  * @timeline: a #GESTimeline
280  * @layer: the #GESTimelineLayer to add
281  *
282  * Add the layer to the timeline. The reference to the @layer will be stolen
283  * by the @timeline.
284  *
285  * Returns: TRUE if the layer was properly added, else FALSE.
286  */
287 gboolean
288 ges_timeline_add_layer (GESTimeline * timeline, GESTimelineLayer * layer)
289 {
290   GST_DEBUG ("timeline:%p, layer:%p", timeline, layer);
291
292   /* We can only add a layer that doesn't already belong to another timeline */
293   if (G_UNLIKELY (layer->timeline)) {
294     GST_WARNING ("Layer belongs to another timeline, can't add it");
295     return FALSE;
296   }
297
298   /* Add to the list of layers, make sure we don't already control it */
299   if (G_UNLIKELY (g_list_find (timeline->layers, (gconstpointer) layer))) {
300     GST_WARNING ("Layer is already controlled by this timeline");
301     return FALSE;
302   }
303
304   /* Reference is stolen */
305   timeline->layers = g_list_append (timeline->layers, layer);
306
307   /* Inform the layer that it belongs to a new timeline */
308   ges_timeline_layer_set_timeline (layer, timeline);
309
310   /* FIXME : GO OVER THE LIST OF ALREADY EXISTING TIMELINE OBJECTS IN THAT
311    * LAYER AND ADD THEM !!! */
312
313   /* Connect to 'object-added'/'object-removed' signal from the new layer */
314   g_signal_connect (layer, "object-added", G_CALLBACK (layer_object_added_cb),
315       timeline);
316   g_signal_connect (layer, "object-removed",
317       G_CALLBACK (layer_object_removed_cb), timeline);
318
319   GST_DEBUG ("Done adding layer, emitting 'layer-added' signal");
320   g_signal_emit (timeline, ges_timeline_signals[LAYER_ADDED], 0, layer);
321
322   return TRUE;
323 }
324
325 /**
326  * ges_timeline_remove_layer:
327  * @timeline: a #GESTimeline
328  * @layer: the #GESTimelineLayer to remove
329  *
330  * Removes the layer from the timeline. The reference that the @timeline holds on
331  * the layer will be dropped. If you wish to use the @layer after calling this
332  * method, you need to take a reference before calling.
333  *
334  * Returns: TRUE if the layer was properly removed, else FALSE.
335  */
336
337 gboolean
338 ges_timeline_remove_layer (GESTimeline * timeline, GESTimelineLayer * layer)
339 {
340   GST_DEBUG ("timeline:%p, layer:%p", timeline, layer);
341
342   if (G_UNLIKELY (!g_list_find (timeline->layers, layer))) {
343     GST_WARNING ("Layer doesn't belong to this timeline");
344     return FALSE;
345   }
346
347   /* Disconnect signals */
348   GST_DEBUG ("Disconnecting signal callbacks");
349   g_signal_handlers_disconnect_by_func (layer, layer_object_added_cb, timeline);
350   g_signal_handlers_disconnect_by_func (layer, layer_object_removed_cb,
351       timeline);
352
353   timeline->layers = g_list_remove (timeline->layers, layer);
354
355   ges_timeline_layer_set_timeline (layer, NULL);
356
357   g_signal_emit (timeline, ges_timeline_signals[LAYER_REMOVED], 0, layer);
358
359   g_object_unref (layer);
360
361   return TRUE;
362 }
363
364 static void
365 pad_added_cb (GESTrack * track, GstPad * pad, TrackPrivate * priv)
366 {
367   gchar *padname;
368
369   GST_DEBUG ("track:%p, pad:%s:%s", track, GST_DEBUG_PAD_NAME (pad));
370
371   if (G_UNLIKELY (priv->pad)) {
372     GST_WARNING ("We are already controlling a pad for this track");
373     return;
374   }
375
376   /* Remember the pad */
377   priv->pad = pad;
378
379   /* ghost it ! */
380   GST_DEBUG ("Ghosting pad and adding it to ourself");
381   padname = g_strdup_printf ("track_%p_src", track);
382   priv->ghostpad = gst_ghost_pad_new (padname, pad);
383   g_free (padname);
384   gst_pad_set_active (priv->ghostpad, TRUE);
385   gst_element_add_pad (GST_ELEMENT (priv->timeline), priv->ghostpad);
386 }
387
388 static void
389 pad_removed_cb (GESTrack * track, GstPad * pad, TrackPrivate * priv)
390 {
391   GST_DEBUG ("track:%p, pad:%s:%s", track, GST_DEBUG_PAD_NAME (pad));
392
393   if (G_UNLIKELY (priv->pad != pad)) {
394     GST_WARNING ("Not the pad we're controlling");
395     return;
396   }
397
398   if (G_UNLIKELY (priv->ghostpad == NULL)) {
399     GST_WARNING ("We don't have a ghostpad for this pad !");
400     return;
401   }
402
403   GST_DEBUG ("Removing ghostpad");
404   gst_pad_set_active (priv->ghostpad, FALSE);
405   gst_element_remove_pad (GST_ELEMENT (priv->timeline), priv->ghostpad);
406   priv->ghostpad = NULL;
407   priv->pad = NULL;
408 }
409
410 gint
411 custom_find_track (TrackPrivate * priv, GESTrack * track)
412 {
413   if (priv->track == track)
414     return 0;
415   return -1;
416 }
417
418 /**
419  * ges_timeline_add_track:
420  * @timeline: a #GESTimeline
421  * @track: the #GESTrack to add
422  *
423  * Add a track to the timeline. The reference to the track will be stolen by the
424  * pipeline.
425  *
426  * Returns: TRUE if the track was properly added, else FALSE.
427  */
428
429 gboolean
430 ges_timeline_add_track (GESTimeline * timeline, GESTrack * track)
431 {
432   GList *tmp;
433   TrackPrivate *priv;
434
435   GST_DEBUG ("timeline:%p, track:%p", timeline, track);
436
437   /* make sure we don't already control it */
438   if (G_UNLIKELY ((tmp =
439               g_list_find_custom (timeline->tracks, (gconstpointer) track,
440                   (GCompareFunc) custom_find_track)))) {
441     GST_WARNING ("Track is already controlled by this timeline");
442     return FALSE;
443   }
444
445   /* Add the track to ourself (as a GstBin) 
446    * Reference is stolen ! */
447   if (G_UNLIKELY (!gst_bin_add (GST_BIN (timeline), GST_ELEMENT (track)))) {
448     GST_WARNING ("Couldn't add track to ourself (GST)");
449     return FALSE;
450   }
451
452   priv = g_new0 (TrackPrivate, 1);
453   priv->timeline = timeline;
454   priv->track = track;
455
456   /* Add the track to the list of tracks we track */
457   timeline->tracks = g_list_append (timeline->tracks, priv);
458
459   /* Listen to pad-added/-removed */
460   g_signal_connect (track, "pad-added", (GCallback) pad_added_cb, priv);
461   g_signal_connect (track, "pad-removed", (GCallback) pad_removed_cb, priv);
462
463   /* Inform the track that it's currently being used by ourself */
464   ges_track_set_timeline (track, timeline);
465
466   GST_DEBUG ("Done adding track, emitting 'track-added' signal");
467
468   /* emit 'track-added' */
469   g_signal_emit (timeline, ges_timeline_signals[TRACK_ADDED], 0, track);
470
471   return TRUE;
472 }
473
474 /**
475  * ges_timeline_remove_track:
476  * @timeline: a #GESTimeline
477  * @track: the #GESTrack to remove
478  *
479  * Remove the @track from the @timeline. The reference stolen when adding the
480  * @track will be removed. If you wish to use the @track after calling this
481  * function you must ensure that you have a reference to it.
482  *
483  * Returns: TRUE if the @track was properly removed, else FALSE.
484  */
485 gboolean
486 ges_timeline_remove_track (GESTimeline * timeline, GESTrack * track)
487 {
488   GList *tmp;
489   TrackPrivate *priv;
490
491   GST_DEBUG ("timeline:%p, track:%p", timeline, track);
492
493   if (G_UNLIKELY (!(tmp =
494               g_list_find_custom (timeline->tracks, (gconstpointer) track,
495                   (GCompareFunc) custom_find_track)))) {
496     GST_WARNING ("Track doesn't belong to this timeline");
497     return FALSE;
498   }
499
500   priv = tmp->data;
501   timeline->tracks = g_list_remove (timeline->tracks, priv);
502
503   ges_track_set_timeline (track, NULL);
504
505   /* Remove ghost pad */
506   if (priv->ghostpad) {
507     GST_DEBUG ("Removing ghostpad");
508     gst_pad_set_active (priv->ghostpad, FALSE);
509     gst_ghost_pad_set_target ((GstGhostPad *) priv->ghostpad, NULL);
510     gst_element_remove_pad (GST_ELEMENT (timeline), priv->ghostpad);
511   }
512
513   /* Remove pad-added/-removed handlers */
514   g_signal_handlers_disconnect_by_func (track, pad_added_cb, priv);
515   g_signal_handlers_disconnect_by_func (track, pad_removed_cb, priv);
516
517   /* Signal track removal to all layers/objects */
518   g_signal_emit (timeline, ges_timeline_signals[TRACK_REMOVED], 0, track);
519
520   /* remove track from our bin */
521   if (G_UNLIKELY (!gst_bin_remove (GST_BIN (timeline), GST_ELEMENT (track)))) {
522     GST_WARNING ("Couldn't remove track to ourself (GST)");
523     return FALSE;
524   }
525
526   g_free (priv);
527
528   return TRUE;
529 }
530
531 /**
532  * ges_timeline_get_track_for_pad:
533  * @timeline: The #GESTimeline
534  * @pad: The #GstPad
535  *
536  * Search the #GESTrack corresponding to the given @timeline's @pad.
537  *
538  * Returns: The corresponding #GESTrack if it is found, or #NULL if there is
539  * an error.
540  */
541
542 GESTrack *
543 ges_timeline_get_track_for_pad (GESTimeline * timeline, GstPad * pad)
544 {
545   GList *tmp;
546
547   for (tmp = timeline->tracks; tmp; tmp = g_list_next (tmp)) {
548     TrackPrivate *priv = (TrackPrivate *) tmp->data;
549     if (pad == priv->ghostpad)
550       return priv->track;
551   }
552
553   return NULL;
554 }
555
556 /**
557  * ges_timeline_get_tracks:
558  * @timeline: a #GESTimeline
559  *
560  * Returns the list of #GESTrack used by the Timeline.
561  *
562  * Returns: A list of #GESTrack. The caller should unref each track
563  * once he is done with them. */
564 GList *
565 ges_timeline_get_tracks (GESTimeline * timeline)
566 {
567   GList *tmp, *res = NULL;
568
569   for (tmp = timeline->tracks; tmp; tmp = g_list_next (tmp)) {
570     TrackPrivate *priv = (TrackPrivate *) tmp->data;
571     res = g_list_append (res, g_object_ref (priv->track));
572   }
573
574   return res;
575 }