Import GNL from 978332e7c4c3bba1949421d28b492540ab471450 'Release 1.4.0'
authorThibault Saunier <tsaunier@gnome.org>
Tue, 21 Oct 2014 08:35:48 +0000 (10:35 +0200)
committerThibault Saunier <tsaunier@gnome.org>
Fri, 31 Oct 2014 10:58:07 +0000 (11:58 +0100)
25 files changed:
gnl/Makefile.am [new file with mode: 0644]
gnl/gnl.c [new file with mode: 0644]
gnl/gnl.h [new file with mode: 0644]
gnl/gnlcomposition.c [new file with mode: 0644]
gnl/gnlcomposition.h [new file with mode: 0644]
gnl/gnlghostpad.c [new file with mode: 0644]
gnl/gnlghostpad.h [new file with mode: 0644]
gnl/gnlmarshal.list [new file with mode: 0644]
gnl/gnlobject.c [new file with mode: 0644]
gnl/gnlobject.h [new file with mode: 0644]
gnl/gnloperation.c [new file with mode: 0644]
gnl/gnloperation.h [new file with mode: 0644]
gnl/gnlsource.c [new file with mode: 0644]
gnl/gnlsource.h [new file with mode: 0644]
gnl/gnltypes.h [new file with mode: 0644]
gnl/gnlurisource.c [new file with mode: 0644]
gnl/gnlurisource.h [new file with mode: 0644]
tests/check/gnl/common.c [new file with mode: 0644]
tests/check/gnl/common.h [new file with mode: 0644]
tests/check/gnl/complex.c [new file with mode: 0644]
tests/check/gnl/gnlcomposition.c [new file with mode: 0644]
tests/check/gnl/gnloperation.c [new file with mode: 0644]
tests/check/gnl/gnlsource.c [new file with mode: 0644]
tests/check/gnl/seek.c [new file with mode: 0644]
tests/check/gnl/simple.c [new file with mode: 0644]

diff --git a/gnl/Makefile.am b/gnl/Makefile.am
new file mode 100644 (file)
index 0000000..0c4876e
--- /dev/null
@@ -0,0 +1,46 @@
+plugin_LTLIBRARIES = libgnl.la
+
+libgnl_la_SOURCES =            \
+       gnl.c                   \
+       gnlobject.c             \
+       gnlcomposition.c        \
+       gnlghostpad.c           \
+       gnloperation.c          \
+       gnlsource.c             \
+       gnlurisource.c
+libgnl_la_CFLAGS = $(GST_CFLAGS)
+libgnl_la_LIBADD = $(GST_LIBS)
+libgnl_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
+libgnl_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS)
+
+gnl_headers =                  \
+       gnl.h                   \
+       gnlobject.h             \
+       gnlcomposition.h        \
+       gnltypes.h              \
+       gnlghostpad.h           \
+       gnloperation.h          \
+       gnlsource.h             \
+       gnltypes.h              \
+       gnlurisource.h
+
+DISTCLEANFILE = $(CLEANFILES)
+
+#files built on make all/check/instal
+BUILT_SOURCES =        $(built_header_configure)
+
+noinst_HEADERS = $(gnl_headers) $(built_header_configure)
+
+Android.mk: Makefile.am $(BUILT_SOURCES)
+       androgenizer \
+       -:PROJECT libgnl -:SHARED libgnl \
+        -:TAGS eng debug \
+         -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \
+        -:SOURCES $(libgnl_la_SOURCES) \
+        -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgnl_la_CFLAGS) \
+        -:LDFLAGS $(libgnl_la_LDFLAGS) \
+                  $(libgnl_la_LIBADD) \
+                  -ldl \
+        -:PASSTHROUGH LOCAL_ARM_MODE:=arm \
+                      LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-@GST_API_VERSION@' \
+       > $@
diff --git a/gnl/gnl.c b/gnl/gnl.c
new file mode 100644 (file)
index 0000000..5a986a1
--- /dev/null
+++ b/gnl/gnl.c
@@ -0,0 +1,60 @@
+/* Gnonlin
+ * Copyright (C) <2001> Wim Taymans <wim.taymans@gmail.com>
+ *               <2004-2008> Edward Hervey <bilboed@bilboed.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gnl.h"
+
+struct _elements_entry
+{
+  const gchar *name;
+    GType (*type) (void);
+};
+
+static struct _elements_entry _elements[] = {
+  {"gnlsource", gnl_source_get_type},
+  {"gnlcomposition", gnl_composition_get_type},
+  {"gnloperation", gnl_operation_get_type},
+  {"gnlurisource", gnl_urisource_get_type},
+  {NULL, 0}
+};
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+  gint i = 0;
+
+  for (; _elements[i].name; i++)
+    if (!(gst_element_register (plugin,
+                _elements[i].name, GST_RANK_NONE, (_elements[i].type) ())))
+      return FALSE;
+
+  gnl_init_ghostpad_category ();
+
+  return TRUE;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+    GST_VERSION_MINOR,
+    gnonlin,
+    "Standard elements for non-linear multimedia editing",
+    plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);
diff --git a/gnl/gnl.h b/gnl/gnl.h
new file mode 100644 (file)
index 0000000..303f4b1
--- /dev/null
+++ b/gnl/gnl.h
@@ -0,0 +1,36 @@
+/* Gnonlin
+ * Copyright (C) <2001> Wim Taymans <wim.taymans@gmail.com>
+ *              <2004-2008> Edward Hervey <bilboed@bilboed.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GNL_H__
+#define __GNL_H__
+
+#include <gst/gst.h>
+
+#include "gnltypes.h"
+
+#include "gnlobject.h"
+#include "gnlghostpad.h"
+#include "gnlsource.h"
+#include "gnlcomposition.h"
+#include "gnloperation.h"
+
+#include "gnlurisource.h"
+
+#endif /* __GST_H__ */
diff --git a/gnl/gnlcomposition.c b/gnl/gnlcomposition.c
new file mode 100644 (file)
index 0000000..d9a0877
--- /dev/null
@@ -0,0 +1,3074 @@
+/* GStreamer
+ * Copyright (C) 2001 Wim Taymans <wim.taymans@gmail.com>
+ *               2004-2008 Edward Hervey <bilboed@bilboed.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gnl.h"
+
+/**
+ * SECTION:element-gnlcomposition
+ *
+ * A GnlComposition contains GnlObjects such as GnlSources and GnlOperations,
+ * and connects them dynamically to create a composition timeline.
+ */
+
+static GstStaticPadTemplate gnl_composition_src_template =
+GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_SOMETIMES,
+    GST_STATIC_CAPS_ANY);
+
+GST_DEBUG_CATEGORY_STATIC (gnlcomposition_debug);
+#define GST_CAT_DEFAULT gnlcomposition_debug
+
+#define _do_init              \
+  GST_DEBUG_CATEGORY_INIT (gnlcomposition_debug,"gnlcomposition", GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GNonLin Composition");
+#define gnl_composition_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GnlComposition, gnl_composition, GNL_TYPE_OBJECT,
+    _do_init);
+
+
+enum
+{
+  PROP_0,
+  PROP_DEACTIVATED_ELEMENTS_STATE,
+  PROP_LAST,
+};
+
+/* Properties from GnlObject */
+enum
+{
+  GNLOBJECT_PROP_START,
+  GNLOBJECT_PROP_STOP,
+  GNLOBJECT_PROP_DURATION,
+  GNLOBJECT_PROP_LAST
+};
+
+enum
+{
+  COMMIT_SIGNAL,
+  LAST_SIGNAL
+};
+
+typedef struct _GnlCompositionEntry GnlCompositionEntry;
+
+struct _GnlCompositionPrivate
+{
+  gboolean dispose_has_run;
+
+  /*
+     Sorted List of GnlObjects , ThreadSafe
+     objects_start : sorted by start-time then priority
+     objects_stop : sorted by stop-time then priority
+     objects_hash : contains signal handlers id for controlled objects
+     objects_lock : mutex to acces/modify any of those lists/hashtable
+   */
+  GList *objects_start;
+  GList *objects_stop;
+  GHashTable *objects_hash;
+  GMutex objects_lock;
+
+  /*
+     thread-safe Seek handling.
+     flushing_lock : mutex to access flushing and pending_idle
+     flushing :
+   */
+  GMutex flushing_lock;
+  gboolean flushing;
+
+  /* source top-level ghostpad, probe and entry */
+  GstPad *ghostpad;
+  gulong ghosteventprobe;
+  GnlCompositionEntry *toplevelentry;
+
+  /* current stack, list of GnlObject* */
+  GNode *current;
+
+  /* List of GnlObject whose start/duration will be the same as the composition */
+  GList *expandables;
+
+  /* TRUE if the stack is valid.
+   * This is meant to prevent the top-level pad to be unblocked before the stack
+   * is fully done. Protected by OBJECTS_LOCK */
+  gboolean stackvalid;
+
+  /*
+     current segment seek start/stop time.
+     Reconstruct pipeline ONLY if seeking outside of those values
+     FIXME : segment_start isn't always the earliest time before which the
+     timeline doesn't need to be modified
+   */
+  GstClockTime segment_start;
+  GstClockTime segment_stop;
+
+  /* pending child seek */
+  GstEvent *childseek;
+
+  /* Seek segment handler */
+  GstSegment *segment;
+  GstSegment *outside_segment;
+
+  /* Next running base_time to set on outgoing segment */
+  guint64 next_base_time;
+
+  /* number of pads we are waiting to appear so be can do proper linking */
+  guint waitingpads;
+
+  /*
+     OUR sync_handler on the child_bus
+     We are called before gnl_object_sync_handler
+   */
+  GstPadEventFunction gnl_event_pad_func;
+  gboolean send_stream_start;
+
+  GThread *update_pipeline_thread;
+  GCond update_pipeline_cond;
+  GMutex update_pipeline_mutex;
+
+  gboolean reset_time;
+
+  gboolean running;
+
+  GstState deactivated_elements_state;
+};
+
+static guint _signals[LAST_SIGNAL] = { 0 };
+
+static GParamSpec *gnlobject_properties[GNLOBJECT_PROP_LAST];
+static GParamSpec *_properties[PROP_LAST];
+
+#define OBJECT_IN_ACTIVE_SEGMENT(comp,element)      \
+  ((GNL_OBJECT_START(element) < comp->priv->segment_stop) &&  \
+   (GNL_OBJECT_STOP(element) >= comp->priv->segment_start))
+
+static void gnl_composition_dispose (GObject * object);
+static void gnl_composition_finalize (GObject * object);
+static void gnl_composition_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspsec);
+static void gnl_composition_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspsec);
+static void gnl_composition_reset (GnlComposition * comp);
+
+static gboolean gnl_composition_add_object (GstBin * bin, GstElement * element);
+
+static void gnl_composition_handle_message (GstBin * bin, GstMessage * message);
+
+static gboolean
+gnl_composition_remove_object (GstBin * bin, GstElement * element);
+
+static GstStateChangeReturn
+gnl_composition_change_state (GstElement * element, GstStateChange transition);
+
+static GstPad *get_src_pad (GstElement * element);
+static GstPadProbeReturn pad_blocked (GstPad * pad, GstPadProbeInfo * info,
+    GnlComposition * comp);
+static inline void gnl_composition_remove_ghostpad (GnlComposition * comp);
+
+static gboolean
+seek_handling (GnlComposition * comp, gboolean initial, gboolean update);
+static gint objects_start_compare (GnlObject * a, GnlObject * b);
+static gint objects_stop_compare (GnlObject * a, GnlObject * b);
+static GstClockTime get_current_position (GnlComposition * comp);
+
+static gboolean update_pipeline (GnlComposition * comp,
+    GstClockTime currenttime, gboolean initial, gboolean modify);
+static void no_more_pads_object_cb (GstElement * element,
+    GnlComposition * comp);
+static gboolean gnl_composition_commit_func (GnlObject * object,
+    gboolean recurse);
+static void update_start_stop_duration (GnlComposition * comp);
+
+
+/* COMP_REAL_START: actual position to start current playback at. */
+#define COMP_REAL_START(comp)                                                  \
+  (MAX (comp->priv->segment->start, GNL_OBJECT_START (comp)))
+
+#define COMP_REAL_STOP(comp)                                                   \
+  (GST_CLOCK_TIME_IS_VALID (comp->priv->segment->stop) ?                       \
+   (MIN (comp->priv->segment->stop, GNL_OBJECT_STOP (comp))) :                 \
+   GNL_OBJECT_STOP (comp))
+
+#define COMP_ENTRY(comp, object)                                               \
+  (g_hash_table_lookup (comp->priv->objects_hash, (gconstpointer) object))
+
+#define COMP_OBJECTS_LOCK(comp) G_STMT_START {                                 \
+    GST_LOG_OBJECT (comp, "locking objects_lock from thread %p",               \
+        g_thread_self());                                                      \
+    g_mutex_lock (&comp->priv->objects_lock);                                  \
+    GST_LOG_OBJECT (comp, "locked objects_lock from thread %p",                \
+        g_thread_self());                                                      \
+  } G_STMT_END
+
+#define COMP_OBJECTS_UNLOCK(comp) G_STMT_START {                               \
+    GST_LOG_OBJECT (comp, "unlocking objects_lock from thread %p",             \
+        g_thread_self());                                                      \
+    g_mutex_unlock (&comp->priv->objects_lock);                                \
+  } G_STMT_END
+
+
+#define COMP_FLUSHING_LOCK(comp) G_STMT_START {                                \
+    GST_LOG_OBJECT (comp, "locking flushing_lock from thread %p",              \
+        g_thread_self());                                                      \
+    g_mutex_lock (&comp->priv->flushing_lock);                                 \
+    GST_LOG_OBJECT (comp, "locked flushing_lock from thread %p",               \
+        g_thread_self());                                                      \
+  } G_STMT_END
+
+#define COMP_FLUSHING_UNLOCK(comp) G_STMT_START {                              \
+    GST_LOG_OBJECT (comp, "unlocking flushing_lock from thread %p",            \
+        g_thread_self());                                                      \
+    g_mutex_unlock (&comp->priv->flushing_lock);                               \
+  } G_STMT_END
+
+#define WAIT_FOR_UPDATE_PIPELINE(comp)   G_STMT_START {                        \
+  GST_INFO_OBJECT (comp, "waiting for EOS from thread %p",                     \
+        g_thread_self());                                                      \
+  g_mutex_lock(&(comp->priv->update_pipeline_mutex));                          \
+  g_cond_wait(&(comp->priv->update_pipeline_cond),                             \
+      &(comp->priv->update_pipeline_mutex));                                   \
+  g_mutex_unlock(&(comp->priv->update_pipeline_mutex));                        \
+  } G_STMT_END
+
+#define SIGNAL_UPDATE_PIPELINE(comp) {                                         \
+  GST_INFO_OBJECT (comp, "signaling EOS from thread %p",                       \
+        g_thread_self());                                                      \
+  g_mutex_lock(&(comp->priv->update_pipeline_mutex));                          \
+  g_cond_signal(&(comp->priv->update_pipeline_cond));                          \
+  g_mutex_unlock(&(comp->priv->update_pipeline_mutex));                        \
+  } G_STMT_END
+
+
+
+struct _GnlCompositionEntry
+{
+  GnlObject *object;
+  GnlComposition *comp;
+
+  /* handler id for 'no-more-pads' signal */
+  gulong nomorepadshandler;
+  gulong padaddedhandler;
+  gulong padremovedhandler;
+
+  /* handler id for block probe */
+  gulong probeid;
+  gulong dataprobeid;
+
+  gboolean seeked;
+};
+
+static void
+gnl_composition_class_init (GnlCompositionClass * klass)
+{
+  GObjectClass *gobject_class;
+  GstElementClass *gstelement_class;
+  GstBinClass *gstbin_class;
+  GnlObjectClass *gnlobject_class;
+
+  gobject_class = (GObjectClass *) klass;
+  gstelement_class = (GstElementClass *) klass;
+  gstbin_class = (GstBinClass *) klass;
+  gnlobject_class = (GnlObjectClass *) klass;
+
+  g_type_class_add_private (klass, sizeof (GnlCompositionPrivate));
+
+  gst_element_class_set_static_metadata (gstelement_class,
+      "GNonLin Composition", "Filter/Editor", "Combines GNL objects",
+      "Wim Taymans <wim.taymans@gmail.com>, Edward Hervey <bilboed@bilboed.com>");
+
+  gobject_class->dispose = GST_DEBUG_FUNCPTR (gnl_composition_dispose);
+  gobject_class->finalize = GST_DEBUG_FUNCPTR (gnl_composition_finalize);
+  gobject_class->set_property =
+      GST_DEBUG_FUNCPTR (gnl_composition_set_property);
+  gobject_class->get_property =
+      GST_DEBUG_FUNCPTR (gnl_composition_get_property);
+
+  gstelement_class->change_state = gnl_composition_change_state;
+
+  gstbin_class->add_element = GST_DEBUG_FUNCPTR (gnl_composition_add_object);
+  gstbin_class->remove_element =
+      GST_DEBUG_FUNCPTR (gnl_composition_remove_object);
+  gstbin_class->handle_message =
+      GST_DEBUG_FUNCPTR (gnl_composition_handle_message);
+
+  gst_element_class_add_pad_template (gstelement_class,
+      gst_static_pad_template_get (&gnl_composition_src_template));
+
+  /* Get the paramspec of the GnlObject klass so we can do
+   * fast notifies */
+  gnlobject_properties[GNLOBJECT_PROP_START] =
+      g_object_class_find_property (gobject_class, "start");
+  gnlobject_properties[GNLOBJECT_PROP_STOP] =
+      g_object_class_find_property (gobject_class, "stop");
+  gnlobject_properties[GNLOBJECT_PROP_DURATION] =
+      g_object_class_find_property (gobject_class, "duration");
+
+  /**
+   * GnlComposition:deactivated-elements-state
+   *
+   * Get or set the #GstState in which elements that are not used
+   * in the currently configured pipeline should be set.
+   * By default the state is GST_STATE_READY to lower memory usage and avoid
+   * using all the avalaible threads from the kernel but that means that in
+   * certain case gapless will be more 'complicated' than if the state was set
+   * to GST_STATE_PAUSED.
+   */
+  _properties[PROP_DEACTIVATED_ELEMENTS_STATE] =
+      g_param_spec_enum ("deactivated-elements-state",
+      "Deactivate elements state", "The state in which elements"
+      " not used in the currently configured pipeline should"
+      " be set", GST_TYPE_STATE, GST_STATE_READY,
+      G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (gobject_class, PROP_LAST, _properties);
+
+  /**
+   * GnlComposition::commit
+   * @comp: a #GnlComposition
+   * @recurse: Whether to commit recursiverly into (GnlComposition) children of
+   *           @object. This is used in case we have composition inside
+   *           a gnlsource composition, telling it to commit the included
+   *           composition state.
+   *
+   * Action signal to commit all the pending changes of the composition and
+   * its children timing properties
+   *
+   * Returns: %TRUE if changes have been commited, %FALSE if nothing had to
+   * be commited
+   */
+  _signals[COMMIT_SIGNAL] = g_signal_new ("commit", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+      G_STRUCT_OFFSET (GnlObjectClass, commit_signal_handler), NULL, NULL, NULL,
+      G_TYPE_BOOLEAN, 1, G_TYPE_BOOLEAN);
+
+  gnlobject_class->commit = gnl_composition_commit_func;
+}
+
+static void
+hash_value_destroy (GnlCompositionEntry * entry)
+{
+  GstPad *srcpad;
+  GstElement *element = GST_ELEMENT (entry->object);
+
+  g_signal_handler_disconnect (entry->object, entry->padremovedhandler);
+  g_signal_handler_disconnect (entry->object, entry->padaddedhandler);
+
+  if (entry->nomorepadshandler)
+    g_signal_handler_disconnect (entry->object, entry->nomorepadshandler);
+
+  if ((srcpad = get_src_pad (element))) {
+    if (entry->probeid) {
+      gst_pad_remove_probe (srcpad, entry->probeid);
+      entry->probeid = 0;
+    }
+
+    if (entry->dataprobeid) {
+      gst_pad_remove_probe (srcpad, entry->dataprobeid);
+      entry->dataprobeid = 0;
+    }
+    gst_object_unref (srcpad);
+  }
+
+  g_slice_free (GnlCompositionEntry, entry);
+}
+
+static void
+gnl_composition_init (GnlComposition * comp)
+{
+  GnlCompositionPrivate *priv;
+
+  GST_OBJECT_FLAG_SET (comp, GNL_OBJECT_SOURCE);
+  GST_OBJECT_FLAG_SET (comp, GNL_OBJECT_COMPOSITION);
+
+  priv = G_TYPE_INSTANCE_GET_PRIVATE (comp, GNL_TYPE_COMPOSITION,
+      GnlCompositionPrivate);
+  g_mutex_init (&priv->objects_lock);
+  priv->objects_start = NULL;
+  priv->objects_stop = NULL;
+
+  g_mutex_init (&priv->flushing_lock);
+  priv->flushing = FALSE;
+
+  priv->segment = gst_segment_new ();
+  priv->outside_segment = gst_segment_new ();
+
+  priv->waitingpads = 0;
+
+  priv->reset_time = FALSE;
+
+  priv->objects_hash = g_hash_table_new_full
+      (g_direct_hash,
+      g_direct_equal, NULL, (GDestroyNotify) hash_value_destroy);
+
+  priv->deactivated_elements_state = GST_STATE_READY;
+
+  comp->priv = priv;
+
+  gnl_composition_reset (comp);
+}
+
+static void
+gnl_composition_dispose (GObject * object)
+{
+  GnlComposition *comp = GNL_COMPOSITION (object);
+  GnlCompositionPrivate *priv = comp->priv;
+
+  if (priv->dispose_has_run)
+    return;
+
+  priv->dispose_has_run = TRUE;
+
+  if (priv->ghostpad)
+    gnl_composition_remove_ghostpad (comp);
+
+  if (priv->childseek) {
+    gst_event_unref (priv->childseek);
+    priv->childseek = NULL;
+  }
+
+  if (priv->current) {
+    g_node_destroy (priv->current);
+    priv->current = NULL;
+  }
+
+  if (priv->expandables) {
+    g_list_free (priv->expandables);
+    priv->expandables = NULL;
+  }
+
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gnl_composition_finalize (GObject * object)
+{
+  GnlComposition *comp = GNL_COMPOSITION (object);
+  GnlCompositionPrivate *priv = comp->priv;
+
+  GST_INFO ("finalize");
+
+  COMP_OBJECTS_LOCK (comp);
+  g_list_free (priv->objects_start);
+  g_list_free (priv->objects_stop);
+  if (priv->current)
+    g_node_destroy (priv->current);
+  g_hash_table_destroy (priv->objects_hash);
+  COMP_OBJECTS_UNLOCK (comp);
+
+  gst_segment_free (priv->segment);
+  gst_segment_free (priv->outside_segment);
+
+  g_mutex_clear (&priv->objects_lock);
+  g_mutex_clear (&priv->flushing_lock);
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gnl_composition_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GnlComposition *comp = GNL_COMPOSITION (object);
+
+  switch (prop_id) {
+    case PROP_DEACTIVATED_ELEMENTS_STATE:
+      comp->priv->deactivated_elements_state = g_value_get_enum (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gnl_composition_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GnlComposition *comp = GNL_COMPOSITION (object);
+
+  switch (prop_id) {
+    case PROP_DEACTIVATED_ELEMENTS_STATE:
+      g_value_set_enum (value, comp->priv->deactivated_elements_state);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+wait_no_more_pads (GnlComposition * comp, gpointer object,
+    GnlCompositionEntry * entry, gboolean wait)
+{
+  if (wait) {
+    GST_INFO_OBJECT (object, "no existing pad, connecting to 'no-more-pads'");
+    entry->nomorepadshandler = g_signal_connect
+        (G_OBJECT (object), "no-more-pads",
+        G_CALLBACK (no_more_pads_object_cb), comp);
+    comp->priv->waitingpads++;
+  } else {
+    GST_INFO_OBJECT (object, "disconnecting from 'no-more-pads'");
+    g_signal_handler_disconnect (object, entry->nomorepadshandler);
+    entry->nomorepadshandler = 0;
+    comp->priv->waitingpads--;
+  }
+
+  GST_INFO_OBJECT (comp, "the number of waiting pads is now %d",
+      comp->priv->waitingpads);
+}
+
+/* signal_duration_change
+ * Creates a new GST_MESSAGE_DURATION_CHANGED with the currently configured
+ * composition duration and sends that on the bus.
+ */
+
+static inline void
+signal_duration_change (GnlComposition * comp)
+{
+  gst_element_post_message (GST_ELEMENT_CAST (comp),
+      gst_message_new_duration_changed (GST_OBJECT_CAST (comp)));
+}
+
+static gboolean
+unblock_child_pads (GValue * item, GValue * ret G_GNUC_UNUSED,
+    GnlComposition * comp)
+{
+  GstPad *pad;
+  GstElement *child = g_value_get_object (item);
+  GnlCompositionEntry *entry = COMP_ENTRY (comp, child);
+
+  GST_DEBUG_OBJECT (child, "unblocking pads");
+
+  pad = get_src_pad (child);
+  if (pad) {
+    if (entry->probeid) {
+      gst_pad_remove_probe (pad, entry->probeid);
+      entry->probeid = 0;
+    }
+    gst_object_unref (pad);
+  }
+  return TRUE;
+}
+
+static void
+unblock_children (GnlComposition * comp)
+{
+  GstIterator *children;
+
+  children = gst_bin_iterate_elements (GST_BIN (comp));
+
+retry:
+  if (G_UNLIKELY (gst_iterator_fold (children,
+              (GstIteratorFoldFunction) unblock_child_pads, NULL,
+              comp) == GST_ITERATOR_RESYNC)) {
+    gst_iterator_resync (children);
+    goto retry;
+  }
+  gst_iterator_free (children);
+}
+
+
+static gboolean
+reset_child (GValue * item, GValue * ret G_GNUC_UNUSED, gpointer user_data)
+{
+  GnlCompositionEntry *entry;
+  GstElement *child = g_value_get_object (item);
+  GnlComposition *comp = GNL_COMPOSITION (user_data);
+
+  GST_DEBUG_OBJECT (child, "unlocking state");
+  gst_element_set_locked_state (child, FALSE);
+
+  entry = COMP_ENTRY (comp, child);
+  if (entry->nomorepadshandler)
+    wait_no_more_pads (comp, child, entry, FALSE);
+
+  return TRUE;
+}
+
+static gboolean
+lock_child_state (GValue * item, GValue * ret G_GNUC_UNUSED,
+    gpointer udata G_GNUC_UNUSED)
+{
+  GstElement *child = g_value_get_object (item);
+
+  GST_DEBUG_OBJECT (child, "locking state");
+  gst_element_set_locked_state (child, TRUE);
+
+  return TRUE;
+}
+
+static void
+reset_children (GnlComposition * comp)
+{
+  GstIterator *children;
+
+  children = gst_bin_iterate_elements (GST_BIN (comp));
+retry:
+  if (G_UNLIKELY (gst_iterator_fold (children,
+              (GstIteratorFoldFunction) reset_child, NULL,
+              comp) == GST_ITERATOR_RESYNC)) {
+    gst_iterator_resync (children);
+    goto retry;
+  }
+  gst_iterator_free (children);
+}
+
+static void
+gnl_composition_reset (GnlComposition * comp)
+{
+  GnlCompositionPrivate *priv = comp->priv;
+
+  GST_DEBUG_OBJECT (comp, "resetting");
+
+  priv->segment_start = GST_CLOCK_TIME_NONE;
+  priv->segment_stop = GST_CLOCK_TIME_NONE;
+  priv->next_base_time = 0;
+
+  gst_segment_init (priv->segment, GST_FORMAT_TIME);
+  gst_segment_init (priv->outside_segment, GST_FORMAT_TIME);
+
+  if (priv->current)
+    g_node_destroy (priv->current);
+  priv->current = NULL;
+
+  priv->stackvalid = FALSE;
+
+  if (priv->ghostpad)
+    gnl_composition_remove_ghostpad (comp);
+
+  if (priv->childseek) {
+    gst_event_unref (priv->childseek);
+    priv->childseek = NULL;
+  }
+
+  reset_children (comp);
+
+  COMP_FLUSHING_LOCK (comp);
+
+  priv->flushing = FALSE;
+
+  COMP_FLUSHING_UNLOCK (comp);
+
+  priv->reset_time = FALSE;
+
+  priv->send_stream_start = TRUE;
+
+  GST_DEBUG_OBJECT (comp, "Composition now resetted");
+}
+
+static GstPadProbeReturn
+ghost_event_probe_handler (GstPad * ghostpad G_GNUC_UNUSED,
+    GstPadProbeInfo * info, GnlComposition * comp)
+{
+  GstPadProbeReturn retval = GST_PAD_PROBE_OK;
+  GnlCompositionPrivate *priv = comp->priv;
+  GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
+  GList *tmp;
+
+  GST_DEBUG_OBJECT (comp, "event: %s", GST_EVENT_TYPE_NAME (event));
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_FLUSH_STOP:
+      GST_DEBUG_OBJECT (comp,
+          "replacing flush stop event with a flush stop event with 'reset_time' = %d",
+          priv->reset_time);
+      GST_PAD_PROBE_INFO_DATA (info) =
+          gst_event_new_flush_stop (priv->reset_time);
+      gst_event_unref (event);
+      break;
+    case GST_EVENT_STREAM_START:
+      if (g_atomic_int_compare_and_exchange (&priv->send_stream_start, TRUE,
+              FALSE)) {
+        /* FIXME: Do we want to create a new stream ID here? */
+        GST_DEBUG_OBJECT (comp, "forward stream-start %p", event);
+      } else {
+        GST_DEBUG_OBJECT (comp, "dropping stream-start %p", event);
+        retval = GST_PAD_PROBE_DROP;
+      }
+      break;
+    case GST_EVENT_SEGMENT:
+    {
+      guint64 rstart, rstop;
+      const GstSegment *segment;
+      GstSegment copy;
+      GstEvent *event2;
+      /* next_base_time */
+
+      COMP_FLUSHING_LOCK (comp);
+
+      priv->flushing = FALSE;
+      COMP_FLUSHING_UNLOCK (comp);
+
+      gst_event_parse_segment (event, &segment);
+      gst_segment_copy_into (segment, &copy);
+
+      rstart =
+          gst_segment_to_running_time (segment, GST_FORMAT_TIME,
+          segment->start);
+      rstop =
+          gst_segment_to_running_time (segment, GST_FORMAT_TIME, segment->stop);
+      copy.base = comp->priv->next_base_time;
+      GST_DEBUG_OBJECT (comp,
+          "Updating base time to %" GST_TIME_FORMAT ", next:%" GST_TIME_FORMAT,
+          GST_TIME_ARGS (comp->priv->next_base_time),
+          GST_TIME_ARGS (comp->priv->next_base_time + rstop - rstart));
+      comp->priv->next_base_time += rstop - rstart;
+
+      event2 = gst_event_new_segment (&copy);
+      GST_EVENT_SEQNUM (event2) = GST_EVENT_SEQNUM (event);
+      GST_PAD_PROBE_INFO_DATA (info) = event2;
+      gst_event_unref (event);
+    }
+      break;
+    case GST_EVENT_EOS:
+    {
+      gboolean reverse = (comp->priv->segment->rate < 0);
+      gboolean should_check_objects = FALSE;
+
+      COMP_FLUSHING_LOCK (comp);
+      if (priv->flushing) {
+        GST_DEBUG_OBJECT (comp, "flushing, bailing out");
+        COMP_FLUSHING_UNLOCK (comp);
+        retval = GST_PAD_PROBE_DROP;
+        break;
+      }
+      COMP_FLUSHING_UNLOCK (comp);
+
+      if (reverse && GST_CLOCK_TIME_IS_VALID (comp->priv->segment_start))
+        should_check_objects = TRUE;
+      else if (!reverse && GST_CLOCK_TIME_IS_VALID (comp->priv->segment_stop))
+        should_check_objects = TRUE;
+
+      if (should_check_objects) {
+        retval = GST_PAD_PROBE_OK;
+        COMP_OBJECTS_LOCK (comp);
+        for (tmp = comp->priv->objects_stop; tmp; tmp = g_list_next (tmp)) {
+          GnlObject *object = (GnlObject *) tmp->data;
+
+          if (!GNL_IS_SOURCE (object))
+            continue;
+
+          if ((!reverse && comp->priv->segment_stop < object->stop) ||
+              (reverse && comp->priv->segment_start > object->start)) {
+            retval = GST_PAD_PROBE_DROP;
+            break;
+          }
+        }
+        COMP_OBJECTS_UNLOCK (comp);
+      }
+
+      if (retval == GST_PAD_PROBE_OK) {
+        GST_DEBUG_OBJECT (comp, "Got EOS for real, fowarding it");
+
+        return GST_PAD_PROBE_OK;
+      }
+
+      SIGNAL_UPDATE_PIPELINE (comp);
+
+      retval = GST_PAD_PROBE_DROP;
+    }
+      break;
+    default:
+      break;
+  }
+
+  return retval;
+}
+
+
+
+/* Warning : Don't take the objects lock in this method */
+static void
+gnl_composition_handle_message (GstBin * bin, GstMessage * message)
+{
+  GnlComposition *comp = (GnlComposition *) bin;
+  gboolean dropit = FALSE;
+
+  GST_DEBUG_OBJECT (comp, "message:%s from %s",
+      gst_message_type_get_name (GST_MESSAGE_TYPE (message)),
+      GST_MESSAGE_SRC (message) ? GST_ELEMENT_NAME (GST_MESSAGE_SRC (message)) :
+      "UNKNOWN");
+
+  switch (GST_MESSAGE_TYPE (message)) {
+    case GST_MESSAGE_ERROR:
+    case GST_MESSAGE_WARNING:
+    {
+      /* FIXME / HACK
+       * There is a massive issue with reverse negotiation and dynamic pipelines.
+       *
+       * Since we're not waiting for the pads of the previous stack to block before
+       * re-switching, we might end up switching sources in the middle of a downstrea
+       * negotiation which will do reverse negotiation... with the new source (which
+       * is no longer the one that issues the request). That negotiation will fail
+       * and the original source will emit an ERROR message.
+       *
+       * In order to avoid those issues, we just ignore error messages from elements
+       * which aren't in the currently configured stack
+       */
+      if (GST_MESSAGE_SRC (message) && GNL_IS_OBJECT (GST_MESSAGE_SRC (message))
+          && !OBJECT_IN_ACTIVE_SEGMENT (comp, GST_MESSAGE_SRC (message))) {
+        GST_DEBUG_OBJECT (comp,
+            "HACK Dropping error message from object not in currently configured stack !");
+        dropit = TRUE;
+      }
+    }
+    default:
+      break;
+  }
+
+  if (dropit)
+    gst_message_unref (message);
+  else
+    GST_BIN_CLASS (parent_class)->handle_message (bin, message);
+}
+
+static gint
+priority_comp (GnlObject * a, GnlObject * b)
+{
+  if (a->priority < b->priority)
+    return -1;
+
+  if (a->priority > b->priority)
+    return 1;
+
+  return 0;
+}
+
+static inline gboolean
+have_to_update_pipeline (GnlComposition * comp)
+{
+  GnlCompositionPrivate *priv = comp->priv;
+
+  GST_DEBUG_OBJECT (comp,
+      "segment[%" GST_TIME_FORMAT "--%" GST_TIME_FORMAT "] current[%"
+      GST_TIME_FORMAT "--%" GST_TIME_FORMAT "]",
+      GST_TIME_ARGS (priv->segment->start),
+      GST_TIME_ARGS (priv->segment->stop),
+      GST_TIME_ARGS (priv->segment_start), GST_TIME_ARGS (priv->segment_stop));
+
+  if (priv->segment->start < priv->segment_start)
+    return TRUE;
+
+  if (priv->segment->start >= priv->segment_stop)
+    return TRUE;
+
+  return FALSE;
+}
+
+/* OBJECTS LOCK must be taken when calling this ! */
+static gboolean
+update_pipeline_at_current_position (GnlComposition * comp)
+{
+  GstClockTime curpos;
+
+  /* Get current position */
+  if ((curpos = get_current_position (comp)) == GST_CLOCK_TIME_NONE) {
+    if (GST_CLOCK_TIME_IS_VALID (comp->priv->segment_start))
+      curpos = comp->priv->segment->start = comp->priv->segment_start;
+    else
+      curpos = 0;
+  }
+
+  update_start_stop_duration (comp);
+
+  return update_pipeline (comp, curpos, TRUE, TRUE);
+}
+
+static gboolean
+gnl_composition_commit_func (GnlObject * object, gboolean recurse)
+{
+  GList *tmp;
+  gboolean commited = FALSE;
+  GnlComposition *comp = GNL_COMPOSITION (object);
+  GnlCompositionPrivate *priv = comp->priv;
+
+
+  GST_DEBUG_OBJECT (object, "Commiting state");
+  COMP_OBJECTS_LOCK (comp);
+  for (tmp = priv->objects_start; tmp; tmp = tmp->next) {
+    if (gnl_object_commit (tmp->data, recurse))
+      commited = TRUE;
+  }
+
+  GST_DEBUG_OBJECT (object, "Linking up commit vmethod");
+  if (commited == FALSE &&
+      (GNL_OBJECT_CLASS (parent_class)->commit (object, recurse) == FALSE)) {
+    COMP_OBJECTS_UNLOCK (comp);
+    GST_DEBUG_OBJECT (object, "Nothing to commit, leaving");
+    return FALSE;
+  }
+
+  /* The topology of the composition might have changed, update the lists */
+  priv->objects_start = g_list_sort
+      (priv->objects_start, (GCompareFunc) objects_start_compare);
+  priv->objects_stop = g_list_sort
+      (priv->objects_stop, (GCompareFunc) objects_stop_compare);
+
+  /* And update the pipeline at current position if needed */
+  update_pipeline_at_current_position (comp);
+  COMP_OBJECTS_UNLOCK (comp);
+
+  GST_DEBUG_OBJECT (object, "Done commiting");
+  return TRUE;
+}
+
+/*
+ * get_new_seek_event:
+ *
+ * Returns a seek event for the currently configured segment
+ * and start/stop values
+ *
+ * The GstSegment and segment_start|stop must have been configured
+ * before calling this function.
+ */
+static GstEvent *
+get_new_seek_event (GnlComposition * comp, gboolean initial,
+    gboolean updatestoponly)
+{
+  GstSeekFlags flags = GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_FLUSH;
+  gint64 start, stop;
+  GstSeekType starttype = GST_SEEK_TYPE_SET;
+  GnlCompositionPrivate *priv = comp->priv;
+
+  GST_DEBUG_OBJECT (comp, "initial:%d", initial);
+  /* remove the seek flag */
+  if (!initial)
+    flags |= (GstSeekFlags) priv->segment->flags;
+
+  GST_DEBUG_OBJECT (comp,
+      "private->segment->start:%" GST_TIME_FORMAT " segment_start%"
+      GST_TIME_FORMAT, GST_TIME_ARGS (priv->segment->start),
+      GST_TIME_ARGS (priv->segment_start));
+
+  GST_DEBUG_OBJECT (comp,
+      "private->segment->stop:%" GST_TIME_FORMAT " segment_stop%"
+      GST_TIME_FORMAT, GST_TIME_ARGS (priv->segment->stop),
+      GST_TIME_ARGS (priv->segment_stop));
+
+  start = MAX (priv->segment->start, priv->segment_start);
+  stop = GST_CLOCK_TIME_IS_VALID (priv->segment->stop)
+      ? MIN (priv->segment->stop, priv->segment_stop)
+      : priv->segment_stop;
+
+  if (updatestoponly) {
+    starttype = GST_SEEK_TYPE_NONE;
+    start = GST_CLOCK_TIME_NONE;
+  }
+
+  GST_DEBUG_OBJECT (comp,
+      "Created new seek event. Flags:%d, start:%" GST_TIME_FORMAT ", stop:%"
+      GST_TIME_FORMAT ", rate:%lf", flags, GST_TIME_ARGS (start),
+      GST_TIME_ARGS (stop), priv->segment->rate);
+
+  return gst_event_new_seek (priv->segment->rate,
+      priv->segment->format, flags, starttype, start, GST_SEEK_TYPE_SET, stop);
+}
+
+/* OBJECTS LOCK must be taken when calling this ! */
+static GstClockTime
+get_current_position (GnlComposition * comp)
+{
+  GstPad *pad;
+  GnlObject *obj;
+  GnlCompositionPrivate *priv = comp->priv;
+  gboolean res;
+  gint64 value = GST_CLOCK_TIME_NONE;
+
+  /* 1. Try querying position downstream */
+  if (priv->ghostpad) {
+    GstPad *peer = gst_pad_get_peer (priv->ghostpad);
+
+    if (peer) {
+      res = gst_pad_query_position (peer, GST_FORMAT_TIME, &value);
+      gst_object_unref (peer);
+
+      if (res) {
+        GST_LOG_OBJECT (comp,
+            "Successfully got downstream position %" GST_TIME_FORMAT,
+            GST_TIME_ARGS ((guint64) value));
+        goto beach;
+      }
+    }
+
+    GST_DEBUG_OBJECT (comp, "Downstream position query failed");
+
+    /* resetting format/value */
+    value = GST_CLOCK_TIME_NONE;
+  }
+
+  /* 2. If downstream fails , try within the current stack */
+  if (!priv->current) {
+    GST_DEBUG_OBJECT (comp, "No current stack, can't send query");
+    goto beach;
+  }
+
+  obj = (GnlObject *) priv->current->data;
+
+  if (!(pad = get_src_pad ((GstElement *) obj)))
+    goto beach;
+
+  res = gst_pad_query_position (pad, GST_FORMAT_TIME, &value);
+
+  if (G_UNLIKELY (res == FALSE)) {
+    GST_WARNING_OBJECT (comp,
+        "query failed or returned a format different from TIME");
+    value = GST_CLOCK_TIME_NONE;
+  } else {
+    GST_LOG_OBJECT (comp, "Query returned %" GST_TIME_FORMAT,
+        GST_TIME_ARGS ((guint64) value));
+  }
+
+beach:
+  return (guint64) value;
+}
+
+static gboolean
+update_base_time (GNode * node, GstClockTime * timestamp)
+{
+  if (GNL_IS_OPERATION (node->data))
+    gnl_operation_update_base_time (GNL_OPERATION (node->data), *timestamp);
+
+  return FALSE;
+}
+
+/* WITH OBJECTS LOCK TAKEN */
+static void
+update_operations_base_time (GnlComposition * comp, gboolean reverse)
+{
+  GstClockTime timestamp;
+
+  if (reverse)
+    timestamp = comp->priv->segment->stop;
+  else
+    timestamp = comp->priv->segment->start;
+
+  g_node_traverse (comp->priv->current, G_IN_ORDER, G_TRAVERSE_ALL, -1,
+      (GNodeTraverseFunc) update_base_time, &timestamp);
+}
+
+/*
+  Figures out if pipeline needs updating.
+  Updates it and sends the seek event.
+  Sends flush events downstream if needed.
+  can be called by user_seek or segment_done
+
+  initial : FIXME : ???? Always seems to be TRUE
+  update : TRUE from EOS, FALSE from seek
+*/
+
+static gboolean
+seek_handling (GnlComposition * comp, gboolean initial, gboolean update)
+{
+  GST_DEBUG_OBJECT (comp, "initial:%d, update:%d", initial, update);
+
+  COMP_FLUSHING_LOCK (comp);
+  GST_DEBUG_OBJECT (comp, "Setting flushing to TRUE");
+  comp->priv->flushing = TRUE;
+  COMP_FLUSHING_UNLOCK (comp);
+
+  COMP_OBJECTS_LOCK (comp);
+  if (update || have_to_update_pipeline (comp)) {
+    if (comp->priv->segment->rate >= 0.0)
+      update_pipeline (comp, comp->priv->segment->start, initial, !update);
+    else
+      update_pipeline (comp, comp->priv->segment->stop, initial, !update);
+  } else {
+    update_operations_base_time (comp, !(comp->priv->segment->rate >= 0.0));
+  }
+  COMP_OBJECTS_UNLOCK (comp);
+
+  return TRUE;
+}
+
+static void
+handle_seek_event (GnlComposition * comp, GstEvent * event)
+{
+  gdouble rate;
+  GstFormat format;
+  GstSeekFlags flags;
+  GstSeekType cur_type, stop_type;
+  gint64 cur, stop;
+  GnlCompositionPrivate *priv = comp->priv;
+
+  gst_event_parse_seek (event, &rate, &format, &flags,
+      &cur_type, &cur, &stop_type, &stop);
+
+  GST_DEBUG_OBJECT (comp,
+      "start:%" GST_TIME_FORMAT " -- stop:%" GST_TIME_FORMAT "  flags:%d",
+      GST_TIME_ARGS (cur), GST_TIME_ARGS (stop), flags);
+
+  gst_segment_do_seek (priv->segment,
+      rate, format, flags, cur_type, cur, stop_type, stop, NULL);
+  gst_segment_do_seek (priv->outside_segment,
+      rate, format, flags, cur_type, cur, stop_type, stop, NULL);
+
+  GST_DEBUG_OBJECT (comp, "Segment now has flags:%d", priv->segment->flags);
+
+  /* crop the segment start/stop values */
+  /* Only crop segment start value if we don't have a default object */
+  if (priv->expandables == NULL)
+    priv->segment->start = MAX (priv->segment->start, GNL_OBJECT_START (comp));
+  priv->segment->stop = MIN (priv->segment->stop, GNL_OBJECT_STOP (comp));
+
+  comp->priv->next_base_time = 0;
+
+  seek_handling (comp, TRUE, FALSE);
+}
+
+static gboolean
+gnl_composition_event_handler (GstPad * ghostpad, GstObject * parent,
+    GstEvent * event)
+{
+  GnlComposition *comp = (GnlComposition *) parent;
+  GnlCompositionPrivate *priv = comp->priv;
+  gboolean res = TRUE;
+
+  GST_DEBUG_OBJECT (comp, "event type:%s", GST_EVENT_TYPE_NAME (event));
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_SEEK:
+    {
+      GstEvent *nevent;
+
+      handle_seek_event (comp, event);
+
+      /* the incoming event might not be quite correct, we get a new proper
+       * event to pass on to the children. */
+      COMP_OBJECTS_LOCK (comp);
+      nevent = get_new_seek_event (comp, FALSE, FALSE);
+      COMP_OBJECTS_UNLOCK (comp);
+      gst_event_unref (event);
+      event = nevent;
+      priv->reset_time = TRUE;
+      break;
+    }
+    case GST_EVENT_QOS:
+    {
+      gdouble prop;
+      GstQOSType qostype;
+      GstClockTimeDiff diff;
+      GstClockTime timestamp;
+
+      gst_event_parse_qos (event, &qostype, &prop, &diff, &timestamp);
+
+      GST_INFO_OBJECT (comp,
+          "timestamp:%" GST_TIME_FORMAT " segment.start:%" GST_TIME_FORMAT
+          " segment.stop:%" GST_TIME_FORMAT " segment_start%" GST_TIME_FORMAT
+          " segment_stop:%" GST_TIME_FORMAT, GST_TIME_ARGS (timestamp),
+          GST_TIME_ARGS (priv->outside_segment->start),
+          GST_TIME_ARGS (priv->outside_segment->stop),
+          GST_TIME_ARGS (priv->segment_start),
+          GST_TIME_ARGS (priv->segment_stop));
+
+      /* The problem with QoS events is the following:
+       * At each new internal segment (i.e. when we re-arrange our internal
+       * elements) we send flushing seeks to those elements (to properly
+       * configure their playback range) but don't let the FLUSH events get
+       * downstream.
+       *
+       * The problem is that the QoS running timestamps we receive from
+       * downstream will not have taken into account those flush.
+       *
+       * What we need to do is to translate to our internal running timestamps
+       * which for each configured segment starts at 0 for those elements.
+       *
+       * The generic algorithm for the incoming running timestamp translation
+       * is therefore:
+       *     (original_seek_time : original seek position received from usptream)
+       *     (current_segment_start : Start position of the currently configured
+       *                              timeline segment)
+       *
+       *     difference = original_seek_time - current_segment_start
+       *     new_qos_position = upstream_qos_position - difference
+       *
+       * The new_qos_position is only valid when:
+       *    * it applies to the current segment (difference > 0)
+       *    * The QoS difference + timestamp is greater than the difference
+       *
+       */
+
+      if (GST_CLOCK_TIME_IS_VALID (priv->outside_segment->start)) {
+        GstClockTimeDiff curdiff;
+
+        /* We'll either create a new event or discard it */
+        gst_event_unref (event);
+
+        if (priv->segment->rate < 0.0)
+          curdiff = priv->outside_segment->stop - priv->segment_stop;
+        else
+          curdiff = priv->segment_start - priv->outside_segment->start;
+        GST_DEBUG ("curdiff %" GST_TIME_FORMAT, GST_TIME_ARGS (curdiff));
+        if ((curdiff != 0) && ((timestamp < curdiff)
+                || (curdiff > timestamp + diff))) {
+          GST_DEBUG_OBJECT (comp,
+              "QoS event outside of current segment, discarding");
+          /* The QoS timestamp is before the currently set-up pipeline */
+          goto beach;
+        }
+
+        /* Substract the amount of running time we've already outputted
+         * until the currently configured pipeline from the QoS timestamp.*/
+        timestamp -= curdiff;
+        GST_INFO_OBJECT (comp,
+            "Creating new QoS event with timestamp %" GST_TIME_FORMAT,
+            GST_TIME_ARGS (timestamp));
+        event = gst_event_new_qos (qostype, prop, diff, timestamp);
+      }
+      break;
+    }
+    default:
+      break;
+  }
+
+  if (res && priv->ghostpad) {
+    COMP_OBJECTS_LOCK (comp);
+
+    /* If the timeline isn't entirely reconstructed, we silently ignore the
+     * event. In the case of seeks the pipeline will already be correctly
+     * configured at this point*/
+    if (priv->waitingpads == 0) {
+      COMP_OBJECTS_UNLOCK (comp);
+      GST_DEBUG_OBJECT (comp, "About to call gnl_event_pad_func()");
+      res = priv->gnl_event_pad_func (priv->ghostpad, parent, event);
+      priv->reset_time = FALSE;
+      GST_DEBUG_OBJECT (comp, "Done calling gnl_event_pad_func() %d", res);
+    } else {
+      COMP_OBJECTS_UNLOCK (comp);
+      gst_event_unref (event);
+    }
+
+  }
+
+beach:
+  return res;
+}
+
+static GstPadProbeReturn
+pad_blocked (GstPad * pad, GstPadProbeInfo * info, GnlComposition * comp)
+{
+  GST_DEBUG_OBJECT (comp, "Pad : %s:%s", GST_DEBUG_PAD_NAME (pad));
+
+  return GST_PAD_PROBE_OK;
+}
+
+static GstPadProbeReturn
+drop_data (GstPad * pad, GstPadProbeInfo * info, GnlCompositionEntry * entry)
+{
+  /* When updating the pipeline, do not let data flowing */
+  if (!GST_IS_EVENT (info->data)) {
+    GST_LOG_OBJECT (pad, "Dropping data while updating pipeline");
+    return GST_PAD_PROBE_DROP;
+  } else {
+    GstEvent *event = GST_EVENT (info->data);
+
+    if (GST_EVENT_TYPE (event) == GST_EVENT_SEEK) {
+      entry->seeked = TRUE;
+      GST_DEBUG_OBJECT (pad, "Got SEEK event");
+    } else if (entry->seeked == TRUE &&
+        GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT) {
+      entry->seeked = FALSE;
+      entry->dataprobeid = 0;
+
+      GST_DEBUG_OBJECT (pad, "Already seeked and got segment,"
+          " removing probe");
+      return GST_PAD_PROBE_REMOVE;
+    }
+  }
+
+  return GST_PAD_PROBE_OK;
+}
+
+static inline void
+gnl_composition_remove_ghostpad (GnlComposition * comp)
+{
+  GnlCompositionPrivate *priv = comp->priv;
+
+  GST_DEBUG_OBJECT (comp, "Removing ghostpad");
+
+  if (priv->ghosteventprobe) {
+    GstPad *target;
+
+    target = gst_ghost_pad_get_target ((GstGhostPad *) priv->ghostpad);
+    if (target)
+      gst_pad_remove_probe (target, priv->ghosteventprobe);
+    priv->ghosteventprobe = 0;
+  }
+
+  gnl_object_remove_ghost_pad (GNL_OBJECT (comp), priv->ghostpad);
+  priv->ghostpad = NULL;
+  priv->toplevelentry = NULL;
+  priv->send_stream_start = TRUE;
+}
+
+/* gnl_composition_ghost_pad_set_target:
+ * target: The target #GstPad. The refcount will be decremented (given to the ghostpad).
+ * entry: The GnlCompositionEntry to which the pad belongs
+ */
+
+static void
+gnl_composition_ghost_pad_set_target (GnlComposition * comp, GstPad * target,
+    GnlCompositionEntry * entry)
+{
+  GnlCompositionPrivate *priv = comp->priv;
+  gboolean hadghost = priv->ghostpad ? TRUE : FALSE;
+
+  if (target)
+    GST_DEBUG_OBJECT (comp, "target:%s:%s , hadghost:%d",
+        GST_DEBUG_PAD_NAME (target), hadghost);
+  else
+    GST_DEBUG_OBJECT (comp, "Removing target, hadghost:%d", hadghost);
+
+  if (!hadghost) {
+    /* Create new ghostpad */
+    GstPad *ghostpad =
+        gnl_object_ghost_pad_no_target ((GnlObject *) comp, "src", GST_PAD_SRC);
+
+    if (!priv->gnl_event_pad_func) {
+      GST_DEBUG_OBJECT (ghostpad, "About to replace event_pad_func");
+      priv->gnl_event_pad_func = GST_PAD_EVENTFUNC (ghostpad);
+    }
+
+    gst_pad_set_event_function (ghostpad,
+        GST_DEBUG_FUNCPTR (gnl_composition_event_handler));
+    GST_DEBUG_OBJECT (ghostpad, "eventfunc is now %s",
+        GST_DEBUG_FUNCPTR_NAME (GST_PAD_EVENTFUNC (ghostpad)));
+
+    priv->ghostpad = ghostpad;
+  } else {
+    GstPad *ptarget = gst_ghost_pad_get_target (GST_GHOST_PAD (priv->ghostpad));
+
+    if (ptarget && ptarget == target) {
+      GST_DEBUG_OBJECT (comp,
+          "Target of ghostpad is the same as existing one, not changing");
+      gst_object_unref (ptarget);
+      return;
+    }
+
+    /* Unset previous target */
+    if (ptarget) {
+      GST_DEBUG_OBJECT (comp, "Previous target was %s:%s",
+          GST_DEBUG_PAD_NAME (ptarget));
+
+      if (!priv->toplevelentry->probeid) {
+        /* If it's not blocked, block it */
+        priv->toplevelentry->probeid =
+            gst_pad_add_probe (ptarget,
+            GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM | GST_PAD_PROBE_TYPE_IDLE,
+            (GstPadProbeCallback) pad_blocked, comp, NULL);
+      }
+
+      if (!priv->toplevelentry->dataprobeid) {
+        priv->toplevelentry->dataprobeid = gst_pad_add_probe (ptarget,
+            GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BUFFER_LIST |
+            GST_PAD_PROBE_TYPE_EVENT_BOTH, (GstPadProbeCallback) drop_data,
+            priv->toplevelentry, NULL);
+      }
+
+      /* remove event probe */
+      if (priv->ghosteventprobe) {
+        gst_pad_remove_probe (ptarget, priv->ghosteventprobe);
+        priv->ghosteventprobe = 0;
+      }
+      gst_object_unref (ptarget);
+
+    }
+  }
+
+  /* Actually set the target */
+  gnl_object_ghost_pad_set_target ((GnlObject *) comp, priv->ghostpad, target);
+
+  /* Set top-level entry (will be NULL if unsetting) */
+  priv->toplevelentry = entry;
+
+  if (target && (priv->ghosteventprobe == 0)) {
+    priv->ghosteventprobe =
+        gst_pad_add_probe (target,
+        GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | GST_PAD_PROBE_TYPE_EVENT_FLUSH,
+        (GstPadProbeCallback) ghost_event_probe_handler, comp, NULL);
+    GST_DEBUG_OBJECT (comp, "added event probe %lu", priv->ghosteventprobe);
+  }
+
+  if (!hadghost) {
+    gst_pad_set_active (priv->ghostpad, TRUE);
+
+    COMP_OBJECTS_UNLOCK (comp);
+    if (!gst_element_add_pad (GST_ELEMENT (comp), priv->ghostpad))
+      GST_WARNING ("Couldn't add the ghostpad");
+    else
+      gst_element_no_more_pads (GST_ELEMENT (comp));
+    COMP_OBJECTS_LOCK (comp);
+  }
+
+  GST_DEBUG_OBJECT (comp, "END");
+}
+
+static void
+refine_start_stop_in_region_above_priority (GnlComposition * composition,
+    GstClockTime timestamp, GstClockTime start,
+    GstClockTime stop,
+    GstClockTime * rstart, GstClockTime * rstop, guint32 priority)
+{
+  GList *tmp;
+  GnlObject *object;
+  GstClockTime nstart = start, nstop = stop;
+
+  GST_DEBUG_OBJECT (composition,
+      "timestamp:%" GST_TIME_FORMAT " start: %" GST_TIME_FORMAT " stop: %"
+      GST_TIME_FORMAT " priority:%u", GST_TIME_ARGS (timestamp),
+      GST_TIME_ARGS (start), GST_TIME_ARGS (stop), priority);
+
+  for (tmp = composition->priv->objects_start; tmp; tmp = tmp->next) {
+    object = (GnlObject *) tmp->data;
+
+    GST_LOG_OBJECT (object, "START %" GST_TIME_FORMAT "--%" GST_TIME_FORMAT,
+        GST_TIME_ARGS (object->start), GST_TIME_ARGS (object->stop));
+
+    if ((object->priority >= priority) || (!object->active))
+      continue;
+
+    if (object->start <= timestamp)
+      continue;
+
+    if (object->start >= nstop)
+      continue;
+
+    nstop = object->start;
+
+    GST_DEBUG_OBJECT (composition,
+        "START Found %s [prio:%u] at %" GST_TIME_FORMAT,
+        GST_OBJECT_NAME (object), object->priority,
+        GST_TIME_ARGS (object->start));
+
+    break;
+  }
+
+  for (tmp = composition->priv->objects_stop; tmp; tmp = tmp->next) {
+    object = (GnlObject *) tmp->data;
+
+    GST_LOG_OBJECT (object, "STOP %" GST_TIME_FORMAT "--%" GST_TIME_FORMAT,
+        GST_TIME_ARGS (object->start), GST_TIME_ARGS (object->stop));
+
+    if ((object->priority >= priority) || (!object->active))
+      continue;
+
+    if (object->stop >= timestamp)
+      continue;
+
+    if (object->stop <= nstart)
+      continue;
+
+    nstart = object->stop;
+
+    GST_DEBUG_OBJECT (composition,
+        "STOP Found %s [prio:%u] at %" GST_TIME_FORMAT,
+        GST_OBJECT_NAME (object), object->priority,
+        GST_TIME_ARGS (object->start));
+
+    break;
+  }
+
+  if (*rstart)
+    *rstart = nstart;
+
+  if (*rstop)
+    *rstop = nstop;
+}
+
+
+/*
+ * Converts a sorted list to a tree
+ * Recursive
+ *
+ * stack will be set to the next item to use in the parent.
+ * If operations number of sinks is limited, it will only use that number.
+ */
+
+static GNode *
+convert_list_to_tree (GList ** stack, GstClockTime * start,
+    GstClockTime * stop, guint32 * highprio)
+{
+  GNode *ret;
+  guint nbsinks;
+  gboolean limit;
+  GList *tmp;
+  GnlObject *object;
+
+  if (!stack || !*stack)
+    return NULL;
+
+  object = (GnlObject *) (*stack)->data;
+
+  GST_DEBUG ("object:%s , *start:%" GST_TIME_FORMAT ", *stop:%"
+      GST_TIME_FORMAT " highprio:%d",
+      GST_ELEMENT_NAME (object), GST_TIME_ARGS (*start),
+      GST_TIME_ARGS (*stop), *highprio);
+
+  /* update earliest stop */
+  if (GST_CLOCK_TIME_IS_VALID (*stop)) {
+    if (GST_CLOCK_TIME_IS_VALID (object->stop) && (*stop > object->stop))
+      *stop = object->stop;
+  } else {
+    *stop = object->stop;
+  }
+
+  if (GST_CLOCK_TIME_IS_VALID (*start)) {
+    if (GST_CLOCK_TIME_IS_VALID (object->start) && (*start < object->start))
+      *start = object->start;
+  } else {
+    *start = object->start;
+  }
+
+  if (GNL_OBJECT_IS_SOURCE (object)) {
+    *stack = g_list_next (*stack);
+
+    /* update highest priority.
+     * We do this here, since it's only used with sources (leafs of the tree) */
+    if (object->priority > *highprio)
+      *highprio = object->priority;
+
+    ret = g_node_new (object);
+
+    goto beach;
+  } else {
+    /* GnlOperation */
+    GnlOperation *oper = (GnlOperation *) object;
+
+    GST_LOG_OBJECT (oper, "operation, num_sinks:%d", oper->num_sinks);
+
+    ret = g_node_new (object);
+    limit = (oper->dynamicsinks == FALSE);
+    nbsinks = oper->num_sinks;
+
+    /* FIXME : if num_sinks == -1 : request the proper number of pads */
+    for (tmp = g_list_next (*stack); tmp && (!limit || nbsinks);) {
+      g_node_append (ret, convert_list_to_tree (&tmp, start, stop, highprio));
+      if (limit)
+        nbsinks--;
+    }
+
+    *stack = tmp;
+  }
+
+beach:
+  GST_DEBUG_OBJECT (object,
+      "*start:%" GST_TIME_FORMAT " *stop:%" GST_TIME_FORMAT
+      " priority:%u", GST_TIME_ARGS (*start), GST_TIME_ARGS (*stop), *highprio);
+
+  return ret;
+}
+
+/*
+ * get_stack_list:
+ * @comp: The #GnlComposition
+ * @timestamp: The #GstClockTime to look at
+ * @priority: The priority level to start looking from
+ * @activeonly: Only look for active elements if TRUE
+ * @start: The biggest start time of the objects in the stack
+ * @stop: The smallest stop time of the objects in the stack
+ * @highprio: The highest priority in the stack
+ *
+ * Not MT-safe, you should take the objects lock before calling it.
+ * Returns: A tree of #GNode sorted in priority order, corresponding
+ * to the given search arguments. The returned value can be #NULL.
+ *
+ * WITH OBJECTS LOCK TAKEN
+ */
+static GNode *
+get_stack_list (GnlComposition * comp, GstClockTime timestamp,
+    guint32 priority, gboolean activeonly, GstClockTime * start,
+    GstClockTime * stop, guint * highprio)
+{
+  GList *tmp;
+  GList *stack = NULL;
+  GNode *ret = NULL;
+  GstClockTime nstart = GST_CLOCK_TIME_NONE;
+  GstClockTime nstop = GST_CLOCK_TIME_NONE;
+  GstClockTime first_out_of_stack = GST_CLOCK_TIME_NONE;
+  guint32 highest = 0;
+  gboolean reverse = (comp->priv->segment->rate < 0.0);
+
+  GST_DEBUG_OBJECT (comp,
+      "timestamp:%" GST_TIME_FORMAT ", priority:%u, activeonly:%d",
+      GST_TIME_ARGS (timestamp), priority, activeonly);
+
+  GST_LOG ("objects_start:%p objects_stop:%p", comp->priv->objects_start,
+      comp->priv->objects_stop);
+
+  if (reverse) {
+    for (tmp = comp->priv->objects_stop; tmp; tmp = g_list_next (tmp)) {
+      GnlObject *object = (GnlObject *) tmp->data;
+
+      GST_LOG_OBJECT (object,
+          "start: %" GST_TIME_FORMAT ", stop:%" GST_TIME_FORMAT " , duration:%"
+          GST_TIME_FORMAT ", priority:%u, active:%d",
+          GST_TIME_ARGS (object->start), GST_TIME_ARGS (object->stop),
+          GST_TIME_ARGS (object->duration), object->priority, object->active);
+
+      if (object->stop >= timestamp) {
+        if ((object->start < timestamp) &&
+            (object->priority >= priority) &&
+            ((!activeonly) || (object->active))) {
+          GST_LOG_OBJECT (comp, "adding %s: sorted to the stack",
+              GST_OBJECT_NAME (object));
+          stack = g_list_insert_sorted (stack, object,
+              (GCompareFunc) priority_comp);
+          if (GNL_IS_OPERATION (object))
+            gnl_operation_update_base_time (GNL_OPERATION (object), timestamp);
+        }
+      } else {
+        GST_LOG_OBJECT (comp, "too far, stopping iteration");
+        first_out_of_stack = object->stop;
+        break;
+      }
+    }
+  } else {
+    for (tmp = comp->priv->objects_start; tmp; tmp = g_list_next (tmp)) {
+      GnlObject *object = (GnlObject *) tmp->data;
+
+      GST_LOG_OBJECT (object,
+          "start: %" GST_TIME_FORMAT " , stop:%" GST_TIME_FORMAT " , duration:%"
+          GST_TIME_FORMAT ", priority:%u", GST_TIME_ARGS (object->start),
+          GST_TIME_ARGS (object->stop), GST_TIME_ARGS (object->duration),
+          object->priority);
+
+      if (object->start <= timestamp) {
+        if ((object->stop > timestamp) &&
+            (object->priority >= priority) &&
+            ((!activeonly) || (object->active))) {
+          GST_LOG_OBJECT (comp, "adding %s: sorted to the stack",
+              GST_OBJECT_NAME (object));
+          stack = g_list_insert_sorted (stack, object,
+              (GCompareFunc) priority_comp);
+          if (GNL_IS_OPERATION (object))
+            gnl_operation_update_base_time (GNL_OPERATION (object), timestamp);
+        }
+      } else {
+        GST_LOG_OBJECT (comp, "too far, stopping iteration");
+        first_out_of_stack = object->start;
+        break;
+      }
+    }
+  }
+
+  /* Insert the expandables */
+  if (G_LIKELY (timestamp < GNL_OBJECT_STOP (comp)))
+    for (tmp = comp->priv->expandables; tmp; tmp = tmp->next) {
+      GST_DEBUG_OBJECT (comp, "Adding expandable %s sorted to the list",
+          GST_OBJECT_NAME (tmp->data));
+      stack = g_list_insert_sorted (stack, tmp->data,
+          (GCompareFunc) priority_comp);
+      if (GNL_IS_OPERATION (tmp->data))
+        gnl_operation_update_base_time (GNL_OPERATION (tmp->data), timestamp);
+    }
+
+  /* convert that list to a stack */
+  tmp = stack;
+  ret = convert_list_to_tree (&tmp, &nstart, &nstop, &highest);
+  if (GST_CLOCK_TIME_IS_VALID (first_out_of_stack)) {
+    if (reverse && nstart < first_out_of_stack)
+      nstart = first_out_of_stack;
+    else if (!reverse && nstop > first_out_of_stack)
+      nstop = first_out_of_stack;
+  }
+
+  GST_DEBUG ("nstart:%" GST_TIME_FORMAT ", nstop:%" GST_TIME_FORMAT,
+      GST_TIME_ARGS (nstart), GST_TIME_ARGS (nstop));
+
+  if (*stop)
+    *stop = nstop;
+  if (*start)
+    *start = nstart;
+  if (highprio)
+    *highprio = highest;
+
+  g_list_free (stack);
+
+  return ret;
+}
+
+/*
+ * get_clean_toplevel_stack:
+ * @comp: The #GnlComposition
+ * @timestamp: The #GstClockTime to look at
+ * @stop_time: Pointer to a #GstClockTime for min stop time of returned stack
+ * @start_time: Pointer to a #GstClockTime for greatest start time of returned stack
+ *
+ * Returns: The new current stack for the given #GnlComposition and @timestamp.
+ *
+ * WITH OBJECTS LOCK TAKEN
+ */
+static GNode *
+get_clean_toplevel_stack (GnlComposition * comp, GstClockTime * timestamp,
+    GstClockTime * start_time, GstClockTime * stop_time)
+{
+  GNode *stack = NULL;
+  GstClockTime start = G_MAXUINT64;
+  GstClockTime stop = G_MAXUINT64;
+  guint highprio;
+  gboolean reverse = (comp->priv->segment->rate < 0.0);
+
+  GST_DEBUG_OBJECT (comp, "timestamp:%" GST_TIME_FORMAT,
+      GST_TIME_ARGS (*timestamp));
+  GST_DEBUG ("start:%" GST_TIME_FORMAT ", stop:%" GST_TIME_FORMAT,
+      GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
+
+  stack = get_stack_list (comp, *timestamp, 0, TRUE, &start, &stop, &highprio);
+
+  if (!stack &&
+      ((reverse && (*timestamp > COMP_REAL_START (comp))) ||
+          (!reverse && (*timestamp < COMP_REAL_STOP (comp))))) {
+    GST_ELEMENT_ERROR (comp, STREAM, WRONG_TYPE,
+        ("Gaps ( at %" GST_TIME_FORMAT
+            ") in the stream is not supported, the application is responsible"
+            " for filling them", GST_TIME_ARGS (*timestamp)),
+        ("Gap in the composition this should never"
+            "append, make sure to fill them"));
+
+    return NULL;
+  }
+
+  GST_DEBUG ("start:%" GST_TIME_FORMAT ", stop:%" GST_TIME_FORMAT,
+      GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
+
+  if (stack) {
+    guint32 top_priority = GNL_OBJECT_PRIORITY (stack->data);
+
+    /* Figure out if there's anything blocking us with smaller priority */
+    refine_start_stop_in_region_above_priority (comp, *timestamp, start,
+        stop, &start, &stop, (highprio == 0) ? top_priority : highprio);
+  }
+
+  if (*stop_time) {
+    if (stack)
+      *stop_time = stop;
+    else
+      *stop_time = 0;
+  }
+
+  if (*start_time) {
+    if (stack)
+      *start_time = start;
+    else
+      *start_time = 0;
+  }
+
+  GST_DEBUG_OBJECT (comp,
+      "Returning timestamp:%" GST_TIME_FORMAT " , start_time:%"
+      GST_TIME_FORMAT " , stop_time:%" GST_TIME_FORMAT,
+      GST_TIME_ARGS (*timestamp), GST_TIME_ARGS (*start_time),
+      GST_TIME_ARGS (*stop_time));
+
+  return stack;
+}
+
+
+/*
+ *
+ * UTILITY FUNCTIONS
+ *
+ */
+
+/*
+ * get_src_pad:
+ * element: a #GstElement
+ *
+ * Returns: The src pad for the given element. A reference was added to the
+ * returned pad, remove it when you don't need that pad anymore.
+ * Returns NULL if there's no source pad.
+ */
+static GstPad *
+get_src_pad (GstElement * element)
+{
+  GstIterator *it;
+  GstIteratorResult itres;
+  GValue item = { 0, };
+  GstPad *srcpad;
+
+  it = gst_element_iterate_src_pads (element);
+
+  itres = gst_iterator_next (it, &item);
+  if (itres != GST_ITERATOR_OK) {
+    GST_DEBUG ("%s doesn't have a src pad !", GST_ELEMENT_NAME (element));
+    srcpad = NULL;
+  } else {
+    srcpad = g_value_get_object (&item);
+    gst_object_ref (srcpad);
+    g_value_reset (&item);
+  }
+
+  gst_iterator_free (it);
+
+  return srcpad;
+}
+
+
+/*
+ *
+ * END OF UTILITY FUNCTIONS
+ *
+ */
+
+static gboolean
+set_child_caps (GValue * item, GValue * ret G_GNUC_UNUSED, GnlObject * comp)
+{
+  GstElement *child = g_value_get_object (item);
+
+  gnl_object_set_caps ((GnlObject *) child, comp->caps);
+
+  return TRUE;
+}
+
+static gpointer
+update_pipeline_func (GnlComposition * comp)
+{
+  while (comp->priv->running) {
+    GnlCompositionPrivate *priv;
+    gboolean reverse;
+
+    WAIT_FOR_UPDATE_PIPELINE (comp);
+
+    /* Set up a non-initial seek on segment_stop */
+    priv = comp->priv;
+    reverse = (priv->segment->rate < 0.0);
+    if (!reverse) {
+      GST_DEBUG_OBJECT (comp,
+          "Setting segment->start to segment_stop:%" GST_TIME_FORMAT,
+          GST_TIME_ARGS (priv->segment_stop));
+      priv->segment->start = priv->segment_stop;
+    } else {
+      GST_DEBUG_OBJECT (comp,
+          "Setting segment->stop to segment_start:%" GST_TIME_FORMAT,
+          GST_TIME_ARGS (priv->segment_start));
+      priv->segment->stop = priv->segment_start;
+    }
+
+    seek_handling (comp, TRUE, TRUE);
+
+    if (!priv->current) {
+      /* If we're at the end, post SEGMENT_DONE, or push EOS */
+      GST_DEBUG_OBJECT (comp, "Nothing else to play");
+
+      if (!(priv->segment->flags & GST_SEEK_FLAG_SEGMENT)
+          && priv->ghostpad) {
+        GST_DEBUG_OBJECT (comp, "Real EOS should be sent now");
+      } else if (priv->segment->flags & GST_SEEK_FLAG_SEGMENT) {
+        gint64 epos;
+
+        if (GST_CLOCK_TIME_IS_VALID (priv->segment->stop))
+          epos = (MIN (priv->segment->stop, GNL_OBJECT_STOP (comp)));
+        else
+          epos = GNL_OBJECT_STOP (comp);
+
+        GST_LOG_OBJECT (comp, "Emitting segment done pos %" GST_TIME_FORMAT,
+            GST_TIME_ARGS (epos));
+        gst_element_post_message (GST_ELEMENT_CAST (comp),
+            gst_message_new_segment_done (GST_OBJECT (comp),
+                priv->segment->format, epos));
+        gst_pad_push_event (priv->ghostpad,
+            gst_event_new_segment_done (priv->segment->format, epos));
+      }
+    }
+
+  }
+
+  return NULL;
+}
+
+static GstStateChangeReturn
+gnl_composition_change_state (GstElement * element, GstStateChange transition)
+{
+  GnlComposition *comp = (GnlComposition *) element;
+  GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+
+  GST_DEBUG_OBJECT (comp, "%s => %s",
+      gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
+      gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)));
+
+  switch (transition) {
+    case GST_STATE_CHANGE_NULL_TO_READY:
+      comp->priv->running = TRUE;
+      comp->priv->update_pipeline_thread =
+          g_thread_new ("update_pipeline_thread",
+          (GThreadFunc) update_pipeline_func, comp);
+      break;
+    case GST_STATE_CHANGE_READY_TO_PAUSED:
+    {
+      GstIterator *children;
+
+      gnl_composition_reset (comp);
+
+      /* state-lock all elements */
+      GST_DEBUG_OBJECT (comp,
+          "Setting all children to READY and locking their state");
+
+      children = gst_bin_iterate_elements (GST_BIN (comp));
+
+    retry_lock:
+      if (G_UNLIKELY (gst_iterator_fold (children,
+                  (GstIteratorFoldFunction) lock_child_state, NULL,
+                  NULL) == GST_ITERATOR_RESYNC)) {
+        gst_iterator_resync (children);
+        goto retry_lock;
+      }
+
+      gst_iterator_free (children);
+
+      /* Set caps on all objects */
+      if (G_UNLIKELY (!gst_caps_is_any (GNL_OBJECT (comp)->caps))) {
+        children = gst_bin_iterate_elements (GST_BIN (comp));
+
+      retry_caps:
+        if (G_UNLIKELY (gst_iterator_fold (children,
+                    (GstIteratorFoldFunction) set_child_caps, NULL,
+                    comp) == GST_ITERATOR_RESYNC)) {
+          gst_iterator_resync (children);
+          goto retry_caps;
+        }
+        gst_iterator_free (children);
+      }
+
+
+      /* set ghostpad target */
+      COMP_OBJECTS_LOCK (comp);
+      if (!(update_pipeline (comp, COMP_REAL_START (comp), TRUE, TRUE))) {
+        ret = GST_STATE_CHANGE_FAILURE;
+        COMP_OBJECTS_UNLOCK (comp);
+        goto beach;
+      }
+
+      COMP_OBJECTS_UNLOCK (comp);
+    }
+      break;
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+      gnl_composition_reset (comp);
+      break;
+    case GST_STATE_CHANGE_READY_TO_NULL:
+      gnl_composition_reset (comp);
+      comp->priv->running = FALSE;
+      SIGNAL_UPDATE_PIPELINE (comp);
+      g_thread_join (comp->priv->update_pipeline_thread);
+      break;
+    default:
+      break;
+  }
+
+  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+
+  if (ret == GST_STATE_CHANGE_FAILURE)
+    return ret;
+
+  switch (transition) {
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+    case GST_STATE_CHANGE_READY_TO_NULL:
+      unblock_children (comp);
+      break;
+    default:
+      break;
+  }
+
+beach:
+  return ret;
+}
+
+static gint
+objects_start_compare (GnlObject * a, GnlObject * b)
+{
+  if (a->start == b->start) {
+    if (a->priority < b->priority)
+      return -1;
+    if (a->priority > b->priority)
+      return 1;
+    return 0;
+  }
+  if (a->start < b->start)
+    return -1;
+  if (a->start > b->start)
+    return 1;
+  return 0;
+}
+
+static gint
+objects_stop_compare (GnlObject * a, GnlObject * b)
+{
+  if (a->stop == b->stop) {
+    if (a->priority < b->priority)
+      return -1;
+    if (a->priority > b->priority)
+      return 1;
+    return 0;
+  }
+  if (b->stop < a->stop)
+    return -1;
+  if (b->stop > a->stop)
+    return 1;
+  return 0;
+}
+
+/* WITH OBJECTS LOCK TAKEN */
+static void
+update_start_stop_duration (GnlComposition * comp)
+{
+  GnlObject *obj;
+  GnlObject *cobj = (GnlObject *) comp;
+  GnlCompositionPrivate *priv = comp->priv;
+
+  if (!priv->objects_start) {
+    GST_LOG ("no objects, resetting everything to 0");
+
+    if (cobj->start) {
+      cobj->start = cobj->pending_start = 0;
+      g_object_notify_by_pspec (G_OBJECT (cobj),
+          gnlobject_properties[GNLOBJECT_PROP_START]);
+    }
+
+    if (cobj->duration) {
+      cobj->pending_duration = cobj->duration = 0;
+      g_object_notify_by_pspec (G_OBJECT (cobj),
+          gnlobject_properties[GNLOBJECT_PROP_DURATION]);
+      signal_duration_change (comp);
+    }
+
+    if (cobj->stop) {
+      cobj->stop = 0;
+      g_object_notify_by_pspec (G_OBJECT (cobj),
+          gnlobject_properties[GNLOBJECT_PROP_STOP]);
+    }
+
+    return;
+  }
+
+  /* If we have a default object, the start position is 0 */
+  if (priv->expandables) {
+    GST_LOG_OBJECT (cobj,
+        "Setting start to 0 because we have a default object");
+
+    if (cobj->start != 0) {
+      cobj->pending_start = cobj->start = 0;
+      g_object_notify_by_pspec (G_OBJECT (cobj),
+          gnlobject_properties[GNLOBJECT_PROP_START]);
+    }
+
+  } else {
+
+    /* Else it's the first object's start value */
+    obj = (GnlObject *) priv->objects_start->data;
+
+    if (obj->start != cobj->start) {
+      GST_LOG_OBJECT (obj, "setting start from %s to %" GST_TIME_FORMAT,
+          GST_OBJECT_NAME (obj), GST_TIME_ARGS (obj->start));
+      cobj->pending_start = cobj->start = obj->start;
+      g_object_notify_by_pspec (G_OBJECT (cobj),
+          gnlobject_properties[GNLOBJECT_PROP_START]);
+    }
+
+  }
+
+  obj = (GnlObject *) priv->objects_stop->data;
+
+  if (obj->stop != cobj->stop) {
+    GST_LOG_OBJECT (obj, "setting stop from %s to %" GST_TIME_FORMAT,
+        GST_OBJECT_NAME (obj), GST_TIME_ARGS (obj->stop));
+
+    if (priv->expandables) {
+      GList *tmp;
+
+      GST_INFO_OBJECT (comp, "RE-setting all expandables duration and commit");
+      for (tmp = priv->expandables; tmp; tmp = tmp->next) {
+        g_object_set (tmp->data, "duration", obj->stop, NULL);
+        gnl_object_commit (GNL_OBJECT (tmp->data), FALSE);
+      }
+    }
+
+    priv->segment->stop = obj->stop;
+    cobj->stop = obj->stop;
+    g_object_notify_by_pspec (G_OBJECT (cobj),
+        gnlobject_properties[GNLOBJECT_PROP_STOP]);
+  }
+
+  if ((cobj->stop - cobj->start) != cobj->duration) {
+    cobj->pending_duration = cobj->duration = cobj->stop - cobj->start;
+    g_object_notify_by_pspec (G_OBJECT (cobj),
+        gnlobject_properties[GNLOBJECT_PROP_DURATION]);
+    signal_duration_change (comp);
+  }
+
+  GST_LOG_OBJECT (comp,
+      "start:%" GST_TIME_FORMAT
+      " stop:%" GST_TIME_FORMAT
+      " duration:%" GST_TIME_FORMAT,
+      GST_TIME_ARGS (cobj->start),
+      GST_TIME_ARGS (cobj->stop), GST_TIME_ARGS (cobj->duration));
+}
+
+static void
+no_more_pads_object_cb (GstElement * element, GnlComposition * comp)
+{
+  GnlCompositionPrivate *priv = comp->priv;
+  GnlObject *object = (GnlObject *) element;
+  GNode *tmp;
+  GstPad *pad = NULL;
+  GnlCompositionEntry *entry;
+
+  GST_LOG_OBJECT (comp, "no more pads on element %s",
+      GST_ELEMENT_NAME (element));
+
+  if (!(pad = get_src_pad (element)))
+    goto no_source;
+
+  COMP_OBJECTS_LOCK (comp);
+
+  if (G_UNLIKELY (priv->current == NULL)) {
+    GST_DEBUG_OBJECT (comp, "current stack is empty !");
+    goto done;
+  }
+
+  tmp = g_node_find (priv->current, G_IN_ORDER, G_TRAVERSE_ALL, object);
+
+  if (G_UNLIKELY (tmp == NULL))
+    goto not_in_stack;
+
+  entry = COMP_ENTRY (comp, object);
+  wait_no_more_pads (comp, object, entry, FALSE);
+
+  if (tmp->parent) {
+    GstElement *parent = (GstElement *) tmp->parent->data;
+    GstPad *sinkpad;
+
+    /* Get an unlinked sinkpad from the parent */
+    sinkpad = get_unlinked_sink_ghost_pad ((GnlOperation *) parent);
+
+    if (G_UNLIKELY (sinkpad == NULL)) {
+      GST_WARNING_OBJECT (comp,
+          "Couldn't find an unlinked sinkpad from %s",
+          GST_ELEMENT_NAME (parent));
+      goto done;
+    }
+
+    /* Link pad to parent sink pad */
+    if (G_UNLIKELY (gst_pad_link_full (pad, sinkpad,
+                GST_PAD_LINK_CHECK_NOTHING) != GST_PAD_LINK_OK)) {
+      GST_WARNING_OBJECT (comp, "Failed to link pads %s:%s - %s:%s",
+          GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (sinkpad));
+      gst_object_unref (sinkpad);
+      goto done;
+    }
+
+    /* inform operation of incoming stream priority */
+    gnl_operation_signal_input_priority_changed ((GnlOperation *) parent,
+        sinkpad, object->priority);
+    gst_object_unref (sinkpad);
+    gst_pad_remove_probe (pad, entry->probeid);
+    entry->probeid = 0;
+  }
+
+  /* If there are no more waiting pads, activate the current stack */
+  if (priv->current && (priv->waitingpads == 0)
+      && priv->stackvalid) {
+    GnlCompositionEntry *topentry = COMP_ENTRY (comp, priv->current->data);
+    GstPad *tpad = NULL;
+
+    /* There are no more waiting pads for the currently configured timeline */
+    /* stack. */
+    tpad = get_src_pad (GST_ELEMENT (priv->current->data));
+    GST_LOG_OBJECT (comp,
+        "top-level pad %s:%s, Setting target of ghostpad to it",
+        GST_DEBUG_PAD_NAME (tpad));
+
+    /* 2. send pending seek */
+    if (priv->childseek) {
+      GstEvent *childseek = priv->childseek;
+
+      priv->childseek = NULL;
+      GST_INFO_OBJECT (comp, "Sending pending seek on %s:%s",
+          GST_DEBUG_PAD_NAME (tpad));
+
+      COMP_OBJECTS_UNLOCK (comp);
+
+      if (!(gst_pad_send_event (tpad, childseek)))
+        GST_ERROR_OBJECT (comp, "Sending seek event failed!");
+
+      COMP_OBJECTS_LOCK (comp);
+    }
+    priv->childseek = NULL;
+
+    /* 1. set target of ghostpad to toplevel element src pad */
+    gnl_composition_ghost_pad_set_target (comp, tpad, topentry);
+
+    /* Check again if the top-level element is still in the stack */
+    if (priv->current &&
+        g_node_find (priv->current, G_IN_ORDER, G_TRAVERSE_ALL, object)) {
+
+      /* 3. unblock ghostpad */
+      if (topentry->probeid) {
+        GST_LOG_OBJECT (comp, "About to unblock top-level pad : %s:%s",
+            GST_DEBUG_PAD_NAME (tpad));
+        gst_pad_remove_probe (tpad, topentry->probeid);
+        topentry->probeid = 0;
+        GST_LOG_OBJECT (comp, "Unblocked top-level pad");
+      }
+    } else
+      GST_DEBUG ("Element went away from currently configured stack");
+
+    if (tpad)
+      gst_object_unref (tpad);
+  }
+
+done:
+  COMP_OBJECTS_UNLOCK (comp);
+
+  if (pad)
+    gst_object_unref (pad);
+
+  GST_DEBUG_OBJECT (comp, "end");
+
+  return;
+
+no_source:
+  {
+    GST_LOG_OBJECT (comp, "no source pad");
+    return;
+  }
+
+not_in_stack:
+  {
+    GST_LOG_OBJECT (comp,
+        "The following object is not in currently configured stack : %s",
+        GST_ELEMENT_NAME (object));
+    goto done;
+  }
+}
+
+/*
+ * recursive depth-first relink stack function on new stack
+ *
+ * _ relink nodes with changed parent/order
+ * _ links new nodes with parents
+ * _ unblocks available source pads (except for toplevel)
+ *
+ * WITH OBJECTS LOCK TAKEN
+ */
+
+static void
+compare_relink_single_node (GnlComposition * comp, GNode * node,
+    GNode * oldstack)
+{
+  GNode *child;
+  GNode *oldnode = NULL;
+  GnlObject *newobj;
+  GnlObject *newparent;
+  GnlObject *oldparent = NULL;
+  GstPad *srcpad = NULL, *sinkpad = NULL;
+  GnlCompositionEntry *entry;
+
+  if (G_UNLIKELY (!node))
+    return;
+
+  newparent = G_NODE_IS_ROOT (node) ? NULL : (GnlObject *) node->parent->data;
+  newobj = (GnlObject *) node->data;
+  if (oldstack) {
+    oldnode = g_node_find (oldstack, G_IN_ORDER, G_TRAVERSE_ALL, newobj);
+    if (oldnode)
+      oldparent =
+          G_NODE_IS_ROOT (oldnode) ? NULL : (GnlObject *) oldnode->parent->data;
+  }
+
+  GST_DEBUG_OBJECT (comp, "newobj:%s",
+      GST_ELEMENT_NAME ((GstElement *) newobj));
+  srcpad = get_src_pad ((GstElement *) newobj);
+
+  /* 1. Make sure the source pad is blocked for new objects */
+  if (G_UNLIKELY (!oldnode && srcpad)) {
+    GnlCompositionEntry *oldentry = COMP_ENTRY (comp, newobj);
+    if (!oldentry->probeid) {
+      GST_LOG_OBJECT (comp, "block_async(%s:%s, TRUE)",
+          GST_DEBUG_PAD_NAME (srcpad));
+      oldentry->probeid =
+          gst_pad_add_probe (srcpad,
+          GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM | GST_PAD_PROBE_TYPE_IDLE,
+          (GstPadProbeCallback) pad_blocked, comp, NULL);
+    }
+    if (!oldentry->dataprobeid) {
+      oldentry->dataprobeid = gst_pad_add_probe (srcpad,
+          GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BUFFER_LIST |
+          GST_PAD_PROBE_TYPE_EVENT_BOTH, (GstPadProbeCallback) drop_data,
+          oldentry, NULL);
+    }
+  }
+
+  entry = COMP_ENTRY (comp, newobj);
+  /* 2. link to parent if needed.
+   *
+   * If entry->nomorepadshandler is not zero, it means that srcpad didn't exist
+   * before and so we connected to no-more-pads. This can happen since there's a
+   * window of time between gnlsource adds its srcpad and then emits
+   * no-more-pads. In that case, we just wait for no-more-pads to be emitted.
+   */
+  if (srcpad && entry->nomorepadshandler == 0) {
+    GST_LOG_OBJECT (comp, "has a valid source pad");
+    /* POST PROCESSING */
+    if ((oldparent != newparent) ||
+        (oldparent && newparent &&
+            (g_node_child_index (node,
+                    newobj) != g_node_child_index (oldnode, newobj)))) {
+      GST_LOG_OBJECT (comp,
+          "not same parent, or same parent but in different order");
+      /* relink to new parent in required order */
+      if (newparent) {
+        GstPad *sinkpad;
+        GST_LOG_OBJECT (comp, "Linking %s and %s",
+            GST_ELEMENT_NAME (GST_ELEMENT (newobj)),
+            GST_ELEMENT_NAME (GST_ELEMENT (newparent)));
+        sinkpad = get_unlinked_sink_ghost_pad ((GnlOperation *) newparent);
+        if (G_UNLIKELY (sinkpad == NULL)) {
+          GST_WARNING_OBJECT (comp,
+              "Couldn't find an unlinked sinkpad from %s",
+              GST_ELEMENT_NAME (newparent));
+        } else {
+          if (G_UNLIKELY (gst_pad_link_full (srcpad, sinkpad,
+                      GST_PAD_LINK_CHECK_NOTHING) != GST_PAD_LINK_OK)) {
+            GST_WARNING_OBJECT (comp, "Failed to link pads %s:%s - %s:%s",
+                GST_DEBUG_PAD_NAME (srcpad), GST_DEBUG_PAD_NAME (sinkpad));
+          }
+          gst_object_unref (sinkpad);
+        }
+      }
+    } else
+      GST_LOG_OBJECT (newobj, "Same parent and same position in the new stack");
+    /* If there's an operation, inform it about priority changes */
+    if (newparent) {
+      sinkpad = gst_pad_get_peer (srcpad);
+      gnl_operation_signal_input_priority_changed ((GnlOperation *)
+          newparent, sinkpad, newobj->priority);
+      gst_object_unref (sinkpad);
+    }
+
+  } else if (entry->nomorepadshandler) {
+    GST_INFO_OBJECT (newobj,
+        "we have a pad but we are connected to 'no-more-pads'");
+  } else {
+    wait_no_more_pads (comp, newobj, entry, TRUE);
+  }
+
+  /* 3. Handle children */
+  if (GNL_IS_OPERATION (newobj)) {
+    guint nbchildren = g_node_n_children (node);
+    GnlOperation *oper = (GnlOperation *) newobj;
+    GST_LOG_OBJECT (newobj, "is a %s operation, analyzing the %d children",
+        oper->dynamicsinks ? "dynamic" : "regular", nbchildren);
+    /* Update the operation's number of sinks, that will make it have the proper
+     * number of sink pads to connect the children to. */
+    if (oper->dynamicsinks)
+      g_object_set (G_OBJECT (newobj), "sinks", nbchildren, NULL);
+    for (child = node->children; child; child = child->next)
+      compare_relink_single_node (comp, child, oldstack);
+    if (G_UNLIKELY (nbchildren < oper->num_sinks))
+      GST_ERROR
+          ("Not enough sinkpads to link all objects to the operation ! %d / %d",
+          oper->num_sinks, nbchildren);
+    if (G_UNLIKELY (nbchildren == 0))
+      GST_ERROR ("Operation has no child objects to be connected to !!!");
+    /* Make sure we have enough sinkpads */
+  } else {
+    /* FIXME : do we need to do something specific for sources ? */
+  }
+
+  /* 4. Unblock source pad */
+  if ((srcpad && entry->nomorepadshandler == 0) && !G_NODE_IS_ROOT (node) &&
+      entry->probeid) {
+    GST_LOG_OBJECT (comp, "Unblocking pad %s:%s", GST_DEBUG_PAD_NAME (srcpad));
+    gst_pad_remove_probe (srcpad, entry->probeid);
+    entry->probeid = 0;
+  }
+
+  if (G_LIKELY (srcpad))
+    gst_object_unref (srcpad);
+  GST_LOG_OBJECT (comp, "done with object %s",
+      GST_ELEMENT_NAME (GST_ELEMENT (newobj)));
+}
+
+/*
+ * recursive depth-first compare stack function on old stack
+ *
+ * _ Add no-longer used objects to the deactivate list
+ * _ unlink child-parent relations that have changed (not same parent, or not same order)
+ * _ blocks available source pads
+ *
+ * FIXME : modify is only used for the root element.
+ *    It is TRUE all the time except when the update is done from a seek
+ *
+ * WITH OBJECTS LOCK TAKEN
+ */
+
+static GList *
+compare_deactivate_single_node (GnlComposition * comp, GNode * node,
+    GNode * newstack, gboolean modify)
+{
+  GNode *child;
+  GNode *newnode = NULL;        /* Same node in newstack */
+  GnlObject *oldparent;
+  GList *deactivate = NULL;
+  GnlObject *oldobj = NULL;
+  GstPad *srcpad = NULL;
+
+  if (G_UNLIKELY (!node))
+    return NULL;
+
+  /* The former parent GnlObject (i.e. downstream) of the given node */
+  oldparent = G_NODE_IS_ROOT (node) ? NULL : (GnlObject *) node->parent->data;
+
+  /* The former GnlObject */
+  oldobj = (GnlObject *) node->data;
+
+  /* The node corresponding to oldobj in the new stack */
+  if (newstack)
+    newnode = g_node_find (newstack, G_IN_ORDER, G_TRAVERSE_ALL, oldobj);
+
+  GST_DEBUG_OBJECT (comp, "oldobj:%s",
+      GST_ELEMENT_NAME ((GstElement *) oldobj));
+  srcpad = get_src_pad ((GstElement *) oldobj);
+
+  if (G_LIKELY (srcpad)) {
+    GstPad *peerpad = NULL;
+    GnlCompositionEntry *entry = COMP_ENTRY (comp, oldobj);
+
+    /* 1. Block source pad
+     *   This makes sure that no data/event flow will come out of this element after this
+     *   point.
+     *
+     * If entry is NULL, this means the element is in the process of being removed.
+     */
+    if (entry && !entry->probeid) {
+      GST_LOG_OBJECT (comp, "Setting BLOCKING probe on %s:%s",
+          GST_DEBUG_PAD_NAME (srcpad));
+      entry->probeid =
+          gst_pad_add_probe (srcpad,
+          GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM | GST_PAD_PROBE_TYPE_IDLE,
+          (GstPadProbeCallback) pad_blocked, comp, NULL);
+    }
+    if (entry && !entry->dataprobeid) {
+      entry->dataprobeid = gst_pad_add_probe (srcpad,
+          GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BUFFER_LIST |
+          GST_PAD_PROBE_TYPE_EVENT_BOTH, (GstPadProbeCallback) drop_data, entry,
+          NULL);
+    }
+
+    /* 2. If we have to modify or we have a parent, flush downstream
+     *   This ensures the streaming thread going through the current object has
+     *   either stopped or is blocking against the source pad. */
+    if ((modify || oldparent) && (peerpad = gst_pad_get_peer (srcpad))) {
+      GST_LOG_OBJECT (comp, "Sending flush start/stop downstream ");
+      gst_pad_send_event (peerpad, gst_event_new_flush_start ());
+      gst_pad_send_event (peerpad, gst_event_new_flush_stop (TRUE));
+      GST_DEBUG_OBJECT (comp, "DONE Sending flush events downstream");
+      gst_object_unref (peerpad);
+    }
+
+  } else {
+    GST_LOG_OBJECT (comp, "No source pad available");
+  }
+
+  /* 3. Unlink from the parent if we've changed position */
+
+  GST_LOG_OBJECT (comp,
+      "Checking if we need to unlink from downstream element");
+  if (G_UNLIKELY (!oldparent)) {
+    GST_LOG_OBJECT (comp, "Top-level object");
+    /* for top-level objects we just set the ghostpad target to NULL */
+    if (comp->priv->ghostpad) {
+      GST_LOG_OBJECT (comp, "Setting ghostpad target to NULL");
+      gnl_composition_ghost_pad_set_target (comp, NULL, NULL);
+    } else
+      GST_LOG_OBJECT (comp, "No ghostpad");
+  } else {
+    GnlObject *newparent = NULL;
+
+    GST_LOG_OBJECT (comp, "non-toplevel object");
+
+    if (newnode)
+      newparent =
+          G_NODE_IS_ROOT (newnode) ? NULL : (GnlObject *) newnode->parent->data;
+
+    if ((!newnode) || (oldparent != newparent) ||
+        (newparent &&
+            (g_node_child_index (node,
+                    oldobj) != g_node_child_index (newnode, oldobj)))) {
+      GstPad *peerpad = NULL;
+
+      GST_LOG_OBJECT (comp, "Topology changed, unlinking from downstream");
+
+      if (srcpad && (peerpad = gst_pad_get_peer (srcpad))) {
+        GST_LOG_OBJECT (peerpad, "Sending flush start/stop");
+        gst_pad_send_event (peerpad, gst_event_new_flush_start ());
+        gst_pad_send_event (peerpad, gst_event_new_flush_stop (TRUE));
+        gst_pad_unlink (srcpad, peerpad);
+        gst_object_unref (peerpad);
+      }
+    } else
+      GST_LOG_OBJECT (comp, "Topology unchanged");
+  }
+
+  /* 4. If we're dealing with an operation, call this method recursively on it */
+  if (G_UNLIKELY (GNL_IS_OPERATION (oldobj))) {
+    GST_LOG_OBJECT (comp,
+        "Object is an operation, recursively calling on children");
+    for (child = node->children; child; child = child->next) {
+      GList *newdeac =
+          compare_deactivate_single_node (comp, child, newstack, modify);
+
+      if (newdeac)
+        deactivate = g_list_concat (deactivate, newdeac);
+    }
+  }
+
+  /* 5. If object isn't used anymore, add it to the list of objects to deactivate */
+  if (G_LIKELY (!newnode)) {
+    GST_LOG_OBJECT (comp, "Object doesn't exist in new stack");
+    deactivate = g_list_prepend (deactivate, oldobj);
+  }
+
+  if (G_LIKELY (srcpad))
+    gst_object_unref (srcpad);
+  GST_LOG_OBJECT (comp, "done with object %s",
+      GST_ELEMENT_NAME (GST_ELEMENT (oldobj)));
+
+  return deactivate;
+}
+
+/*
+ * compare_relink_stack:
+ * @comp: The #GnlComposition
+ * @stack: The new stack
+ * @modify: TRUE if the timeline has changed and needs downstream flushes.
+ *
+ * Compares the given stack to the current one and relinks it if needed.
+ *
+ * WITH OBJECTS LOCK TAKEN
+ *
+ * Returns: The #GList of #GnlObject no longer used
+ */
+
+static GList *
+compare_relink_stack (GnlComposition * comp, GNode * stack, gboolean modify)
+{
+  GList *deactivate = NULL;
+
+  /* 1. Traverse old stack to deactivate no longer used objects */
+  deactivate =
+      compare_deactivate_single_node (comp, comp->priv->current, stack, modify);
+
+  /* 2. Traverse new stack to do needed (re)links */
+  compare_relink_single_node (comp, stack, comp->priv->current);
+
+  return deactivate;
+}
+
+static void
+unlock_activate_stack (GnlComposition * comp, GNode * node, GstState state)
+{
+  GNode *child;
+
+  GST_LOG_OBJECT (comp, "object:%s",
+      GST_ELEMENT_NAME ((GstElement *) (node->data)));
+
+  gst_element_set_locked_state ((GstElement *) (node->data), FALSE);
+  gst_element_set_state (GST_ELEMENT (node->data), state);
+
+  for (child = node->children; child; child = child->next)
+    unlock_activate_stack (comp, child, state);
+}
+
+static gboolean
+are_same_stacks (GNode * stack1, GNode * stack2)
+{
+  gboolean res = FALSE;
+
+  /* TODO : FIXME : we should also compare start/inpoint */
+  /* stacks are not equal if one of them is NULL but not the other */
+  if ((!stack1 && stack2) || (stack1 && !stack2))
+    goto beach;
+
+  if (stack1 && stack2) {
+    GNode *child1, *child2;
+
+    /* if they don't contain the same source, not equal */
+    if (!(stack1->data == stack2->data))
+      goto beach;
+
+    /* if they don't have the same number of children, not equal */
+    if (!(g_node_n_children (stack1) == g_node_n_children (stack2)))
+      goto beach;
+
+    child1 = stack1->children;
+    child2 = stack2->children;
+    while (child1 && child2) {
+      if (!(are_same_stacks (child1, child2)))
+        goto beach;
+      child1 = g_node_next_sibling (child1);
+      child2 = g_node_next_sibling (child2);
+    }
+
+    /* if there's a difference in child number, stacks are not equal */
+    if (child1 || child2)
+      goto beach;
+  }
+
+  /* if stack1 AND stack2 are NULL, then they're equal (both empty) */
+  res = TRUE;
+
+beach:
+  GST_LOG ("Stacks are equal : %d", res);
+
+  return res;
+}
+
+/*
+ * update_pipeline:
+ * @comp: The #GnlComposition
+ * @currenttime: The #GstClockTime to update at, can be GST_CLOCK_TIME_NONE.
+ * @initial: TRUE if this is the first setup
+ * @change_state: Change the state of the (de)activated objects if TRUE.
+ * @modify: Flush downstream if TRUE. Needed for modified timelines.
+ *
+ * Updates the internal pipeline and properties. If @currenttime is
+ * GST_CLOCK_TIME_NONE, it will not modify the current pipeline
+ *
+ * Returns: FALSE if there was an error updating the pipeline.
+ *
+ * WITH OBJECTS LOCK TAKEN
+ */
+static gboolean
+update_pipeline (GnlComposition * comp, GstClockTime currenttime,
+    gboolean initial, gboolean modify)
+{
+  gboolean startchanged, stopchanged;
+
+  GNode *stack = NULL;
+  gboolean ret = TRUE;
+  GList *todeactivate = NULL;
+  gboolean samestack = FALSE;
+  GstState state = GST_STATE (comp);
+  GnlCompositionPrivate *priv = comp->priv;
+  GstClockTime new_stop = GST_CLOCK_TIME_NONE;
+  GstClockTime new_start = GST_CLOCK_TIME_NONE;
+  GstState nextstate = (GST_STATE_NEXT (comp) == GST_STATE_VOID_PENDING) ?
+      GST_STATE (comp) : GST_STATE_NEXT (comp);
+
+  GST_DEBUG_OBJECT (comp,
+      "currenttime:%" GST_TIME_FORMAT
+      " initial:%d , modify:%d", GST_TIME_ARGS (currenttime), initial, modify);
+
+  if (!GST_CLOCK_TIME_IS_VALID (currenttime))
+    return FALSE;
+
+  if (state == GST_STATE_NULL && nextstate == GST_STATE_NULL) {
+    GST_DEBUG_OBJECT (comp, "STATE_NULL: not updating pipeline");
+    return FALSE;
+  }
+
+
+  GST_DEBUG_OBJECT (comp,
+      "now really updating the pipeline, current-state:%s",
+      gst_element_state_get_name (state));
+
+  /* 1. Get new stack and compare it to current one */
+  stack = get_clean_toplevel_stack (comp, &currenttime, &new_start, &new_stop);
+  samestack = are_same_stacks (priv->current, stack);
+
+  /* invalidate the stack while modifying it */
+  priv->stackvalid = FALSE;
+
+  /* 2. If stacks are different, unlink/relink objects */
+  if (!samestack)
+    todeactivate = compare_relink_stack (comp, stack, modify);
+
+  if (priv->segment->rate >= 0.0) {
+    startchanged = priv->segment_start != currenttime;
+    stopchanged = priv->segment_stop != new_stop;
+  } else {
+    startchanged = priv->segment_start != new_start;
+    stopchanged = priv->segment_stop != currenttime;
+  }
+
+  /* 3. set new segment_start/stop (the current zone over which the new stack
+   *    is valid) */
+  if (priv->segment->rate >= 0.0) {
+    priv->segment_start = currenttime;
+    priv->segment_stop = new_stop;
+  } else {
+    priv->segment_start = new_start;
+    priv->segment_stop = currenttime;
+  }
+
+  /* 4. Clear pending child seek
+   *    We'll be creating a new one */
+  if (priv->childseek) {
+    GST_DEBUG ("unreffing event %p", priv->childseek);
+    gst_event_unref (priv->childseek);
+    priv->childseek = NULL;
+  }
+
+  /* Invalidate current stack */
+  if (priv->current)
+    g_node_destroy (priv->current);
+  priv->current = NULL;
+
+  /* 5. deactivate unused elements */
+  if (todeactivate) {
+    GList *tmp;
+    GnlCompositionEntry *entry;
+    GstElement *element;
+
+    GST_DEBUG_OBJECT (comp, "De-activating objects no longer used");
+
+    /* state-lock elements no more used */
+    for (tmp = todeactivate; tmp; tmp = tmp->next) {
+      element = GST_ELEMENT_CAST (tmp->data);
+
+      gst_element_set_state (element, priv->deactivated_elements_state);
+      gst_element_set_locked_state (element, TRUE);
+      entry = COMP_ENTRY (comp, element);
+
+      /* entry can be NULL here if update_pipeline was called by
+       * gnl_composition_remove_object (comp, tmp->data)
+       */
+      if (entry && entry->nomorepadshandler)
+        wait_no_more_pads (comp, element, entry, FALSE);
+    }
+
+    g_list_free (todeactivate);
+
+    GST_DEBUG_OBJECT (comp, "Finished de-activating objects no longer used");
+  }
+
+  /* 6. Unlock all elements in new stack */
+  GST_DEBUG_OBJECT (comp, "Setting current stack");
+  priv->current = stack;
+
+  if (!samestack && stack) {
+    GST_DEBUG_OBJECT (comp, "activating objects in new stack to %s",
+        gst_element_state_get_name (nextstate));
+    unlock_activate_stack (comp, stack, nextstate);
+    GST_DEBUG_OBJECT (comp, "Finished activating objects in new stack");
+  }
+
+  /* 7. Activate stack (might happen asynchronously) */
+  if (priv->current) {
+    GstEvent *event;
+
+    priv->stackvalid = TRUE;
+
+    /* 7.1. Create new seek event for newly configured timeline stack */
+    if (samestack && (startchanged || stopchanged))
+      event =
+          get_new_seek_event (comp,
+          (state == GST_STATE_PLAYING) ? FALSE : TRUE, !startchanged);
+    else
+      event = get_new_seek_event (comp, initial, FALSE);
+
+    /* 7.2.a If the stack entirely ready, send seek out synchronously */
+    if (priv->waitingpads == 0) {
+      GstPad *pad;
+      GstElement *topelement = GST_ELEMENT (priv->current->data);
+
+      /* Get toplevel object source pad */
+      if ((pad = get_src_pad (topelement))) {
+        GnlCompositionEntry *topentry = COMP_ENTRY (comp, topelement);
+
+        GST_DEBUG_OBJECT (comp,
+            "We have a valid toplevel element pad %s:%s",
+            GST_DEBUG_PAD_NAME (pad));
+
+        /* Send seek event */
+        GST_LOG_OBJECT (comp, "sending seek event");
+        if (gst_pad_send_event (pad, event)) {
+          /* Unconditionnaly set the ghostpad target to pad */
+          GST_LOG_OBJECT (comp,
+              "Setting the composition's ghostpad target to %s:%s",
+              GST_DEBUG_PAD_NAME (pad));
+
+          gnl_composition_ghost_pad_set_target (comp, pad, topentry);
+
+          if (topentry->probeid) {
+
+            /* unblock top-level pad */
+            GST_LOG_OBJECT (comp, "About to unblock top-level srcpad");
+            gst_pad_remove_probe (pad, topentry->probeid);
+            topentry->probeid = 0;
+          }
+        } else {
+          ret = FALSE;
+        }
+        gst_object_unref (pad);
+
+      } else {
+        GST_WARNING_OBJECT (comp,
+            "Timeline is entirely linked, but couldn't get top-level element's source pad");
+
+        ret = FALSE;
+      }
+    } else {
+      /* 7.2.b. Stack isn't entirely ready, save seek event for later on */
+      GST_LOG_OBJECT (comp,
+          "The timeline stack isn't entirely linked, delaying sending seek event (waitingpads:%d)",
+          priv->waitingpads);
+
+      priv->childseek = event;
+      ret = TRUE;
+    }
+  } else {
+    if ((!priv->objects_start) && priv->ghostpad) {
+      GST_DEBUG_OBJECT (comp, "composition is now empty, removing ghostpad");
+      gnl_composition_remove_ghostpad (comp);
+      priv->segment_start = 0;
+      priv->segment_stop = GST_CLOCK_TIME_NONE;
+    }
+  }
+
+  GST_DEBUG_OBJECT (comp, "Returning %d", ret);
+  return ret;
+}
+
+/*
+ * Child modification updates
+ */
+
+static void
+object_pad_removed (GnlObject * object, GstPad * pad, GnlComposition * comp)
+{
+  GnlCompositionPrivate *priv = comp->priv;
+
+  GST_DEBUG_OBJECT (comp, "pad %s:%s was removed", GST_DEBUG_PAD_NAME (pad));
+
+  if (GST_PAD_IS_SRC (pad)) {
+    /* remove ghostpad if it's the current top stack object */
+    if (priv->current && GNL_OBJECT (priv->current->data) == object
+        && priv->ghostpad)
+      gnl_composition_remove_ghostpad (comp);
+    else {
+      GnlCompositionEntry *entry = COMP_ENTRY (comp, object);
+
+      if (entry && entry->probeid) {
+        gst_pad_remove_probe (pad, entry->probeid);
+        entry->probeid = 0;
+      }
+      if (entry && entry->dataprobeid) {
+        gst_pad_remove_probe (pad, entry->dataprobeid);
+        entry->dataprobeid = 0;
+      }
+    }
+  }
+}
+
+static void
+object_pad_added (GnlObject * object G_GNUC_UNUSED, GstPad * pad,
+    GnlComposition * comp)
+{
+  GnlCompositionEntry *entry;
+
+  if (GST_PAD_DIRECTION (pad) == GST_PAD_SINK)
+    return;
+
+  entry = COMP_ENTRY (comp, object);
+
+  if (!entry->probeid) {
+    GST_DEBUG_OBJECT (comp, "pad %s:%s was added, blocking it",
+        GST_DEBUG_PAD_NAME (pad));
+    entry->probeid =
+        gst_pad_add_probe (pad,
+        GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM | GST_PAD_PROBE_TYPE_IDLE,
+        (GstPadProbeCallback) pad_blocked, comp, NULL);
+  }
+
+  if (!entry->dataprobeid) {
+    entry->dataprobeid = gst_pad_add_probe (pad,
+        GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BUFFER_LIST |
+        GST_PAD_PROBE_TYPE_EVENT_BOTH, (GstPadProbeCallback) drop_data, entry,
+        NULL);
+  }
+}
+
+static gboolean
+gnl_composition_add_object (GstBin * bin, GstElement * element)
+{
+  gboolean ret;
+  GnlCompositionEntry *entry;
+  GnlComposition *comp = (GnlComposition *) bin;
+  GnlCompositionPrivate *priv = comp->priv;
+
+  /* we only accept GnlObject */
+  g_return_val_if_fail (GNL_IS_OBJECT (element), FALSE);
+
+  GST_DEBUG_OBJECT (bin, "element %s", GST_OBJECT_NAME (element));
+  GST_DEBUG_OBJECT (element, "%" GST_TIME_FORMAT "--%" GST_TIME_FORMAT,
+      GST_TIME_ARGS (GNL_OBJECT_START (element)),
+      GST_TIME_ARGS (GNL_OBJECT_STOP (element)));
+
+  gst_object_ref (element);
+
+  COMP_OBJECTS_LOCK (comp);
+
+  if ((GNL_OBJECT_IS_EXPANDABLE (element)) &&
+      g_list_find (priv->expandables, element)) {
+    GST_WARNING_OBJECT (comp,
+        "We already have an expandable, remove it before adding new one");
+    ret = FALSE;
+
+    goto chiringuito;
+  }
+
+  /* Call parent class ::add_element() */
+  ret = GST_BIN_CLASS (parent_class)->add_element (bin, element);
+
+  gnl_object_set_commit_needed (GNL_OBJECT (comp));
+
+  if (!ret) {
+    GST_WARNING_OBJECT (bin, "couldn't add element");
+    goto chiringuito;
+  }
+
+  /* lock state of child ! */
+  GST_LOG_OBJECT (bin, "Locking state of %s", GST_ELEMENT_NAME (element));
+  gst_element_set_locked_state (element, TRUE);
+
+  /* wrap new element in a GnlCompositionEntry ... */
+  entry = g_slice_new0 (GnlCompositionEntry);
+  entry->object = (GnlObject *) element;
+  entry->comp = comp;
+
+  if (GNL_OBJECT_IS_EXPANDABLE (element)) {
+    /* Only react on non-default objects properties */
+    g_object_set (element,
+        "start", (GstClockTime) 0,
+        "inpoint", (GstClockTime) 0,
+        "duration", (GstClockTimeDiff) GNL_OBJECT_STOP (comp), NULL);
+
+    GST_INFO_OBJECT (element, "Used as expandable, commiting now");
+    gnl_object_commit (GNL_OBJECT (element), FALSE);
+  }
+
+  /* ...and add it to the hash table */
+  g_hash_table_insert (priv->objects_hash, element, entry);
+
+  entry->padremovedhandler = g_signal_connect (G_OBJECT (element),
+      "pad-removed", G_CALLBACK (object_pad_removed), comp);
+  entry->padaddedhandler = g_signal_connect (G_OBJECT (element),
+      "pad-added", G_CALLBACK (object_pad_added), comp);
+
+  /* Set the caps of the composition */
+  if (G_UNLIKELY (!gst_caps_is_any (((GnlObject *) comp)->caps)))
+    gnl_object_set_caps ((GnlObject *) element, ((GnlObject *) comp)->caps);
+
+  /* Special case for default source. */
+  if (GNL_OBJECT_IS_EXPANDABLE (element)) {
+    /* It doesn't get added to objects_start and objects_stop. */
+    priv->expandables = g_list_prepend (priv->expandables, element);
+    goto beach;
+  }
+
+  /* add it sorted to the objects list */
+  priv->objects_start = g_list_insert_sorted
+      (priv->objects_start, element, (GCompareFunc) objects_start_compare);
+
+  if (priv->objects_start)
+    GST_LOG_OBJECT (comp,
+        "Head of objects_start is now %s [%" GST_TIME_FORMAT "--%"
+        GST_TIME_FORMAT "]",
+        GST_OBJECT_NAME (priv->objects_start->data),
+        GST_TIME_ARGS (GNL_OBJECT_START (priv->objects_start->data)),
+        GST_TIME_ARGS (GNL_OBJECT_STOP (priv->objects_start->data)));
+
+  priv->objects_stop = g_list_insert_sorted
+      (priv->objects_stop, element, (GCompareFunc) objects_stop_compare);
+
+  /* Now the object is ready to be commited and then used */
+
+beach:
+  COMP_OBJECTS_UNLOCK (comp);
+
+  gst_object_unref (element);
+  return ret;
+
+chiringuito:
+  {
+    update_start_stop_duration (comp);
+    goto beach;
+  }
+}
+
+
+static gboolean
+gnl_composition_remove_object (GstBin * bin, GstElement * element)
+{
+  GnlComposition *comp = (GnlComposition *) bin;
+  GnlCompositionPrivate *priv = comp->priv;
+  gboolean ret = FALSE;
+  gboolean update_required;
+  GnlCompositionEntry *entry;
+
+  GST_DEBUG_OBJECT (bin, "element %s", GST_OBJECT_NAME (element));
+
+  /* we only accept GnlObject */
+  g_return_val_if_fail (GNL_IS_OBJECT (element), FALSE);
+  COMP_OBJECTS_LOCK (comp);
+  entry = COMP_ENTRY (comp, element);
+  if (entry == NULL) {
+    COMP_OBJECTS_UNLOCK (comp);
+    goto out;
+  }
+
+  if (entry->nomorepadshandler)
+    wait_no_more_pads (comp, element, entry, FALSE);
+  gst_object_ref (element);
+  gst_element_set_locked_state (element, FALSE);
+
+  /* handle default source */
+  if (GNL_OBJECT_IS_EXPANDABLE (element)) {
+    /* Find it in the list */
+    priv->expandables = g_list_remove (priv->expandables, element);
+  } else {
+    /* remove it from the objects list and resort the lists */
+    priv->objects_start = g_list_remove (priv->objects_start, element);
+    priv->objects_stop = g_list_remove (priv->objects_stop, element);
+    GST_LOG_OBJECT (element, "Removed from the objects start/stop list");
+  }
+
+  g_hash_table_remove (priv->objects_hash, element);
+  update_required = OBJECT_IN_ACTIVE_SEGMENT (comp, element) ||
+      (GNL_OBJECT_PRIORITY (element) == G_MAXUINT32) ||
+      GNL_OBJECT_IS_EXPANDABLE (element);
+
+  if (G_LIKELY (update_required)) {
+    /* And update the pipeline at current position if needed */
+    update_pipeline_at_current_position (comp);
+  } else
+    update_start_stop_duration (comp);
+
+  ret = GST_BIN_CLASS (parent_class)->remove_element (bin, element);
+  GST_LOG_OBJECT (element, "Done removing from the composition, now updating");
+  COMP_OBJECTS_UNLOCK (comp);
+
+
+  /* Make it possible to reuse the same object later */
+  gnl_object_reset (GNL_OBJECT (element));
+  gst_object_unref (element);
+out:
+  return ret;
+}
diff --git a/gnl/gnlcomposition.h b/gnl/gnlcomposition.h
new file mode 100644 (file)
index 0000000..8299522
--- /dev/null
@@ -0,0 +1,63 @@
+/* GStreamer
+ * Copyright (C) 2001 Wim Taymans <wim.taymans@gmail.com>
+ *               2004-2008 Edward Hervey <bilboed@bilboed.com>
+ *
+ * gnlcomposition.h: Header for base GnlComposition
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef __GNL_COMPOSITION_H__
+#define __GNL_COMPOSITION_H__
+
+#include <gst/gst.h>
+#include "gnlobject.h"
+
+G_BEGIN_DECLS
+#define GNL_TYPE_COMPOSITION \
+  (gnl_composition_get_type())
+#define GNL_COMPOSITION(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GNL_TYPE_COMPOSITION,GnlComposition))
+#define GNL_COMPOSITION_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GNL_TYPE_COMPOSITION,GnlCompositionClass))
+#define GNL_COMPOSITION_GET_CLASS(obj) \
+  (GNL_COMPOSITION_CLASS (G_OBJECT_GET_CLASS (obj)))
+#define GNL_IS_COMPOSITION(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GNL_TYPE_COMPOSITION))
+#define GNL_IS_COMPOSITION_CLASS(obj) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),GNL_TYPE_COMPOSITION))
+
+typedef struct _GnlCompositionPrivate GnlCompositionPrivate;
+
+struct _GnlComposition
+{
+  GnlObject parent;
+
+  /*< private >*/
+
+  GnlCompositionPrivate *priv;
+};
+
+struct _GnlCompositionClass
+{
+  GnlObjectClass parent_class;
+};
+
+GType gnl_composition_get_type (void);
+
+G_END_DECLS
+#endif /* __GNL_COMPOSITION_H__ */
diff --git a/gnl/gnlghostpad.c b/gnl/gnlghostpad.c
new file mode 100644 (file)
index 0000000..5db4e44
--- /dev/null
@@ -0,0 +1,770 @@
+/* Gnonlin
+ * Copyright (C) <2009> Edward Hervey <bilboed@bilboed.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gnl.h"
+
+GST_DEBUG_CATEGORY_STATIC (gnlghostpad);
+#define GST_CAT_DEFAULT gnlghostpad
+
+typedef struct _GnlPadPrivate GnlPadPrivate;
+
+struct _GnlPadPrivate
+{
+  GnlObject *object;
+  GnlPadPrivate *ghostpriv;
+  GstPadDirection dir;
+  GstPadEventFunction eventfunc;
+  GstPadQueryFunction queryfunc;
+};
+
+static GstEvent *
+translate_incoming_seek (GnlObject * object, GstEvent * event)
+{
+  GstEvent *event2;
+  GstFormat format;
+  gdouble rate;
+  GstSeekFlags flags;
+  GstSeekType curtype, stoptype;
+  GstSeekType ncurtype;
+  gint64 cur;
+  guint64 ncur;
+  gint64 stop;
+  guint64 nstop;
+  guint32 seqnum = GST_EVENT_SEQNUM (event);
+
+  gst_event_parse_seek (event, &rate, &format, &flags,
+      &curtype, &cur, &stoptype, &stop);
+
+  GST_DEBUG_OBJECT (object,
+      "GOT SEEK rate:%f, format:%d, flags:%d, curtype:%d, stoptype:%d, %"
+      GST_TIME_FORMAT " -- %" GST_TIME_FORMAT, rate, format, flags, curtype,
+      stoptype, GST_TIME_ARGS (cur), GST_TIME_ARGS (stop));
+
+  if (G_UNLIKELY (format != GST_FORMAT_TIME))
+    goto invalid_format;
+
+  /* convert cur */
+  ncurtype = GST_SEEK_TYPE_SET;
+  if (G_LIKELY ((curtype == GST_SEEK_TYPE_SET)
+          && (gnl_object_to_media_time (object, cur, &ncur)))) {
+    /* cur is TYPE_SET and value is valid */
+    if (ncur > G_MAXINT64)
+      GST_WARNING_OBJECT (object, "return value too big...");
+    GST_LOG_OBJECT (object, "Setting cur to %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (ncur));
+  } else if ((curtype != GST_SEEK_TYPE_NONE)) {
+    GST_DEBUG_OBJECT (object, "Limiting seek start to inpoint");
+    ncur = object->inpoint;
+  } else {
+    GST_DEBUG_OBJECT (object, "leaving GST_SEEK_TYPE_NONE");
+    ncur = cur;
+    ncurtype = GST_SEEK_TYPE_NONE;
+  }
+
+  /* convert stop, we also need to limit it to object->stop */
+  if (G_LIKELY ((stoptype == GST_SEEK_TYPE_SET)
+          && (gnl_object_to_media_time (object, stop, &nstop)))) {
+    if (nstop > G_MAXINT64)
+      GST_WARNING_OBJECT (object, "return value too big...");
+    GST_LOG_OBJECT (object, "Setting stop to %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (nstop));
+  } else {
+    GST_DEBUG_OBJECT (object, "Limiting end of seek to media_stop");
+    gnl_object_to_media_time (object, object->stop, &nstop);
+    if (nstop > G_MAXINT64)
+      GST_WARNING_OBJECT (object, "return value too big...");
+    GST_LOG_OBJECT (object, "Setting stop to %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (nstop));
+  }
+
+
+  /* add accurate seekflags */
+  if (G_UNLIKELY (!(flags & GST_SEEK_FLAG_ACCURATE))) {
+    GST_DEBUG_OBJECT (object, "Adding GST_SEEK_FLAG_ACCURATE");
+    flags |= GST_SEEK_FLAG_ACCURATE;
+  } else {
+    GST_DEBUG_OBJECT (object,
+        "event already has GST_SEEK_FLAG_ACCURATE : %d", flags);
+  }
+
+
+
+  GST_DEBUG_OBJECT (object,
+      "SENDING SEEK rate:%f, format:TIME, flags:%d, curtype:%d, stoptype:SET, %"
+      GST_TIME_FORMAT " -- %" GST_TIME_FORMAT, rate, flags, ncurtype,
+      GST_TIME_ARGS (ncur), GST_TIME_ARGS (nstop));
+
+  event2 = gst_event_new_seek (rate, GST_FORMAT_TIME, flags,
+      ncurtype, (gint64) ncur, GST_SEEK_TYPE_SET, (gint64) nstop);
+  GST_EVENT_SEQNUM (event2) = seqnum;
+
+  gst_event_unref (event);
+
+  return event2;
+
+  /* ERRORS */
+invalid_format:
+  {
+    GST_WARNING ("GNonLin time shifting only works with GST_FORMAT_TIME");
+    return event;
+  }
+}
+
+static GstEvent *
+translate_outgoing_seek (GnlObject * object, GstEvent * event)
+{
+  GstEvent *event2;
+  GstFormat format;
+  gdouble rate;
+  GstSeekFlags flags;
+  GstSeekType curtype, stoptype;
+  GstSeekType ncurtype;
+  gint64 cur;
+  guint64 ncur;
+  gint64 stop;
+  guint64 nstop;
+  guint32 seqnum = GST_EVENT_SEQNUM (event);
+
+  gst_event_parse_seek (event, &rate, &format, &flags,
+      &curtype, &cur, &stoptype, &stop);
+
+  GST_DEBUG_OBJECT (object,
+      "GOT SEEK rate:%f, format:%d, flags:%d, curtype:%d, stoptype:%d, %"
+      GST_TIME_FORMAT " -- %" GST_TIME_FORMAT, rate, format, flags, curtype,
+      stoptype, GST_TIME_ARGS (cur), GST_TIME_ARGS (stop));
+
+  if (G_UNLIKELY (format != GST_FORMAT_TIME))
+    goto invalid_format;
+
+  /* convert cur */
+  ncurtype = GST_SEEK_TYPE_SET;
+  if (G_LIKELY ((curtype == GST_SEEK_TYPE_SET)
+          && (gnl_media_to_object_time (object, cur, &ncur)))) {
+    /* cur is TYPE_SET and value is valid */
+    if (ncur > G_MAXINT64)
+      GST_WARNING_OBJECT (object, "return value too big...");
+    GST_LOG_OBJECT (object, "Setting cur to %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (ncur));
+  } else if ((curtype != GST_SEEK_TYPE_NONE)) {
+    GST_DEBUG_OBJECT (object, "Limiting seek start to start");
+    ncur = object->start;
+  } else {
+    GST_DEBUG_OBJECT (object, "leaving GST_SEEK_TYPE_NONE");
+    ncur = cur;
+    ncurtype = GST_SEEK_TYPE_NONE;
+  }
+
+  /* convert stop, we also need to limit it to object->stop */
+  if (G_LIKELY ((stoptype == GST_SEEK_TYPE_SET)
+          && (gnl_media_to_object_time (object, stop, &nstop)))) {
+    if (nstop > G_MAXINT64)
+      GST_WARNING_OBJECT (object, "return value too big...");
+    GST_LOG_OBJECT (object, "Setting stop to %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (nstop));
+  } else {
+    GST_DEBUG_OBJECT (object, "Limiting end of seek to stop");
+    nstop = object->stop;
+    if (nstop > G_MAXINT64)
+      GST_WARNING_OBJECT (object, "return value too big...");
+    GST_LOG_OBJECT (object, "Setting stop to %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (nstop));
+  }
+
+  GST_DEBUG_OBJECT (object,
+      "SENDING SEEK rate:%f, format:TIME, flags:%d, curtype:%d, stoptype:SET, %"
+      GST_TIME_FORMAT " -- %" GST_TIME_FORMAT, rate, flags, ncurtype,
+      GST_TIME_ARGS (ncur), GST_TIME_ARGS (nstop));
+
+  event2 = gst_event_new_seek (rate, GST_FORMAT_TIME, flags,
+      ncurtype, (gint64) ncur, GST_SEEK_TYPE_SET, (gint64) nstop);
+  GST_EVENT_SEQNUM (event2) = seqnum;
+
+  gst_event_unref (event);
+
+  return event2;
+
+  /* ERRORS */
+invalid_format:
+  {
+    GST_WARNING ("GNonLin time shifting only works with GST_FORMAT_TIME");
+    return event;
+  }
+}
+
+static GstEvent *
+translate_outgoing_segment (GnlObject * object, GstEvent * event)
+{
+  const GstSegment *orig;
+  GstSegment segment;
+  GstEvent *event2;
+  guint32 seqnum = GST_EVENT_SEQNUM (event);
+
+  /* only modify the streamtime */
+  gst_event_parse_segment (event, &orig);
+
+  GST_DEBUG_OBJECT (object,
+      "Got SEGMENT %" GST_TIME_FORMAT " -- %" GST_TIME_FORMAT " // %"
+      GST_TIME_FORMAT, GST_TIME_ARGS (orig->start), GST_TIME_ARGS (orig->stop),
+      GST_TIME_ARGS (orig->time));
+
+  if (G_UNLIKELY (orig->format != GST_FORMAT_TIME)) {
+    GST_WARNING_OBJECT (object,
+        "Can't translate segments with format != GST_FORMAT_TIME");
+    return event;
+  }
+
+  gst_segment_copy_into (orig, &segment);
+
+  gnl_media_to_object_time (object, orig->time, &segment.time);
+
+  if (G_UNLIKELY (segment.time > G_MAXINT64))
+    GST_WARNING_OBJECT (object, "Return value too big...");
+
+  GST_DEBUG_OBJECT (object,
+      "Sending SEGMENT %" GST_TIME_FORMAT " -- %" GST_TIME_FORMAT " // %"
+      GST_TIME_FORMAT, GST_TIME_ARGS (segment.start),
+      GST_TIME_ARGS (segment.stop), GST_TIME_ARGS (segment.time));
+
+  event2 = gst_event_new_segment (&segment);
+  GST_EVENT_SEQNUM (event2) = seqnum;
+  gst_event_unref (event);
+
+  return event2;
+}
+
+static GstEvent *
+translate_incoming_segment (GnlObject * object, GstEvent * event)
+{
+  GstEvent *event2;
+  const GstSegment *orig;
+  GstSegment segment;
+  guint32 seqnum = GST_EVENT_SEQNUM (event);
+
+  /* only modify the streamtime */
+  gst_event_parse_segment (event, &orig);
+
+  GST_DEBUG_OBJECT (object,
+      "Got SEGMENT %" GST_TIME_FORMAT " -- %" GST_TIME_FORMAT " // %"
+      GST_TIME_FORMAT, GST_TIME_ARGS (orig->start), GST_TIME_ARGS (orig->stop),
+      GST_TIME_ARGS (orig->time));
+
+  if (G_UNLIKELY (orig->format != GST_FORMAT_TIME)) {
+    GST_WARNING_OBJECT (object,
+        "Can't translate segments with format != GST_FORMAT_TIME");
+    return event;
+  }
+
+  gst_segment_copy_into (orig, &segment);
+
+  if (!gnl_object_to_media_time (object, orig->time, &segment.time)) {
+    GST_DEBUG ("Can't convert media_time, using 0");
+    segment.time = 0;
+  };
+
+  if (GNL_IS_OPERATION (object)) {
+    segment.base = GNL_OPERATION (object)->next_base_time;
+    GST_INFO_OBJECT (object, "Using operation base time %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (GNL_OPERATION (object)->next_base_time));
+  }
+
+  if (G_UNLIKELY (segment.time > G_MAXINT64))
+    GST_WARNING_OBJECT (object, "Return value too big...");
+
+  GST_DEBUG_OBJECT (object,
+      "Sending SEGMENT %" GST_TIME_FORMAT " -- %" GST_TIME_FORMAT " // %"
+      GST_TIME_FORMAT, GST_TIME_ARGS (segment.start),
+      GST_TIME_ARGS (segment.stop), GST_TIME_ARGS (segment.time));
+
+  event2 = gst_event_new_segment (&segment);
+  GST_EVENT_SEQNUM (event2) = seqnum;
+  gst_event_unref (event);
+
+  return event2;
+}
+
+static gboolean
+internalpad_event_function (GstPad * internal, GstObject * parent,
+    GstEvent * event)
+{
+  GnlPadPrivate *priv = gst_pad_get_element_private (internal);
+  GnlObject *object = priv->object;
+  gboolean res;
+
+  GST_DEBUG_OBJECT (internal, "event:%s (seqnum::%d)",
+      GST_EVENT_TYPE_NAME (event), GST_EVENT_SEQNUM (event));
+
+  if (G_UNLIKELY (!(priv->eventfunc))) {
+    GST_WARNING_OBJECT (internal,
+        "priv->eventfunc == NULL !! What is going on ?");
+    return FALSE;
+  }
+
+  switch (priv->dir) {
+    case GST_PAD_SRC:{
+      switch (GST_EVENT_TYPE (event)) {
+        case GST_EVENT_SEGMENT:
+          event = translate_outgoing_segment (object, event);
+          break;
+        default:
+          break;
+      }
+
+      break;
+    }
+    case GST_PAD_SINK:{
+      switch (GST_EVENT_TYPE (event)) {
+        case GST_EVENT_SEEK:
+          event = translate_outgoing_seek (object, event);
+          break;
+        default:
+          break;
+      }
+      break;
+    }
+    default:
+      break;
+  }
+  GST_DEBUG_OBJECT (internal, "Calling priv->eventfunc %p", priv->eventfunc);
+  res = priv->eventfunc (internal, parent, event);
+
+  return res;
+}
+
+/*
+  translate_outgoing_position_query
+
+  Should only be called:
+  _ if the query is a GST_QUERY_POSITION
+  _ after the query was sent upstream
+  _ if the upstream query returned TRUE
+*/
+
+static gboolean
+translate_incoming_position_query (GnlObject * object, GstQuery * query)
+{
+  GstFormat format;
+  gint64 cur, cur2;
+
+  gst_query_parse_position (query, &format, &cur);
+  if (G_UNLIKELY (format != GST_FORMAT_TIME)) {
+    GST_WARNING_OBJECT (object,
+        "position query is in a format different from time, returning without modifying values");
+    goto beach;
+  }
+
+  gnl_media_to_object_time (object, (guint64) cur, (guint64 *) & cur2);
+
+  GST_DEBUG_OBJECT (object,
+      "Adjust position from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (cur), GST_TIME_ARGS (cur2));
+  gst_query_set_position (query, GST_FORMAT_TIME, cur2);
+
+beach:
+  return TRUE;
+}
+
+static gboolean
+translate_outgoing_position_query (GnlObject * object, GstQuery * query)
+{
+  GstFormat format;
+  gint64 cur, cur2;
+
+  gst_query_parse_position (query, &format, &cur);
+  if (G_UNLIKELY (format != GST_FORMAT_TIME)) {
+    GST_WARNING_OBJECT (object,
+        "position query is in a format different from time, returning without modifying values");
+    goto beach;
+  }
+
+  if (G_UNLIKELY (!(gnl_object_to_media_time (object, (guint64) cur,
+                  (guint64 *) & cur2)))) {
+    GST_WARNING_OBJECT (object,
+        "Couldn't get media time for %" GST_TIME_FORMAT, GST_TIME_ARGS (cur));
+    goto beach;
+  }
+
+  GST_DEBUG_OBJECT (object,
+      "Adjust position from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (cur), GST_TIME_ARGS (cur2));
+  gst_query_set_position (query, GST_FORMAT_TIME, cur2);
+
+beach:
+  return TRUE;
+}
+
+static gboolean
+translate_incoming_duration_query (GnlObject * object, GstQuery * query)
+{
+  GstFormat format;
+  gint64 cur;
+
+  gst_query_parse_duration (query, &format, &cur);
+  if (G_UNLIKELY (format != GST_FORMAT_TIME)) {
+    GST_WARNING_OBJECT (object,
+        "We can only handle duration queries in GST_FORMAT_TIME");
+    return FALSE;
+  }
+
+  gst_query_set_duration (query, GST_FORMAT_TIME, object->duration);
+
+  return TRUE;
+}
+
+static gboolean
+internalpad_query_function (GstPad * internal, GstObject * parent,
+    GstQuery * query)
+{
+  GnlPadPrivate *priv = gst_pad_get_element_private (internal);
+  GnlObject *object = priv->object;
+  gboolean ret;
+
+  GST_DEBUG_OBJECT (internal, "querytype:%s",
+      gst_query_type_get_name (GST_QUERY_TYPE (query)));
+
+  if (!(priv->queryfunc)) {
+    GST_WARNING_OBJECT (internal,
+        "priv->queryfunc == NULL !! What is going on ?");
+    return FALSE;
+  }
+
+  if ((ret = priv->queryfunc (internal, parent, query))) {
+
+    switch (priv->dir) {
+      case GST_PAD_SRC:
+        break;
+      case GST_PAD_SINK:
+        switch (GST_QUERY_TYPE (query)) {
+          case GST_QUERY_POSITION:
+            ret = translate_outgoing_position_query (object, query);
+            break;
+          default:
+            break;
+        }
+        break;
+      default:
+        break;
+    }
+  }
+  return ret;
+}
+
+static gboolean
+ghostpad_event_function (GstPad * ghostpad, GstObject * parent,
+    GstEvent * event)
+{
+  GnlPadPrivate *priv;
+  GnlObject *object;
+  gboolean ret = FALSE;
+
+  priv = gst_pad_get_element_private (ghostpad);
+  object = priv->object;
+
+  GST_DEBUG_OBJECT (ghostpad, "event:%s", GST_EVENT_TYPE_NAME (event));
+
+  if (G_UNLIKELY (priv->eventfunc == NULL))
+    goto no_function;
+
+  switch (priv->dir) {
+    case GST_PAD_SRC:
+    {
+      switch (GST_EVENT_TYPE (event)) {
+        case GST_EVENT_SEEK:
+          event = translate_incoming_seek (object, event);
+          break;
+        default:
+          break;
+      }
+    }
+      break;
+    case GST_PAD_SINK:{
+      switch (GST_EVENT_TYPE (event)) {
+        case GST_EVENT_SEGMENT:
+          event = translate_incoming_segment (object, event);
+          break;
+        default:
+          break;
+      }
+    }
+      break;
+    default:
+      break;
+  }
+
+  if (event) {
+    GST_DEBUG_OBJECT (ghostpad, "Calling priv->eventfunc");
+    ret = priv->eventfunc (ghostpad, parent, event);
+    GST_DEBUG_OBJECT (ghostpad, "Returned from calling priv->eventfunc : %d",
+        ret);
+  }
+
+  return ret;
+
+  /* ERRORS */
+no_function:
+  {
+    GST_WARNING_OBJECT (ghostpad,
+        "priv->eventfunc == NULL !! What's going on ?");
+    return FALSE;
+  }
+}
+
+static gboolean
+ghostpad_query_function (GstPad * ghostpad, GstObject * parent,
+    GstQuery * query)
+{
+  GnlPadPrivate *priv = gst_pad_get_element_private (ghostpad);
+  GnlObject *object = GNL_OBJECT (parent);
+  gboolean pret = TRUE;
+
+  GST_DEBUG_OBJECT (ghostpad, "querytype:%s", GST_QUERY_TYPE_NAME (query));
+
+  switch (GST_QUERY_TYPE (query)) {
+    case GST_QUERY_DURATION:
+      /* skip duration upstream query, we'll fill it in ourselves */
+      break;
+    default:
+      pret = priv->queryfunc (ghostpad, parent, query);
+  }
+
+  if (pret) {
+    /* translate result */
+    switch (GST_QUERY_TYPE (query)) {
+      case GST_QUERY_POSITION:
+        pret = translate_incoming_position_query (object, query);
+        break;
+      case GST_QUERY_DURATION:
+        pret = translate_incoming_duration_query (object, query);
+        break;
+      default:
+        break;
+    }
+  }
+
+  return pret;
+}
+
+/* internal pad going away */
+static void
+internal_pad_finalizing (GnlPadPrivate * priv, GObject * pad G_GNUC_UNUSED)
+{
+  g_slice_free (GnlPadPrivate, priv);
+}
+
+static inline GstPad *
+get_proxy_pad (GstPad * ghostpad)
+{
+  GValue item = { 0, };
+  GstIterator *it;
+  GstPad *ret = NULL;
+
+  it = gst_pad_iterate_internal_links (ghostpad);
+  g_assert (it);
+  gst_iterator_next (it, &item);
+  ret = g_value_dup_object (&item);
+  g_value_unset (&item);
+  g_assert (ret);
+  gst_iterator_free (it);
+
+  return ret;
+}
+
+static void
+control_internal_pad (GstPad * ghostpad, GnlObject * object)
+{
+  GnlPadPrivate *priv;
+  GnlPadPrivate *privghost;
+  GstPad *internal;
+
+  if (!ghostpad) {
+    GST_DEBUG_OBJECT (object, "We don't have a valid ghostpad !");
+    return;
+  }
+  privghost = gst_pad_get_element_private (ghostpad);
+
+  GST_LOG_OBJECT (ghostpad, "overriding ghostpad's internal pad function");
+
+  internal = get_proxy_pad (ghostpad);
+
+  if (G_UNLIKELY (!(priv = gst_pad_get_element_private (internal)))) {
+    GST_DEBUG_OBJECT (internal,
+        "Creating a GnlPadPrivate to put in element_private");
+    priv = g_slice_new0 (GnlPadPrivate);
+
+    /* Remember existing pad functions */
+    priv->eventfunc = GST_PAD_EVENTFUNC (internal);
+    priv->queryfunc = GST_PAD_QUERYFUNC (internal);
+    gst_pad_set_element_private (internal, priv);
+
+    g_object_weak_ref ((GObject *) internal,
+        (GWeakNotify) internal_pad_finalizing, priv);
+
+    /* add query/event function overrides on internal pad */
+    gst_pad_set_event_function (internal,
+        GST_DEBUG_FUNCPTR (internalpad_event_function));
+    gst_pad_set_query_function (internal,
+        GST_DEBUG_FUNCPTR (internalpad_query_function));
+  }
+
+  priv->object = object;
+  priv->ghostpriv = privghost;
+  priv->dir = GST_PAD_DIRECTION (ghostpad);
+  gst_object_unref (internal);
+
+  GST_DEBUG_OBJECT (ghostpad, "Done with pad %s:%s",
+      GST_DEBUG_PAD_NAME (ghostpad));
+}
+
+
+/**
+ * gnl_object_ghost_pad:
+ * @object: #GnlObject to add the ghostpad to
+ * @name: Name for the new pad
+ * @target: Target #GstPad to ghost
+ *
+ * Adds a #GstGhostPad overridding the correct pad [query|event]_function so
+ * that time shifting is done correctly
+ * The #GstGhostPad is added to the #GnlObject
+ *
+ * /!\ This function doesn't check if the existing [src|sink] pad was removed
+ * first, so you might end up with more pads than wanted
+ *
+ * Returns: The #GstPad if everything went correctly, else NULL.
+ */
+GstPad *
+gnl_object_ghost_pad (GnlObject * object, const gchar * name, GstPad * target)
+{
+  GstPadDirection dir = GST_PAD_DIRECTION (target);
+  GstPad *ghost;
+
+  GST_DEBUG_OBJECT (object, "name:%s, target:%p", name, target);
+
+  g_return_val_if_fail (target, FALSE);
+  g_return_val_if_fail ((dir != GST_PAD_UNKNOWN), FALSE);
+
+  ghost = gnl_object_ghost_pad_no_target (object, name, dir);
+  if (!ghost) {
+    GST_WARNING_OBJECT (object, "Couldn't create ghostpad");
+    return NULL;
+  }
+
+  if (!(gnl_object_ghost_pad_set_target (object, ghost, target))) {
+    GST_WARNING_OBJECT (object,
+        "Couldn't set the target pad... removing ghostpad");
+    gst_object_unref (ghost);
+    return NULL;
+  }
+
+  GST_DEBUG_OBJECT (object, "activating ghostpad");
+  /* activate pad */
+  gst_pad_set_active (ghost, TRUE);
+  /* add it to element */
+  if (!(gst_element_add_pad (GST_ELEMENT (object), ghost))) {
+    GST_WARNING ("couldn't add newly created ghostpad");
+    return NULL;
+  }
+
+  return ghost;
+}
+
+/*
+ * gnl_object_ghost_pad_no_target:
+ * /!\ Doesn't add the pad to the GnlObject....
+ */
+GstPad *
+gnl_object_ghost_pad_no_target (GnlObject * object, const gchar * name,
+    GstPadDirection dir)
+{
+  GstPad *ghost;
+  GnlPadPrivate *priv;
+
+  /* create a no_target ghostpad */
+  ghost = gst_ghost_pad_new_no_target (name, dir);
+  if (!ghost)
+    return NULL;
+
+  GST_DEBUG ("grabbing existing pad functions");
+
+  /* remember the existing ghostpad event/query/link/unlink functions */
+  priv = g_slice_new0 (GnlPadPrivate);
+  priv->dir = dir;
+  priv->object = object;
+
+  /* grab/replace event/query functions */
+  GST_DEBUG_OBJECT (ghost, "Setting priv->eventfunc to %p",
+      GST_PAD_EVENTFUNC (ghost));
+  priv->eventfunc = GST_PAD_EVENTFUNC (ghost);
+  priv->queryfunc = GST_PAD_QUERYFUNC (ghost);
+
+  gst_pad_set_event_function (ghost,
+      GST_DEBUG_FUNCPTR (ghostpad_event_function));
+  gst_pad_set_query_function (ghost,
+      GST_DEBUG_FUNCPTR (ghostpad_query_function));
+
+  gst_pad_set_element_private (ghost, priv);
+  control_internal_pad (ghost, object);
+
+  return ghost;
+}
+
+void
+gnl_object_remove_ghost_pad (GnlObject * object, GstPad * ghost)
+{
+  GnlPadPrivate *priv;
+
+  GST_DEBUG_OBJECT (object, "ghostpad %s:%s", GST_DEBUG_PAD_NAME (ghost));
+
+  priv = gst_pad_get_element_private (ghost);
+  gst_ghost_pad_set_target (GST_GHOST_PAD (ghost), NULL);
+  gst_element_remove_pad (GST_ELEMENT (object), ghost);
+  if (priv)
+    g_slice_free (GnlPadPrivate, priv);
+}
+
+gboolean
+gnl_object_ghost_pad_set_target (GnlObject * object, GstPad * ghost,
+    GstPad * target)
+{
+  GnlPadPrivate *priv = gst_pad_get_element_private (ghost);
+
+  g_return_val_if_fail (priv, FALSE);
+
+  if (target)
+    GST_DEBUG_OBJECT (object, "setting target %s:%s on ghostpad",
+        GST_DEBUG_PAD_NAME (target));
+  else
+    GST_DEBUG_OBJECT (object, "removing target from ghostpad");
+
+  /* set target */
+  if (!(gst_ghost_pad_set_target (GST_GHOST_PAD (ghost), target)))
+    return FALSE;
+
+  return TRUE;
+}
+
+void
+gnl_init_ghostpad_category (void)
+{
+  GST_DEBUG_CATEGORY_INIT (gnlghostpad, "gnlghostpad",
+      GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GNonLin GhostPad");
+
+}
diff --git a/gnl/gnlghostpad.h b/gnl/gnlghostpad.h
new file mode 100644 (file)
index 0000000..f43ab0d
--- /dev/null
@@ -0,0 +1,47 @@
+/* GStreamer
+ * Copyright (C) 2009 Edward Hervey <bilboed@bilboed.com>
+ *
+ * gnlghostpad.h: Header for helper ghostpad
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef __GNL_GHOSTPAD_H__
+#define __GNL_GHOSTPAD_H__
+
+#include <gst/gst.h>
+
+#include "gnltypes.h"
+
+G_BEGIN_DECLS
+
+GstPad *gnl_object_ghost_pad (GnlObject * object,
+    const gchar * name, GstPad * target);
+
+GstPad *gnl_object_ghost_pad_no_target (GnlObject * object,
+    const gchar * name, GstPadDirection dir);
+
+gboolean gnl_object_ghost_pad_set_target (GnlObject * object,
+    GstPad * ghost, GstPad * target);
+
+void gnl_object_remove_ghost_pad (GnlObject * object, GstPad * ghost);
+
+void gnl_init_ghostpad_category (void);
+
+G_END_DECLS
+
+#endif /* __GNL_GHOSTPAD_H__ */
diff --git a/gnl/gnlmarshal.list b/gnl/gnlmarshal.list
new file mode 100644 (file)
index 0000000..67c6394
--- /dev/null
@@ -0,0 +1,2 @@
+VOID:OBJECT,UINT
+
diff --git a/gnl/gnlobject.c b/gnl/gnlobject.c
new file mode 100644 (file)
index 0000000..3052ea9
--- /dev/null
@@ -0,0 +1,656 @@
+/* Gnonlin
+ * Copyright (C) <2001> Wim Taymans <wim.taymans@gmail.com>
+ *               <2004-2008> Edward Hervey <bilboed@bilboed.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+#include "gnl.h"
+
+/**
+ * SECTION:gnlobject
+ * @short_description: Base class for GNonLin elements
+ *
+ * <refsect2>
+ * <para>
+ * GnlObject encapsulates default behaviour and implements standard
+ * properties provided by all the GNonLin elements.
+ * </para>
+ * </refsect2>
+ *
+ */
+
+
+GST_DEBUG_CATEGORY_STATIC (gnlobject_debug);
+#define GST_CAT_DEFAULT gnlobject_debug
+
+#define _do_init \
+  GST_DEBUG_CATEGORY_INIT (gnlobject_debug, "gnlobject", GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GNonLin Object base class");
+#define gnl_object_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GnlObject, gnl_object, GST_TYPE_BIN, _do_init);
+
+/****************************************************
+ *              Helper macros                       *
+ ****************************************************/
+#define CHECK_AND_SET(PROPERTY, property, prop_str, print_format)            \
+{                                                                            \
+GstObject *parent = gst_object_get_parent (GST_OBJECT (object));             \
+if (parent == NULL && !GNL_OBJECT_IS_COMPOSITION (object)) {                 \
+  GST_INFO_OBJECT (object, "Not in a composition yet, "                      \
+      "not commiting" prop_str);                                             \
+} else if (object->pending_##property != object->property)      {            \
+  object->property = object->pending_##property;                             \
+  GST_DEBUG_OBJECT(object, "Setting " prop_str " to %"                       \
+      print_format, object->property);                                       \
+} else                                                                       \
+  GST_DEBUG_OBJECT(object, "Nothing to do for " prop_str);                   \
+if (parent)                                                                  \
+  gst_object_unref (parent);                                                 \
+}
+
+#define SET_PENDING_VALUE(property, property_str, type, print_format)      \
+gnlobject->pending_##property = g_value_get_##type (value);                \
+if (gnlobject->property != gnlobject->pending_##property) {                \
+  GST_DEBUG_OBJECT(object, "Setting pending " property_str " to %"         \
+      print_format, gnlobject->pending_##property);                        \
+  gnl_object_set_commit_needed (gnlobject);                                \
+} else                                                                     \
+  GST_DEBUG_OBJECT(object, "Pending " property_str " did not change");
+
+enum
+{
+  PROP_0,
+  PROP_START,
+  PROP_DURATION,
+  PROP_STOP,
+  PROP_INPOINT,
+  PROP_PRIORITY,
+  PROP_ACTIVE,
+  PROP_CAPS,
+  PROP_EXPANDABLE,
+  PROP_LAST
+};
+
+static GParamSpec *properties[PROP_LAST];
+
+static void gnl_object_dispose (GObject * object);
+
+static void gnl_object_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec);
+static void gnl_object_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec);
+
+static GstStateChangeReturn gnl_object_change_state (GstElement * element,
+    GstStateChange transition);
+
+static gboolean gnl_object_prepare_func (GnlObject * object);
+static gboolean gnl_object_cleanup_func (GnlObject * object);
+static gboolean gnl_object_commit_func (GnlObject * object, gboolean recurse);
+
+static GstStateChangeReturn gnl_object_prepare (GnlObject * object);
+
+static void
+gnl_object_class_init (GnlObjectClass * klass)
+{
+  GObjectClass *gobject_class;
+  GstElementClass *gstelement_class;
+  GnlObjectClass *gnlobject_class;
+
+  gobject_class = (GObjectClass *) klass;
+  gstelement_class = (GstElementClass *) klass;
+  gnlobject_class = (GnlObjectClass *) klass;
+
+  gobject_class->set_property = GST_DEBUG_FUNCPTR (gnl_object_set_property);
+  gobject_class->get_property = GST_DEBUG_FUNCPTR (gnl_object_get_property);
+  gobject_class->dispose = GST_DEBUG_FUNCPTR (gnl_object_dispose);
+
+  gstelement_class->change_state = GST_DEBUG_FUNCPTR (gnl_object_change_state);
+
+  gnlobject_class->prepare = GST_DEBUG_FUNCPTR (gnl_object_prepare_func);
+  gnlobject_class->cleanup = GST_DEBUG_FUNCPTR (gnl_object_cleanup_func);
+  gnlobject_class->commit_signal_handler =
+      GST_DEBUG_FUNCPTR (gnl_object_commit);
+  gnlobject_class->commit = GST_DEBUG_FUNCPTR (gnl_object_commit_func);
+
+  /**
+   * GnlObject:start
+   *
+   * The start position relative to the parent in nanoseconds.
+   */
+  properties[PROP_START] = g_param_spec_uint64 ("start", "Start",
+      "The start position relative to the parent (in nanoseconds)",
+      0, G_MAXUINT64, 0, G_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_START,
+      properties[PROP_START]);
+
+  /**
+   * GnlObject:duration
+   *
+   * The outgoing duration in nanoseconds.
+   */
+  properties[PROP_DURATION] = g_param_spec_int64 ("duration", "Duration",
+      "Outgoing duration (in nanoseconds)", 0, G_MAXINT64, 0,
+      G_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_DURATION,
+      properties[PROP_DURATION]);
+
+  /**
+   * GnlObject:stop
+   *
+   * The stop position relative to the parent in nanoseconds.
+   *
+   * This value is computed based on the values of start and duration.
+   */
+  properties[PROP_STOP] = g_param_spec_uint64 ("stop", "Stop",
+      "The stop position relative to the parent (in nanoseconds)",
+      0, G_MAXUINT64, 0, G_PARAM_READABLE);
+  g_object_class_install_property (gobject_class, PROP_STOP,
+      properties[PROP_STOP]);
+
+  /**
+   * GnlObject:inpoint
+   *
+   * The media start position in nanoseconds.
+   *
+   * Also called 'in-point' in video-editing, this corresponds to
+   * what position in the 'contained' object we should start outputting from.
+   */
+  properties[PROP_INPOINT] =
+      g_param_spec_uint64 ("inpoint", "Media start",
+      "The media start position (in nanoseconds)", 0, G_MAXUINT64,
+      GST_CLOCK_TIME_NONE, G_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_INPOINT,
+      properties[PROP_INPOINT]);
+
+  /**
+   * GnlObject:priority
+   *
+   * The priority of the object in the container.
+   *
+   * The highest priority is 0, meaning this object will be selected over
+   * any other between start and stop.
+   *
+   * The lowest priority is G_MAXUINT32.
+   *
+   * Objects whose priority is (-1) will be considered as 'default' objects
+   * in GnlComposition and their start/stop values will be modified as to
+   * fit the whole duration of the composition.
+   */
+  properties[PROP_PRIORITY] = g_param_spec_uint ("priority", "Priority",
+      "The priority of the object (0 = highest priority)", 0, G_MAXUINT, 0,
+      G_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_PRIORITY,
+      properties[PROP_PRIORITY]);
+
+  /**
+   * GnlObject:active
+   *
+   * Indicates whether this object should be used by its container.
+   *
+   * Set to #TRUE to temporarily disable this object in a #GnlComposition.
+   */
+  properties[PROP_ACTIVE] = g_param_spec_boolean ("active", "Active",
+      "Use this object in the GnlComposition", TRUE, G_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_ACTIVE,
+      properties[PROP_ACTIVE]);
+
+  /**
+   * GnlObject:caps
+   *
+   * Caps used to filter/choose the output stream.
+   *
+   * If the controlled object produces several stream, you can set this
+   * property to choose a specific stream.
+   *
+   * If nothing is specified then a source pad will be chosen at random.
+   */
+  properties[PROP_CAPS] = g_param_spec_boxed ("caps", "Caps",
+      "Caps used to filter/choose the output stream",
+      GST_TYPE_CAPS, G_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_CAPS,
+      properties[PROP_CAPS]);
+
+  /**
+   * GnlObject:expandable
+   *
+   * Indicates whether this object should expand to the full duration of its
+   * container #GnlComposition.
+   */
+  properties[PROP_EXPANDABLE] =
+      g_param_spec_boolean ("expandable", "Expandable",
+      "Expand to the full duration of the container composition", FALSE,
+      G_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_EXPANDABLE,
+      properties[PROP_EXPANDABLE]);
+}
+
+static void
+gnl_object_init (GnlObject * object)
+{
+  object->start = object->pending_start = 0;
+  object->duration = object->pending_duration = 0;
+  object->stop = 0;
+
+  object->inpoint = object->pending_inpoint = GST_CLOCK_TIME_NONE;
+  object->priority = object->pending_priority = 0;
+  object->active = object->pending_active = TRUE;
+
+  object->caps = gst_caps_new_any ();
+
+  object->segment_rate = 1.0;
+  object->segment_start = -1;
+  object->segment_stop = -1;
+}
+
+static void
+gnl_object_dispose (GObject * object)
+{
+  GnlObject *gnl = (GnlObject *) object;
+
+  if (gnl->caps) {
+    gst_caps_unref (gnl->caps);
+    gnl->caps = NULL;
+  }
+
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+/**
+ * gnl_object_to_media_time:
+ * @object: a #GnlObject
+ * @objecttime: The #GstClockTime we want to convert
+ * @mediatime: A pointer on a #GstClockTime to fill
+ *
+ * Converts a #GstClockTime from the object (container) context to the media context
+ *
+ * Returns: TRUE if @objecttime was within the limits of the @object start/stop time,
+ * FALSE otherwise
+ */
+gboolean
+gnl_object_to_media_time (GnlObject * object, GstClockTime otime,
+    GstClockTime * mtime)
+{
+  g_return_val_if_fail (mtime, FALSE);
+
+  GST_DEBUG_OBJECT (object, "ObjectTime : %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (otime));
+
+  GST_DEBUG_OBJECT (object,
+      "Start/Stop:[%" GST_TIME_FORMAT " -- %" GST_TIME_FORMAT "] "
+      "Media start: %" GST_TIME_FORMAT, GST_TIME_ARGS (object->start),
+      GST_TIME_ARGS (object->stop), GST_TIME_ARGS (object->inpoint));
+
+  /* limit check */
+  if (G_UNLIKELY ((otime < object->start))) {
+    GST_DEBUG_OBJECT (object, "ObjectTime is before start");
+    *mtime = (object->inpoint == GST_CLOCK_TIME_NONE) ? 0 : object->inpoint;
+    return FALSE;
+  }
+
+  if (G_UNLIKELY ((otime >= object->stop))) {
+    GST_DEBUG_OBJECT (object, "ObjectTime is after stop");
+    if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (object->inpoint)))
+      *mtime = object->inpoint + object->duration;
+    else
+      *mtime = object->stop - object->start;
+    return FALSE;
+  }
+
+  if (G_UNLIKELY (object->inpoint == GST_CLOCK_TIME_NONE)) {
+    /* no time shifting, for live sources ? */
+    *mtime = otime - object->start;
+  } else {
+    *mtime = otime - object->start + object->inpoint;
+  }
+
+  GST_DEBUG_OBJECT (object, "Returning MediaTime : %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (*mtime));
+
+  return TRUE;
+}
+
+/**
+ * gnl_media_to_object_time:
+ * @object: The #GnlObject
+ * @mediatime: The #GstClockTime we want to convert
+ * @objecttime: A pointer on a #GstClockTime to fill
+ *
+ * Converts a #GstClockTime from the media context to the object (container) context
+ *
+ * Returns: TRUE if @objecttime was within the limits of the @object media start/stop time,
+ * FALSE otherwise
+ */
+
+gboolean
+gnl_media_to_object_time (GnlObject * object, GstClockTime mtime,
+    GstClockTime * otime)
+{
+  g_return_val_if_fail (otime, FALSE);
+
+  GST_DEBUG_OBJECT (object, "MediaTime : %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (mtime));
+
+  GST_DEBUG_OBJECT (object,
+      "Start/Stop:[%" GST_TIME_FORMAT " -- %" GST_TIME_FORMAT "] "
+      "inpoint  %" GST_TIME_FORMAT, GST_TIME_ARGS (object->start),
+      GST_TIME_ARGS (object->stop), GST_TIME_ARGS (object->inpoint));
+
+
+  /* limit check */
+  if (G_UNLIKELY ((object->inpoint != GST_CLOCK_TIME_NONE)
+          && (mtime < object->inpoint))) {
+    GST_DEBUG_OBJECT (object, "media time is before inpoint, forcing to start");
+    *otime = object->start;
+    return FALSE;
+  }
+
+  if (G_LIKELY (object->inpoint != GST_CLOCK_TIME_NONE)) {
+    *otime = mtime - object->inpoint + object->start;
+  } else
+    *otime = mtime + object->start;
+
+  GST_DEBUG_OBJECT (object, "Returning ObjectTime : %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (*otime));
+  return TRUE;
+}
+
+static gboolean
+gnl_object_prepare_func (GnlObject * object)
+{
+  GST_DEBUG_OBJECT (object, "default prepare function, returning TRUE");
+
+  return TRUE;
+}
+
+static GstStateChangeReturn
+gnl_object_prepare (GnlObject * object)
+{
+  GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+
+  GST_DEBUG_OBJECT (object, "preparing");
+
+  if (!(GNL_OBJECT_GET_CLASS (object)->prepare (object)))
+    ret = GST_STATE_CHANGE_FAILURE;
+
+  GST_DEBUG_OBJECT (object, "finished preparing, returning %d", ret);
+
+  return ret;
+}
+
+static gboolean
+gnl_object_cleanup_func (GnlObject * object)
+{
+  GST_DEBUG_OBJECT (object, "default cleanup function, returning TRUE");
+
+  return TRUE;
+}
+
+static GstStateChangeReturn
+gnl_object_cleanup (GnlObject * object)
+{
+  GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+
+  GST_DEBUG_OBJECT (object, "cleaning-up");
+
+  if (!(GNL_OBJECT_GET_CLASS (object)->cleanup (object)))
+    ret = GST_STATE_CHANGE_FAILURE;
+
+  GST_DEBUG_OBJECT (object, "finished preparing, returning %d", ret);
+
+  return ret;
+}
+
+void
+gnl_object_set_caps (GnlObject * object, const GstCaps * caps)
+{
+  if (object->caps)
+    gst_caps_unref (object->caps);
+
+  object->caps = gst_caps_copy (caps);
+}
+
+static inline void
+_update_stop (GnlObject * gnlobject)
+{
+  /* check if start/duration has changed */
+
+  if ((gnlobject->pending_start + gnlobject->pending_duration) !=
+      gnlobject->stop) {
+    gnlobject->stop = gnlobject->pending_start + gnlobject->pending_duration;
+
+    GST_LOG_OBJECT (gnlobject,
+        "Updating stop value : %" GST_TIME_FORMAT " [start:%" GST_TIME_FORMAT
+        ", duration:%" GST_TIME_FORMAT "]", GST_TIME_ARGS (gnlobject->stop),
+        GST_TIME_ARGS (gnlobject->pending_start),
+        GST_TIME_ARGS (gnlobject->pending_duration));
+    g_object_notify_by_pspec (G_OBJECT (gnlobject), properties[PROP_STOP]);
+  }
+}
+
+static void
+update_values (GnlObject * object)
+{
+  CHECK_AND_SET (START, start, "start", G_GUINT64_FORMAT);
+  CHECK_AND_SET (INPOINT, inpoint, "inpoint", G_GUINT64_FORMAT);
+  CHECK_AND_SET (DURATION, duration, "duration", G_GINT64_FORMAT);
+  CHECK_AND_SET (PRIORITY, priority, "priority", G_GUINT32_FORMAT);
+  CHECK_AND_SET (ACTIVE, active, "active", G_GUINT32_FORMAT);
+
+  _update_stop (object);
+}
+
+static gboolean
+gnl_object_commit_func (GnlObject * object, gboolean recurse)
+{
+  GST_INFO_OBJECT (object, "Commiting object changed");
+
+  if (object->commit_needed == FALSE) {
+    GST_INFO_OBJECT (object, "No changes to commit");
+
+    return FALSE;
+  }
+
+  update_values (object);
+
+  GST_INFO_OBJECT (object, "Done commiting");
+
+  return TRUE;
+}
+
+static void
+gnl_object_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GnlObject *gnlobject = (GnlObject *) object;
+
+  g_return_if_fail (GNL_IS_OBJECT (object));
+
+  GST_OBJECT_LOCK (object);
+  switch (prop_id) {
+    case PROP_START:
+      SET_PENDING_VALUE (start, "start", uint64, G_GUINT64_FORMAT);
+      break;
+    case PROP_DURATION:
+      SET_PENDING_VALUE (duration, "duration", int64, G_GINT64_FORMAT);
+      break;
+    case PROP_INPOINT:
+      SET_PENDING_VALUE (inpoint, "inpoint", uint64, G_GUINT64_FORMAT);
+      break;
+    case PROP_PRIORITY:
+      SET_PENDING_VALUE (priority, "priority", uint, G_GUINT32_FORMAT);
+      break;
+    case PROP_ACTIVE:
+      SET_PENDING_VALUE (active, "active", boolean, G_GUINT32_FORMAT);
+      break;
+    case PROP_CAPS:
+      gnl_object_set_caps (gnlobject, gst_value_get_caps (value));
+      break;
+    case PROP_EXPANDABLE:
+      if (g_value_get_boolean (value))
+        GST_OBJECT_FLAG_SET (gnlobject, GNL_OBJECT_EXPANDABLE);
+      else
+        GST_OBJECT_FLAG_UNSET (gnlobject, GNL_OBJECT_EXPANDABLE);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+  GST_OBJECT_UNLOCK (object);
+
+  _update_stop (gnlobject);
+}
+
+static void
+gnl_object_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GnlObject *gnlobject = (GnlObject *) object;
+
+  switch (prop_id) {
+    case PROP_START:
+      g_value_set_uint64 (value, gnlobject->pending_start);
+      break;
+    case PROP_DURATION:
+      g_value_set_int64 (value, gnlobject->pending_duration);
+      break;
+    case PROP_STOP:
+      g_value_set_uint64 (value, gnlobject->stop);
+      break;
+    case PROP_INPOINT:
+      g_value_set_uint64 (value, gnlobject->pending_inpoint);
+      break;
+    case PROP_PRIORITY:
+      g_value_set_uint (value, gnlobject->pending_priority);
+      break;
+    case PROP_ACTIVE:
+      g_value_set_boolean (value, gnlobject->pending_active);
+      break;
+    case PROP_CAPS:
+      gst_value_set_caps (value, gnlobject->caps);
+      break;
+    case PROP_EXPANDABLE:
+      g_value_set_boolean (value, GNL_OBJECT_IS_EXPANDABLE (object));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static GstStateChangeReturn
+gnl_object_change_state (GstElement * element, GstStateChange transition)
+{
+  GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+
+  switch (transition) {
+    case GST_STATE_CHANGE_NULL_TO_READY:
+    {
+      GstObject *parent = gst_object_get_parent (GST_OBJECT (element));
+
+      /* Going to READY and if we are not in a composition, we need to make
+       * sure that the object positioning state is properly commited  */
+      if (parent) {
+        if (!GNL_OBJECT_IS_COMPOSITION (parent) &&
+            !GNL_OBJECT_IS_COMPOSITION (GNL_OBJECT (element))) {
+          GST_DEBUG ("Adding gnlobject to something that is not a composition,"
+              " commiting ourself");
+          gnl_object_commit (GNL_OBJECT (element), FALSE);
+        }
+
+        gst_object_unref (parent);
+      }
+    }
+      break;
+    case GST_STATE_CHANGE_READY_TO_PAUSED:
+      gnl_object_commit (GNL_OBJECT (element), FALSE);
+      if (gnl_object_prepare (GNL_OBJECT (element)) == GST_STATE_CHANGE_FAILURE) {
+        ret = GST_STATE_CHANGE_FAILURE;
+        goto beach;
+      }
+      break;
+    default:
+      break;
+  }
+
+  GST_DEBUG_OBJECT (element, "Calling parent change_state");
+
+  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+
+  GST_DEBUG_OBJECT (element, "Return from parent change_state was %d", ret);
+
+  if (ret == GST_STATE_CHANGE_FAILURE)
+    goto beach;
+
+  switch (transition) {
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+      /* cleanup gnlobject */
+      if (gnl_object_cleanup (GNL_OBJECT (element)) == GST_STATE_CHANGE_FAILURE)
+        ret = GST_STATE_CHANGE_FAILURE;
+      break;
+    default:
+      break;
+  }
+
+beach:
+  return ret;
+}
+
+void
+gnl_object_set_commit_needed (GnlObject * object)
+{
+  if (G_UNLIKELY (object->commiting)) {
+    GST_WARNING_OBJECT (object,
+        "Trying to set 'commit-needed' while commiting");
+
+    return;
+  }
+
+  GST_DEBUG_OBJECT (object, "Setting 'commit_needed'");
+  object->commit_needed = TRUE;
+}
+
+gboolean
+gnl_object_commit (GnlObject * object, gboolean recurse)
+{
+  gboolean ret;
+
+  GST_DEBUG_OBJECT (object, "Commiting object state");
+
+  object->commiting = TRUE;
+  ret = GNL_OBJECT_GET_CLASS (object)->commit (object, recurse);
+  object->commiting = FALSE;
+
+  return ret;
+
+}
+
+void
+gnl_object_reset (GnlObject * object)
+{
+  GST_INFO_OBJECT (object, "Resetting child timing values to default");
+
+  object->start = 0;
+  object->duration = 0;
+  object->stop = 0;
+  object->inpoint = GST_CLOCK_TIME_NONE;
+  object->priority = 0;
+  object->active = TRUE;
+}
diff --git a/gnl/gnlobject.h b/gnl/gnlobject.h
new file mode 100644 (file)
index 0000000..4ba7dd8
--- /dev/null
@@ -0,0 +1,160 @@
+/* GStreamer
+ * Copyright (C) 2001 Wim Taymans <wim.taymans@gmail.com>
+ *               2004-2008 Edward Hervey <bilboed@bilboed.com>
+ *
+ * gnlobject.h: Header for base GnlObject
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef __GNL_OBJECT_H__
+#define __GNL_OBJECT_H__
+
+#include <gst/gst.h>
+
+#include "gnltypes.h"
+
+G_BEGIN_DECLS
+#define GNL_TYPE_OBJECT \
+  (gnl_object_get_type())
+#define GNL_OBJECT(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GNL_TYPE_OBJECT,GnlObject))
+#define GNL_OBJECT_CAST(obj) ((GnlObject*) (obj))
+#define GNL_OBJECT_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GNL_TYPE_OBJECT,GnlObjectClass))
+#define GNL_OBJECT_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), GNL_TYPE_OBJECT, GnlObjectClass))
+#define GNL_IS_OBJECT(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GNL_TYPE_OBJECT))
+#define GNL_IS_OBJECT_CLASS(obj) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),GNL_TYPE_OBJECT))
+
+/**
+ * GnlObjectFlags:
+ * @GNL_OBJECT_IS_SOURCE:
+ * @GNL_OBJECT_IS_OPERATION:
+ * @GNL_OBJECT_IS_EXPANDABLE: The #GnlObject start/stop will extend accross the full composition.
+ * @GNL_OBJECT_LAST_FLAG:
+*/
+
+typedef enum
+{
+  GNL_OBJECT_SOURCE = (GST_BIN_FLAG_LAST << 0),
+  GNL_OBJECT_OPERATION = (GST_BIN_FLAG_LAST << 1),
+  GNL_OBJECT_EXPANDABLE = (GST_BIN_FLAG_LAST << 2),
+  GNL_OBJECT_COMPOSITION = (GST_BIN_FLAG_LAST << 3),
+  /* padding */
+  GNL_OBJECT_LAST_FLAG = (GST_BIN_FLAG_LAST << 5)
+} GnlObjectFlags;
+
+
+#define GNL_OBJECT_IS_SOURCE(obj) \
+  (GST_OBJECT_FLAG_IS_SET(obj, GNL_OBJECT_SOURCE))
+#define GNL_OBJECT_IS_OPERATION(obj) \
+  (GST_OBJECT_FLAG_IS_SET(obj, GNL_OBJECT_OPERATION))
+#define GNL_OBJECT_IS_EXPANDABLE(obj) \
+  (GST_OBJECT_FLAG_IS_SET(obj, GNL_OBJECT_EXPANDABLE))
+#define GNL_OBJECT_IS_COMPOSITION(obj) \
+  (GST_OBJECT_FLAG_IS_SET(obj, GNL_OBJECT_COMPOSITION))
+
+/* For internal usage only */
+#define GNL_OBJECT_START(obj) (GNL_OBJECT_CAST (obj)->start)
+#define GNL_OBJECT_STOP(obj) (GNL_OBJECT_CAST (obj)->stop)
+#define GNL_OBJECT_DURATION(obj) (GNL_OBJECT_CAST (obj)->duration)
+#define GNL_OBJECT_INPOINT(obj) (GNL_OBJECT_CAST (obj)->inpoint)
+#define GNL_OBJECT_PRIORITY(obj) (GNL_OBJECT_CAST (obj)->priority)
+
+#define GNL_OBJECT_IS_COMMITING(obj) (GNL_OBJECT_CAST (obj)->commiting)
+
+struct _GnlObject
+{
+  GstBin parent;
+
+  /* Time positionning */
+  GstClockTime start;
+  GstClockTime inpoint;
+  GstClockTimeDiff duration;
+
+  /* Pending time positionning
+   * Should be == GST_CLOCK_TIME_NONE when nothing to do
+   */
+  GstClockTime pending_start;
+  GstClockTime pending_inpoint;
+  GstClockTimeDiff pending_duration;
+  guint32 pending_priority;
+  gboolean pending_active;
+
+  gboolean commit_needed;
+  gboolean commiting; /* Set to TRUE during the commiting time only */
+
+  gboolean expandable;
+
+  /* read-only */
+  GstClockTime stop;
+
+  /* priority in parent */
+  guint32 priority;
+
+  /* active in parent */
+  gboolean active;
+
+  /* Filtering caps */
+  GstCaps *caps;
+
+  /* current segment seek <RO> */
+  gdouble segment_rate;
+  GstSeekFlags segment_flags;
+  gint64 segment_start;
+  gint64 segment_stop;
+};
+
+struct _GnlObjectClass
+{
+  GstBinClass parent_class;
+
+  /* Signal method handler */
+  gboolean (*commit_signal_handler) (GnlObject * object, gboolean recurse);
+
+  /* virtual methods for subclasses */
+    gboolean (*prepare) (GnlObject * object);
+    gboolean (*cleanup) (GnlObject * object);
+    gboolean (*commit) (GnlObject * object, gboolean recurse);
+};
+
+GType gnl_object_get_type (void);
+
+gboolean
+gnl_object_to_media_time (GnlObject * object, GstClockTime otime,
+                         GstClockTime * mtime);
+
+gboolean
+gnl_media_to_object_time (GnlObject * object, GstClockTime mtime,
+                         GstClockTime * otime);
+
+void
+gnl_object_set_caps (GnlObject * object, const GstCaps * caps);
+
+void
+gnl_object_set_commit_needed (GnlObject *object);
+
+gboolean
+gnl_object_commit (GnlObject *object, gboolean recurse);
+
+void
+gnl_object_reset (GnlObject *object);
+G_END_DECLS
+#endif /* __GNL_OBJECT_H__ */
diff --git a/gnl/gnloperation.c b/gnl/gnloperation.c
new file mode 100644 (file)
index 0000000..dcc811f
--- /dev/null
@@ -0,0 +1,792 @@
+/* GStreamer
+ * Copyright (C) 2001 Wim Taymans <wim.taymans@gmail.com>
+ *               2004-2008 Edward Hervey <bilboed@bilboed.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gnl.h"
+
+/**
+ * SECTION:element-gnloperation
+ *
+ * <refsect2>
+ * <para>
+ * A GnlOperation performs a transformation or mixing operation on the
+ * data from one or more #GnlSources, which is used to implement filters or 
+ * effects.
+ * </para>
+ * </refsect2>
+ */
+
+static GstStaticPadTemplate gnl_operation_src_template =
+GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_SOMETIMES,
+    GST_STATIC_CAPS_ANY);
+
+static GstStaticPadTemplate gnl_operation_sink_template =
+GST_STATIC_PAD_TEMPLATE ("sink%d",
+    GST_PAD_SINK,
+    GST_PAD_REQUEST,
+    GST_STATIC_CAPS_ANY);
+
+GST_DEBUG_CATEGORY_STATIC (gnloperation);
+#define GST_CAT_DEFAULT gnloperation
+
+#define _do_init \
+  GST_DEBUG_CATEGORY_INIT (gnloperation, "gnloperation", GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GNonLin Operation element");
+#define gnl_operation_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GnlOperation, gnl_operation, GNL_TYPE_OBJECT,
+    _do_init);
+
+enum
+{
+  ARG_0,
+  ARG_SINKS,
+};
+
+enum
+{
+  INPUT_PRIORITY_CHANGED,
+  LAST_SIGNAL
+};
+
+static guint gnl_operation_signals[LAST_SIGNAL] = { 0 };
+
+static void gnl_operation_dispose (GObject * object);
+
+static void gnl_operation_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec);
+static void gnl_operation_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec);
+
+static gboolean gnl_operation_prepare (GnlObject * object);
+static gboolean gnl_operation_cleanup (GnlObject * object);
+
+static gboolean gnl_operation_add_element (GstBin * bin, GstElement * element);
+static gboolean gnl_operation_remove_element (GstBin * bin,
+    GstElement * element);
+
+static GstPad *gnl_operation_request_new_pad (GstElement * element,
+    GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
+static void gnl_operation_release_pad (GstElement * element, GstPad * pad);
+
+static void synchronize_sinks (GnlOperation * operation);
+static gboolean remove_sink_pad (GnlOperation * operation, GstPad * sinkpad);
+
+static void
+gnl_operation_class_init (GnlOperationClass * klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+  GstBinClass *gstbin_class = (GstBinClass *) klass;
+
+  GstElementClass *gstelement_class = (GstElementClass *) klass;
+  GnlObjectClass *gnlobject_class = (GnlObjectClass *) klass;
+
+  gst_element_class_set_static_metadata (gstelement_class, "GNonLin Operation",
+      "Filter/Editor",
+      "Encapsulates filters/effects for use with GNL Objects",
+      "Wim Taymans <wim.taymans@gmail.com>, Edward Hervey <bilboed@bilboed.com>");
+
+  gobject_class->dispose = GST_DEBUG_FUNCPTR (gnl_operation_dispose);
+
+  gobject_class->set_property = GST_DEBUG_FUNCPTR (gnl_operation_set_property);
+  gobject_class->get_property = GST_DEBUG_FUNCPTR (gnl_operation_get_property);
+
+  /**
+   * GnlOperation:sinks:
+   *
+   * Specifies the number of sink pads the operation should provide.
+   * If the sinks property is -1 (the default) pads are only created as
+   * demanded via get_request_pad() calls on the element.
+   */
+  g_object_class_install_property (gobject_class, ARG_SINKS,
+      g_param_spec_int ("sinks", "Sinks",
+          "Number of input sinks (-1 for automatic handling)", -1, G_MAXINT, -1,
+          G_PARAM_READWRITE));
+
+  /**
+   * GnlOperation:input-priority-changed:
+   * @pad: The operation's input pad whose priority changed.
+   * @priority: The new priority
+   *
+   * Signals that the @priority of the stream being fed to the given @pad
+   * might have changed.
+   */
+  gnl_operation_signals[INPUT_PRIORITY_CHANGED] =
+      g_signal_new ("input-priority-changed", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GnlOperationClass,
+          input_priority_changed), NULL, NULL, g_cclosure_marshal_generic,
+      G_TYPE_NONE, 2, GST_TYPE_PAD, G_TYPE_UINT);
+
+  gstelement_class->request_new_pad =
+      GST_DEBUG_FUNCPTR (gnl_operation_request_new_pad);
+  gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gnl_operation_release_pad);
+
+  gstbin_class->add_element = GST_DEBUG_FUNCPTR (gnl_operation_add_element);
+  gstbin_class->remove_element =
+      GST_DEBUG_FUNCPTR (gnl_operation_remove_element);
+
+  gnlobject_class->prepare = GST_DEBUG_FUNCPTR (gnl_operation_prepare);
+  gnlobject_class->cleanup = GST_DEBUG_FUNCPTR (gnl_operation_cleanup);
+
+  gst_element_class_add_pad_template (gstelement_class,
+      gst_static_pad_template_get (&gnl_operation_src_template));
+
+  gst_element_class_add_pad_template (gstelement_class,
+      gst_static_pad_template_get (&gnl_operation_sink_template));
+
+}
+
+static void
+gnl_operation_dispose (GObject * object)
+{
+  GnlOperation *oper = (GnlOperation *) object;
+
+  GST_DEBUG_OBJECT (object, "Disposing of source pad");
+  if (oper->ghostpad) {
+    gnl_object_remove_ghost_pad (GNL_OBJECT (oper), oper->ghostpad);
+    oper->ghostpad = NULL;
+  }
+
+  GST_DEBUG_OBJECT (object, "Disposing of sink pad(s)");
+  while (oper->sinks) {
+    GstPad *ghost = (GstPad *) oper->sinks->data;
+    remove_sink_pad (oper, ghost);
+  }
+
+  GST_DEBUG_OBJECT (object, "Done, calling parent class ::dispose()");
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gnl_operation_reset (GnlOperation * operation)
+{
+  operation->num_sinks = 1;
+  operation->realsinks = 0;
+  operation->next_base_time = 0;
+}
+
+static void
+gnl_operation_init (GnlOperation * operation)
+{
+  gnl_operation_reset (operation);
+  operation->ghostpad = NULL;
+  operation->element = NULL;
+}
+
+static gboolean
+element_is_valid_filter (GstElement * element, gboolean * isdynamic)
+{
+  gboolean havesink = FALSE;
+  gboolean havesrc = FALSE;
+  gboolean done = FALSE;
+  GstIterator *pads;
+  GValue item = { 0, };
+
+  if (isdynamic)
+    *isdynamic = FALSE;
+
+  pads = gst_element_iterate_pads (element);
+
+  while (!done) {
+    switch (gst_iterator_next (pads, &item)) {
+      case GST_ITERATOR_OK:
+      {
+        GstPad *pad = g_value_get_object (&item);
+
+        if (gst_pad_get_direction (pad) == GST_PAD_SRC)
+          havesrc = TRUE;
+        else if (gst_pad_get_direction (pad) == GST_PAD_SINK)
+          havesink = TRUE;
+
+        g_value_reset (&item);
+        break;
+      }
+      case GST_ITERATOR_RESYNC:
+        gst_iterator_resync (pads);
+        havesrc = FALSE;
+        havesink = FALSE;
+        break;
+      default:
+        /* ERROR and DONE */
+        done = TRUE;
+        break;
+    }
+  }
+
+  g_value_unset (&item);
+  gst_iterator_free (pads);
+
+  /* just look at the element's class, not the factory, since there might
+   * not be a factory (in case of python elements) or the factory is the
+   * wrong one (in case of a GstBin sub-class) and doesn't have complete
+   * information. */
+  {
+    GList *tmp =
+        gst_element_class_get_pad_template_list (GST_ELEMENT_GET_CLASS
+        (element));
+
+    while (tmp) {
+      GstPadTemplate *template = (GstPadTemplate *) tmp->data;
+
+      if (template->direction == GST_PAD_SRC)
+        havesrc = TRUE;
+      else if (template->direction == GST_PAD_SINK) {
+        if (!havesink && (template->presence == GST_PAD_REQUEST) && isdynamic)
+          *isdynamic = TRUE;
+        havesink = TRUE;
+      }
+      tmp = tmp->next;
+    }
+  }
+  return (havesink && havesrc);
+}
+
+/*
+ * get_src_pad:
+ * element: a #GstElement
+ *
+ * Returns: The src pad for the given element. A reference was added to the
+ * returned pad, remove it when you don't need that pad anymore.
+ * Returns NULL if there's no source pad.
+ */
+
+static GstPad *
+get_src_pad (GstElement * element)
+{
+  GstIterator *it;
+  GstIteratorResult itres;
+  GValue item = { 0, };
+  GstPad *srcpad = NULL;
+
+  it = gst_element_iterate_src_pads (element);
+  itres = gst_iterator_next (it, &item);
+  if (itres != GST_ITERATOR_OK) {
+    GST_DEBUG ("%s doesn't have a src pad !", GST_ELEMENT_NAME (element));
+  } else {
+    srcpad = g_value_get_object (&item);
+    gst_object_ref (srcpad);
+  }
+  g_value_reset (&item);
+  gst_iterator_free (it);
+
+  return srcpad;
+}
+
+/* get_nb_static_sinks:
+ * 
+ * Returns : The number of static sink pads of the controlled element.
+ */
+static guint
+get_nb_static_sinks (GnlOperation * oper)
+{
+  GstIterator *sinkpads;
+  gboolean done = FALSE;
+  guint nbsinks = 0;
+  GValue item = { 0, };
+
+  sinkpads = gst_element_iterate_sink_pads (oper->element);
+
+  while (!done) {
+    switch (gst_iterator_next (sinkpads, &item)) {
+      case GST_ITERATOR_OK:{
+        nbsinks++;
+        g_value_unset (&item);
+      }
+        break;
+      case GST_ITERATOR_RESYNC:
+        nbsinks = 0;
+        gst_iterator_resync (sinkpads);
+        break;
+      default:
+        /* ERROR and DONE */
+        done = TRUE;
+        break;
+    }
+  }
+
+  g_value_reset (&item);
+  gst_iterator_free (sinkpads);
+
+  GST_DEBUG ("We found %d static sinks", nbsinks);
+
+  return nbsinks;
+}
+
+static gboolean
+gnl_operation_add_element (GstBin * bin, GstElement * element)
+{
+  GnlOperation *operation = (GnlOperation *) bin;
+  gboolean res = FALSE;
+  gboolean isdynamic;
+
+  GST_DEBUG_OBJECT (bin, "element:%s", GST_ELEMENT_NAME (element));
+
+  if (operation->element) {
+    GST_WARNING_OBJECT (operation,
+        "We already control an element : %s , remove it first",
+        GST_OBJECT_NAME (operation->element));
+  } else {
+    if (!element_is_valid_filter (element, &isdynamic)) {
+      GST_WARNING_OBJECT (operation,
+          "Element %s is not a valid filter element",
+          GST_ELEMENT_NAME (element));
+    } else {
+      if ((res = GST_BIN_CLASS (parent_class)->add_element (bin, element))) {
+        GstPad *srcpad;
+
+        srcpad = get_src_pad (element);
+        if (!srcpad)
+          return FALSE;
+
+        operation->element = element;
+        operation->dynamicsinks = isdynamic;
+
+        /* Source ghostpad */
+        if (operation->ghostpad)
+          gnl_object_ghost_pad_set_target (GNL_OBJECT (operation),
+              operation->ghostpad, srcpad);
+        else
+          operation->ghostpad = gnl_object_ghost_pad (GNL_OBJECT (operation),
+              GST_PAD_NAME (srcpad), srcpad);
+
+        /* Remove the reference get_src_pad gave us */
+        gst_object_unref (srcpad);
+
+        /* Figure out number of static sink pads */
+        operation->num_sinks = get_nb_static_sinks (operation);
+
+        /* Finally sync the ghostpads with the real pads */
+        synchronize_sinks (operation);
+      }
+    }
+  }
+
+  return res;
+}
+
+static gboolean
+gnl_operation_remove_element (GstBin * bin, GstElement * element)
+{
+  GnlOperation *operation = (GnlOperation *) bin;
+  gboolean res = FALSE;
+
+  if (operation->element) {
+    if ((res = GST_BIN_CLASS (parent_class)->remove_element (bin, element)))
+      operation->element = NULL;
+  } else {
+    GST_WARNING_OBJECT (bin,
+        "Element %s is not the one controlled by this operation",
+        GST_ELEMENT_NAME (element));
+  }
+  return res;
+}
+
+static void
+gnl_operation_set_sinks (GnlOperation * operation, guint sinks)
+{
+  /* FIXME : Check if sinkpad of element is on-demand .... */
+
+  operation->num_sinks = sinks;
+  synchronize_sinks (operation);
+}
+
+static void
+gnl_operation_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GnlOperation *operation = (GnlOperation *) object;
+
+  switch (prop_id) {
+    case ARG_SINKS:
+      gnl_operation_set_sinks (operation, g_value_get_int (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gnl_operation_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GnlOperation *operation = (GnlOperation *) object;
+
+  switch (prop_id) {
+    case ARG_SINKS:
+      g_value_set_int (value, operation->num_sinks);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+
+/*
+ * Returns the first unused sink pad of the controlled element.
+ * Only use with static element. Unref after usage.
+ * Returns NULL if there's no more unused sink pads.
+ */
+static GstPad *
+get_unused_static_sink_pad (GnlOperation * operation)
+{
+  GstIterator *pads;
+  gboolean done = FALSE;
+  GValue item = { 0, };
+  GstPad *ret = NULL;
+
+  if (!operation->element)
+    return NULL;
+
+  pads = gst_element_iterate_pads (operation->element);
+
+  while (!done) {
+    switch (gst_iterator_next (pads, &item)) {
+      case GST_ITERATOR_OK:
+      {
+        GstPad *pad = g_value_get_object (&item);
+
+        if (gst_pad_get_direction (pad) == GST_PAD_SINK) {
+          GList *tmp;
+          gboolean istaken = FALSE;
+
+          /* 1. figure out if one of our sink ghostpads has this pad as target */
+          for (tmp = operation->sinks; tmp; tmp = tmp->next) {
+            GstGhostPad *gpad = (GstGhostPad *) tmp->data;
+            GstPad *target = gst_ghost_pad_get_target (gpad);
+
+            GST_LOG ("found ghostpad with target %s:%s",
+                GST_DEBUG_PAD_NAME (target));
+
+            if (target) {
+              if (target == pad)
+                istaken = TRUE;
+              gst_object_unref (target);
+            }
+          }
+
+          /* 2. if not taken, return that pad */
+          if (!istaken) {
+            gst_object_ref (pad);
+            ret = pad;
+            done = TRUE;
+          }
+        }
+        g_value_reset (&item);
+        break;
+      }
+      case GST_ITERATOR_RESYNC:
+        gst_iterator_resync (pads);
+        break;
+      default:
+        /* ERROR and DONE */
+        done = TRUE;
+        break;
+    }
+  }
+
+  g_value_unset (&item);
+  gst_iterator_free (pads);
+
+  if (ret)
+    GST_DEBUG_OBJECT (operation, "found free sink pad %s:%s",
+        GST_DEBUG_PAD_NAME (ret));
+  else
+    GST_DEBUG_OBJECT (operation, "Couldn't find an unused sink pad");
+
+  return ret;
+}
+
+GstPad *
+get_unlinked_sink_ghost_pad (GnlOperation * operation)
+{
+  GstIterator *pads;
+  gboolean done = FALSE;
+  GValue item = { 0, };
+  GstPad *ret = NULL;
+
+  if (!operation->element)
+    return NULL;
+
+  pads = gst_element_iterate_sink_pads ((GstElement *) operation);
+
+  while (!done) {
+    switch (gst_iterator_next (pads, &item)) {
+      case GST_ITERATOR_OK:
+      {
+        GstPad *pad = g_value_get_object (&item);
+        GstPad *peer = gst_pad_get_peer (pad);
+
+        if (peer == NULL) {
+          ret = pad;
+          gst_object_ref (ret);
+          done = TRUE;
+        } else {
+          gst_object_unref (peer);
+        }
+        g_value_reset (&item);
+        break;
+      }
+      case GST_ITERATOR_RESYNC:
+        gst_iterator_resync (pads);
+        break;
+      default:
+        /* ERROR and DONE */
+        done = TRUE;
+        break;
+    }
+  }
+
+  g_value_unset (&item);
+  gst_iterator_free (pads);
+
+  if (ret)
+    GST_DEBUG_OBJECT (operation, "found unlinked ghost sink pad %s:%s",
+        GST_DEBUG_PAD_NAME (ret));
+  else
+    GST_DEBUG_OBJECT (operation, "Couldn't find an unlinked ghost sink pad");
+
+  return ret;
+
+}
+
+static GstPad *
+get_request_sink_pad (GnlOperation * operation)
+{
+  GstPad *pad = NULL;
+  GList *templates;
+
+  if (!operation->element)
+    return NULL;
+
+  templates = gst_element_class_get_pad_template_list
+      (GST_ELEMENT_GET_CLASS (operation->element));
+
+  for (; templates; templates = templates->next) {
+    GstPadTemplate *templ = (GstPadTemplate *) templates->data;
+
+    GST_LOG_OBJECT (operation->element, "Trying template %s",
+        GST_PAD_TEMPLATE_NAME_TEMPLATE (templ));
+
+    if ((GST_PAD_TEMPLATE_DIRECTION (templ) == GST_PAD_SINK) &&
+        (GST_PAD_TEMPLATE_PRESENCE (templ) == GST_PAD_REQUEST)) {
+      pad =
+          gst_element_get_request_pad (operation->element,
+          GST_PAD_TEMPLATE_NAME_TEMPLATE (templ));
+      if (pad)
+        break;
+    }
+  }
+
+  return pad;
+}
+
+static GstPad *
+add_sink_pad (GnlOperation * operation)
+{
+  GstPad *gpad = NULL;
+  GstPad *ret = NULL;
+
+  if (!operation->element)
+    return NULL;
+
+  /* FIXME : implement */
+  GST_LOG_OBJECT (operation, "element:%s , dynamicsinks:%d",
+      GST_ELEMENT_NAME (operation->element), operation->dynamicsinks);
+
+
+  if (!operation->dynamicsinks) {
+    /* static sink pads */
+    ret = get_unused_static_sink_pad (operation);
+    if (ret) {
+      gpad = gnl_object_ghost_pad ((GnlObject *) operation, GST_PAD_NAME (ret),
+          ret);
+      gst_object_unref (ret);
+    }
+  }
+
+  if (!gpad) {
+    /* request sink pads */
+    ret = get_request_sink_pad (operation);
+    if (ret) {
+      gpad = gnl_object_ghost_pad ((GnlObject *) operation, GST_PAD_NAME (ret),
+          ret);
+      gst_object_unref (ret);
+    }
+  }
+
+  if (gpad) {
+    operation->sinks = g_list_append (operation->sinks, gpad);
+    operation->realsinks++;
+    GST_DEBUG ("Created new pad %s:%s ghosting %s:%s",
+        GST_DEBUG_PAD_NAME (gpad), GST_DEBUG_PAD_NAME (ret));
+  } else {
+    GST_WARNING ("Couldn't find a usable sink pad!");
+  }
+
+  return gpad;
+}
+
+static gboolean
+remove_sink_pad (GnlOperation * operation, GstPad * sinkpad)
+{
+  gboolean ret = TRUE;
+
+  GST_DEBUG ("sinkpad %s:%s", GST_DEBUG_PAD_NAME (sinkpad));
+
+  /*
+     We can't remove any random pad.
+     We should remove an unused pad ... which is hard to figure out in a
+     thread-safe way.
+   */
+
+  if ((sinkpad == NULL) && operation->dynamicsinks) {
+    /* Find an unlinked sinkpad */
+    if ((sinkpad = get_unlinked_sink_ghost_pad (operation)) == NULL) {
+      ret = FALSE;
+      goto beach;
+    }
+  }
+
+  if (sinkpad) {
+    GstPad *target = gst_ghost_pad_get_target ((GstGhostPad *) sinkpad);
+
+    if (target) {
+      /* release the target pad */
+      gnl_object_ghost_pad_set_target ((GnlObject *) operation, sinkpad, NULL);
+      if (operation->dynamicsinks)
+        gst_element_release_request_pad (operation->element, target);
+      gst_object_unref (target);
+    }
+    operation->sinks = g_list_remove (operation->sinks, sinkpad);
+    gnl_object_remove_ghost_pad ((GnlObject *) operation, sinkpad);
+    operation->realsinks--;
+  }
+
+beach:
+  return ret;
+}
+
+static void
+synchronize_sinks (GnlOperation * operation)
+{
+
+  GST_DEBUG_OBJECT (operation, "num_sinks:%d , realsinks:%d, dynamicsinks:%d",
+      operation->num_sinks, operation->realsinks, operation->dynamicsinks);
+
+  if (operation->num_sinks == operation->realsinks)
+    return;
+
+  if (operation->num_sinks > operation->realsinks) {
+    while (operation->num_sinks > operation->realsinks) /* Add pad */
+      if (!(add_sink_pad (operation))) {
+        break;
+      }
+  } else {
+    /* Remove pad */
+    /* FIXME, which one do we remove ? :) */
+    while (operation->num_sinks < operation->realsinks)
+      if (!remove_sink_pad (operation, NULL))
+        break;
+  }
+}
+
+static gboolean
+gnl_operation_prepare (GnlObject * object)
+{
+  /* Prepare the pads */
+  synchronize_sinks ((GnlOperation *) object);
+
+  return TRUE;
+}
+
+static gboolean
+gnl_operation_cleanup (GnlObject * object)
+{
+  GnlOperation *oper = (GnlOperation *) object;
+
+  if (oper->dynamicsinks) {
+    GST_DEBUG ("Resetting dynamic sinks");
+    gnl_operation_set_sinks (oper, 0);
+  }
+
+  return TRUE;
+}
+
+
+static GstPad *
+gnl_operation_request_new_pad (GstElement * element, GstPadTemplate * templ,
+    const gchar * name, const GstCaps * caps)
+{
+  GnlOperation *operation = (GnlOperation *) element;
+  GstPad *ret;
+
+  GST_DEBUG ("template:%s name:%s", templ->name_template, name);
+
+  if (operation->num_sinks == operation->realsinks) {
+    GST_WARNING_OBJECT (element,
+        "We already have the maximum number of pads : %d",
+        operation->num_sinks);
+    return NULL;
+  }
+
+  ret = add_sink_pad ((GnlOperation *) element);
+
+  return ret;
+}
+
+static void
+gnl_operation_release_pad (GstElement * element, GstPad * pad)
+{
+  GST_DEBUG ("pad %s:%s", GST_DEBUG_PAD_NAME (pad));
+
+  remove_sink_pad ((GnlOperation *) element, pad);
+}
+
+void
+gnl_operation_signal_input_priority_changed (GnlOperation * operation,
+    GstPad * pad, guint32 priority)
+{
+  GST_DEBUG_OBJECT (operation, "pad:%s:%s, priority:%d",
+      GST_DEBUG_PAD_NAME (pad), priority);
+  g_signal_emit (operation, gnl_operation_signals[INPUT_PRIORITY_CHANGED],
+      0, pad, priority);
+}
+
+void
+gnl_operation_update_base_time (GnlOperation * operation,
+    GstClockTime timestamp)
+{
+  if (!gnl_object_to_media_time (GNL_OBJECT (operation),
+          timestamp, &operation->next_base_time)) {
+    GST_WARNING_OBJECT (operation, "Trying to set a basetime outside of "
+        "ourself");
+
+    return;
+  }
+
+  GST_INFO_OBJECT (operation, "Setting next_basetime to %"
+      GST_TIME_FORMAT, GST_TIME_ARGS (operation->next_base_time));
+}
diff --git a/gnl/gnloperation.h b/gnl/gnloperation.h
new file mode 100644 (file)
index 0000000..3d6454b
--- /dev/null
@@ -0,0 +1,90 @@
+/* GStreamer
+ * Copyright (C) 2001 Wim Taymans <wim.taymans@chello.be>
+ *               2004 Edward Hervey <bilboed@bilboed.com>
+ *
+ * gnloperation.h: Header for base GnlOperation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef __GNL_OPERATION_H__
+#define __GNL_OPERATION_H__
+
+#include <gst/gst.h>
+#include "gnlobject.h"
+
+G_BEGIN_DECLS
+#define GNL_TYPE_OPERATION \
+  (gnl_operation_get_type())
+#define GNL_OPERATION(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GNL_TYPE_OPERATION,GnlOperation))
+#define GNL_OPERATION_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GNL_TYPE_OPERATION,GnlOperationClass))
+#define GNL_IS_OPERATION(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GNL_TYPE_OPERATION))
+#define GNL_IS_OPERATION_CLASS(obj) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),GNL_TYPE_OPERATION))
+    struct _GnlOperation
+{
+  GnlObject parent;
+
+  /* <private> */
+
+  /* num_sinks:
+   * Number of sink inputs of the controlled element.
+   * -1 if the sink pads are dynamic */
+  gint num_sinks;
+
+  /* TRUE if element has request pads */
+  gboolean dynamicsinks;
+
+  /* realsinks:
+   * Number of sink pads currently used on the contolled element. */
+  gint realsinks;
+
+  /* FIXME : We might need to use a lock to access this list */
+  GList * sinks;               /* The sink ghostpads */
+  
+  GstPad *ghostpad;            /* src ghostpad */
+
+  GstElement *element;         /* controlled element */
+
+  GstClockTime next_base_time;
+};
+
+struct _GnlOperationClass
+{
+  GnlObjectClass parent_class;
+
+  void (*input_priority_changed) (GnlOperation * operation, GstPad *pad, guint32 priority);
+};
+
+GstPad * get_unlinked_sink_ghost_pad (GnlOperation * operation);
+
+void
+gnl_operation_signal_input_priority_changed(GnlOperation * operation, GstPad *pad,
+                                           guint32 priority);
+
+void gnl_operation_update_base_time (GnlOperation *operation,
+                                     GstClockTime timestamp);
+
+
+/* normal GOperation stuff */
+GType gnl_operation_get_type (void);
+
+G_END_DECLS
+#endif /* __GNL_OPERATION_H__ */
diff --git a/gnl/gnlsource.c b/gnl/gnlsource.c
new file mode 100644 (file)
index 0000000..af9db9b
--- /dev/null
@@ -0,0 +1,590 @@
+/* Gnonlin
+ * Copyright (C) <2001> Wim Taymans <wim.taymans@gmail.com>
+ *               <2004-2008> Edward Hervey <bilboed@bilboed.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gnl.h"
+
+/**
+ * SECTION:element-gnlsource
+ *
+ * The GnlSource encapsulates a pipeline which produces data for processing
+ * in a #GnlComposition.
+ */
+
+static GstStaticPadTemplate gnl_source_src_template =
+GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_SOMETIMES,
+    GST_STATIC_CAPS_ANY);
+
+GST_DEBUG_CATEGORY_STATIC (gnlsource);
+#define GST_CAT_DEFAULT gnlsource
+
+#define _do_init \
+  GST_DEBUG_CATEGORY_INIT (gnlsource, "gnlsource", GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GNonLin Source Element");
+#define gnl_source_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GnlSource, gnl_source, GNL_TYPE_OBJECT, _do_init);
+
+struct _GnlSourcePrivate
+{
+  gboolean dispose_has_run;
+
+  gboolean dynamicpads;         /* TRUE if the controlled element has dynamic pads */
+  GstPad *ghostpad;             /* The source ghostpad */
+  GstEvent *event;              /* queued event */
+
+  gulong padremovedid;          /* signal handler for element pad-removed signal */
+  gulong padaddedid;            /* signal handler for element pad-added signal */
+  gulong probeid;               /* source pad probe id */
+
+  gboolean pendingblock;        /* We have a pending pad_block */
+  gboolean areblocked;          /* We already got blocked */
+  GstPad *ghostedpad;           /* Pad (to be) ghosted */
+  GstPad *staticpad;            /* The only pad. We keep an extra ref */
+};
+
+static gboolean gnl_source_prepare (GnlObject * object);
+static gboolean gnl_source_cleanup (GnlObject * object);
+
+static gboolean gnl_source_add_element (GstBin * bin, GstElement * element);
+
+static gboolean gnl_source_remove_element (GstBin * bin, GstElement * element);
+
+static void gnl_source_dispose (GObject * object);
+
+static gboolean gnl_source_send_event (GstElement * element, GstEvent * event);
+
+static GstPadProbeReturn
+pad_blocked_cb (GstPad * pad, GstPadProbeInfo * info, GnlSource * source);
+
+static gboolean
+gnl_source_control_element_func (GnlSource * source, GstElement * element);
+
+static void
+gnl_source_class_init (GnlSourceClass * klass)
+{
+  GObjectClass *gobject_class;
+  GstElementClass *gstelement_class;
+  GstBinClass *gstbin_class;
+  GnlObjectClass *gnlobject_class;
+
+  gobject_class = (GObjectClass *) klass;
+  gstelement_class = (GstElementClass *) klass;
+  gstbin_class = (GstBinClass *) klass;
+  gnlobject_class = (GnlObjectClass *) klass;
+
+  g_type_class_add_private (klass, sizeof (GnlSourcePrivate));
+
+  gst_element_class_set_static_metadata (gstelement_class, "GNonLin Source",
+      "Filter/Editor",
+      "Manages source elements",
+      "Wim Taymans <wim.taymans@gmail.com>, Edward Hervey <bilboed@bilboed.com>");
+
+  parent_class = g_type_class_ref (GNL_TYPE_OBJECT);
+
+  klass->controls_one = TRUE;
+  klass->control_element = GST_DEBUG_FUNCPTR (gnl_source_control_element_func);
+
+  gnlobject_class->prepare = GST_DEBUG_FUNCPTR (gnl_source_prepare);
+  gnlobject_class->cleanup = GST_DEBUG_FUNCPTR (gnl_source_cleanup);
+
+  gstbin_class->add_element = GST_DEBUG_FUNCPTR (gnl_source_add_element);
+  gstbin_class->remove_element = GST_DEBUG_FUNCPTR (gnl_source_remove_element);
+
+  gstelement_class->send_event = GST_DEBUG_FUNCPTR (gnl_source_send_event);
+
+  gobject_class->dispose = GST_DEBUG_FUNCPTR (gnl_source_dispose);
+
+  gst_element_class_add_pad_template (gstelement_class,
+      gst_static_pad_template_get (&gnl_source_src_template));
+
+}
+
+
+static void
+gnl_source_init (GnlSource * source)
+{
+  GST_OBJECT_FLAG_SET (source, GNL_OBJECT_SOURCE);
+  source->element = NULL;
+  source->priv =
+      G_TYPE_INSTANCE_GET_PRIVATE (source, GNL_TYPE_SOURCE, GnlSourcePrivate);
+
+  GST_DEBUG_OBJECT (source, "Setting GstBin async-handling to TRUE");
+  g_object_set (G_OBJECT (source), "async-handling", TRUE, NULL);
+}
+
+static void
+gnl_source_dispose (GObject * object)
+{
+  GnlSource *source = (GnlSource *) object;
+  GnlSourcePrivate *priv = source->priv;
+
+  GST_DEBUG_OBJECT (object, "dispose");
+
+  if (priv->dispose_has_run)
+    return;
+
+  if (source->element) {
+    gst_object_unref (source->element);
+    source->element = NULL;
+  }
+
+  priv->dispose_has_run = TRUE;
+  if (priv->event)
+    gst_event_unref (priv->event);
+
+  if (priv->ghostpad)
+    gnl_object_remove_ghost_pad ((GnlObject *) object, priv->ghostpad);
+  priv->ghostpad = NULL;
+
+  if (priv->staticpad) {
+    gst_object_unref (priv->staticpad);
+    priv->staticpad = NULL;
+  }
+
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+element_pad_added_cb (GstElement * element G_GNUC_UNUSED, GstPad * pad,
+    GnlSource * source)
+{
+  GstCaps *srccaps;
+  GnlSourcePrivate *priv = source->priv;
+
+  GST_DEBUG_OBJECT (source, "pad %s:%s", GST_DEBUG_PAD_NAME (pad));
+
+  if (priv->ghostpad || priv->pendingblock) {
+    GST_WARNING_OBJECT (source,
+        "We already have (pending) ghost-ed a valid source pad (ghostpad:%s:%s, pendingblock:%d",
+        GST_DEBUG_PAD_NAME (priv->ghostpad), priv->pendingblock);
+    return;
+  }
+
+  /* FIXME: pass filter caps to query_caps directly */
+  srccaps = gst_pad_query_caps (pad, NULL);
+  if (!gst_caps_can_intersect (srccaps, GNL_OBJECT (source)->caps)) {
+    gst_caps_unref (srccaps);
+    GST_DEBUG_OBJECT (source, "Pad doesn't have valid caps, ignoring");
+    return;
+  }
+  gst_caps_unref (srccaps);
+
+  GST_DEBUG_OBJECT (pad, "valid pad, about to add event probe and pad block");
+
+  priv->probeid = gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM,
+      (GstPadProbeCallback) pad_blocked_cb, source, NULL);
+  if (priv->probeid == 0)
+    GST_WARNING_OBJECT (source, "Couldn't set Async pad blocking");
+  else {
+    priv->ghostedpad = pad;
+    priv->pendingblock = TRUE;
+  }
+
+  GST_DEBUG_OBJECT (source, "Done handling pad %s:%s",
+      GST_DEBUG_PAD_NAME (pad));
+}
+
+static void
+element_pad_removed_cb (GstElement * element G_GNUC_UNUSED, GstPad * pad,
+    GnlSource * source)
+{
+  GnlSourcePrivate *priv = source->priv;
+
+  GST_DEBUG_OBJECT (source, "pad %s:%s (controlled pad %s:%s)",
+      GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (priv->ghostedpad));
+
+  if (pad == priv->ghostedpad) {
+    GST_DEBUG_OBJECT (source,
+        "The removed pad is the controlled pad, clearing up");
+
+    if (priv->ghostpad) {
+      GST_DEBUG_OBJECT (source, "Clearing up ghostpad");
+
+      priv->areblocked = FALSE;
+      if (priv->probeid) {
+        gst_pad_remove_probe (pad, priv->probeid);
+        priv->probeid = 0;
+      }
+
+      gnl_object_remove_ghost_pad ((GnlObject *) source, priv->ghostpad);
+      priv->ghostpad = NULL;
+    }
+
+    priv->pendingblock = FALSE;
+    priv->ghostedpad = NULL;
+  } else {
+    GST_DEBUG_OBJECT (source, "The removed pad is NOT our controlled pad");
+  }
+}
+
+static gint
+compare_src_pad (GValue * item, GstCaps * caps)
+{
+  gint ret = 1;
+  GstPad *pad = g_value_get_object (item);
+  GstCaps *padcaps;
+
+  GST_DEBUG_OBJECT (pad, "Trying pad for caps %" GST_PTR_FORMAT, caps);
+
+  /* FIXME: can pass the filter caps right away.. */
+  padcaps = gst_pad_query_caps (pad, NULL);
+
+  if (gst_caps_can_intersect (padcaps, caps))
+    ret = 0;
+
+  gst_caps_unref (padcaps);
+
+  return ret;
+}
+
+/*
+  get_valid_src_pad
+
+  Returns True if there's a src pad compatible with the GnlObject caps in the
+  given element. Fills in pad if so. The returned pad has an incremented refcount
+*/
+
+static gboolean
+get_valid_src_pad (GnlSource * source, GstElement * element, GstPad ** pad)
+{
+  gboolean res = FALSE;
+  GstIterator *srcpads;
+  GValue item = { 0, };
+
+  g_return_val_if_fail (pad, FALSE);
+
+  srcpads = gst_element_iterate_src_pads (element);
+  if (gst_iterator_find_custom (srcpads, (GCompareFunc) compare_src_pad, &item,
+          GNL_OBJECT (source)->caps)) {
+    *pad = g_value_get_object (&item);
+    gst_object_ref (*pad);
+    g_value_reset (&item);
+    res = TRUE;
+  }
+  gst_iterator_free (srcpads);
+
+  return res;
+}
+
+static gpointer
+ghost_seek_pad (GnlSource * source)
+{
+  GnlSourcePrivate *priv = source->priv;
+  GstPad *pad = priv->ghostedpad;
+
+  if (priv->ghostpad || !pad)
+    goto beach;
+
+  GST_DEBUG_OBJECT (source, "ghosting %s:%s", GST_DEBUG_PAD_NAME (pad));
+
+  priv->ghostpad = gnl_object_ghost_pad ((GnlObject *) source,
+      GST_PAD_NAME (pad), pad);
+  GST_DEBUG_OBJECT (source, "emitting no more pads");
+  gst_pad_set_active (priv->ghostpad, TRUE);
+
+  if (priv->event) {
+    GST_DEBUG_OBJECT (source, "sending queued seek event");
+    if (!(gst_pad_send_event (priv->ghostpad, priv->event)))
+      GST_ELEMENT_ERROR (source, RESOURCE, SEEK,
+          (NULL), ("Sending initial seek to upstream element failed"));
+    else
+      GST_DEBUG_OBJECT (source, "queued seek sent");
+    priv->event = NULL;
+  }
+
+  GST_DEBUG_OBJECT (source, "about to unblock %s:%s", GST_DEBUG_PAD_NAME (pad));
+  priv->areblocked = FALSE;
+  if (priv->probeid) {
+    gst_pad_remove_probe (pad, priv->probeid);
+    priv->probeid = 0;
+  }
+  gst_element_no_more_pads (GST_ELEMENT (source));
+
+  priv->pendingblock = FALSE;
+
+beach:
+  return NULL;
+}
+
+static GstPadProbeReturn
+pad_blocked_cb (GstPad * pad, GstPadProbeInfo * info, GnlSource * source)
+{
+  GST_DEBUG_OBJECT (pad, "probe callback");
+
+  if (!source->priv->ghostpad && !source->priv->areblocked) {
+    GThread *lthread;
+
+    source->priv->areblocked = TRUE;
+    GST_DEBUG_OBJECT (pad, "starting thread to call ghost_seek_pad");
+    lthread =
+        g_thread_new ("gnlsourceseek", (GThreadFunc) ghost_seek_pad, source);
+    g_thread_unref (lthread);
+  }
+
+  return GST_PAD_PROBE_OK;
+}
+
+
+/*
+ * has_dynamic_pads
+ * Returns TRUE if the element has only dynamic pads.
+ */
+
+static gboolean
+has_dynamic_srcpads (GstElement * element)
+{
+  gboolean ret = TRUE;
+  GList *templates;
+  GstPadTemplate *template;
+
+  templates =
+      gst_element_class_get_pad_template_list (GST_ELEMENT_GET_CLASS (element));
+
+  while (templates) {
+    template = (GstPadTemplate *) templates->data;
+
+    if ((GST_PAD_TEMPLATE_DIRECTION (template) == GST_PAD_SRC)
+        && (GST_PAD_TEMPLATE_PRESENCE (template) == GST_PAD_ALWAYS)) {
+      ret = FALSE;
+      break;
+    }
+
+    templates = templates->next;
+  }
+
+  return ret;
+}
+
+static gboolean
+gnl_source_control_element_func (GnlSource * source, GstElement * element)
+{
+  GnlSourcePrivate *priv = source->priv;
+  GstPad *pad = NULL;
+
+  g_return_val_if_fail (source->element == NULL, FALSE);
+
+  GST_DEBUG_OBJECT (source, "element:%s, source->element:%p",
+      GST_ELEMENT_NAME (element), source->element);
+
+  source->element = element;
+  gst_object_ref (element);
+
+  if (get_valid_src_pad (source, source->element, &pad)) {
+    priv->staticpad = pad;
+    GST_DEBUG_OBJECT (source,
+        "There is a valid source pad, we consider the object as NOT having dynamic pads");
+    priv->dynamicpads = FALSE;
+  } else {
+    priv->dynamicpads = has_dynamic_srcpads (element);
+    GST_DEBUG_OBJECT (source, "No valid source pad yet, dynamicpads:%d",
+        priv->dynamicpads);
+    if (priv->dynamicpads) {
+      /* connect to pad-added/removed signals */
+      priv->padremovedid = g_signal_connect
+          (G_OBJECT (element), "pad-removed",
+          G_CALLBACK (element_pad_removed_cb), source);
+      priv->padaddedid =
+          g_signal_connect (G_OBJECT (element), "pad-added",
+          G_CALLBACK (element_pad_added_cb), source);
+    }
+  }
+
+  return TRUE;
+}
+
+static gboolean
+gnl_source_add_element (GstBin * bin, GstElement * element)
+{
+  GnlSource *source = (GnlSource *) bin;
+  gboolean pret;
+
+  GST_DEBUG_OBJECT (source, "Adding element %s", GST_ELEMENT_NAME (element));
+
+  if (GNL_SOURCE_GET_CLASS (source)->controls_one && source->element) {
+    GST_WARNING_OBJECT (bin, "GnlSource can only handle one element at a time");
+    return FALSE;
+  }
+
+  /* call parent add_element */
+  pret = GST_BIN_CLASS (parent_class)->add_element (bin, element);
+
+  if (pret && GNL_SOURCE_GET_CLASS (source)->controls_one) {
+    gnl_source_control_element_func (source, element);
+  }
+  return pret;
+}
+
+static gboolean
+gnl_source_remove_element (GstBin * bin, GstElement * element)
+{
+  GnlSource *source = (GnlSource *) bin;
+  GnlSourcePrivate *priv = source->priv;
+  gboolean pret;
+
+  GST_DEBUG_OBJECT (source, "Removing element %s", GST_ELEMENT_NAME (element));
+
+  /* try to remove it */
+  pret = GST_BIN_CLASS (parent_class)->remove_element (bin, element);
+
+  if ((!source->element) || (source->element != element)) {
+    return TRUE;
+  }
+
+  if (pret) {
+    /* remove ghostpad */
+    if (priv->ghostpad) {
+      gnl_object_remove_ghost_pad ((GnlObject *) bin, priv->ghostpad);
+      priv->ghostpad = NULL;
+    }
+
+    /* discard events */
+    if (priv->event) {
+      gst_event_unref (priv->event);
+      priv->event = NULL;
+    }
+
+    /* remove signal handlers */
+    if (priv->padremovedid) {
+      g_signal_handler_disconnect (source->element, priv->padremovedid);
+      priv->padremovedid = 0;
+    }
+    if (priv->padaddedid) {
+      g_signal_handler_disconnect (source->element, priv->padaddedid);
+      priv->padaddedid = 0;
+    }
+
+    priv->dynamicpads = FALSE;
+    gst_object_unref (element);
+    source->element = NULL;
+  }
+  return pret;
+}
+
+static gboolean
+gnl_source_send_event (GstElement * element, GstEvent * event)
+{
+  GnlSource *source = (GnlSource *) element;
+  gboolean res = TRUE;
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_SEEK:
+      if (source->priv->ghostpad)
+        res = gst_pad_send_event (source->priv->ghostpad, event);
+      else {
+        if (source->priv->event)
+          gst_event_unref (source->priv->event);
+        source->priv->event = event;
+      }
+      break;
+    default:
+      res = GST_ELEMENT_CLASS (parent_class)->send_event (element, event);
+      break;
+  }
+
+  return res;
+}
+
+static gboolean
+gnl_source_prepare (GnlObject * object)
+{
+  GnlSource *source = GNL_SOURCE (object);
+  GnlSourcePrivate *priv = source->priv;
+  GstElement *parent =
+      (GstElement *) gst_element_get_parent ((GstElement *) object);
+
+  if (!source->element) {
+    GST_WARNING_OBJECT (source,
+        "GnlSource doesn't have an element to control !");
+    return FALSE;
+  }
+
+  GST_LOG_OBJECT (source, "ghostpad:%p, dynamicpads:%d",
+      priv->ghostpad, priv->dynamicpads);
+
+  if (!(priv->ghostpad) && !priv->pendingblock) {
+    GstPad *pad;
+
+    GST_LOG_OBJECT (source, "no ghostpad and no dynamic pads");
+
+    /* Do an async block on valid source pad */
+
+    if (!priv->staticpad
+        && !(get_valid_src_pad (source, source->element, &pad))) {
+      GST_DEBUG_OBJECT (source, "Couldn't find a valid source pad");
+    } else {
+      if (priv->staticpad)
+        pad = gst_object_ref (priv->staticpad);
+      GST_LOG_OBJECT (source, "Trying to async block source pad %s:%s",
+          GST_DEBUG_PAD_NAME (pad));
+      priv->ghostedpad = pad;
+      priv->probeid = gst_pad_add_probe (pad,
+          GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM,
+          (GstPadProbeCallback) pad_blocked_cb, source, NULL);
+      gst_object_unref (pad);
+    }
+  }
+
+  if (!GNL_IS_COMPOSITION (parent)) {
+    /* Figure out if we're in a composition */
+    if (source->priv->event)
+      gst_event_unref (source->priv->event);
+
+    GST_DEBUG_OBJECT (object, "Creating initial seek");
+
+    source->priv->event = gst_event_new_seek (1.0, GST_FORMAT_TIME,
+        GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_FLUSH,
+        GST_SEEK_TYPE_SET, object->start, GST_SEEK_TYPE_SET, object->stop);
+  }
+
+  gst_object_unref (parent);
+
+  return TRUE;
+}
+
+static gboolean
+gnl_source_cleanup (GnlObject * object)
+{
+  GnlSource *source = GNL_SOURCE (object);
+  GnlSourcePrivate *priv = source->priv;
+
+  if (priv->ghostpad) {
+    GstPad *target = gst_ghost_pad_get_target ((GstGhostPad *) priv->ghostpad);
+
+    if (target) {
+      if (priv->probeid) {
+        gst_pad_remove_probe (target, priv->probeid);
+        priv->probeid = 0;
+      }
+      gst_object_unref (target);
+    }
+    gnl_object_remove_ghost_pad ((GnlObject *) source, priv->ghostpad);
+    priv->ghostpad = NULL;
+    priv->ghostedpad = NULL;
+    priv->areblocked = FALSE;
+    priv->pendingblock = FALSE;
+  }
+
+  return TRUE;
+}
diff --git a/gnl/gnlsource.h b/gnl/gnlsource.h
new file mode 100644 (file)
index 0000000..120270a
--- /dev/null
@@ -0,0 +1,68 @@
+/* GStreamer
+ * Copyright (C) 2001 Wim Taymans <wim.taymans@gmail.com>
+ *               2004-2008 Edward Hervey <bilboed@bilboed.com>
+ *
+ * gnlsource.h: Header for base GnlSource
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef __GNL_SOURCE_H__
+#define __GNL_SOURCE_H__
+
+#include <gst/gst.h>
+#include "gnlobject.h"
+
+G_BEGIN_DECLS
+#define GNL_TYPE_SOURCE \
+  (gnl_source_get_type())
+#define GNL_SOURCE(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GNL_TYPE_SOURCE,GnlSource))
+#define GNL_SOURCE_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GNL_TYPE_SOURCE,GnlSourceClass))
+#define GNL_SOURCE_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), GNL_TYPE_SOURCE, GnlSourceClass))
+#define GNL_IS_SOURCE(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GNL_TYPE_SOURCE))
+#define GNL_IS_SOURCE_CLASS(obj) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),GNL_TYPE_SOURCE))
+typedef struct _GnlSourcePrivate GnlSourcePrivate;
+
+struct _GnlSource
+{
+  GnlObject parent;
+
+  /* controlled source element, acces with gst_bin_[add|remove]_element */
+  GstElement *element;
+
+  GnlSourcePrivate *priv;
+};
+
+struct _GnlSourceClass
+{
+  GnlObjectClass parent_class;
+
+  /* controls_one is TRUE if the class only controls one element */
+  gboolean controls_one;
+  /* control_element() takes care of controlling the given element */
+    gboolean (*control_element) (GnlSource * source, GstElement * element);
+};
+
+GType gnl_source_get_type (void);
+
+G_END_DECLS
+#endif /* __GNL_SOURCE_H__ */
diff --git a/gnl/gnltypes.h b/gnl/gnltypes.h
new file mode 100644 (file)
index 0000000..559c7d6
--- /dev/null
@@ -0,0 +1,42 @@
+/* GStreamer
+ * Copyright (C) 2004 Edward Hervey <bilboed@bilboed.com>
+ *
+ * gnltypes.h: Header for class definition
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GNL_TYPES_H__
+#define __GNL_TYPES_H__
+
+#include <glib.h>
+
+typedef struct _GnlObject GnlObject;
+typedef struct _GnlObjectClass GnlObjectClass;
+
+typedef struct _GnlComposition GnlComposition;
+typedef struct _GnlCompositionClass GnlCompositionClass;
+
+typedef struct _GnlOperation GnlOperation;
+typedef struct _GnlOperationClass GnlOperationClass;
+
+typedef struct _GnlSource GnlSource;
+typedef struct _GnlSourceClass GnlSourceClass;
+
+typedef struct _GnlURISource GnlURISource;
+typedef struct _GnlURISourceClass GnlURISourceClass;
+
+#endif
diff --git a/gnl/gnlurisource.c b/gnl/gnlurisource.c
new file mode 100644 (file)
index 0000000..b02a879
--- /dev/null
@@ -0,0 +1,166 @@
+/* Gnonlin
+ * Copyright (C) <2005-2008> Edward Hervey <bilboed@bilboed.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gnl.h"
+#include "gnlurisource.h"
+
+/**
+ * SECTION:element-gnlurisource
+ *
+ * GnlURISource is a #GnlSource which reads and decodes the contents
+ * of a given file. The data in the file is decoded using any available
+ * GStreamer plugins.
+ */
+
+static GstStaticPadTemplate gnl_urisource_src_template =
+GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_SOMETIMES,
+    GST_STATIC_CAPS_ANY);
+
+GST_DEBUG_CATEGORY_STATIC (gnlurisource);
+#define GST_CAT_DEFAULT gnlurisource
+
+#define _do_init \
+  GST_DEBUG_CATEGORY_INIT (gnlurisource, "gnlurisource", GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GNonLin URI Source Element");
+#define  gnl_urisource_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GnlURISource, gnl_urisource, GNL_TYPE_SOURCE,
+    _do_init);
+
+enum
+{
+  ARG_0,
+  ARG_URI,
+};
+
+static gboolean gnl_urisource_prepare (GnlObject * object);
+
+static void
+gnl_urisource_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec);
+
+static void
+gnl_urisource_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec);
+
+static void
+gnl_urisource_class_init (GnlURISourceClass * klass)
+{
+  GObjectClass *gobject_class;
+  GnlObjectClass *gnlobject_class;
+  GstElementClass *gstelement_class;
+
+  gobject_class = (GObjectClass *) klass;
+  gstelement_class = (GstElementClass *) klass;
+  gnlobject_class = (GnlObjectClass *) klass;
+  parent_class = g_type_class_ref (GNL_TYPE_SOURCE);
+
+  gst_element_class_set_static_metadata (gstelement_class, "GNonLin URI Source",
+      "Filter/Editor",
+      "High-level URI Source element", "Edward Hervey <bilboed@bilboed.com>");
+
+  gobject_class->set_property = GST_DEBUG_FUNCPTR (gnl_urisource_set_property);
+  gobject_class->get_property = GST_DEBUG_FUNCPTR (gnl_urisource_get_property);
+
+  g_object_class_install_property (gobject_class, ARG_URI,
+      g_param_spec_string ("uri", "Uri",
+          "Uri of the file to use", NULL, G_PARAM_READWRITE));
+
+  gst_element_class_add_pad_template (gstelement_class,
+      gst_static_pad_template_get (&gnl_urisource_src_template));
+
+  gnlobject_class->prepare = gnl_urisource_prepare;
+}
+
+static void
+gnl_urisource_init (GnlURISource * urisource)
+{
+  GstElement *decodebin = NULL;
+
+  GST_OBJECT_FLAG_SET (urisource, GNL_OBJECT_SOURCE);
+
+  /* We create a bin with source and decodebin within */
+  decodebin =
+      gst_element_factory_make ("uridecodebin", "internal-uridecodebin");
+  g_object_set (decodebin, "expose-all-streams", FALSE, NULL);
+
+  gst_bin_add (GST_BIN (urisource), decodebin);
+}
+
+static inline void
+gnl_urisource_set_uri (GnlURISource * fs, const gchar * uri)
+{
+  g_object_set (GNL_SOURCE (fs)->element, "uri", uri, NULL);
+}
+
+static void
+gnl_urisource_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GnlURISource *fs = (GnlURISource *) object;
+
+  switch (prop_id) {
+    case ARG_URI:
+      gnl_urisource_set_uri (fs, g_value_get_string (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gnl_urisource_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GnlURISource *fs = (GnlURISource *) object;
+
+  switch (prop_id) {
+    case ARG_URI:
+      g_object_get_property ((GObject *) GNL_SOURCE (fs)->element, "uri",
+          value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+
+}
+
+static gboolean
+gnl_urisource_prepare (GnlObject * object)
+{
+  GnlSource *fs = (GnlSource *) object;
+
+  GST_DEBUG ("prepare");
+
+  /* Set the caps on uridecodebin */
+  if (!gst_caps_is_any (object->caps)) {
+    GST_DEBUG_OBJECT (object, "Setting uridecodebin caps to %" GST_PTR_FORMAT,
+        object->caps);
+    g_object_set (fs->element, "caps", object->caps, NULL);
+  }
+
+  return GNL_OBJECT_CLASS (parent_class)->prepare (object);
+}
diff --git a/gnl/gnlurisource.h b/gnl/gnlurisource.h
new file mode 100644 (file)
index 0000000..455b20e
--- /dev/null
@@ -0,0 +1,57 @@
+/* GStreamer
+ * Copyright (C) 2001 Wim Taymans <wim.taymans@gmail.com>
+ *               2004-2008 Edward Hervey <bilboed@bilboed.com>
+ *
+ * gnlurisource.h: Header for GnlURISource
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef __GNL_URI_SOURCE_H__
+#define __GNL_URI_SOURCE_H__
+
+#include <gst/gst.h>
+#include "gnlsource.h"
+
+G_BEGIN_DECLS
+#define GNL_TYPE_URI_SOURCE \
+  (gnl_urisource_get_type())
+#define GNL_URI_SOURCE(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GNL_TYPE_URI_SOURCE,GnlURIsource))
+#define GNL_URI_SOURCE_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GNL_TYPE_URI_SOURCE,GnlURIsourceClass))
+#define GNL_IS_URI_SOURCE(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GNL_TYPE_URI_SOURCE))
+#define GNL_IS_URI_SOURCE_CLASS(obj) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),GNL_TYPE_URI_SOURCE))
+
+struct _GnlURISource
+{
+  GnlSource parent;
+
+  gchar *uri;
+};
+
+struct _GnlURISourceClass
+{
+  GnlSourceClass parent_class;
+};
+
+GType gnl_urisource_get_type (void);
+
+G_END_DECLS
+#endif /* __GNL_URI_SOURCE_H__ */
diff --git a/tests/check/gnl/common.c b/tests/check/gnl/common.c
new file mode 100644 (file)
index 0000000..68c849b
--- /dev/null
@@ -0,0 +1,348 @@
+#include "common.h"
+
+void
+poll_the_bus (GstBus * bus)
+{
+  GstMessage *message;
+  gboolean carry_on = TRUE;
+
+  while (carry_on) {
+    message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
+    if (message) {
+      switch (GST_MESSAGE_TYPE (message)) {
+        case GST_MESSAGE_EOS:
+          /* we should check if we really finished here */
+          GST_DEBUG ("Got an EOS");
+          carry_on = FALSE;
+          break;
+        case GST_MESSAGE_SEGMENT_START:
+        case GST_MESSAGE_SEGMENT_DONE:
+          /* We shouldn't see any segement messages, since we didn't do a segment seek */
+          GST_WARNING ("Saw a Segment start/stop");
+          fail_if (TRUE);
+          break;
+        case GST_MESSAGE_ERROR:
+          fail_error_message (message);
+        default:
+          break;
+      }
+      gst_mini_object_unref (GST_MINI_OBJECT (message));
+    }
+  }
+}
+
+GstElement *
+gst_element_factory_make_or_warn (const gchar * factoryname, const gchar * name)
+{
+  GstElement *element;
+
+  element = gst_element_factory_make (factoryname, name);
+  fail_unless (element != NULL, "Failed to make element %s", factoryname);
+  return element;
+}
+
+void
+composition_pad_added_cb (GstElement * composition, GstPad * pad,
+    CollectStructure * collect)
+{
+  fail_if (!(gst_element_link_pads_full (composition, GST_OBJECT_NAME (pad),
+              collect->sink, "sink", GST_PAD_LINK_CHECK_NOTHING)));
+}
+
+/* return TRUE to discard the Segment */
+static gboolean
+compare_segments (CollectStructure * collect, Segment * segment,
+    GstEvent * event)
+{
+  const GstSegment *orig;
+  guint64 running_stop, running_start, running_duration;
+
+  gst_event_parse_segment (event, &orig);
+
+  GST_DEBUG ("Got Segment rate:%f, format:%s, start:%" GST_TIME_FORMAT
+      ", stop:%" GST_TIME_FORMAT ", time:%" GST_TIME_FORMAT
+      ", base:%" GST_TIME_FORMAT ", offset:%" GST_TIME_FORMAT,
+      orig->rate, gst_format_get_name (orig->format),
+      GST_TIME_ARGS (orig->start), GST_TIME_ARGS (orig->stop),
+      GST_TIME_ARGS (orig->time), GST_TIME_ARGS (orig->base),
+      GST_TIME_ARGS (orig->offset));
+  GST_DEBUG ("[RUNNING] start:%" GST_TIME_FORMAT " [STREAM] start:%"
+      GST_TIME_FORMAT, GST_TIME_ARGS (gst_segment_to_running_time (orig,
+              GST_FORMAT_TIME, orig->start)),
+      GST_TIME_ARGS (gst_segment_to_stream_time (orig, GST_FORMAT_TIME,
+              orig->start)));
+
+  GST_DEBUG ("Expecting rate:%f, format:%s, start:%" GST_TIME_FORMAT
+      ", stop:%" GST_TIME_FORMAT ", position:%" GST_TIME_FORMAT ", base:%"
+      GST_TIME_FORMAT, segment->rate, gst_format_get_name (segment->format),
+      GST_TIME_ARGS (segment->start), GST_TIME_ARGS (segment->stop),
+      GST_TIME_ARGS (segment->position),
+      GST_TIME_ARGS (collect->expected_base));
+
+  running_start =
+      gst_segment_to_running_time (orig, GST_FORMAT_TIME, orig->start);
+  running_stop =
+      gst_segment_to_running_time (orig, GST_FORMAT_TIME, orig->stop);
+  running_duration = running_stop - running_start;
+  fail_if (orig->rate != segment->rate);
+  fail_if (orig->format != segment->format);
+  fail_unless_equals_int64 (orig->time, segment->position);
+  fail_unless_equals_int64 (orig->base, collect->expected_base);
+  fail_unless_equals_uint64 (orig->stop - orig->start,
+      segment->stop - segment->start);
+
+  collect->expected_base += running_duration;
+
+  GST_DEBUG ("Segment was valid, discarding expected Segment");
+
+  return TRUE;
+}
+
+static GstPadProbeReturn
+sinkpad_event_probe (GstPad * sinkpad, GstEvent * event,
+    CollectStructure * collect)
+{
+  Segment *segment;
+
+  GST_DEBUG_OBJECT (sinkpad, "event:%p (%s seqnum:%d) , collect:%p", event,
+      GST_EVENT_TYPE_NAME (event), GST_EVENT_SEQNUM (event), collect);
+
+  if (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT) {
+    fail_if (collect->expected_segments == NULL,
+        "Received unexpected segment on pad: %s:%s",
+        GST_DEBUG_PAD_NAME (sinkpad));
+
+    if (!collect->gotsegment)
+      collect->seen_segments =
+          g_list_append (NULL, GINT_TO_POINTER (GST_EVENT_SEQNUM (event)));
+    else {
+      fail_if (g_list_find (collect->seen_segments,
+              GINT_TO_POINTER (GST_EVENT_SEQNUM (event))),
+          "Got a segment event we already saw before !");
+      collect->seen_segments =
+          g_list_append (collect->seen_segments,
+          GINT_TO_POINTER (GST_EVENT_SEQNUM (event)));
+    }
+
+    segment = (Segment *) collect->expected_segments->data;
+
+    if (compare_segments (collect, segment, event) &&
+        collect->keep_expected_segments == FALSE) {
+      collect->expected_segments =
+          g_list_remove (collect->expected_segments, segment);
+      g_free (segment);
+    }
+
+    collect->gotsegment = TRUE;
+  }
+
+  return GST_PAD_PROBE_OK;
+}
+
+static GstPadProbeReturn
+sinkpad_buffer_probe (GstPad * sinkpad, GstBuffer * buffer,
+    CollectStructure * collect)
+{
+  GST_DEBUG_OBJECT (sinkpad, "buffer:%p (%" GST_TIME_FORMAT ") , collect:%p",
+      buffer, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)), collect);
+  fail_if (!collect->gotsegment,
+      "Received a buffer without a preceding segment");
+  return GST_PAD_PROBE_OK;
+}
+
+GstPadProbeReturn
+sinkpad_probe (GstPad * sinkpad, GstPadProbeInfo * info,
+    CollectStructure * collect)
+{
+  if (info->type & GST_PAD_PROBE_TYPE_BUFFER)
+    return sinkpad_buffer_probe (sinkpad, (GstBuffer *) info->data, collect);
+  if (info->type & GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM)
+    return sinkpad_event_probe (sinkpad, (GstEvent *) info->data, collect);
+  return GST_PAD_PROBE_OK;
+}
+
+static GstElement *
+new_gnl_src (const gchar * name, guint64 start, gint64 duration, gint priority)
+{
+  GstElement *gnlsource = NULL;
+
+  gnlsource = gst_element_factory_make_or_warn ("gnlsource", name);
+  fail_if (gnlsource == NULL);
+
+  g_object_set (G_OBJECT (gnlsource),
+      "start", start,
+      "duration", duration, "inpoint", start, "priority", priority, NULL);
+
+  return gnlsource;
+}
+
+GstElement *
+videotest_gnl_src (const gchar * name, guint64 start, gint64 duration,
+    gint pattern, guint priority)
+{
+  GstElement *gnlsource = NULL;
+  GstElement *videotestsrc = NULL;
+  GstCaps *caps =
+      gst_caps_from_string
+      ("video/x-raw,format=(string)I420,framerate=(fraction)3/2");
+
+  fail_if (caps == NULL);
+
+  videotestsrc = gst_element_factory_make_or_warn ("videotestsrc", NULL);
+  g_object_set (G_OBJECT (videotestsrc), "pattern", pattern, NULL);
+
+  gnlsource = new_gnl_src (name, start, duration, priority);
+  g_object_set (G_OBJECT (gnlsource), "caps", caps, NULL);
+  gst_caps_unref (caps);
+
+  gst_bin_add (GST_BIN (gnlsource), videotestsrc);
+
+  return gnlsource;
+}
+
+GstElement *
+videotest_gnl_src_full (const gchar * name, guint64 start, gint64 duration,
+    guint64 inpoint, gint pattern, guint priority)
+{
+  GstElement *gnls;
+
+  gnls = videotest_gnl_src (name, start, duration, pattern, priority);
+  if (gnls) {
+    g_object_set (G_OBJECT (gnls), "inpoint", inpoint, NULL);
+  }
+
+
+  return gnls;
+}
+
+GstElement *
+videotest_in_bin_gnl_src (const gchar * name, guint64 start, gint64 duration,
+    gint pattern, guint priority)
+{
+  GstElement *gnlsource = NULL;
+  GstElement *videotestsrc = NULL;
+  GstElement *bin = NULL;
+  GstElement *alpha = NULL;
+  GstPad *srcpad = NULL;
+
+  alpha = gst_element_factory_make ("alpha", NULL);
+  if (alpha == NULL)
+    return NULL;
+
+  videotestsrc = gst_element_factory_make_or_warn ("videotestsrc", NULL);
+  g_object_set (G_OBJECT (videotestsrc), "pattern", pattern, NULL);
+  bin = gst_bin_new (NULL);
+
+  gnlsource = new_gnl_src (name, start, duration, priority);
+
+  gst_bin_add (GST_BIN (bin), videotestsrc);
+  gst_bin_add (GST_BIN (bin), alpha);
+
+  gst_element_link_pads_full (videotestsrc, "src", alpha, "sink",
+      GST_PAD_LINK_CHECK_NOTHING);
+
+  gst_bin_add (GST_BIN (gnlsource), bin);
+
+  srcpad = gst_element_get_static_pad (alpha, "src");
+
+  gst_element_add_pad (bin, gst_ghost_pad_new ("src", srcpad));
+
+  gst_object_unref (srcpad);
+
+  return gnlsource;
+}
+
+GstElement *
+audiotest_bin_src (const gchar * name, guint64 start,
+    gint64 duration, guint priority, gboolean intaudio)
+{
+  GstElement *source = NULL;
+  GstElement *identity = NULL;
+  GstElement *audiotestsrc = NULL;
+  GstElement *audioconvert = NULL;
+  GstElement *bin = NULL;
+  GstCaps *caps;
+  GstPad *srcpad = NULL;
+
+  audiotestsrc = gst_element_factory_make_or_warn ("audiotestsrc", NULL);
+  identity = gst_element_factory_make_or_warn ("identity", NULL);
+  bin = gst_bin_new (NULL);
+  source = new_gnl_src (name, start, duration, priority);
+  audioconvert = gst_element_factory_make_or_warn ("audioconvert", NULL);
+
+  if (intaudio)
+    caps = gst_caps_from_string ("audio/x-raw,format=(string)S16LE");
+  else
+    caps = gst_caps_from_string ("audio/x-raw,format=(string)F32LE");
+
+  gst_bin_add_many (GST_BIN (bin), audiotestsrc, audioconvert, identity, NULL);
+  gst_element_link_pads_full (audiotestsrc, "src", audioconvert, "sink",
+      GST_PAD_LINK_CHECK_NOTHING);
+  fail_if ((gst_element_link_filtered (audioconvert, identity, caps)) != TRUE);
+
+  gst_caps_unref (caps);
+
+  gst_bin_add (GST_BIN (source), bin);
+
+  srcpad = gst_element_get_static_pad (identity, "src");
+
+  gst_element_add_pad (bin, gst_ghost_pad_new ("src", srcpad));
+
+  gst_object_unref (srcpad);
+
+  return source;
+}
+
+GstElement *
+new_operation (const gchar * name, const gchar * factory, guint64 start,
+    gint64 duration, guint priority)
+{
+  GstElement *gnloperation = NULL;
+  GstElement *operation = NULL;
+
+  operation = gst_element_factory_make_or_warn (factory, NULL);
+  gnloperation = gst_element_factory_make_or_warn ("gnloperation", name);
+
+  g_object_set (G_OBJECT (gnloperation),
+      "start", start, "duration", duration, "priority", priority, NULL);
+
+  gst_bin_add (GST_BIN (gnloperation), operation);
+
+  return gnloperation;
+}
+
+
+Segment *
+segment_new (gdouble rate, GstFormat format, gint64 start, gint64 stop,
+    gint64 position)
+{
+  Segment *segment;
+
+  segment = g_new0 (Segment, 1);
+
+  segment->rate = rate;
+  segment->format = format;
+  segment->start = start;
+  segment->stop = stop;
+  segment->position = position;
+
+  return segment;
+}
+
+GList *
+copy_segment_list (GList * list)
+{
+  GList *res = NULL;
+
+  while (list) {
+    Segment *pdata = (Segment *) list->data;
+
+    res =
+        g_list_append (res, segment_new (pdata->rate, pdata->format,
+            pdata->start, pdata->stop, pdata->position));
+
+    list = list->next;
+  }
+
+  return res;
+}
diff --git a/tests/check/gnl/common.h b/tests/check/gnl/common.h
new file mode 100644 (file)
index 0000000..b1d6d9e
--- /dev/null
@@ -0,0 +1,73 @@
+
+#include <gst/check/gstcheck.h>
+
+#define fail_error_message(msg)     \
+  G_STMT_START {        \
+    GError *error;        \
+    gst_message_parse_error(msg, &error, NULL);       \
+    fail_unless(FALSE, "Error Message from %s : %s",      \
+    GST_OBJECT_NAME (GST_MESSAGE_SRC(msg)), error->message); \
+    g_error_free (error);           \
+  } G_STMT_END;
+
+#define check_start_stop_duration(object, startval, stopval, durval)  \
+  G_STMT_START { guint64 start, stop;         \
+    gint64 duration;              \
+    GST_DEBUG_OBJECT (object, "Checking for valid start/stop/duration values");         \
+    g_object_get (object, "start", &start, "stop", &stop,   \
+      "duration", &duration, NULL);       \
+    fail_unless_equals_uint64(start, startval);       \
+    fail_unless_equals_uint64(stop, stopval);       \
+    fail_unless_equals_int64(duration, durval);       \
+    GST_DEBUG_OBJECT (object, "start/stop/duration values valid");  \
+  } G_STMT_END;
+
+#define check_state_simple(object, expected_state)      \
+  G_STMT_START {              \
+    GstStateChangeReturn ret;           \
+    GstState state, pending;            \
+    ret = gst_element_get_state(GST_ELEMENT_CAST(object), &state, &pending, 5 * GST_SECOND); \
+    fail_if (ret == GST_STATE_CHANGE_FAILURE);        \
+    fail_unless (state == expected_state, "Element state (%s) is not the expected one (%s)", \
+     gst_element_state_get_name(state), gst_element_state_get_name(expected_state)); \
+  } G_STMT_END;
+
+typedef struct _Segment {
+  gdouble rate;
+  GstFormat format;
+  guint64 start, stop, position;
+} Segment;
+
+typedef struct _CollectStructure {
+  GstElement  *comp;
+  GstElement  *sink;
+  guint64 last_time;
+  gboolean  gotsegment;
+  GList         *seen_segments;
+  GList   *expected_segments;
+  guint64 expected_base;
+
+  gboolean keep_expected_segments;
+} CollectStructure;
+
+void poll_the_bus(GstBus *bus);
+void composition_pad_added_cb (GstElement *composition, GstPad *pad, CollectStructure * collect);
+GstPadProbeReturn sinkpad_probe (GstPad *sinkpad, GstPadProbeInfo * info, CollectStructure * collect);
+GstElement *videotest_gnl_src (const gchar * name, guint64 start, gint64 duration,
+                              gint pattern, guint priority);
+GstElement * videotest_gnl_src_full (const gchar * name, guint64 start, gint64 duration,
+                                    guint64 inpoint,
+                                    gint pattern, guint priority);
+GstElement *
+videotest_in_bin_gnl_src (const gchar * name, guint64 start, gint64 duration, gint pattern, guint priority);
+GstElement *
+audiotest_bin_src (const gchar * name, guint64 start,
+                  gint64 duration, guint priority, gboolean intaudio);
+GstElement *
+new_operation (const gchar * name, const gchar * factory, guint64 start, gint64 duration, guint priority);
+GList *
+copy_segment_list (GList *list);
+GstElement *
+gst_element_factory_make_or_warn (const gchar * factoryname, const gchar * name);
+Segment *
+segment_new (gdouble rate, GstFormat format, gint64 start, gint64 stop, gint64 position);
diff --git a/tests/check/gnl/complex.c b/tests/check/gnl/complex.c
new file mode 100644 (file)
index 0000000..f4b7eb8
--- /dev/null
@@ -0,0 +1,834 @@
+#include "common.h"
+
+static void
+fill_pipeline_and_check (GstElement * comp, GList * segments,
+    gint expected_error_domain)
+{
+  GstElement *pipeline, *sink;
+  CollectStructure *collect;
+  GstBus *bus;
+  GstMessage *message;
+  gboolean carry_on = TRUE;
+  GstPad *sinkpad;
+  GList *listcopy = copy_segment_list (segments);
+
+  pipeline = gst_pipeline_new ("test_pipeline");
+  sink = gst_element_factory_make_or_warn ("fakesink", "sink");
+  fail_if (sink == NULL);
+
+  gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL);
+
+  /* Shared data */
+  collect = g_new0 (CollectStructure, 1);
+  collect->comp = comp;
+  collect->sink = sink;
+
+  /* Expected segments */
+  collect->expected_segments = segments;
+
+  g_signal_connect (G_OBJECT (comp), "pad-added",
+      G_CALLBACK (composition_pad_added_cb), collect);
+
+  sinkpad = gst_element_get_static_pad (sink, "sink");
+  gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM,
+      (GstPadProbeCallback) sinkpad_probe, collect, NULL);
+
+  bus = gst_element_get_bus (GST_ELEMENT (pipeline));
+
+  GST_DEBUG ("Setting pipeline to PLAYING");
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE);
+
+  GST_DEBUG ("Let's poll the bus");
+  while (carry_on) {
+    message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
+    if (message) {
+      switch (GST_MESSAGE_TYPE (message)) {
+        case GST_MESSAGE_EOS:
+          /* we should check if we really finished here */
+          GST_WARNING ("Got an EOS");
+          carry_on = FALSE;
+          break;
+        case GST_MESSAGE_SEGMENT_START:
+        case GST_MESSAGE_SEGMENT_DONE:
+          /* We shouldn't see any segement messages, since we didn't do a segment seek */
+          GST_WARNING ("Saw a Segment start/stop");
+          fail_if (TRUE);
+          break;
+        case GST_MESSAGE_ERROR:
+        {
+          GError *error;
+
+          gst_message_parse_error (message, &error, NULL);
+          if (comp == GST_ELEMENT (GST_MESSAGE_SRC (message)) &&
+              expected_error_domain == error->domain) {
+            GST_DEBUG ("Expected Error Message from %s : %s",
+                GST_OBJECT_NAME (GST_MESSAGE_SRC (message)), error->message);
+
+            carry_on = FALSE;
+          } else
+            fail_error_message (message);
+        }
+          break;
+        default:
+          break;
+      }
+      gst_mini_object_unref (GST_MINI_OBJECT (message));
+    }
+  }
+
+  GST_DEBUG ("Setting pipeline to READY");
+
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
+
+
+  fail_if (collect->expected_segments != NULL);
+
+  GST_DEBUG ("Resetted pipeline to READY");
+
+  collect->expected_segments = listcopy;
+  collect->gotsegment = FALSE;
+  collect->expected_base = 0;
+
+  if (expected_error_domain)
+    goto done;
+
+  GST_DEBUG ("Setting pipeline to PLAYING again");
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE);
+
+  carry_on = TRUE;
+
+  GST_DEBUG ("Let's poll the bus");
+
+  while (carry_on) {
+    message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
+    if (message) {
+      switch (GST_MESSAGE_TYPE (message)) {
+        case GST_MESSAGE_EOS:
+          /* we should check if we really finished here */
+          carry_on = FALSE;
+          break;
+        case GST_MESSAGE_SEGMENT_START:
+        case GST_MESSAGE_SEGMENT_DONE:
+          /* We shouldn't see any segement messages, since we didn't do a segment seek */
+          GST_WARNING ("Saw a Segment start/stop");
+          fail_if (TRUE);
+          break;
+        case GST_MESSAGE_ERROR:
+          fail_error_message (message);
+        default:
+          break;
+      }
+      gst_mini_object_unref (GST_MINI_OBJECT (message));
+    } else {
+      GST_DEBUG ("bus_poll responded, but there wasn't any message...");
+    }
+  }
+
+  fail_if (collect->expected_segments != NULL);
+
+done:
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE);
+
+  gst_object_unref (GST_OBJECT (sinkpad));
+  ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2);
+  gst_object_unref (pipeline);
+  ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2);
+  gst_object_unref (bus);
+
+  g_free (collect);
+}
+
+GST_START_TEST (test_one_space_another)
+{
+  GstElement *comp, *source1, *source2;
+  gboolean ret = FALSE;
+  GList *segments = NULL;
+
+  comp =
+      gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
+  fail_if (comp == NULL);
+
+  /* TOPOLOGY
+   *
+   * 0           1           2           3           4          5 | Priority
+   * ----------------------------------------------------------------------------
+   * [-source1--]            [-source2--]                         | 1
+   * */
+
+  /*
+     Source 1
+     Start : 0s
+     Duration : 1s
+     Priority : 1
+   */
+  source1 = videotest_gnl_src ("source1", 0, 1 * GST_SECOND, 2, 1);
+  fail_if (source1 == NULL);
+  check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND);
+
+  /*
+     Source 2
+     Start : 2s
+     Duration : 1s
+     Priority : 1
+   */
+  source2 = videotest_gnl_src ("source2", 2 * GST_SECOND, 1 * GST_SECOND, 3, 1);
+  fail_if (source2 == NULL);
+  check_start_stop_duration (source2, 2 * GST_SECOND, 3 * GST_SECOND,
+      1 * GST_SECOND);
+
+  /* Add one source */
+
+  gst_bin_add (GST_BIN (comp), source1);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND);
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  /* Second source */
+
+  gst_bin_add (GST_BIN (comp), source2);
+  check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND);
+  ASSERT_OBJECT_REFCOUNT (source2, "source2", 1);
+
+  /* Remove first source */
+
+  gst_object_ref (source1);
+  gst_bin_remove (GST_BIN (comp), source1);
+  check_start_stop_duration (comp, 2 * GST_SECOND, 3 * GST_SECOND,
+      1 * GST_SECOND);
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  /* Re-add first source */
+
+  gst_bin_add (GST_BIN (comp), source1);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND);
+  gst_object_unref (source1);
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  /* Expected segments */
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0));
+
+  fill_pipeline_and_check (comp, segments, GST_STREAM_ERROR);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_one_default_another)
+{
+  gboolean ret = FALSE;
+  GstElement *comp, *source1, *source2, *source3, *defaultsrc;
+  GList *segments = NULL;
+
+  comp =
+      gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
+  fail_if (comp == NULL);
+
+  /* TOPOLOGY
+   *
+   * 0           1           2           3           4          5 | Priority
+   * ----------------------------------------------------------------------------
+   *             [-source1--]            [-source2--][-source3-]  | 1
+   * [--------------------------defaultsource------------------]  | MAXUINT32
+   * */
+
+
+  /*
+     defaultsrc source
+     Start : 0s
+     Duration : 5s
+     Priority : 2
+   */
+
+  defaultsrc =
+      videotest_gnl_src ("defaultsrc", 0, 5 * GST_SECOND, 2, G_MAXUINT32);
+  g_object_set (defaultsrc, "expandable", TRUE, NULL);
+  fail_if (defaultsrc == NULL);
+  check_start_stop_duration (defaultsrc, 0, 5 * GST_SECOND, 5 * GST_SECOND);
+
+  /*
+     Source 1
+     Start : 1s
+     Duration : 1s
+     Priority : 1
+   */
+  source1 = videotest_gnl_src ("source1", 1 * GST_SECOND, 1 * GST_SECOND, 3, 1);
+  fail_if (source1 == NULL);
+  check_start_stop_duration (source1, GST_SECOND, 2 * GST_SECOND,
+      1 * GST_SECOND);
+
+  /*
+     Source 2
+     Start : 3s
+     Duration : 1s
+     Priority : 1
+   */
+  source2 = videotest_gnl_src ("source2", 3 * GST_SECOND, 1 * GST_SECOND, 2, 1);
+  fail_if (source2 == NULL);
+  check_start_stop_duration (source2, 3 * GST_SECOND, 4 * GST_SECOND,
+      1 * GST_SECOND);
+
+  /*
+     Source 3
+     Start : 4s
+     Duration : 1s
+     Priority : 1
+   */
+  source3 = videotest_gnl_src ("source3", 4 * GST_SECOND, 1 * GST_SECOND, 2, 1);
+  fail_if (source3 == NULL);
+  check_start_stop_duration (source3, 4 * GST_SECOND, 5 * GST_SECOND,
+      1 * GST_SECOND);
+
+
+  /* Add one source */
+
+  gst_bin_add (GST_BIN (comp), source1);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  /* defaultsrc source */
+  gst_bin_add (GST_BIN (comp), defaultsrc);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+  check_start_stop_duration (defaultsrc, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (defaultsrc, "defaultsrc", 1);
+
+  /* Second source */
+
+  gst_bin_add (GST_BIN (comp), source2);
+  ASSERT_OBJECT_REFCOUNT (source2, "source2", 1);
+  /* Third source */
+  gst_bin_add (GST_BIN (comp), source3);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  fail_unless (ret);
+  check_start_stop_duration (comp, 0, 5 * GST_SECOND, 5 * GST_SECOND);
+  check_start_stop_duration (defaultsrc, 0, 5 * GST_SECOND, 5 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source3, "source3", 1);
+
+  /* Expected segments */
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0));
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND));
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          2 * GST_SECOND, 3 * GST_SECOND, 2 * GST_SECOND));
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          3 * GST_SECOND, 4 * GST_SECOND, 3 * GST_SECOND));
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          4 * GST_SECOND, 5 * GST_SECOND, 4 * GST_SECOND));
+
+  fill_pipeline_and_check (comp, segments, GST_STREAM_ERROR);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_one_expandable_another)
+{
+  GstElement *comp, *source1, *source2, *source3, *defaultsrc;
+  GList *segments = NULL;
+  gboolean ret = FALSE;
+
+  comp =
+      gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
+  fail_if (comp == NULL);
+
+  /* TOPOLOGY
+   *
+   * 0           1           2           3           4          5 | Priority
+   * ----------------------------------------------------------------------------
+   *             [ source1  ]            [ source2  ][ source3 ]  | 1
+   * [--------------------- defaultsrc ------------------------]  | 1000 EXPANDABLE
+   * */
+
+  /*
+     defaultsrc source
+     Start : 0s
+     Duration : 5s
+     Priority : 1000
+   */
+
+  defaultsrc = videotest_gnl_src ("defaultsrc", 0, 5 * GST_SECOND, 2, 1000);
+  g_object_set (defaultsrc, "expandable", TRUE, NULL);
+  fail_if (defaultsrc == NULL);
+  check_start_stop_duration (defaultsrc, 0, 5 * GST_SECOND, 5 * GST_SECOND);
+
+  /*
+     Source 1
+     Start : 1s
+     Duration : 1s
+     Priority : 1
+   */
+  source1 = videotest_gnl_src ("source1", 1 * GST_SECOND, 1 * GST_SECOND, 3, 1);
+  fail_if (source1 == NULL);
+  check_start_stop_duration (source1, GST_SECOND, 2 * GST_SECOND,
+      1 * GST_SECOND);
+
+  /*
+     Source 2
+     Start : 3s
+     Duration : 1s
+     Priority : 1
+   */
+  source2 = videotest_gnl_src ("source2", 3 * GST_SECOND, 1 * GST_SECOND, 2, 1);
+  fail_if (source2 == NULL);
+  check_start_stop_duration (source2, 3 * GST_SECOND, 4 * GST_SECOND,
+      1 * GST_SECOND);
+
+  /*
+     Source 3
+     Start : 4s
+     Duration : 1s
+     Priority : 1
+   */
+  source3 = videotest_gnl_src ("source3", 4 * GST_SECOND, 1 * GST_SECOND, 2, 1);
+  fail_if (source3 == NULL);
+  check_start_stop_duration (source3, 4 * GST_SECOND, 5 * GST_SECOND,
+      1 * GST_SECOND);
+
+
+  /* Add one source */
+
+  gst_bin_add (GST_BIN (comp), source1);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  /* defaultsrc source */
+
+  gst_bin_add (GST_BIN (comp), defaultsrc);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+  check_start_stop_duration (defaultsrc, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (defaultsrc, "defaultsrc", 1);
+
+  /* Second source */
+
+  gst_bin_add (GST_BIN (comp), source2);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 4 * GST_SECOND, 4 * GST_SECOND);
+  check_start_stop_duration (defaultsrc, 0, 4 * GST_SECOND, 4 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source2, "source2", 1);
+
+
+  /* Third source */
+
+  gst_bin_add (GST_BIN (comp), source3);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 5 * GST_SECOND, 5 * GST_SECOND);
+  check_start_stop_duration (defaultsrc, 0, 5 * GST_SECOND, 5 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source3, "source3", 1);
+
+  /* Expected segments */
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0));
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND));
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          2 * GST_SECOND, 3 * GST_SECOND, 2 * GST_SECOND));
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          3 * GST_SECOND, 4 * GST_SECOND, 3 * GST_SECOND));
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          4 * GST_SECOND, 5 * GST_SECOND, 4 * GST_SECOND));
+
+  fill_pipeline_and_check (comp, segments, 0);
+}
+
+GST_END_TEST;
+
+
+
+GST_START_TEST (test_renegotiation)
+{
+  gboolean ret;
+  GstElement *pipeline;
+  GstElement *comp, *sink, *source1, *source2, *source3;
+  GstElement *audioconvert;
+  CollectStructure *collect;
+  GstBus *bus;
+  GstMessage *message;
+  gboolean carry_on = TRUE;
+  GstPad *sinkpad;
+  GstCaps *caps;
+
+  pipeline = gst_pipeline_new ("test_pipeline");
+  comp =
+      gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
+  fail_if (comp == NULL);
+
+  /*
+     Source 1
+     Start : 1s
+     Duration : 1s
+     Priority : 1
+   */
+  source1 =
+      audiotest_bin_src ("source1", 0 * GST_SECOND, 1 * GST_SECOND, 1, FALSE);
+  check_start_stop_duration (source1, 0 * GST_SECOND, 1 * GST_SECOND,
+      1 * GST_SECOND);
+
+  /*
+     Source 2
+     Start : 1s
+     Duration : 1s
+     Priority : 1
+   */
+  source2 =
+      audiotest_bin_src ("source2", 1 * GST_SECOND, 1 * GST_SECOND, 1, TRUE);
+  check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND,
+      1 * GST_SECOND);
+
+  /*
+     Source 3
+     Start : 2s
+     Duration : 1s
+     Priority : 1
+   */
+  source3 =
+      audiotest_bin_src ("source3", 2 * GST_SECOND, 1 * GST_SECOND, 1, FALSE);
+  check_start_stop_duration (source3, 2 * GST_SECOND, 3 * GST_SECOND,
+      1 * GST_SECOND);
+
+  /* Add one source */
+
+  gst_bin_add (GST_BIN (comp), source1);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  /* Second source */
+
+  gst_bin_add (GST_BIN (comp), source2);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source2, "source2", 1);
+
+
+  /* Third source */
+
+  gst_bin_add (GST_BIN (comp), source3);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source3, "source3", 1);
+
+
+  sink = gst_element_factory_make_or_warn ("fakesink", "sink");
+  audioconvert = gst_element_factory_make_or_warn ("audioconvert", "aconv");
+
+  gst_bin_add_many (GST_BIN (pipeline), comp, audioconvert, sink, NULL);
+  caps = gst_caps_from_string ("audio/x-raw,format=(string)S16LE");
+  gst_element_link_filtered (audioconvert, sink, caps);
+  gst_caps_unref (caps);
+
+  /* Shared data */
+  collect = g_new0 (CollectStructure, 1);
+  collect->comp = comp;
+  collect->sink = audioconvert;
+
+  /* Expected segments */
+  collect->expected_segments = g_list_append (collect->expected_segments,
+      segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0));
+  collect->expected_segments = g_list_append (collect->expected_segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND));
+  collect->expected_segments = g_list_append (collect->expected_segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          2 * GST_SECOND, 3 * GST_SECOND, 2 * GST_SECOND));
+
+  g_signal_connect (G_OBJECT (comp), "pad-added",
+      G_CALLBACK (composition_pad_added_cb), collect);
+
+  sinkpad = gst_element_get_static_pad (sink, "sink");
+  gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM,
+      (GstPadProbeCallback) sinkpad_probe, collect, NULL);
+
+  bus = gst_element_get_bus (GST_ELEMENT (pipeline));
+
+  GST_DEBUG ("Setting pipeline to PLAYING");
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE);
+
+  GST_DEBUG ("Let's poll the bus");
+
+  while (carry_on) {
+    message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
+    if (message) {
+      switch (GST_MESSAGE_TYPE (message)) {
+        case GST_MESSAGE_EOS:
+          /* we should check if we really finished here */
+          GST_WARNING ("Got an EOS");
+          carry_on = FALSE;
+          break;
+        case GST_MESSAGE_SEGMENT_START:
+        case GST_MESSAGE_SEGMENT_DONE:
+          /* We shouldn't see any segement messages, since we didn't do a segment seek */
+          GST_WARNING ("Saw a Segment start/stop");
+          fail_if (TRUE);
+          break;
+        case GST_MESSAGE_ERROR:
+          fail_error_message (message);
+        default:
+          break;
+      }
+      gst_mini_object_unref (GST_MINI_OBJECT (message));
+    }
+  }
+
+  GST_DEBUG ("Setting pipeline to NULL");
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
+
+  fail_if (collect->expected_segments != NULL);
+
+  GST_DEBUG ("Resetted pipeline to READY");
+
+  /* Expected segments */
+  collect->expected_segments = g_list_append (collect->expected_segments,
+      segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0));
+  collect->expected_segments = g_list_append (collect->expected_segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND));
+  collect->expected_segments = g_list_append (collect->expected_segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          2 * GST_SECOND, 3 * GST_SECOND, 2 * GST_SECOND));
+  collect->gotsegment = FALSE;
+  collect->expected_base = 0;
+
+
+  GST_DEBUG ("Setting pipeline to PLAYING again");
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE);
+
+  carry_on = TRUE;
+
+  GST_DEBUG ("Let's poll the bus");
+
+  while (carry_on) {
+    message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
+    if (message) {
+      switch (GST_MESSAGE_TYPE (message)) {
+        case GST_MESSAGE_EOS:
+          /* we should check if we really finished here */
+          carry_on = FALSE;
+          break;
+        case GST_MESSAGE_SEGMENT_START:
+        case GST_MESSAGE_SEGMENT_DONE:
+          /* We shouldn't see any segement messages, since we didn't do a segment seek */
+          GST_WARNING ("Saw a Segment start/stop");
+          fail_if (TRUE);
+          break;
+        case GST_MESSAGE_ERROR:
+          fail_error_message (message);
+        default:
+          break;
+      }
+      gst_mini_object_unref (GST_MINI_OBJECT (message));
+    } else {
+      GST_DEBUG ("bus_poll responded, but there wasn't any message...");
+    }
+  }
+
+  fail_if (collect->expected_segments != NULL);
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE);
+
+  gst_object_unref (GST_OBJECT (sinkpad));
+  ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2);
+  gst_object_unref (pipeline);
+  ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2);
+  gst_object_unref (bus);
+
+  g_free (collect);
+}
+
+GST_END_TEST;
+
+
+GST_START_TEST (test_one_bin_space_another)
+{
+  GstElement *comp, *source1, *source2;
+  gboolean ret = FALSE;
+  GList *segments = NULL;
+
+  comp =
+      gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
+  fail_if (comp == NULL);
+
+  /*
+     Source 1
+     Start : 0s
+     Duration : 1s
+     Priority : 1
+   */
+  source1 = videotest_in_bin_gnl_src ("source1", 0, 1 * GST_SECOND, 3, 1);
+  fail_if (source1 == NULL);
+  check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND);
+
+  /*
+     Source 2
+     Start : 2s
+     Duration : 1s
+     Priority : 1
+   */
+  source2 =
+      videotest_in_bin_gnl_src ("source2", 2 * GST_SECOND, 1 * GST_SECOND, 2,
+      1);
+  fail_if (source2 == NULL);
+  check_start_stop_duration (source2, 2 * GST_SECOND, 3 * GST_SECOND,
+      1 * GST_SECOND);
+
+  /* Add one source */
+
+  gst_bin_add (GST_BIN (comp), source1);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND);
+
+  /* Second source */
+
+  gst_bin_add (GST_BIN (comp), source2);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND);
+
+  /* Remove second source */
+
+  gst_object_ref (source1);
+  gst_bin_remove (GST_BIN (comp), source1);
+  check_start_stop_duration (comp, 2 * GST_SECOND, 3 * GST_SECOND,
+      1 * GST_SECOND);
+
+  /* Re-add second source */
+
+  gst_bin_add (GST_BIN (comp), source1);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND);
+  gst_object_unref (source1);
+
+  /* Expected segments */
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0));
+
+  fill_pipeline_and_check (comp, segments, GST_STREAM_ERROR);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_one_above_another)
+{
+  GstElement *comp, *source1, *source2;
+  gboolean ret = FALSE;
+  GList *segments = NULL;
+
+  comp =
+      gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
+  fail_if (comp == NULL);
+
+  /*
+     Source 1
+     Start : 0s
+     Duration : 2s
+     Priority : 2
+   */
+  source1 = videotest_gnl_src ("source1", 0, 2 * GST_SECOND, 3, 2);
+  fail_if (source1 == NULL);
+  check_start_stop_duration (source1, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+
+  /*
+     Source 2
+     Start : 2s
+     Duration : 2s
+     Priority : 1
+   */
+  source2 = videotest_gnl_src ("source2", 1 * GST_SECOND, 2 * GST_SECOND, 2, 1);
+  fail_if (source2 == NULL);
+  check_start_stop_duration (source2, 1 * GST_SECOND, 3 * GST_SECOND,
+      2 * GST_SECOND);
+
+  /* Add one source */
+
+  gst_bin_add (GST_BIN (comp), source1);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+
+  /* Second source */
+
+  gst_bin_add (GST_BIN (comp), source2);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND);
+
+  /* Remove second source */
+
+  gst_object_ref (source1);
+  gst_bin_remove (GST_BIN (comp), source1);
+  check_start_stop_duration (comp, 1 * GST_SECOND, 3 * GST_SECOND,
+      2 * GST_SECOND);
+
+  /* Re-add second source */
+
+  gst_bin_add (GST_BIN (comp), source1);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND);
+  gst_object_unref (source1);
+
+  /* Expected segments */
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0));
+
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          1 * GST_SECOND, 3 * GST_SECOND, 1 * GST_SECOND));
+
+  fill_pipeline_and_check (comp, segments, 0);
+}
+
+GST_END_TEST;
+
+static Suite *
+gnonlin_suite (void)
+{
+  Suite *s = suite_create ("gnonlin-complex");
+  TCase *tc_chain = tcase_create ("complex");
+
+  suite_add_tcase (s, tc_chain);
+
+  tcase_add_test (tc_chain, test_one_space_another);
+  tcase_add_test (tc_chain, test_one_default_another);
+  tcase_add_test (tc_chain, test_one_expandable_another);
+  tcase_add_test (tc_chain, test_renegotiation);
+  tcase_add_test (tc_chain, test_one_bin_space_another);
+  tcase_add_test (tc_chain, test_one_above_another);
+  return s;
+}
+
+GST_CHECK_MAIN (gnonlin)
diff --git a/tests/check/gnl/gnlcomposition.c b/tests/check/gnl/gnlcomposition.c
new file mode 100644 (file)
index 0000000..1710e2f
--- /dev/null
@@ -0,0 +1,560 @@
+/* Gnonlin
+ * Copyright (C) <2009> Alessandro Decina <alessandro.decina@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+#include "common.h"
+
+typedef struct
+{
+  GstElement *composition;
+  GstElement *source3;
+} TestClosure;
+
+static int composition_pad_added;
+static int composition_pad_removed;
+static int seek_events;
+static gulong blockprobeid = 0;
+static GMutex pad_added_lock;
+static GCond pad_added_cond;
+
+static GstPadProbeReturn
+on_source1_pad_event_cb (GstPad * pad, GstPadProbeInfo * info,
+    gpointer user_data)
+{
+  if (GST_EVENT_TYPE (info->data) == GST_EVENT_SEEK)
+    ++seek_events;
+
+  return GST_PAD_PROBE_OK;
+}
+
+static void
+on_source1_pad_added_cb (GstElement * source, GstPad * pad, gpointer user_data)
+{
+  gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_UPSTREAM,
+      (GstPadProbeCallback) on_source1_pad_event_cb, NULL, NULL);
+}
+
+static void
+on_composition_pad_added_cb (GstElement * composition, GstPad * pad,
+    GstElement * sink)
+{
+  GstPad *s = gst_element_get_static_pad (sink, "sink");
+  gst_pad_link (pad, s);
+  ++composition_pad_added;
+  g_mutex_lock (&pad_added_lock);
+  g_cond_broadcast (&pad_added_cond);
+  g_mutex_unlock (&pad_added_lock);
+  gst_object_unref (s);
+}
+
+static void
+on_composition_pad_removed_cb (GstElement * composition, GstPad * pad,
+    GstElement * sink)
+{
+  ++composition_pad_removed;
+}
+
+GST_START_TEST (test_change_object_start_stop_in_current_stack)
+{
+  GstElement *pipeline;
+  GstElement *comp, *source1, *def, *sink;
+  GstBus *bus;
+  GstMessage *message;
+  gboolean carry_on, ret = FALSE;
+  int seek_events_before;
+
+  pipeline = gst_pipeline_new ("test_pipeline");
+  comp =
+      gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
+
+  sink = gst_element_factory_make_or_warn ("fakesink", "sink");
+  gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL);
+
+  /* connect to pad-added */
+  g_object_connect (comp, "signal::pad-added",
+      on_composition_pad_added_cb, sink, NULL);
+  g_object_connect (comp, "signal::pad-removed",
+      on_composition_pad_removed_cb, NULL, NULL);
+
+  /*
+     source1
+     Start : 0s
+     Duration : 2s
+     Priority : 2
+   */
+
+  source1 = videotest_gnl_src ("source1", 0, 2 * GST_SECOND, 2, 2);
+  g_object_connect (source1, "signal::pad-added",
+      on_source1_pad_added_cb, NULL, NULL);
+
+  /*
+     def (default source)
+     Priority = G_MAXUINT32
+   */
+  def =
+      videotest_gnl_src ("default", 0 * GST_SECOND, 0 * GST_SECOND, 2,
+      G_MAXUINT32);
+  g_object_set (def, "expandable", TRUE, NULL);
+
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+  ASSERT_OBJECT_REFCOUNT (def, "default", 1);
+
+  /* Add source 1 */
+
+  /* keep an extra ref to source1 as we remove it from the bin */
+  gst_object_ref (source1);
+  gst_bin_add (GST_BIN (comp), source1);
+
+  /* Add default */
+  gst_bin_add (GST_BIN (comp), def);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (source1, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+  check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+
+  bus = gst_element_get_bus (GST_ELEMENT (pipeline));
+
+  GST_DEBUG ("Setting pipeline to PLAYING");
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 2);
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE);
+
+  GST_DEBUG ("Let's poll the bus");
+
+  carry_on = TRUE;
+  while (carry_on) {
+    message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
+    if (message) {
+      switch (GST_MESSAGE_TYPE (message)) {
+        case GST_MESSAGE_ASYNC_DONE:
+        {
+          carry_on = FALSE;
+          GST_DEBUG ("Pipeline reached PAUSED, stopping polling");
+          break;
+        }
+        case GST_MESSAGE_EOS:
+        {
+          GST_WARNING ("Saw EOS");
+
+          fail_if (TRUE);
+        }
+        case GST_MESSAGE_ERROR:
+          fail_error_message (message);
+        default:
+          break;
+      }
+      gst_mini_object_unref (GST_MINI_OBJECT (message));
+    }
+  }
+
+  fail_unless_equals_int (composition_pad_added, 1);
+  fail_unless_equals_int (composition_pad_removed, 0);
+
+  seek_events_before = seek_events;
+
+  /* pipeline is paused at this point */
+
+  /* move source1 out of the active segment */
+  g_object_set (source1, "start", (guint64) 4 * GST_SECOND, NULL);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  fail_unless (seek_events > seek_events_before);
+
+  /* remove source1 from the composition, which will become empty and remove the
+   * ghostpad */
+  gst_bin_remove (GST_BIN (comp), source1);
+
+  fail_unless_equals_int (composition_pad_added, 1);
+  fail_unless_equals_int (composition_pad_removed, 1);
+
+  g_object_set (source1, "start", (guint64) 0 * GST_SECOND, NULL);
+  /* add the source again and check that the ghostpad is added again */
+  gst_bin_add (GST_BIN (comp), source1);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+
+  g_mutex_lock (&pad_added_lock);
+  g_cond_wait (&pad_added_cond, &pad_added_lock);
+  fail_unless_equals_int (composition_pad_added, 2);
+  fail_unless_equals_int (composition_pad_removed, 1);
+  g_mutex_unlock (&pad_added_lock);
+
+  seek_events_before = seek_events;
+
+  g_object_set (source1, "duration", (guint64) 1 * GST_SECOND, NULL);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  fail_unless (seek_events > seek_events_before);
+
+  GST_DEBUG ("Setting pipeline to NULL");
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE);
+  gst_element_set_state (source1, GST_STATE_NULL);
+  gst_object_unref (source1);
+
+  GST_DEBUG ("Resetted pipeline to READY");
+
+  ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2);
+  gst_object_unref (pipeline);
+  ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2);
+  gst_object_unref (bus);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_remove_invalid_object)
+{
+  GstBin *composition;
+  GstElement *source1, *source2;
+
+  composition = GST_BIN (gst_element_factory_make ("gnlcomposition",
+          "composition"));
+  source1 = gst_element_factory_make ("gnlsource", "source1");
+  source2 = gst_element_factory_make ("gnlsource", "source2");
+
+  gst_bin_add (composition, source1);
+  fail_if (gst_bin_remove (composition, source2));
+  fail_unless (gst_bin_remove (composition, source1));
+
+  gst_object_unref (composition);
+  gst_object_unref (source2);
+}
+
+GST_END_TEST;
+
+static GstPadProbeReturn
+pad_block (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
+{
+  GstPad *ghost;
+  GstBin *bin;
+
+  bin = GST_BIN (user_data);
+
+  GST_DEBUG_OBJECT (pad, "probe type:0x%x", GST_PAD_PROBE_INFO_TYPE (info));
+
+  ghost = gst_ghost_pad_new ("src", pad);
+  gst_pad_set_active (ghost, TRUE);
+
+  gst_element_add_pad (GST_ELEMENT (bin), ghost);
+
+  return GST_PAD_PROBE_REMOVE;
+}
+
+static void
+no_more_pads_test_cb (GObject * object, TestClosure * c)
+{
+  gboolean ret;
+
+  GST_WARNING ("NO MORE PADS");
+  gst_bin_add (GST_BIN (c->composition), c->source3);
+  g_signal_emit_by_name (c->composition, "commit", TRUE, &ret);
+}
+
+GST_START_TEST (test_no_more_pads_race)
+{
+  gboolean ret;
+  GstElement *source1, *source2, *source3;
+  GstBin *bin;
+  GstElement *videotestsrc1, *videotestsrc2;
+  GstElement *operation;
+  GstElement *composition;
+  GstElement *videomixer, *fakesink;
+  GstElement *pipeline;
+  GstBus *bus;
+  GstMessage *message;
+  GstPad *pad;
+  TestClosure closure;
+
+  /* We create a composition with an operation and three sources. The operation
+   * contains a videomixer instance and the three sources are videotestsrc's.
+   *
+   * One of the sources, source2, contains videotestsrc inside a bin. Initially
+   * the bin doesn't have a source pad. We do this to exercise the dynamic src
+   * pad code path in gnlcomposition. We block on the videotestsrc srcpad and in
+   * the pad block callback we ghost the pad and add the ghost to the parent
+   * bin. This makes gnlsource emit no-more-pads, which is used by
+   * gnlcomposition to link the source2:src pad to videomixer.
+   *
+   * We start with the composition containing operation and source1. We preroll
+   * and then add source2. Source2 will do what described above and emit
+   * no-more-pads. We connect to that no-more-pads and from there we add source3 to
+   * the composition. Adding a new source will make gnlcomposition deactivate
+   * the old stack and activate a new one. The new one contains operation,
+   * source1, source2 and source3. Source2 was active in the old stack as well and
+   * gnlcomposition is *still waiting* for no-more-pads to be emitted on it
+   * (since the no-more-pads emission is now blocked in our test's no-more-pads
+   * callback, calling gst_bin_add). In short, here, we're simulating a race between
+   * no-more-pads and someone modifying the composition.
+   *
+   * Activating the new stack, gnlcomposition calls compare_relink_single_node,
+   * which finds an existing source pad for source2 this time since we have
+   * already blocked and ghosted. It takes another code path that assumes that
+   * source2 doesn't have dynamic pads and *BOOM*.
+   */
+
+  pipeline = GST_ELEMENT (gst_pipeline_new (NULL));
+  bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
+
+  composition = gst_element_factory_make ("gnlcomposition", "composition");
+  fakesink = gst_element_factory_make ("fakesink", NULL);
+  fail_unless (fakesink != NULL);
+  g_object_set (fakesink, "sync", TRUE, NULL);
+
+  /* operation */
+  operation = gst_element_factory_make ("gnloperation", "operation");
+  videomixer = gst_element_factory_make ("videomixer", "videomixer");
+  fail_unless (videomixer != NULL);
+  gst_bin_add (GST_BIN (operation), videomixer);
+  g_object_set (operation, "start", (guint64) 0 * GST_SECOND,
+      "duration", (guint64) 10 * GST_SECOND,
+      "inpoint", (guint64) 0 * GST_SECOND, "priority", 10, NULL);
+  gst_bin_add (GST_BIN (composition), operation);
+
+  /* source 1 */
+  source1 = gst_element_factory_make ("gnlsource", "source1");
+  videotestsrc1 = gst_element_factory_make ("videotestsrc", "videotestsrc1");
+  gst_bin_add (GST_BIN (source1), videotestsrc1);
+  g_object_set (source1, "start", (guint64) 0 * GST_SECOND, "duration",
+      (guint64) 5 * GST_SECOND, "inpoint", (guint64) 0 * GST_SECOND, "priority",
+      20, NULL);
+
+  /* source2 */
+  source2 = gst_element_factory_make ("gnlsource", "source2");
+  bin = GST_BIN (gst_bin_new (NULL));
+  videotestsrc2 = gst_element_factory_make ("videotestsrc", "videotestsrc2");
+  pad = gst_element_get_static_pad (videotestsrc2, "src");
+  blockprobeid =
+      gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM,
+      (GstPadProbeCallback) pad_block, bin, NULL);
+  gst_bin_add (bin, videotestsrc2);
+  gst_bin_add (GST_BIN (source2), GST_ELEMENT (bin));
+  g_object_set (source2, "start", (guint64) 0 * GST_SECOND, "duration",
+      (guint64) 5 * GST_SECOND, "inpoint", (guint64) 0 * GST_SECOND, "priority",
+      20, NULL);
+
+  /* source3 */
+  source3 = gst_element_factory_make ("gnlsource", "source3");
+  videotestsrc2 = gst_element_factory_make ("videotestsrc", "videotestsrc3");
+  gst_bin_add (GST_BIN (source3), videotestsrc2);
+  g_object_set (source3, "start", (guint64) 0 * GST_SECOND, "duration",
+      (guint64) 5 * GST_SECOND, "inpoint", (guint64) 0 * GST_SECOND, "priority",
+      20, NULL);
+
+  closure.composition = composition;
+  closure.source3 = source3;
+  g_object_connect (source2, "signal::no-more-pads",
+      no_more_pads_test_cb, &closure, NULL);
+
+  gst_bin_add (GST_BIN (composition), source1);
+  g_signal_emit_by_name (composition, "commit", TRUE, &ret);
+  g_object_connect (composition, "signal::pad-added",
+      on_composition_pad_added_cb, fakesink, NULL);
+  g_object_connect (composition, "signal::pad-removed",
+      on_composition_pad_removed_cb, NULL, NULL);
+
+  GST_DEBUG ("Adding composition to pipeline");
+
+  gst_bin_add_many (GST_BIN (pipeline), composition, fakesink, NULL);
+
+  GST_DEBUG ("Setting pipeline to PAUSED");
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PAUSED)
+      == GST_STATE_CHANGE_FAILURE);
+
+  message = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
+      GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_ERROR);
+  if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR) {
+    fail_error_message (message);
+  }
+  gst_message_unref (message);
+
+  GST_DEBUG ("Adding second source");
+
+  /* FIXME: maybe slow down the videotestsrc steaming thread */
+  gst_bin_add (GST_BIN (composition), source2);
+  g_signal_emit_by_name (composition, "commit", TRUE, &ret);
+
+  message =
+      gst_bus_timed_pop_filtered (bus, GST_SECOND / 10, GST_MESSAGE_ERROR);
+  if (message) {
+    if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR) {
+      fail_error_message (message);
+    } else {
+      fail_if (TRUE);
+    }
+
+    gst_message_unref (message);
+  }
+
+  gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL);
+  gst_object_unref (pipeline);
+  gst_object_unref (bus);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_simple_adder)
+{
+  GstBus *bus;
+  GstMessage *message;
+  GstElement *pipeline;
+  GstElement *gnl_adder;
+  GstElement *composition;
+  GstElement *adder, *fakesink;
+  GstClockTime start_playing_time;
+  GstElement *gnlsource1, *gnlsource2;
+  GstElement *audiotestsrc1, *audiotestsrc2;
+
+  gboolean carry_on = TRUE, ret;
+  GstClockTime total_time = 10 * GST_SECOND;
+
+  pipeline = GST_ELEMENT (gst_pipeline_new (NULL));
+  bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
+
+  composition = gst_element_factory_make ("gnlcomposition", "composition");
+  fakesink = gst_element_factory_make ("fakesink", NULL);
+  g_object_set (fakesink, "sync", TRUE, NULL);
+
+  /* gnl_adder */
+  gnl_adder = gst_element_factory_make ("gnloperation", "gnl_adder");
+  adder = gst_element_factory_make ("adder", "adder");
+  fail_unless (adder != NULL);
+  gst_bin_add (GST_BIN (gnl_adder), adder);
+  g_object_set (gnl_adder, "start", (guint64) 0 * GST_SECOND,
+      "duration", total_time, "inpoint", (guint64) 0 * GST_SECOND,
+      "priority", 0, NULL);
+  gst_bin_add (GST_BIN (composition), gnl_adder);
+
+  /* source 1 */
+  gnlsource1 = gst_element_factory_make ("gnlsource", "gnlsource1");
+  audiotestsrc1 = gst_element_factory_make ("audiotestsrc", "audiotestsrc1");
+  gst_bin_add (GST_BIN (gnlsource1), audiotestsrc1);
+  g_object_set (gnlsource1, "start", (guint64) 0 * GST_SECOND,
+      "duration", total_time / 2, "inpoint", (guint64) 0, "priority", 1, NULL);
+  fail_unless (gst_bin_add (GST_BIN (composition), gnlsource1));
+
+  /* gnlsource2 */
+  gnlsource2 = gst_element_factory_make ("gnlsource", "gnlsource2");
+  audiotestsrc2 = gst_element_factory_make ("audiotestsrc", "audiotestsrc2");
+  gst_bin_add (GST_BIN (gnlsource2), GST_ELEMENT (audiotestsrc2));
+  g_object_set (gnlsource2, "start", (guint64) 0 * GST_SECOND,
+      "duration", total_time, "inpoint", (guint64) 0 * GST_SECOND, "priority",
+      2, NULL);
+  fail_unless (gst_bin_add (GST_BIN (composition), gnlsource2));
+
+  /* Connecting signals */
+  g_object_connect (composition, "signal::pad-added",
+      on_composition_pad_added_cb, fakesink, NULL);
+  g_object_connect (composition, "signal::pad-removed",
+      on_composition_pad_removed_cb, NULL, NULL);
+
+
+  GST_DEBUG ("Adding composition to pipeline");
+
+  gst_bin_add_many (GST_BIN (pipeline), composition, fakesink, NULL);
+
+  GST_DEBUG ("Setting pipeline to PAUSED");
+
+  g_signal_emit_by_name (composition, "commit", TRUE, &ret);
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING)
+      == GST_STATE_CHANGE_FAILURE);
+
+  message = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
+      GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_ERROR);
+
+  if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR)
+    fail_error_message (message);
+
+  GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
+      GST_DEBUG_GRAPH_SHOW_ALL, "gnl-simple-adder-test-play");
+
+  /* Now play the 10 second composition */
+  start_playing_time = gst_util_get_timestamp ();
+  while (carry_on) {
+
+    if (GST_CLOCK_DIFF (start_playing_time, gst_util_get_timestamp ()) >
+        total_time + GST_SECOND) {
+      GST_ERROR ("No EOS found after %" GST_TIME_FORMAT " sec",
+          GST_TIME_ARGS ((total_time / GST_SECOND) + 1));
+      GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
+          GST_DEBUG_GRAPH_SHOW_ALL, "gnl-simple-adder-test-fail");
+
+      fail_unless ("No EOS received" == NULL);
+
+      break;
+    }
+
+    message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
+    GST_LOG ("poll: %" GST_PTR_FORMAT, message);
+    if (message) {
+      switch (GST_MESSAGE_TYPE (message)) {
+        case GST_MESSAGE_EOS:
+          /* we should check if we really finished here */
+          GST_WARNING ("Got an EOS");
+          carry_on = FALSE;
+          break;
+        case GST_MESSAGE_SEGMENT_START:
+        case GST_MESSAGE_SEGMENT_DONE:
+          /* We shouldn't see any segement messages, since we didn't do a segment seek */
+          GST_WARNING ("Saw a Segment start/stop");
+          fail_if (TRUE);
+          carry_on = FALSE;
+          break;
+        case GST_MESSAGE_ERROR:
+          fail_error_message (message);
+        default:
+          break;
+      }
+      gst_mini_object_unref (GST_MINI_OBJECT (message));
+    }
+  }
+
+  gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL);
+  gst_object_unref (pipeline);
+  gst_object_unref (bus);
+}
+
+GST_END_TEST;
+
+static Suite *
+gnonlin_suite (void)
+{
+  Suite *s = suite_create ("gnlcomposition");
+  TCase *tc_chain = tcase_create ("gnlcomposition");
+
+  suite_add_tcase (s, tc_chain);
+
+  g_cond_init (&pad_added_cond);
+  g_mutex_init (&pad_added_lock);
+  tcase_add_test (tc_chain, test_change_object_start_stop_in_current_stack);
+  tcase_add_test (tc_chain, test_remove_invalid_object);
+  if (gst_registry_check_feature_version (gst_registry_get (), "videomixer", 0,
+          11, 0)) {
+    tcase_add_test (tc_chain, test_no_more_pads_race);
+  } else {
+    GST_WARNING ("videomixer element not available, skipping 1 test");
+  }
+
+  if (gst_registry_check_feature_version (gst_registry_get (), "adder", 1,
+          0, 0)) {
+    tcase_add_test (tc_chain, test_simple_adder);
+  } else {
+    GST_WARNING ("adder element not available, skipping 1 test");
+  }
+
+  return s;
+}
+
+GST_CHECK_MAIN (gnonlin)
diff --git a/tests/check/gnl/gnloperation.c b/tests/check/gnl/gnloperation.c
new file mode 100644 (file)
index 0000000..d6f7a78
--- /dev/null
@@ -0,0 +1,698 @@
+#include "common.h"
+
+static void
+fill_pipeline_and_check (GstElement * comp, GList * segments)
+{
+  GstElement *pipeline, *sink;
+  CollectStructure *collect;
+  GstBus *bus;
+  GstMessage *message;
+  gboolean carry_on = TRUE;
+  GstPad *sinkpad;
+  GList *listcopy = copy_segment_list (segments);
+
+  pipeline = gst_pipeline_new ("test_pipeline");
+  sink = gst_element_factory_make_or_warn ("fakesink", "sink");
+  fail_if (sink == NULL);
+
+  gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL);
+
+  /* Shared data */
+  collect = g_new0 (CollectStructure, 1);
+  collect->comp = comp;
+  collect->sink = sink;
+
+  /* Expected segments */
+  collect->expected_segments = segments;
+
+  g_signal_connect (G_OBJECT (comp), "pad-added",
+      G_CALLBACK (composition_pad_added_cb), collect);
+
+  sinkpad = gst_element_get_static_pad (sink, "sink");
+  gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM,
+      (GstPadProbeCallback) sinkpad_probe, collect, NULL);
+
+  bus = gst_element_get_bus (GST_ELEMENT (pipeline));
+
+  GST_DEBUG ("Setting pipeline to PLAYING");
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE);
+
+  GST_DEBUG ("Let's poll the bus");
+  while (carry_on) {
+    message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
+    if (message) {
+      switch (GST_MESSAGE_TYPE (message)) {
+        case GST_MESSAGE_EOS:
+          /* we should check if we really finished here */
+          GST_WARNING ("Got an EOS");
+          carry_on = FALSE;
+          break;
+        case GST_MESSAGE_SEGMENT_START:
+        case GST_MESSAGE_SEGMENT_DONE:
+          /* We shouldn't see any segement messages, since we didn't do a segment seek */
+          GST_WARNING ("Saw a Segment start/stop");
+          fail_if (TRUE);
+          break;
+        case GST_MESSAGE_ERROR:
+          fail_error_message (message);
+        default:
+          break;
+      }
+      gst_mini_object_unref (GST_MINI_OBJECT (message));
+    }
+  }
+
+  GST_DEBUG ("Setting pipeline to READY");
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
+
+  fail_if (collect->expected_segments != NULL);
+
+  GST_DEBUG ("Resetted pipeline to READY");
+
+  collect->expected_base = 0;
+  collect->expected_segments = listcopy;
+  collect->gotsegment = FALSE;
+
+  GST_DEBUG ("Setting pipeline to PLAYING again");
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE);
+
+  carry_on = TRUE;
+
+  GST_DEBUG ("Let's poll the bus");
+
+  while (carry_on) {
+    message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
+    if (message) {
+      switch (GST_MESSAGE_TYPE (message)) {
+        case GST_MESSAGE_EOS:
+          /* we should check if we really finished here */
+          carry_on = FALSE;
+          break;
+        case GST_MESSAGE_SEGMENT_START:
+        case GST_MESSAGE_SEGMENT_DONE:
+          /* We shouldn't see any segement messages, since we didn't do a segment seek */
+          GST_WARNING ("Saw a Segment start/stop");
+          fail_if (TRUE);
+          break;
+        case GST_MESSAGE_ERROR:
+          fail_error_message (message);
+        default:
+          break;
+      }
+      gst_mini_object_unref (GST_MINI_OBJECT (message));
+    } else {
+      GST_DEBUG ("bus_poll responded, but there wasn't any message...");
+    }
+  }
+
+  fail_if (collect->expected_segments != NULL);
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE);
+
+  gst_object_unref (GST_OBJECT (sinkpad));
+  ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2);
+  gst_object_unref (pipeline);
+  ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2);
+  gst_object_unref (bus);
+
+  g_free (collect);
+}
+
+GST_START_TEST (test_simple_operation)
+{
+  gboolean ret = FALSE;
+  GstElement *comp, *oper, *source;
+  GList *segments = NULL;
+
+  comp =
+      gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
+
+  /* TOPOLOGY
+   *
+   * 0           1           2           3           4          5 | Priority
+   * ----------------------------------------------------------------------------
+   *             [-- oper --]                                     | 0
+   * [------------- source -------------]                         | 1
+   * */
+
+  /*
+     source
+     Start : 0s
+     Duration : 3s
+     Priority : 1
+   */
+
+  source = videotest_gnl_src ("source", 0, 3 * GST_SECOND, 2, 1);
+  fail_if (source == NULL);
+
+  /*
+     operation
+     Start : 1s
+     Duration : 1s
+     Priority : 0
+   */
+
+  oper = new_operation ("oper", "identity", 1 * GST_SECOND, 1 * GST_SECOND, 0);
+  fail_if (oper == NULL);
+
+  /* Add source */
+  ASSERT_OBJECT_REFCOUNT (source, "source", 1);
+  ASSERT_OBJECT_REFCOUNT (oper, "oper", 1);
+
+  gst_bin_add (GST_BIN (comp), source);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source, "source", 1);
+
+  /* Add operaton */
+
+  gst_bin_add (GST_BIN (comp), oper);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (oper, "oper", 1);
+
+  /* remove source */
+
+  gst_object_ref (source);
+  gst_bin_remove (GST_BIN (comp), source);
+  check_start_stop_duration (comp, 1 * GST_SECOND, 2 * GST_SECOND,
+      1 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source, "source", 1);
+
+  /* re-add source */
+  gst_bin_add (GST_BIN (comp), source);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND);
+  gst_object_unref (source);
+
+  ASSERT_OBJECT_REFCOUNT (source, "source", 1);
+
+  /* Expected segments */
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0));
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND));
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          2 * GST_SECOND, 3 * GST_SECOND, 2 * GST_SECOND));
+
+  fill_pipeline_and_check (comp, segments);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_pyramid_operations)
+{
+  GstElement *comp, *oper1, *oper2, *source;
+  gboolean ret = FALSE;
+  GList *segments = NULL;
+
+  comp =
+      gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
+
+  /*
+     source
+     Start : 0s
+     Duration : 10s
+     Priority : 2
+   */
+
+  source = videotest_gnl_src ("source", 0, 10 * GST_SECOND, 2, 2);
+
+  /*
+     operation1
+     Start : 4s
+     Duration : 2s
+     Priority : 1
+   */
+
+  oper1 =
+      new_operation ("oper1", "identity", 4 * GST_SECOND, 2 * GST_SECOND, 1);
+
+  /*
+     operation2
+     Start : 2s
+     Duration : 6s
+     Priority : 0
+   */
+
+  oper2 =
+      new_operation ("oper2", "identity", 2 * GST_SECOND, 6 * GST_SECOND, 0);
+
+  /* Add source */
+  ASSERT_OBJECT_REFCOUNT (source, "source", 1);
+  ASSERT_OBJECT_REFCOUNT (oper1, "oper1", 1);
+  ASSERT_OBJECT_REFCOUNT (oper2, "oper2", 1);
+
+  gst_bin_add (GST_BIN (comp), source);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (source, 0, 10 * GST_SECOND, 10 * GST_SECOND);
+  check_start_stop_duration (comp, 0, 10 * GST_SECOND, 10 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source, "source", 1);
+
+  /* Add operation 1 */
+
+  gst_bin_add (GST_BIN (comp), oper1);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (oper1, 4 * GST_SECOND, 6 * GST_SECOND,
+      2 * GST_SECOND);
+  check_start_stop_duration (comp, 0, 10 * GST_SECOND, 10 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (oper1, "oper1", 1);
+
+  /* Add operation 2 */
+
+  gst_bin_add (GST_BIN (comp), oper2);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (oper2, 2 * GST_SECOND, 8 * GST_SECOND,
+      6 * GST_SECOND);
+  check_start_stop_duration (comp, 0, 10 * GST_SECOND, 10 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (oper1, "oper2", 1);
+
+  /* Expected segments */
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME, 0, 2 * GST_SECOND, 0));
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          2 * GST_SECOND, 4 * GST_SECOND, 2 * GST_SECOND));
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          4 * GST_SECOND, 6 * GST_SECOND, 4 * GST_SECOND));
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          6 * GST_SECOND, 8 * GST_SECOND, 6 * GST_SECOND));
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          8 * GST_SECOND, 10 * GST_SECOND, 8 * GST_SECOND));
+
+  fill_pipeline_and_check (comp, segments);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_pyramid_operations2)
+{
+  gboolean ret;
+  GstElement *comp, *oper, *source1, *source2, *def;
+  GList *segments = NULL;
+
+  comp =
+      gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
+
+  /*
+     source1
+     Start : 0s
+     Duration : 2s
+     Priority : 2
+   */
+
+  source1 = videotest_gnl_src ("source1", 0, 2 * GST_SECOND, 2, 2);
+
+  /*
+     operation
+     Start : 1s
+     Duration : 4s
+     Priority : 1
+   */
+
+  oper = new_operation ("oper", "identity", 1 * GST_SECOND, 4 * GST_SECOND, 1);
+
+  /*
+     source2
+     Start : 4s
+     Duration : 2s
+     Priority : 2
+   */
+
+  source2 = videotest_gnl_src ("source2", 4 * GST_SECOND, 2 * GST_SECOND, 2, 2);
+
+  /*
+     def (default source)
+     Priority = G_MAXUINT32
+   */
+  def =
+      videotest_gnl_src ("default", 0 * GST_SECOND, 0 * GST_SECOND, 2,
+      G_MAXUINT32);
+  g_object_set (def, "expandable", TRUE, NULL);
+
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+  ASSERT_OBJECT_REFCOUNT (source2, "source2", 1);
+  ASSERT_OBJECT_REFCOUNT (oper, "oper", 1);
+  ASSERT_OBJECT_REFCOUNT (def, "default", 1);
+
+  /* Add source 1 */
+
+  gst_bin_add (GST_BIN (comp), source1);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+
+  /* Add source 2 */
+
+  gst_bin_add (GST_BIN (comp), source2);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND);
+
+  /* Add operation */
+
+  gst_bin_add (GST_BIN (comp), oper);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND);
+
+  /* Add default */
+
+  gst_bin_add (GST_BIN (comp), def);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND);
+
+
+  /* Expected segments */
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0));
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND));
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          2 * GST_SECOND, 4 * GST_SECOND, 2 * GST_SECOND));
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          4 * GST_SECOND, 5 * GST_SECOND, 4 * GST_SECOND));
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          5 * GST_SECOND, 6 * GST_SECOND, 5 * GST_SECOND));
+
+  fill_pipeline_and_check (comp, segments);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_pyramid_operations_expandable)
+{
+  GstElement *comp, *oper, *source1, *source2, *def;
+  gboolean ret = FALSE;
+  GList *segments = NULL;
+
+  comp =
+      gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
+
+  /*
+     source1
+     Start : 0s
+     Duration : 2s
+     Priority : 2
+   */
+
+  source1 = videotest_gnl_src ("source1", 0, 2 * GST_SECOND, 2, 2);
+
+  /*
+     operation (expandable)
+     Start : XX
+     Duration : XX
+     Priority : 1
+   */
+
+  oper = new_operation ("oper", "identity", 1 * GST_SECOND, 4 * GST_SECOND, 1);
+  g_object_set (oper, "expandable", TRUE, NULL);
+
+  /*
+     source2
+     Start : 4s
+     Duration : 2s
+     Priority : 2
+   */
+
+  source2 = videotest_gnl_src ("source2", 4 * GST_SECOND, 2 * GST_SECOND, 2, 2);
+
+  /*
+     def (default source)
+     Priority = G_MAXUINT32
+   */
+  def =
+      videotest_gnl_src ("default", 0 * GST_SECOND, 0 * GST_SECOND, 2,
+      G_MAXUINT32);
+  g_object_set (def, "expandable", TRUE, NULL);
+
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+  ASSERT_OBJECT_REFCOUNT (source2, "source2", 1);
+  ASSERT_OBJECT_REFCOUNT (oper, "oper", 1);
+  ASSERT_OBJECT_REFCOUNT (def, "default", 1);
+
+  /* Add source 1 */
+  gst_bin_add (GST_BIN (comp), source1);
+  /* Add source 2 */
+  gst_bin_add (GST_BIN (comp), source2);
+  /* Add operation */
+  gst_bin_add (GST_BIN (comp), oper);
+  /* Add default */
+  gst_bin_add (GST_BIN (comp), def);
+
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (source1, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+  check_start_stop_duration (oper, 0 * GST_SECOND, 6 * GST_SECOND,
+      6 * GST_SECOND);
+  check_start_stop_duration (source2, 4 * GST_SECOND, 6 * GST_SECOND,
+      2 * GST_SECOND);
+  check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND);
+
+  /* Expected segments */
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME, 0, 2 * GST_SECOND, 0));
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          2 * GST_SECOND, 4 * GST_SECOND, 2 * GST_SECOND));
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          4 * GST_SECOND, 6 * GST_SECOND, 4 * GST_SECOND));
+
+  fill_pipeline_and_check (comp, segments);
+}
+
+GST_END_TEST;
+
+
+GST_START_TEST (test_complex_operations)
+{
+  GstElement *comp, *oper, *source1, *source2;
+  gboolean ret = FALSE;
+  GList *segments = NULL;
+
+  comp =
+      gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
+
+  /* TOPOLOGY
+   *
+   * 0           1           2           3           4     5    6 | Priority
+   * ----------------------------------------------------------------------------
+   *                         [    -oper-            ]             | 1
+   *                         [    -source2-                   -]  | 2
+   * [                    -source1-                -]             | 3
+   * */
+
+  /*
+     source1
+     Start : 0s
+     Duration : 4s
+     Priority : 3
+   */
+
+  source1 = videotest_in_bin_gnl_src ("source1", 0, 4 * GST_SECOND, 2, 3);
+  fail_if (source1 == NULL);
+
+  /*
+     source2
+     Start : 2s
+     Duration : 4s
+     Priority : 2
+   */
+
+  source2 =
+      videotest_in_bin_gnl_src ("source2", 2 * GST_SECOND, 4 * GST_SECOND, 2,
+      2);
+  fail_if (source2 == NULL);
+
+  /*
+     operation
+     Start : 2s
+     Duration : 2s
+     Priority : 1
+   */
+
+  oper =
+      new_operation ("oper", "videomixer", 2 * GST_SECOND, 2 * GST_SECOND, 1);
+  fail_if (oper == NULL);
+
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+  ASSERT_OBJECT_REFCOUNT (source2, "source2", 1);
+  ASSERT_OBJECT_REFCOUNT (oper, "oper", 1);
+
+  /* Add source1 */
+  gst_bin_add (GST_BIN (comp), source1);
+  check_start_stop_duration (comp, 0, 0, 0);
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  /* Add source2 */
+  gst_bin_add (GST_BIN (comp), source2);
+  check_start_stop_duration (comp, 0, 0, 0);
+  ASSERT_OBJECT_REFCOUNT (source2, "source2", 1);
+
+  /* Add operaton */
+  gst_bin_add (GST_BIN (comp), oper);
+  check_start_stop_duration (comp, 0, 0, 0);
+
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (oper, "oper", 1);
+
+  /* Expected segments */
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME, 0, 2 * GST_SECOND, 0));
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          0 * GST_SECOND, 2 * GST_SECOND, 2 * GST_SECOND));
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          4 * GST_SECOND, 6 * GST_SECOND, 4 * GST_SECOND));
+
+  fill_pipeline_and_check (comp, segments);
+}
+
+GST_END_TEST;
+
+
+GST_START_TEST (test_complex_operations_bis)
+{
+  GstElement *comp, *oper, *source1, *source2;
+  gboolean ret;
+  GList *segments = NULL;
+
+  comp =
+      gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
+
+  /* TOPOLOGY
+   *
+   * 0           1           2           3           4     ..   6 | Priority
+   * ----------------------------------------------------------------------------
+   * [ ......................[------ oper ----------]..........]  | 1 EXPANDABLE
+   * [--------------------- source1 ----------------]             | 2
+   *                         [------------ source2       ------]  | 3
+   * */
+
+
+  /*
+     source1
+     Start : 0s
+     Duration : 4s
+     Priority : 2
+   */
+
+  source1 = videotest_in_bin_gnl_src ("source1", 0, 4 * GST_SECOND, 3, 2);
+  fail_if (source1 == NULL);
+
+  /*
+     source2
+     Start : 2s
+     Duration : 4s
+     Priority : 3
+   */
+
+  source2 =
+      videotest_in_bin_gnl_src ("source2", 2 * GST_SECOND, 4 * GST_SECOND, 2,
+      3);
+  fail_if (source2 == NULL);
+
+  /*
+     operation
+     Start : 2s
+     Duration : 2s
+     Priority : 1
+     EXPANDABLE
+   */
+
+  oper =
+      new_operation ("oper", "videomixer", 2 * GST_SECOND, 2 * GST_SECOND, 1);
+  fail_if (oper == NULL);
+  g_object_set (oper, "expandable", TRUE, NULL);
+
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+  ASSERT_OBJECT_REFCOUNT (source2, "source2", 1);
+  ASSERT_OBJECT_REFCOUNT (oper, "oper", 1);
+
+  /* Add source1 */
+  gst_bin_add (GST_BIN (comp), source1);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 4 * GST_SECOND, 4 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  /* Add source2 */
+  gst_bin_add (GST_BIN (comp), source2);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source2, "source2", 1);
+
+  /* Add operaton */
+
+  gst_bin_add (GST_BIN (comp), oper);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND);
+  /* Since it's expandable, it should have changed to full length */
+  check_start_stop_duration (oper, 0 * GST_SECOND, 6 * GST_SECOND,
+      6 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (oper, "oper", 1);
+
+  /* Expected segments */
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME, 0, 2 * GST_SECOND, 0));
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          0 * GST_SECOND, 2 * GST_SECOND, 2 * GST_SECOND));
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          0 * GST_SECOND, 2 * GST_SECOND, 4 * GST_SECOND));
+
+  fill_pipeline_and_check (comp, segments);
+}
+
+GST_END_TEST;
+
+
+
+static Suite *
+gnonlin_suite (void)
+{
+  Suite *s = suite_create ("gnloperation");
+  TCase *tc_chain = tcase_create ("gnloperation");
+
+  suite_add_tcase (s, tc_chain);
+
+  tcase_add_test (tc_chain, test_simple_operation);
+  tcase_add_test (tc_chain, test_pyramid_operations);
+  tcase_add_test (tc_chain, test_pyramid_operations2);
+  tcase_add_test (tc_chain, test_pyramid_operations_expandable);
+  if (gst_registry_check_feature_version (gst_registry_get (), "videomixer", 0,
+          11, 0)) {
+    tcase_add_test (tc_chain, test_complex_operations);
+    tcase_add_test (tc_chain, test_complex_operations_bis);
+  } else
+    GST_WARNING ("videomixer element not available, skipping 1 test");
+
+  return s;
+}
+
+GST_CHECK_MAIN (gnonlin)
diff --git a/tests/check/gnl/gnlsource.c b/tests/check/gnl/gnlsource.c
new file mode 100644 (file)
index 0000000..e2d568a
--- /dev/null
@@ -0,0 +1,220 @@
+#include "common.h"
+
+GST_START_TEST (test_simple_videotestsrc)
+{
+  GstElement *pipeline;
+  GstElement *gnlsource, *sink;
+  CollectStructure *collect;
+  GstBus *bus;
+  GstMessage *message;
+  gboolean carry_on = TRUE;
+  GstPad *sinkpad;
+
+  pipeline = gst_pipeline_new ("test_pipeline");
+
+  /*
+     Source 1
+     Start : 1s
+     Duration : 1s
+     Priority : 1
+   */
+  gnlsource =
+      videotest_gnl_src ("source1", 1 * GST_SECOND, 1 * GST_SECOND, 2, 1);
+  fail_if (gnlsource == NULL);
+  check_start_stop_duration (gnlsource, 1 * GST_SECOND, 2 * GST_SECOND,
+      1 * GST_SECOND);
+
+  sink = gst_element_factory_make_or_warn ("fakesink", "sink");
+  fail_if (sink == NULL);
+
+  gst_bin_add_many (GST_BIN (pipeline), gnlsource, sink, NULL);
+
+  /* Shared data */
+  collect = g_new0 (CollectStructure, 1);
+  collect->comp = gnlsource;
+  collect->sink = sink;
+
+  /* Expected segments */
+  collect->expected_segments = g_list_append (collect->expected_segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND));
+
+  g_signal_connect (G_OBJECT (gnlsource), "pad-added",
+      G_CALLBACK (composition_pad_added_cb), collect);
+
+  sinkpad = gst_element_get_static_pad (sink, "sink");
+  fail_if (sinkpad == NULL);
+  gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM,
+      (GstPadProbeCallback) sinkpad_probe, collect, NULL);
+
+  bus = gst_element_get_bus (pipeline);
+
+  GST_DEBUG ("Setting pipeline to PLAYING");
+  ASSERT_OBJECT_REFCOUNT (gnlsource, "gnlsource", 1);
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE);
+
+  GST_DEBUG ("Let's poll the bus");
+
+  while (carry_on) {
+    message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
+    GST_LOG ("poll");
+    if (message) {
+      switch (GST_MESSAGE_TYPE (message)) {
+        case GST_MESSAGE_EOS:
+          /* we should check if we really finished here */
+          GST_WARNING ("Got an EOS");
+          carry_on = FALSE;
+          break;
+        case GST_MESSAGE_SEGMENT_START:
+        case GST_MESSAGE_SEGMENT_DONE:
+          /* We shouldn't see any segement messages, since we didn't do a segment seek */
+          GST_WARNING ("Saw a Segment start/stop");
+          fail_if (FALSE);
+          carry_on = FALSE;
+          break;
+        case GST_MESSAGE_ERROR:
+          fail_error_message (message);
+        default:
+          break;
+      }
+      gst_mini_object_unref (GST_MINI_OBJECT (message));
+    }
+  }
+
+  GST_DEBUG ("Setting pipeline to NULL");
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE);
+
+  fail_if (collect->expected_segments != NULL);
+
+  gst_object_unref (GST_OBJECT (sinkpad));
+
+  GST_DEBUG ("Resetted pipeline to NULL");
+
+  gst_object_unref (pipeline);
+  gst_object_unref (bus);
+
+  g_free (collect);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_videotestsrc_in_bin)
+{
+  GstElement *pipeline;
+  GstElement *gnlsource, *sink;
+  CollectStructure *collect;
+  GstBus *bus;
+  GstMessage *message;
+  gboolean carry_on = TRUE;
+  GstPad *sinkpad;
+
+  pipeline = gst_pipeline_new ("test_pipeline");
+
+  /*
+     Source 1
+     Start : 1s
+     Duration : 1s
+     Priority : 1
+   */
+  gnlsource = videotest_in_bin_gnl_src ("source1", 0, 1 * GST_SECOND, 2, 1);
+  /* Handle systems which don't have alpha available */
+  if (gnlsource == NULL)
+    return;
+
+  sink = gst_element_factory_make_or_warn ("fakesink", "sink");
+  fail_if (sink == NULL);
+
+  gst_bin_add_many (GST_BIN (pipeline), gnlsource, sink, NULL);
+
+  /* Shared data */
+  collect = g_new0 (CollectStructure, 1);
+  collect->comp = gnlsource;
+  collect->sink = sink;
+
+  /* Expected segments */
+  collect->expected_segments = g_list_append (collect->expected_segments,
+      segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0));
+
+  g_signal_connect (G_OBJECT (gnlsource), "pad-added",
+      G_CALLBACK (composition_pad_added_cb), collect);
+
+  sinkpad = gst_element_get_static_pad (sink, "sink");
+  fail_if (sinkpad == NULL);
+  gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM,
+      (GstPadProbeCallback) sinkpad_probe, collect, NULL);
+
+  bus = gst_element_get_bus (pipeline);
+
+  GST_DEBUG ("Setting pipeline to PLAYING");
+  ASSERT_OBJECT_REFCOUNT (gnlsource, "gnlsource", 1);
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE);
+
+  GST_DEBUG ("Let's poll the bus");
+
+  while (carry_on) {
+    message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
+    GST_LOG ("poll");
+    if (message) {
+      switch (GST_MESSAGE_TYPE (message)) {
+        case GST_MESSAGE_EOS:
+          /* we should check if we really finished here */
+          GST_WARNING ("Got an EOS");
+          carry_on = FALSE;
+          break;
+        case GST_MESSAGE_SEGMENT_START:
+        case GST_MESSAGE_SEGMENT_DONE:
+          /* We shouldn't see any segement messages, since we didn't do a segment seek */
+          GST_WARNING ("Saw a Segment start/stop");
+          fail_if (FALSE);
+          carry_on = FALSE;
+          break;
+        case GST_MESSAGE_ERROR:
+          fail_error_message (message);
+        default:
+          break;
+      }
+      gst_mini_object_unref (GST_MINI_OBJECT (message));
+    }
+  }
+
+  GST_DEBUG ("Setting pipeline to NULL");
+
+  gst_object_unref (GST_OBJECT (sinkpad));
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE);
+
+  fail_if (collect->expected_segments != NULL);
+
+  GST_DEBUG ("Resetted pipeline to NULL");
+
+  gst_object_unref (pipeline);
+  gst_object_unref (bus);
+
+  g_free (collect);
+}
+
+GST_END_TEST;
+
+static Suite *
+gnonlin_suite (void)
+{
+  Suite *s = suite_create ("gnlsource");
+  TCase *tc_chain = tcase_create ("gnlsource");
+
+  suite_add_tcase (s, tc_chain);
+
+  if (0)
+    tcase_add_test (tc_chain, test_simple_videotestsrc);
+  tcase_add_test (tc_chain, test_videotestsrc_in_bin);
+
+  return s;
+}
+
+GST_CHECK_MAIN (gnonlin)
diff --git a/tests/check/gnl/seek.c b/tests/check/gnl/seek.c
new file mode 100644 (file)
index 0000000..a3471d8
--- /dev/null
@@ -0,0 +1,764 @@
+#include "common.h"
+
+typedef struct _SeekInfo
+{
+  GstClockTime position;        /* Seek value and segment position */
+  GstClockTime start;           /* Segment start */
+  GstClockTime stop;            /* Segment stop */
+  gboolean expect_failure;      /* Whether we expect the seek to fail or not */
+} SeekInfo;
+
+static SeekInfo *
+new_seek_info (GstClockTime position, GstClockTime start, GstClockTime stop,
+    gboolean expect_failure)
+{
+  SeekInfo *info = g_new0 (SeekInfo, 1);
+
+  info->position = position;
+  info->start = start;
+  info->stop = stop;
+  info->expect_failure = expect_failure;
+
+  return info;
+}
+
+static void
+fill_pipeline_and_check (GstElement * comp, GList * segments, GList * seeks)
+{
+  GstElement *pipeline, *sink;
+  CollectStructure *collect;
+  GstBus *bus;
+  GstMessage *message;
+  gboolean carry_on = TRUE, expected_failure;
+  GstPad *sinkpad;
+  GList *ltofree = seeks;
+
+  pipeline = gst_pipeline_new ("test_pipeline");
+  sink = gst_element_factory_make_or_warn ("fakesink", "sink");
+  fail_if (sink == NULL);
+
+  gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL);
+
+  /* Shared data */
+  collect = g_new0 (CollectStructure, 1);
+  collect->comp = comp;
+  collect->sink = sink;
+
+  /* Expected segments */
+  collect->expected_segments = segments;
+  collect->keep_expected_segments = TRUE;
+
+  g_signal_connect (G_OBJECT (comp), "pad-added",
+      G_CALLBACK (composition_pad_added_cb), collect);
+
+  sinkpad = gst_element_get_static_pad (sink, "sink");
+  gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
+      (GstPadProbeCallback) sinkpad_probe, collect, NULL);
+
+  bus = gst_element_get_bus (GST_ELEMENT (pipeline));
+
+  GST_DEBUG ("Setting pipeline to PLAYING");
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE);
+
+  GST_DEBUG ("Let's poll the bus");
+
+  while (carry_on) {
+    message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
+    if (message) {
+      switch (GST_MESSAGE_TYPE (message)) {
+        case GST_MESSAGE_EOS:
+          /* we should check if we really finished here */
+          GST_WARNING ("Got an EOS");
+          carry_on = FALSE;
+          break;
+        case GST_MESSAGE_SEGMENT_START:
+        case GST_MESSAGE_SEGMENT_DONE:
+          /* We shouldn't see any segement messages, since we didn't do a segment seek */
+          GST_WARNING ("Saw a Segment start/stop");
+          fail_if (TRUE);
+          break;
+        case GST_MESSAGE_ERROR:
+          fail_error_message (message);
+          break;
+        case GST_MESSAGE_ASYNC_DONE:
+          GST_DEBUG ("prerolling done");
+
+          if (seeks == NULL) {
+            carry_on = FALSE;
+            g_list_free_full (collect->expected_segments, g_free);
+            collect->expected_segments = NULL;
+            GST_DEBUG ("Done seeking");
+            break;
+          }
+
+          g_list_free_full (collect->expected_segments, g_free);
+          collect->expected_segments = NULL;
+          expected_failure = TRUE;
+          while (expected_failure && carry_on) {
+            SeekInfo *sinfo = (SeekInfo *) seeks->data;
+
+            seeks = seeks->next;
+
+            if (!sinfo->expect_failure) {
+              collect->gotsegment = FALSE;
+              collect->expected_base = 0;
+              collect->expected_segments =
+                  g_list_append (collect->expected_segments, segment_new (1.0,
+                      GST_FORMAT_TIME, sinfo->start, sinfo->stop,
+                      sinfo->position));
+
+              expected_failure = FALSE;
+            }
+
+            GST_DEBUG ("Seeking to %" GST_TIME_FORMAT ", Expecting (%"
+                GST_TIME_FORMAT " %" GST_TIME_FORMAT ")",
+                GST_TIME_ARGS (sinfo->position), GST_TIME_ARGS (sinfo->start),
+                GST_TIME_ARGS (sinfo->stop));
+
+            fail_unless_equals_int (gst_element_seek_simple (pipeline,
+                    GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, sinfo->position),
+                !sinfo->expect_failure);
+
+            if (!sinfo->expect_failure) {
+              g_free (sinfo);
+              break;
+            }
+
+            if (seeks == NULL)
+              carry_on = FALSE;
+            g_free (sinfo);
+          }
+          break;
+        default:
+          break;
+      }
+      gst_mini_object_unref (GST_MINI_OBJECT (message));
+    }
+  }
+
+  GST_DEBUG ("Setting pipeline to READY");
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
+
+  fail_if (collect->expected_segments != NULL);
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE);
+
+  ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2);
+  gst_object_unref (pipeline);
+  ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2);
+  gst_object_unref (bus);
+
+  g_list_free (ltofree);
+  g_free (collect);
+}
+
+static void
+test_simplest_full (void)
+{
+  gboolean ret;
+  GstElement *comp, *source1;
+  GList *segments = NULL;
+  GList *seeks = NULL;
+
+  comp =
+      gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
+  fail_if (comp == NULL);
+
+  /*
+     Source 1
+     Start : 0s
+     Duration : 1s
+     Media start : 5s
+     Priority : 1
+   */
+  source1 =
+      videotest_gnl_src_full ("source1", 0, 1 * GST_SECOND, 5 * GST_SECOND, 3,
+      1);
+  fail_if (source1 == NULL);
+  check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND);
+
+  /* Add one source */
+
+  gst_bin_add (GST_BIN (comp), source1);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  /* Expected segments */
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME, 5 * GST_SECOND, 6 * GST_SECOND, 0));
+
+  seeks =
+      g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 5.5 * GST_SECOND,
+          6 * GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (0 * GST_SECOND, 5 * GST_SECOND,
+          6 * GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (GST_SECOND - 1, 6 * GST_SECOND - 1,
+          6 * GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (GST_SECOND, 6 * GST_SECOND,
+          6 * GST_SECOND, TRUE));
+  seeks =
+      g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 5.5 * GST_SECOND,
+          6 * GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (0 * GST_SECOND, 5 * GST_SECOND,
+          6 * GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (GST_SECOND - 1, 6 * GST_SECOND - 1,
+          6 * GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (GST_SECOND, 6 * GST_SECOND,
+          6 * GST_SECOND, TRUE));
+
+  fill_pipeline_and_check (comp, segments, seeks);
+}
+
+static void
+test_one_after_other_full (void)
+{
+  gboolean ret;
+  GstElement *comp, *source1, *source2;
+  GList *segments = NULL, *seeks = NULL;
+
+  comp =
+      gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
+  fail_if (comp == NULL);
+
+  /* TOPOLOGY
+   *
+   * 0           1           2           3           4          5 | Priority
+   * ----------------------------------------------------------------------------
+   * [5 source1 ][2 source2 ]                                     | 1
+   *
+   * */
+
+  /*
+     Source 1
+     Start : 0s
+     Duration : 1s
+     Media start : 5s
+     Priority : 1
+   */
+  source1 =
+      videotest_gnl_src_full ("source1", 0, 1 * GST_SECOND, 5 * GST_SECOND, 3,
+      1);
+  fail_if (source1 == NULL);
+  check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND);
+
+  /*
+     Source 2
+     Start : 1s
+     Duration : 1s
+     Media start : 2s
+     Priority : 1
+   */
+  source2 = videotest_gnl_src_full ("source2", 1 * GST_SECOND, 1 * GST_SECOND,
+      2 * GST_SECOND, 2, 1);
+  fail_if (source2 == NULL);
+  check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND,
+      1 * GST_SECOND);
+
+  /* Add sources */
+  gst_bin_add (GST_BIN (comp), source1);
+  gst_bin_add (GST_BIN (comp), source2);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND);
+  check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND,
+      1 * GST_SECOND);
+  check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+  ASSERT_OBJECT_REFCOUNT (source2, "source2", 1);
+
+
+  /* Expected segments */
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME, 5 * GST_SECOND, 6 * GST_SECOND, 0));
+
+  seeks =
+      g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 5.5 * GST_SECOND,
+          6 * GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (0 * GST_SECOND, 5 * GST_SECOND,
+          6 * GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (GST_SECOND - 1, 6 * GST_SECOND - 1,
+          6 * GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (GST_SECOND, 2 * GST_SECOND,
+          3 * GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (2 * GST_SECOND - 1,
+          3 * GST_SECOND - 1, 3 * GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (2 * GST_SECOND, 3 * GST_SECOND,
+          3 * GST_SECOND, TRUE));
+
+
+  fill_pipeline_and_check (comp, segments, seeks);
+}
+
+static void
+test_one_under_another_full (void)
+{
+  gboolean ret;
+  GstElement *comp, *source1, *source2;
+  GList *segments = NULL, *seeks = NULL;
+
+  comp =
+      gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
+  fail_if (comp == NULL);
+
+  /* TOPOLOGY
+   *
+   * 0           1           2           3           4          5 | Priority
+   * ----------------------------------------------------------------------------
+   * [       source1        ]                                     | 1
+   *             [        source2       ]                         | 2
+   *
+   * */
+
+  /*
+     Source 1
+     Start : 0s
+     Duration : 2s
+     Priority : 1
+   */
+  source1 = videotest_gnl_src ("source1", 0, 2 * GST_SECOND, 3, 1);
+  fail_if (source1 == NULL);
+  check_start_stop_duration (source1, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+
+  /*
+     Source 2
+     Start : 1s
+     Duration : 2s
+     Priority : 2
+   */
+  source2 = videotest_gnl_src ("source2", 1 * GST_SECOND, 2 * GST_SECOND, 2, 2);
+  fail_if (source2 == NULL);
+  check_start_stop_duration (source2, 1 * GST_SECOND, 3 * GST_SECOND,
+      2 * GST_SECOND);
+
+  /* Add two sources */
+
+  gst_bin_add (GST_BIN (comp), source1);
+  gst_bin_add (GST_BIN (comp), source2);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (source1, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+  check_start_stop_duration (source2, 1 * GST_SECOND, 3 * GST_SECOND,
+      2 * GST_SECOND);
+  check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND);
+
+  /* Expected segments */
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME, 0, GST_SECOND, 0));
+
+
+  /* Hit source1 */
+  seeks =
+      g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 0.5 * GST_SECOND,
+          1 * GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (0 * GST_SECOND, 0 * GST_SECOND,
+          1 * GST_SECOND, FALSE));
+  /* Hit source1 over source2 */
+  seeks =
+      g_list_append (seeks, new_seek_info (1 * GST_SECOND, 1 * GST_SECOND,
+          2 * GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (1.5 * GST_SECOND, 1.5 * GST_SECOND,
+          2 * GST_SECOND, FALSE));
+  /* Hit source2 */
+  seeks =
+      g_list_append (seeks, new_seek_info (2 * GST_SECOND, 2 * GST_SECOND,
+          3 * GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (2.5 * GST_SECOND, 2.5 * GST_SECOND,
+          3 * GST_SECOND, FALSE));
+
+  fill_pipeline_and_check (comp, segments, seeks);
+}
+
+static void
+test_one_bin_after_other_full (void)
+{
+  gboolean ret = FALSE;
+  GstElement *comp, *source1, *source2;
+  GList *segments = NULL, *seeks = NULL;
+
+  comp =
+      gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
+  fail_if (comp == NULL);
+
+  /*
+     Source 1
+     Start : 0s
+     Duration : 1s
+     Priority : 1
+   */
+  source1 = videotest_in_bin_gnl_src ("source1", 0, 1 * GST_SECOND, 3, 1);
+  fail_if (source1 == NULL);
+  check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND);
+
+  /*
+     Source 2
+     Start : 1s
+     Duration : 1s
+     Priority : 1
+   */
+  source2 =
+      videotest_in_bin_gnl_src ("source2", 1 * GST_SECOND, 1 * GST_SECOND, 2,
+      1);
+  fail_if (source2 == NULL);
+
+  /* Add one source */
+
+  gst_bin_add (GST_BIN (comp), source1);
+
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  /* Second source */
+
+  gst_bin_add (GST_BIN (comp), source2);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND);
+  check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND,
+      1 * GST_SECOND);
+  check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source2, "source2", 1);
+
+  /* Expected segments */
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0));
+
+  /* Hit source1 */
+  seeks =
+      g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 0.5 * GST_SECOND,
+          GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (0 * GST_SECOND, 0 * GST_SECOND,
+          GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (GST_SECOND - 1, GST_SECOND - 1,
+          GST_SECOND, FALSE));
+  /* Hit source2 */
+  seeks =
+      g_list_append (seeks, new_seek_info (1.5 * GST_SECOND, 1.5 * GST_SECOND,
+          2 * GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (GST_SECOND, GST_SECOND,
+          2 * GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (2 * GST_SECOND - 1,
+          2 * GST_SECOND - 1, 2 * GST_SECOND, FALSE));
+  /* Should fail */
+  seeks =
+      g_list_append (seeks, new_seek_info (2 * GST_SECOND, GST_SECOND,
+          GST_SECOND, TRUE));
+
+  fill_pipeline_and_check (comp, segments, seeks);
+}
+
+
+GST_START_TEST (test_complex_operations)
+{
+  gboolean ret = FALSE;
+  GstElement *comp, *oper, *source1, *source2;
+  GList *segments = NULL, *seeks = NULL;
+
+  comp =
+      gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
+
+  /* TOPOLOGY
+   *
+   * 0           1           2           3           4     ..   6 | Priority
+   * ----------------------------------------------------------------------------
+   *                         [------ oper ----------]             | 1
+   * [--------------------- source1 ----------------]             | 2
+   *                         [------------ source2       ------]  | 3
+   * */
+
+  /*
+     source1
+     Start : 0s
+     Duration : 4s
+     Priority : 3
+   */
+
+  source1 = videotest_in_bin_gnl_src ("source1", 0, 4 * GST_SECOND, 2, 3);
+  fail_if (source1 == NULL);
+  check_start_stop_duration (source1, 0, 4 * GST_SECOND, 4 * GST_SECOND);
+
+  /*
+     source2
+     Start : 2s
+     Duration : 4s
+     Priority : 2
+   */
+
+  source2 =
+      videotest_in_bin_gnl_src ("source2", 2 * GST_SECOND, 4 * GST_SECOND, 2,
+      2);
+  fail_if (source2 == NULL);
+  check_start_stop_duration (source2, 2 * GST_SECOND, 6 * GST_SECOND,
+      4 * GST_SECOND);
+
+  /*
+     operation
+     Start : 2s
+     Duration : 2s
+     Priority : 1
+   */
+
+  oper =
+      new_operation ("oper", "videomixer", 2 * GST_SECOND, 2 * GST_SECOND, 1);
+  fail_if (oper == NULL);
+  check_start_stop_duration (oper, 2 * GST_SECOND, 4 * GST_SECOND,
+      2 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+  ASSERT_OBJECT_REFCOUNT (source2, "source2", 1);
+  ASSERT_OBJECT_REFCOUNT (oper, "oper", 1);
+
+  /* Add source1 */
+  gst_bin_add (GST_BIN (comp), source1);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 4 * GST_SECOND, 4 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  /* Add source2 */
+  gst_bin_add (GST_BIN (comp), source2);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source2, "source2", 1);
+
+  /* Add operaton */
+
+  gst_bin_add (GST_BIN (comp), oper);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (oper, "oper", 1);
+
+  /* Expected segments */
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME, 0, 2 * GST_SECOND, 0));
+
+  /* Seeks */
+  seeks =
+      g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 0.5 * GST_SECOND,
+          2 * GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (2.5 * GST_SECOND, 0 * GST_SECOND,
+          1.5 * GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (4.5 * GST_SECOND, 4.5 * GST_SECOND,
+          6 * GST_SECOND, FALSE));
+  /* and backwards */
+  seeks =
+      g_list_append (seeks, new_seek_info (2.5 * GST_SECOND, 0 * GST_SECOND,
+          1.5 * GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 0.5 * GST_SECOND,
+          2 * GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (2.5 * GST_SECOND, 0 * GST_SECOND,
+          1.5 * GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (4.5 * GST_SECOND, 4.5 * GST_SECOND,
+          6 * GST_SECOND, FALSE));
+
+  fill_pipeline_and_check (comp, segments, seeks);
+}
+
+GST_END_TEST;
+
+
+GST_START_TEST (test_complex_operations_bis)
+{
+  gboolean ret = FALSE;
+  GstElement *comp, *oper, *source1, *source2;
+  GList *segments = NULL, *seeks = NULL;
+
+  comp =
+      gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
+
+  /* TOPOLOGY
+   *
+   * 0           1           2           3           4     ..   6 | Priority
+   * ----------------------------------------------------------------------------
+   * [ ......................[------ oper ----------]..........]  | 1 EXPANDABLE
+   * [--------------------- source1 ----------------]             | 2
+   *                         [------------ source2       ------]  | 3
+   * */
+
+
+  /*
+     source1
+     Start : 0s
+     Duration : 4s
+     Priority : 2
+   */
+
+  source1 = videotest_in_bin_gnl_src ("source1", 0, 4 * GST_SECOND, 3, 2);
+  fail_if (source1 == NULL);
+  check_start_stop_duration (source1, 0, 4 * GST_SECOND, 4 * GST_SECOND);
+
+  /*
+     source2
+     Start : 2s
+     Duration : 4s
+     Priority : 3
+   */
+
+  source2 =
+      videotest_in_bin_gnl_src ("source2", 2 * GST_SECOND, 4 * GST_SECOND, 2,
+      3);
+  fail_if (source2 == NULL);
+  check_start_stop_duration (source2, 2 * GST_SECOND, 6 * GST_SECOND,
+      4 * GST_SECOND);
+
+  /*
+     operation
+     Start : 2s
+     Duration : 2s
+     Priority : 1
+     EXPANDABLE
+   */
+
+  oper =
+      new_operation ("oper", "videomixer", 2 * GST_SECOND, 2 * GST_SECOND, 1);
+  fail_if (oper == NULL);
+  check_start_stop_duration (oper, 2 * GST_SECOND, 4 * GST_SECOND,
+      2 * GST_SECOND);
+  g_object_set (oper, "expandable", TRUE, NULL);
+
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+  ASSERT_OBJECT_REFCOUNT (source2, "source2", 1);
+  ASSERT_OBJECT_REFCOUNT (oper, "oper", 1);
+
+  /* Add source1 */
+  gst_bin_add (GST_BIN (comp), source1);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 4 * GST_SECOND, 4 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  /* Add source2 */
+  gst_bin_add (GST_BIN (comp), source2);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source2, "source2", 1);
+
+  /* Add operaton */
+
+  gst_bin_add (GST_BIN (comp), oper);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (source1, 0, 4 * GST_SECOND, 4 * GST_SECOND);
+  check_start_stop_duration (source2, 2 * GST_SECOND, 6 * GST_SECOND,
+      4 * GST_SECOND);
+  check_start_stop_duration (comp, 0, 6 * GST_SECOND, 6 * GST_SECOND);
+  check_start_stop_duration (oper, 0 * GST_SECOND, 6 * GST_SECOND,
+      6 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (oper, "oper", 1);
+
+  /* Expected segments */
+  segments = g_list_append (segments,
+      segment_new (1.0, GST_FORMAT_TIME, 0, 2 * GST_SECOND, 0));
+
+  /* Seeks */
+  seeks =
+      g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 0 * GST_SECOND,
+          1.5 * GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (2.5 * GST_SECOND, 0 * GST_SECOND,
+          1.5 * GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (4.5 * GST_SECOND, 0 * GST_SECOND,
+          1.5 * GST_SECOND, FALSE));
+  /* and backwards */
+  seeks =
+      g_list_append (seeks, new_seek_info (2.5 * GST_SECOND, 0 * GST_SECOND,
+          1.5 * GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (0.5 * GST_SECOND, 0 * GST_SECOND,
+          1.5 * GST_SECOND, FALSE));
+
+  seeks =
+      g_list_append (seeks, new_seek_info (2.5 * GST_SECOND, 0 * GST_SECOND,
+          1.5 * GST_SECOND, FALSE));
+  seeks =
+      g_list_append (seeks, new_seek_info (4.5 * GST_SECOND, 0 * GST_SECOND,
+          1.5 * GST_SECOND, FALSE));
+
+  fill_pipeline_and_check (comp, segments, seeks);
+}
+
+GST_END_TEST;
+
+
+GST_START_TEST (test_simplest)
+{
+  test_simplest_full ();
+}
+
+GST_END_TEST;
+
+
+GST_START_TEST (test_one_after_other)
+{
+  test_one_after_other_full ();
+}
+
+GST_END_TEST;
+
+
+GST_START_TEST (test_one_under_another)
+{
+  test_one_under_another_full ();
+}
+
+GST_END_TEST;
+
+
+GST_START_TEST (test_one_bin_after_other)
+{
+  test_one_bin_after_other_full ();
+}
+
+GST_END_TEST;
+
+
+static Suite *
+gnonlin_suite (void)
+{
+  Suite *s = suite_create ("gnonlin-seek");
+  TCase *tc_chain = tcase_create ("general");
+
+  suite_add_tcase (s, tc_chain);
+
+  tcase_add_test (tc_chain, test_simplest);
+  tcase_add_test (tc_chain, test_one_after_other);
+  tcase_add_test (tc_chain, test_one_under_another);
+  tcase_add_test (tc_chain, test_one_bin_after_other);
+  tcase_add_test (tc_chain, test_complex_operations);
+  tcase_add_test (tc_chain, test_complex_operations_bis);
+
+  return s;
+}
+
+GST_CHECK_MAIN (gnonlin)
diff --git a/tests/check/gnl/simple.c b/tests/check/gnl/simple.c
new file mode 100644 (file)
index 0000000..c305a6c
--- /dev/null
@@ -0,0 +1,791 @@
+#include "common.h"
+
+static void
+test_simplest_full (void)
+{
+  gboolean ret = FALSE;
+  GstElement *pipeline;
+  GstElement *comp, *sink, *source1;
+  CollectStructure *collect;
+  GstBus *bus;
+  GstPad *sinkpad;
+
+  pipeline = gst_pipeline_new ("test_pipeline");
+  comp =
+      gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
+  fail_if (comp == NULL);
+
+  /*
+     Source 1
+     Start : 0s
+     Duration : 1s
+     Media start : 5s
+     Media Duartion : 1s
+     Priority : 1
+   */
+  source1 =
+      videotest_gnl_src_full ("source1", 0, 1 * GST_SECOND, 5 * GST_SECOND, 3,
+      1);
+  fail_if (source1 == NULL);
+  check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND);
+
+  /* Add one source */
+
+  gst_bin_add (GST_BIN (comp), source1);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  fail_unless (ret);
+  check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND);
+  check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND);
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  sink = gst_element_factory_make_or_warn ("fakesink", "sink");
+  fail_if (sink == NULL);
+
+  gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL);
+
+  /* Shared data */
+  collect = g_new0 (CollectStructure, 1);
+  collect->comp = comp;
+  collect->sink = sink;
+
+  /* Expected segments */
+  collect->expected_segments = g_list_append (collect->expected_segments,
+      segment_new (1.0, GST_FORMAT_TIME, 5 * GST_SECOND, 6 * GST_SECOND, 0));
+
+  g_signal_connect (G_OBJECT (comp), "pad-added",
+      G_CALLBACK (composition_pad_added_cb), collect);
+
+  sinkpad = gst_element_get_static_pad (sink, "sink");
+  gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM,
+      (GstPadProbeCallback) sinkpad_probe, collect, NULL);
+
+  bus = gst_element_get_bus (GST_ELEMENT (pipeline));
+
+  GST_DEBUG ("Setting pipeline to PLAYING");
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE);
+
+  GST_DEBUG ("Let's poll the bus");
+
+  poll_the_bus (bus);
+
+  GST_DEBUG ("Setting pipeline to NULL");
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
+
+  fail_if (collect->expected_segments != NULL);
+
+  GST_DEBUG ("Resetted pipeline to READY");
+
+  /* Expected segments */
+  collect->expected_segments = g_list_append (collect->expected_segments,
+      segment_new (1.0, GST_FORMAT_TIME, 5 * GST_SECOND, 6 * GST_SECOND, 0));
+  collect->expected_base = 0;
+  collect->gotsegment = FALSE;
+
+  GST_DEBUG ("Setting pipeline to PLAYING again");
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE);
+
+  GST_DEBUG ("Let's poll the bus AGAIN");
+
+  poll_the_bus (bus);
+
+  fail_if (collect->expected_segments != NULL);
+
+  gst_object_unref (GST_OBJECT (sinkpad));
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE);
+
+  ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2);
+  gst_object_unref (pipeline);
+  ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2);
+  gst_object_unref (bus);
+
+  g_free (collect);
+}
+
+static void
+test_time_duration_full (void)
+{
+  gboolean ret = FALSE;
+  GstElement *comp, *source1, *source2;
+
+  comp =
+      gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
+
+  /*
+     Source 1
+     Start : 0s
+     Duration : 1s
+     Priority : 1
+   */
+  source1 = videotest_gnl_src ("source1", 0, 1 * GST_SECOND, 3, 1);
+  fail_if (source1 == NULL);
+  check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND);
+
+  /*
+     Source 2
+     Start : 1s
+     Duration : 1s
+     Priority : 1
+   */
+  source2 = videotest_gnl_src ("source2", 1 * GST_SECOND, 1 * GST_SECOND, 2, 1);
+  fail_if (source2 == NULL);
+  check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND,
+      1 * GST_SECOND);
+
+  /* Add one source */
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+  ASSERT_OBJECT_REFCOUNT (source2, "source2", 1);
+
+  gst_bin_add (GST_BIN (comp), source1);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  fail_unless (ret == TRUE);
+  check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  /* Second source */
+
+  ret = FALSE;
+  gst_bin_add (GST_BIN (comp), source2);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  fail_unless (ret == TRUE);
+  check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source2, "source2", 1);
+
+  /* Remove first source */
+
+  gst_object_ref (source1);
+  gst_bin_remove (GST_BIN (comp), source1);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 1 * GST_SECOND, 2 * GST_SECOND,
+      1 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  /* Re-add first source */
+
+  gst_bin_add (GST_BIN (comp), source1);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+  gst_object_unref (source1);
+
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  gst_object_unref (comp);
+}
+
+static void
+test_one_after_other_full (void)
+{
+  GstElement *pipeline;
+  GstElement *comp, *sink, *source1, *source2;
+  CollectStructure *collect;
+  GstBus *bus;
+  GstMessage *message;
+  gboolean carry_on = TRUE;
+  GstPad *sinkpad;
+
+  gboolean ret = FALSE;
+
+  pipeline = gst_pipeline_new ("test_pipeline");
+  comp =
+      gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
+  fail_if (comp == NULL);
+
+  /*
+     Source 1
+     Start : 0s
+     Duration : 1s
+     Media start : 5s
+     Priority : 1
+   */
+  source1 =
+      videotest_gnl_src_full ("source1", 0, 1 * GST_SECOND, 5 * GST_SECOND, 3,
+      1);
+  fail_if (source1 == NULL);
+  check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND);
+
+  /*
+     Source 2
+     Start : 1s
+     Duration : 1s
+     Media start : 2s
+     Priority : 1
+   */
+  source2 = videotest_gnl_src_full ("source2", 1 * GST_SECOND, 1 * GST_SECOND,
+      2 * GST_SECOND, 2, 1);
+  fail_if (source2 == NULL);
+  check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND,
+      1 * GST_SECOND);
+
+  /* Add one source */
+  gst_bin_add (GST_BIN (comp), source1);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  /* Second source */
+  gst_bin_add (GST_BIN (comp), source2);
+
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  fail_unless (ret);
+  check_start_stop_duration (source1, 0 * GST_SECOND, 1 * GST_SECOND,
+      1 * GST_SECOND);
+  check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND,
+      1 * GST_SECOND);
+  check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source2, "source2", 1);
+
+  /* Remove first source */
+
+  gst_object_ref (source1);
+  gst_bin_remove (GST_BIN (comp), source1);
+  check_start_stop_duration (comp, 1 * GST_SECOND, 2 * GST_SECOND,
+      1 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  /* Re-add first source */
+
+  gst_bin_add (GST_BIN (comp), source1);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+  gst_object_unref (source1);
+
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  sink = gst_element_factory_make_or_warn ("fakesink", "sink");
+  fail_if (sink == NULL);
+
+  gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL);
+
+  /* Shared data */
+  collect = g_new0 (CollectStructure, 1);
+  collect->comp = comp;
+  collect->sink = sink;
+
+  /* Expected segments */
+  collect->expected_segments = g_list_append (collect->expected_segments,
+      segment_new (1.0, GST_FORMAT_TIME, 5 * GST_SECOND, 6 * GST_SECOND, 0));
+  collect->expected_segments = g_list_append (collect->expected_segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          2 * GST_SECOND, 3 * GST_SECOND, 1 * GST_SECOND));
+
+  g_signal_connect (G_OBJECT (comp), "pad-added",
+      G_CALLBACK (composition_pad_added_cb), collect);
+
+  sinkpad = gst_element_get_static_pad (sink, "sink");
+  gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM,
+      (GstPadProbeCallback) sinkpad_probe, collect, NULL);
+
+  bus = gst_element_get_bus (GST_ELEMENT (pipeline));
+
+  GST_DEBUG ("Setting pipeline to PLAYING");
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE);
+
+  GST_DEBUG ("Let's poll the bus");
+
+  while (carry_on) {
+    message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
+    if (message) {
+      switch (GST_MESSAGE_TYPE (message)) {
+        case GST_MESSAGE_EOS:
+          /* we should check if we really finished here */
+          GST_WARNING ("Got an EOS");
+          carry_on = FALSE;
+          break;
+        case GST_MESSAGE_SEGMENT_START:
+        case GST_MESSAGE_SEGMENT_DONE:
+          /* We shouldn't see any segement messages, since we didn't do a segment seek */
+          GST_WARNING ("Saw a Segment start/stop");
+          fail_if (TRUE);
+          break;
+        case GST_MESSAGE_ERROR:
+          fail_error_message (message);
+        default:
+          break;
+      }
+      gst_mini_object_unref (GST_MINI_OBJECT (message));
+    }
+  }
+
+  GST_DEBUG ("Setting pipeline to NULL");
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
+
+  fail_if (collect->expected_segments != NULL);
+
+  GST_DEBUG ("Resetted pipeline to READY");
+
+  /* Expected segments */
+  collect->expected_segments = g_list_append (collect->expected_segments,
+      segment_new (1.0, GST_FORMAT_TIME, 5 * GST_SECOND, 6 * GST_SECOND, 0));
+  collect->expected_segments = g_list_append (collect->expected_segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          2 * GST_SECOND, 3 * GST_SECOND, 1 * GST_SECOND));
+  collect->gotsegment = FALSE;
+  collect->expected_base = 0;
+
+
+  GST_DEBUG ("Setting pipeline to PLAYING again");
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE);
+
+  carry_on = TRUE;
+
+  GST_DEBUG ("Let's poll the bus AGAIN");
+
+  while (carry_on) {
+    message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
+    if (message) {
+      switch (GST_MESSAGE_TYPE (message)) {
+        case GST_MESSAGE_EOS:
+          /* we should check if we really finished here */
+          carry_on = FALSE;
+          break;
+        case GST_MESSAGE_SEGMENT_START:
+        case GST_MESSAGE_SEGMENT_DONE:
+          /* We shouldn't see any segement messages, since we didn't do a segment seek */
+          GST_WARNING ("Saw a Segment start/stop");
+          fail_if (TRUE);
+          break;
+        case GST_MESSAGE_ERROR:
+          fail_error_message (message);
+        default:
+          break;
+      }
+      gst_mini_object_unref (GST_MINI_OBJECT (message));
+    } else {
+      GST_DEBUG ("bus_poll responded, but there wasn't any message...");
+    }
+  }
+
+  fail_if (collect->expected_segments != NULL);
+
+  gst_object_unref (GST_OBJECT (sinkpad));
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE);
+
+  ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2);
+  gst_object_unref (pipeline);
+  ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2);
+  gst_object_unref (bus);
+
+  g_free (collect);
+}
+
+static void
+test_one_under_another_full (void)
+{
+  gboolean ret = FALSE;
+  GstElement *pipeline;
+  GstElement *comp, *sink, *source1, *source2;
+  CollectStructure *collect;
+  GstBus *bus;
+  GstMessage *message;
+  gboolean carry_on = TRUE;
+  GstPad *sinkpad;
+
+  pipeline = gst_pipeline_new ("test_pipeline");
+  comp =
+      gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
+  fail_if (comp == NULL);
+
+  /* TOPOLOGY
+   *
+   * 0           1           2           3           4          5 | Priority
+   * ----------------------------------------------------------------------------
+   * [-      source1       -]                                     | 1
+   *             [-      source2       -]                         | 2
+   * */
+
+  /*
+     Source 1
+     Start : 0s
+     Duration : 2s
+     Priority : 1
+   */
+  source1 = videotest_gnl_src ("source1", 0, 2 * GST_SECOND, 3, 1);
+  fail_if (source1 == NULL);
+  check_start_stop_duration (source1, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+
+  /*
+     Source 2
+     Start : 1s
+     Duration : 2s
+     Priority : 2
+   */
+  source2 = videotest_gnl_src ("source2", 1 * GST_SECOND, 2 * GST_SECOND, 2, 2);
+  fail_if (source2 == NULL);
+  check_start_stop_duration (source2, 1 * GST_SECOND, 3 * GST_SECOND,
+      2 * GST_SECOND);
+
+  /* Add two sources */
+
+  gst_bin_add (GST_BIN (comp), source1);
+  gst_bin_add (GST_BIN (comp), source2);
+  check_start_stop_duration (comp, 0, 0 * GST_SECOND, 0 * GST_SECOND);
+  /* Now commiting changes */
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND);
+  check_start_stop_duration (source1, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+  check_start_stop_duration (source2, 1 * GST_SECOND, 3 * GST_SECOND,
+      2 * GST_SECOND);
+
+  /* Remove second source */
+
+  gst_object_ref (source1);
+  gst_bin_remove (GST_BIN (comp), source1);
+  check_start_stop_duration (comp, 1 * GST_SECOND, 3 * GST_SECOND,
+      2 * GST_SECOND);
+
+  /* Re-add second source */
+
+  gst_bin_add (GST_BIN (comp), source1);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 3 * GST_SECOND, 3 * GST_SECOND);
+  gst_object_unref (source1);
+
+  sink = gst_element_factory_make_or_warn ("fakesink", "sink");
+  fail_if (sink == NULL);
+
+  gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL);
+
+  /* Shared data */
+  collect = g_new0 (CollectStructure, 1);
+  collect->comp = comp;
+  collect->sink = sink;
+
+  /* Expected segments */
+  collect->expected_segments = g_list_append (collect->expected_segments,
+      segment_new (1.0, GST_FORMAT_TIME, 0, GST_SECOND, 0));
+
+  collect->expected_segments = g_list_append (collect->expected_segments,
+      segment_new (1.0, GST_FORMAT_TIME, GST_SECOND, 2 * GST_SECOND,
+          GST_SECOND));
+
+  collect->expected_segments = g_list_append (collect->expected_segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          2 * GST_SECOND, 3 * GST_SECOND, 2 * GST_SECOND));
+
+  g_signal_connect (G_OBJECT (comp), "pad-added",
+      G_CALLBACK (composition_pad_added_cb), collect);
+
+  sinkpad = gst_element_get_static_pad (sink, "sink");
+  gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM,
+      (GstPadProbeCallback) sinkpad_probe, collect, NULL);
+
+  bus = gst_element_get_bus (GST_ELEMENT (pipeline));
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE);
+
+  while (carry_on) {
+    message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
+    if (message) {
+      switch (GST_MESSAGE_TYPE (message)) {
+        case GST_MESSAGE_EOS:
+          /* we should check if we really finished here */
+          carry_on = FALSE;
+          break;
+        case GST_MESSAGE_SEGMENT_START:
+        case GST_MESSAGE_SEGMENT_DONE:
+          /* check if the segment is the correct one (0s-4s) */
+          carry_on = FALSE;
+          break;
+        case GST_MESSAGE_ERROR:
+          fail_error_message (message);
+        default:
+          break;
+      }
+      gst_message_unref (message);
+    }
+  }
+
+  fail_if (collect->expected_segments != NULL);
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE);
+
+  gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
+  gst_object_unref (GST_OBJECT (sinkpad));
+  ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2);
+  gst_object_unref (pipeline);
+  ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2);
+  gst_object_unref (bus);
+
+  g_free (collect);
+}
+
+static void
+test_one_bin_after_other_full (void)
+{
+  gboolean ret = FALSE;
+  GstElement *pipeline;
+  GstElement *comp, *sink, *source1, *source2;
+  CollectStructure *collect;
+  GstBus *bus;
+  GstMessage *message;
+  gboolean carry_on = TRUE;
+  GstPad *sinkpad;
+
+  pipeline = gst_pipeline_new ("test_pipeline");
+  comp =
+      gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
+  fail_if (comp == NULL);
+
+  /*
+     Source 1
+     Start : 0s
+     Duration : 1s
+     Priority : 1
+   */
+  source1 = videotest_in_bin_gnl_src ("source1", 0, 1 * GST_SECOND, 3, 1);
+  if (source1 == NULL) {
+    gst_object_unref (pipeline);
+    gst_object_unref (comp);
+    return;
+  }
+  check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND);
+
+  /*
+     Source 2
+     Start : 1s
+     Duration : 1s
+     Priority : 1
+   */
+  source2 =
+      videotest_in_bin_gnl_src ("source2", 1 * GST_SECOND, 1 * GST_SECOND, 2,
+      1);
+  fail_if (source2 == NULL);
+  check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND,
+      1 * GST_SECOND);
+
+  /* Add one source */
+
+  gst_bin_add (GST_BIN (comp), source1);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  fail_unless (ret);
+  check_start_stop_duration (comp, 0, 1 * GST_SECOND, 1 * GST_SECOND);
+  check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  /* Second source */
+
+  gst_bin_add (GST_BIN (comp), source2);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+  check_start_stop_duration (source1, 0, 1 * GST_SECOND, 1 * GST_SECOND);
+  check_start_stop_duration (source2, 1 * GST_SECOND, 2 * GST_SECOND,
+      1 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source2, "source2", 1);
+
+  /* Remove first source */
+
+  gst_object_ref (source1);
+  gst_bin_remove (GST_BIN (comp), source1);
+  check_start_stop_duration (comp, 1 * GST_SECOND, 2 * GST_SECOND,
+      1 * GST_SECOND);
+
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  /* Re-add first source */
+
+  gst_bin_add (GST_BIN (comp), source1);
+  g_signal_emit_by_name (comp, "commit", TRUE, &ret);
+  check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND);
+  gst_object_unref (source1);
+
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  sink = gst_element_factory_make_or_warn ("fakesink", "sink");
+  fail_if (sink == NULL);
+
+  gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL);
+
+  /* Shared data */
+  collect = g_new0 (CollectStructure, 1);
+  collect->comp = comp;
+  collect->sink = sink;
+
+  /* Expected segments */
+  collect->expected_segments = g_list_append (collect->expected_segments,
+      segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0));
+  collect->expected_segments = g_list_append (collect->expected_segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND));
+
+  g_signal_connect (G_OBJECT (comp), "pad-added",
+      G_CALLBACK (composition_pad_added_cb), collect);
+
+  sinkpad = gst_element_get_static_pad (sink, "sink");
+  gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM,
+      (GstPadProbeCallback) sinkpad_probe, collect, NULL);
+
+  bus = gst_element_get_bus (GST_ELEMENT (pipeline));
+
+  GST_DEBUG ("Setting pipeline to PLAYING");
+  ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE);
+
+  GST_DEBUG ("Let's poll the bus");
+
+  while (carry_on) {
+    message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
+    if (message) {
+      switch (GST_MESSAGE_TYPE (message)) {
+        case GST_MESSAGE_EOS:
+          /* we should check if we really finished here */
+          GST_WARNING ("Got an EOS");
+          carry_on = FALSE;
+          break;
+        case GST_MESSAGE_SEGMENT_START:
+        case GST_MESSAGE_SEGMENT_DONE:
+          /* We shouldn't see any segement messages, since we didn't do a segment seek */
+          GST_WARNING ("Saw a Segment start/stop");
+          fail_if (FALSE);
+          break;
+        case GST_MESSAGE_ERROR:
+          fail_error_message (message);
+        default:
+          break;
+      }
+      gst_mini_object_unref (GST_MINI_OBJECT (message));
+    }
+  }
+
+  GST_DEBUG ("Setting pipeline to NULL");
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
+
+  fail_if (collect->expected_segments != NULL);
+
+  GST_DEBUG ("Resetted pipeline to READY");
+
+  /* Expected segments */
+  collect->expected_segments = g_list_append (collect->expected_segments,
+      segment_new (1.0, GST_FORMAT_TIME, 0, 1 * GST_SECOND, 0));
+  collect->expected_segments = g_list_append (collect->expected_segments,
+      segment_new (1.0, GST_FORMAT_TIME,
+          1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND));
+  collect->gotsegment = FALSE;
+  collect->expected_base = 0;
+
+  GST_DEBUG ("Setting pipeline to PLAYING again");
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE);
+
+  carry_on = TRUE;
+  while (carry_on) {
+    message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
+    if (message) {
+      switch (GST_MESSAGE_TYPE (message)) {
+        case GST_MESSAGE_EOS:
+          /* we should check if we really finished here */
+          carry_on = FALSE;
+          break;
+        case GST_MESSAGE_SEGMENT_START:
+        case GST_MESSAGE_SEGMENT_DONE:
+          /* We shouldn't see any segement messages, since we didn't do a segment seek */
+          GST_WARNING ("Saw a Segment start/stop");
+          fail_if (FALSE);
+          break;
+        case GST_MESSAGE_ERROR:
+          fail_error_message (message);
+        default:
+          break;
+      }
+      gst_mini_object_unref (GST_MINI_OBJECT (message));
+    }
+  }
+
+  gst_object_unref (GST_OBJECT (sinkpad));
+
+  fail_if (collect->expected_segments != NULL);
+
+  fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
+          GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE);
+
+  ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2);
+  gst_object_unref (pipeline);
+  ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2);
+  gst_object_unref (bus);
+
+  g_free (collect);
+}
+
+GST_START_TEST (test_simplest)
+{
+  test_simplest_full ();
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_time_duration)
+{
+  test_time_duration_full ();
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_one_after_other)
+{
+  test_one_after_other_full ();
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_one_under_another)
+{
+  test_one_under_another_full ();
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_one_bin_after_other)
+{
+  test_one_bin_after_other_full ();
+}
+
+GST_END_TEST;
+
+static Suite *
+gnonlin_suite (void)
+{
+  Suite *s = suite_create ("gnonlin-simple");
+  TCase *tc_chain = tcase_create ("general");
+
+  suite_add_tcase (s, tc_chain);
+
+  tcase_add_test (tc_chain, test_time_duration);
+  tcase_add_test (tc_chain, test_simplest);
+  tcase_add_test (tc_chain, test_one_after_other);
+  tcase_add_test (tc_chain, test_one_under_another);
+  tcase_add_test (tc_chain, test_one_bin_after_other);
+  return s;
+}
+
+GST_CHECK_MAIN (gnonlin)