playbin3: Move gapless to uridecodebin3
authorEdward Hervey <edward@centricular.com>
Thu, 21 Jul 2022 14:43:47 +0000 (16:43 +0200)
committerGStreamer Marge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Wed, 16 Nov 2022 14:01:47 +0000 (14:01 +0000)
This was the intention from the start, just took me a few years *cough* to
actually implement it properly.

Gapless is handled by re-using as much as possible the same decoders and sinks
if present, and only pre-rolling switching at the sources level (with buffering
if/when needed).

In order to enable "gapless" playback, the "next" uri should be set at any time
between the moment the `about-to-finish` signal is emitted and the moment the
current play item is done. Previously this could only be done with the signal
emission.

This new implementation also allows "Instantaneous URI switching". This allows a
much faster way of switching playback entries while re-using as many elements as
possible. To enable this set `instant-uri` property to TRUE, the default being
FALSE.

API: instant-uri properties
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/2784>

subprojects/gst-plugins-base/docs/plugins/gst_plugins_cache.json
subprojects/gst-plugins-base/gst/playback/gstplaybin3.c
subprojects/gst-plugins-base/gst/playback/gsturidecodebin3.c
subprojects/gst-plugins-base/tools/gst-play.c

index efddbb8..c3d8b03 100644 (file)
                         "type": "gboolean",
                         "writable": true
                     },
+                    "instant-uri": {
+                        "blurb": "When enabled, URI changes are applied immediately",
+                        "conditionally-available": false,
+                        "construct": false,
+                        "construct-only": false,
+                        "controllable": false,
+                        "default": "false",
+                        "mutable": "null",
+                        "readable": true,
+                        "type": "gboolean",
+                        "writable": true
+                    },
                     "mute": {
                         "blurb": "Mute the audio channel without changing the volume",
                         "conditionally-available": false,
                         "type": "gboolean",
                         "writable": true
                     },
+                    "instant-uri": {
+                        "blurb": "When enabled, URI changes are applied immediately",
+                        "conditionally-available": false,
+                        "construct": false,
+                        "construct-only": false,
+                        "controllable": false,
+                        "default": "false",
+                        "mutable": "null",
+                        "readable": true,
+                        "type": "gboolean",
+                        "writable": true
+                    },
                     "ring-buffer-max-size": {
                         "blurb": "Max. amount of data in the ring buffer (bytes, 0 = ring buffer disabled)",
                         "conditionally-available": false,
index 5618d04..14b160e 100644 (file)
@@ -249,7 +249,6 @@ GST_DEBUG_CATEGORY_STATIC (gst_play_bin3_debug);
 
 typedef struct _GstPlayBin3 GstPlayBin3;
 typedef struct _GstPlayBin3Class GstPlayBin3Class;
-typedef struct _GstSourceGroup GstSourceGroup;
 typedef struct _GstSourceCombine GstSourceCombine;
 typedef struct _SourcePad SourcePad;
 
@@ -272,14 +271,8 @@ struct _GstSourceCombine
   GPtrArray *streams;           /* Sorted array of GstStream for the given type */
 
   gboolean has_active_pad;      /* stream combiner has the "active-pad" property */
-
-  gboolean is_concat;           /* The stream combiner is the 'concat' element */
 };
 
-#define GST_SOURCE_GROUP_GET_LOCK(group) (&((GstSourceGroup*)(group))->lock)
-#define GST_SOURCE_GROUP_LOCK(group) (g_mutex_lock (GST_SOURCE_GROUP_GET_LOCK(group)))
-#define GST_SOURCE_GROUP_UNLOCK(group) (g_mutex_unlock (GST_SOURCE_GROUP_GET_LOCK(group)))
-
 enum
 {
   PLAYBIN_STREAM_AUDIO = 0,
@@ -309,94 +302,10 @@ struct _SourcePad
   gulong event_probe_id;
 };
 
-/* a structure to hold the objects for decoding a uri and the subtitle uri
- */
-struct _GstSourceGroup
-{
-  GstPlayBin3 *playbin;
-
-  GMutex lock;
-
-  gboolean valid;               /* the group has valid info to start playback */
-  gboolean active;              /* the group is active */
-
-  gboolean playing;             /* the group is currently playing
-                                 * (outputted on the sinks) */
-
-  /* properties */
-  gchar *uri;
-  gchar *suburi;
-
-  /* The currently outputted group_id */
-  guint group_id;
-
-  /* Bit-wise set of stream types we have requested from uridecodebin3 */
-  GstStreamType selected_stream_types;
-
-  /* Bit-wise set of stream types for which pads are present */
-  GstStreamType present_stream_types;
-
-  /* TRUE if a 'about-to-finish' needs to be posted once we have
-   * got source pads for all requested stream types
-   *
-   * FIXME : Move this logic to uridecodebin3 later */
-  gboolean pending_about_to_finish;
-
-  /* uridecodebin to handle uri and suburi */
-  GstElement *uridecodebin;
-
-  /* Active sinks for each media type. These are initialized with
-   * the configured or currently used sink, otherwise
-   * left as NULL and playbin tries to automatically
-   * select a good sink */
-  GstElement *audio_sink;
-  GstElement *video_sink;
-  GstElement *text_sink;
-
-  /* List of source pads */
-  GList *source_pads;
-
-  /* uridecodebin signals */
-  gulong pad_added_id;
-  gulong pad_removed_id;
-  gulong select_stream_id;
-  gulong source_setup_id;
-  gulong about_to_finish_id;
-
-  gboolean stream_changed_pending;
-
-  /* Active stream collection */
-  GstStreamCollection *collection;
-
-
-  /* buffering message stored for after switching */
-  GstMessage *pending_buffering_msg;
-};
-
 #define GST_PLAY_BIN3_GET_LOCK(bin) (&((GstPlayBin3*)(bin))->lock)
 #define GST_PLAY_BIN3_LOCK(bin) (g_rec_mutex_lock (GST_PLAY_BIN3_GET_LOCK(bin)))
 #define GST_PLAY_BIN3_UNLOCK(bin) (g_rec_mutex_unlock (GST_PLAY_BIN3_GET_LOCK(bin)))
 
-/* lock to protect dynamic callbacks, like no-more-pads */
-#define GST_PLAY_BIN3_DYN_LOCK(bin)    g_mutex_lock (&(bin)->dyn_lock)
-#define GST_PLAY_BIN3_DYN_UNLOCK(bin)  g_mutex_unlock (&(bin)->dyn_lock)
-
-/* lock for shutdown */
-#define GST_PLAY_BIN3_SHUTDOWN_LOCK(bin,label)                 \
-  G_STMT_START {                                               \
-    if (G_UNLIKELY (g_atomic_int_get (&bin->shutdown)))                \
-      goto label;                                              \
-    GST_PLAY_BIN3_DYN_LOCK (bin);                              \
-    if (G_UNLIKELY (g_atomic_int_get (&bin->shutdown))) {      \
-      GST_PLAY_BIN3_DYN_UNLOCK (bin);                          \
-      goto label;                                              \
-    }                                                          \
-  } G_STMT_END
-
-/* unlock for shutdown */
-#define GST_PLAY_BIN3_SHUTDOWN_UNLOCK(bin)     \
-  GST_PLAY_BIN3_DYN_UNLOCK (bin);              \
-
 /**
  * GstPlayBin3:
  *
@@ -406,19 +315,21 @@ struct _GstPlayBin3
 {
   GstPipeline parent;
 
-  GRecMutex lock;               /* to protect group switching */
+  GRecMutex lock;               /* to protect various properties */
+
+  /* uridecodebin to handle uri and suburi */
+  GstElement *uridecodebin;
 
-  /* the input groups, we use a double buffer to switch between current and next */
-  GstSourceGroup groups[2];     /* array with group info */
-  GstSourceGroup *curr_group;   /* pointer to the currently playing group */
-  GstSourceGroup *next_group;   /* pointer to the next group */
+  /* List of SourcePad structures */
+  GList *source_pads;
+
+  /* Active stream collection */
+  GstStreamCollection *collection;
 
   /* combiners for different streams */
   GstSourceCombine combiner[PLAYBIN_STREAM_LAST];
 
-  /* Bit-wise set of stream types we have requested from uridecodebin3.
-   * Calculated as the combination of the 'selected_stream_types' of
-   * each sourcegroup */
+  /* Bit-wise set of stream types we have requested from uridecodebin3. */
   GstStreamType selected_stream_types;
 
   /* Bit-wise set of configured output stream types (i.e. active
@@ -426,7 +337,6 @@ struct _GstPlayBin3
   GstStreamType active_stream_types;
 
   /* properties */
-  guint64 connection_speed;     /* connection speed in bits/sec (0 = unknown) */
   gint current_video;           /* the currently selected stream */
   gint current_audio;           /* the currently selected stream */
   gint current_text;            /* the currently selected stream */
@@ -434,8 +344,7 @@ struct _GstPlayBin3
   gboolean do_stream_selections;        /* Set to TRUE when any of current-{video|audio|text} are set to
                                            say playbin should do backwards-compatibility behaviours */
 
-  guint64 buffer_duration;      /* When buffering, the max buffer duration (ns) */
-  guint buffer_size;            /* When buffering, the max buffer size (bytes) */
+  GstObject *collection_source; /* The element that provided the latest stream collection */
   gboolean force_aspect_ratio;
 
   /* Multiview/stereoscopic overrides */
@@ -445,14 +354,6 @@ struct _GstPlayBin3
   /* our play sink */
   GstPlaySink *playsink;
 
-  /* Task for (de)activating groups, protected by the activation lock */
-  GstTask *activation_task;
-  GRecMutex activation_lock;
-
-  /* lock protecting dynamic adding/removing */
-  GMutex dyn_lock;
-  /* if we are shutting down or not */
-  gint shutdown;
   gboolean async_pending;       /* async-start has been emitted */
 
   gboolean have_selector;       /* set to FALSE when we fail to create an
@@ -477,11 +378,7 @@ struct _GstPlayBin3
   GstElement *video_stream_combiner;    /* configured video stream combiner, or NULL */
   GstElement *text_stream_combiner;     /* configured text stream combiner, or NULL */
 
-  guint64 ring_buffer_max_size; /* 0 means disabled */
-
-  gboolean is_live;             /* Whether our current group is live */
-
-  GMutex buffering_post_lock;   /* Protect serialisation of buffering messages. Must not acquire this while holding any SOURCE_GROUP lock */
+  gboolean is_live;             /* Whether we are live */
 };
 
 struct _GstPlayBin3Class
@@ -549,7 +446,8 @@ enum
   PROP_AUDIO_FILTER,
   PROP_VIDEO_FILTER,
   PROP_MULTIVIEW_MODE,
-  PROP_MULTIVIEW_FLAGS
+  PROP_MULTIVIEW_FLAGS,
+  PROP_INSTANT_URI
 };
 
 /* signals */
@@ -581,19 +479,22 @@ static gboolean gst_play_bin3_send_event (GstElement * element,
 static GstSample *gst_play_bin3_convert_sample (GstPlayBin3 * playbin,
     GstCaps * caps);
 
-static GstStateChangeReturn setup_next_source (GstPlayBin3 * playbin);
-
-static void gst_play_bin3_check_group_status (GstPlayBin3 * playbin);
-static void emit_about_to_finish (GstPlayBin3 * playbin);
 static void reconfigure_output (GstPlayBin3 * playbin);
-static void pad_removed_cb (GstElement * decodebin, GstPad * pad,
-    GstSourceGroup * group);
 
+/* uridecodebin3 signal callbacks */
+static void pad_added_cb (GstElement * uridecodebin, GstPad * pad,
+    GstPlayBin3 * playbin);
+static void pad_removed_cb (GstElement * decodebin, GstPad * pad,
+    GstPlayBin3 * playbin);
 static gint select_stream_cb (GstElement * decodebin,
     GstStreamCollection * collection, GstStream * stream,
-    GstSourceGroup * group);
+    GstPlayBin3 * playbin);
+static void source_setup_cb (GstElement * element, GstElement * source,
+    GstPlayBin3 * playbin);
+static void about_to_finish_cb (GstElement * uridecodebin,
+    GstPlayBin3 * playbin);
 
-static void do_stream_selection (GstPlayBin3 * playbin, GstSourceGroup * group);
+static void do_stream_selection (GstPlayBin3 * playbin);
 
 static GstElementClass *parent_class;
 
@@ -839,6 +740,7 @@ gst_play_bin3_class_init (GstPlayBin3Class * klass)
           "Buffer size when buffering network streams",
           -1, G_MAXINT, DEFAULT_BUFFER_SIZE,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
   g_object_class_install_property (gobject_klass, PROP_BUFFER_DURATION,
       g_param_spec_int64 ("buffer-duration", "Buffer duration (ns)",
           "Buffer duration when buffering network streams",
@@ -928,6 +830,19 @@ gst_play_bin3_class_init (GstPlayBin3Class * klass)
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
   /**
+   * GstPlayBin3:instant-uri:
+   *
+   * Changes to uri are applied immediately, instead of on EOS or when the
+   * element is set back to PLAYING.
+   *
+   * Since: 1.22
+   */
+  g_object_class_install_property (gobject_klass, PROP_INSTANT_URI,
+      g_param_spec_boolean ("instant-uri", "Instantaneous URI change",
+          "When enabled, URI changes are applied immediately", FALSE,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
    * GstPlayBin3::about-to-finish
    * @playbin: a #GstPlayBin3
    *
@@ -1026,6 +941,8 @@ do_async_start (GstPlayBin3 * playbin)
 
   playbin->async_pending = TRUE;
 
+  GST_DEBUG_OBJECT (playbin, "posting ASYNC_START");
+
   message = gst_message_new_async_start (GST_OBJECT_CAST (playbin));
   GST_BIN_CLASS (parent_class)->handle_message (GST_BIN_CAST (playbin),
       message);
@@ -1072,10 +989,7 @@ init_combiners (GstPlayBin3 * playbin)
       g_ptr_array_new_with_free_func ((GDestroyNotify) gst_object_unref);
 }
 
-/* Update the combiner information to be in sync with the current collection
- *
- * FIXME : "current" collection doesn't mean anything until we have a "combined"
- *  collection of all groups */
+/* Update the combiner information to be in sync with the current collection */
 static void
 update_combiner_info (GstPlayBin3 * playbin, GstStreamCollection * collection)
 {
@@ -1122,57 +1036,6 @@ update_combiner_info (GstPlayBin3 * playbin, GstStreamCollection * collection)
       playbin->combiner[PLAYBIN_STREAM_TEXT].streams->len);
 }
 
-#ifndef GST_DISABLE_GST_DEBUG
-#define debug_groups(playbin) G_STMT_START {   \
-    guint i;                                   \
-                                               \
-    for (i = 0; i < 2; i++) {                          \
-      GstSourceGroup *group = &playbin->groups[i];     \
-                                                       \
-      GST_DEBUG ("GstSourceGroup #%d (%s) : %s", i, (group == playbin->curr_group) ? "current" : (group == playbin->next_group) ? "next" : "unused", \
-                group->uridecodebin ? GST_ELEMENT_NAME (group->uridecodebin) : "NULL" ); \
-      GST_DEBUG ("  valid:%d , active:%d , playing:%d", group->valid, group->active, group->playing); \
-      GST_DEBUG ("  uri:%s", group->uri);                              \
-      GST_DEBUG ("  suburi:%s", group->suburi);                                \
-      GST_DEBUG ("  group_id:%d", group->group_id);                    \
-      GST_DEBUG ("  pending_about_to_finish:%d", group->pending_about_to_finish); \
-    }                                                                  \
-  } G_STMT_END
-#else
-#define debug_groups(p) {}
-#endif
-
-static void
-init_group (GstPlayBin3 * playbin, GstSourceGroup * group)
-{
-  g_mutex_init (&group->lock);
-
-  group->stream_changed_pending = FALSE;
-  group->group_id = GST_GROUP_ID_INVALID;
-
-  group->playbin = playbin;
-}
-
-static void
-free_group (GstPlayBin3 * playbin, GstSourceGroup * group)
-{
-  g_free (group->uri);
-  g_free (group->suburi);
-
-  g_mutex_clear (&group->lock);
-  group->stream_changed_pending = FALSE;
-
-  if (group->pending_buffering_msg)
-    gst_message_unref (group->pending_buffering_msg);
-  group->pending_buffering_msg = NULL;
-
-  gst_object_replace ((GstObject **) & group->collection, NULL);
-
-  gst_object_replace ((GstObject **) & group->audio_sink, NULL);
-  gst_object_replace ((GstObject **) & group->video_sink, NULL);
-  gst_object_replace ((GstObject **) & group->text_sink, NULL);
-}
-
 static void
 notify_volume_cb (GObject * combiner, GParamSpec * pspec, GstPlayBin3 * playbin)
 {
@@ -1196,22 +1059,30 @@ static void
 gst_play_bin3_init (GstPlayBin3 * playbin)
 {
   g_rec_mutex_init (&playbin->lock);
-  g_mutex_init (&playbin->dyn_lock);
-
-  g_mutex_init (&playbin->buffering_post_lock);
 
   /* assume we can create an input-selector */
   playbin->have_selector = TRUE;
 
   init_combiners (playbin);
 
-  /* init groups */
-  playbin->curr_group = &playbin->groups[0];
-  playbin->next_group = &playbin->groups[1];
-  init_group (playbin, &playbin->groups[0]);
-  init_group (playbin, &playbin->groups[1]);
-
-  g_rec_mutex_init (&playbin->activation_lock);
+  /* Create uridecodebin3 */
+  playbin->uridecodebin =
+      gst_element_factory_make ("uridecodebin3", "uridecodebin3");
+  /* Set default property (based on default flags value) */
+  g_object_set (playbin->uridecodebin, "use-buffering", TRUE, NULL);
+  gst_bin_add (GST_BIN_CAST (playbin),
+      GST_ELEMENT_CAST (playbin->uridecodebin));
+
+  g_signal_connect (playbin->uridecodebin, "pad-added",
+      G_CALLBACK (pad_added_cb), playbin);
+  g_signal_connect (playbin->uridecodebin, "pad-removed",
+      G_CALLBACK (pad_removed_cb), playbin);
+  g_signal_connect (playbin->uridecodebin, "select-stream",
+      G_CALLBACK (select_stream_cb), playbin);
+  g_signal_connect (playbin->uridecodebin, "source-setup",
+      G_CALLBACK (source_setup_cb), playbin);
+  g_signal_connect (playbin->uridecodebin, "about-to-finish",
+      G_CALLBACK (about_to_finish_cb), playbin);
 
   /* add sink */
   playbin->playsink =
@@ -1231,10 +1102,6 @@ gst_play_bin3_init (GstPlayBin3 * playbin)
   playbin->current_audio = DEFAULT_CURRENT_AUDIO;
   playbin->current_text = DEFAULT_CURRENT_TEXT;
 
-  playbin->buffer_duration = DEFAULT_BUFFER_DURATION;
-  playbin->buffer_size = DEFAULT_BUFFER_SIZE;
-  playbin->ring_buffer_max_size = DEFAULT_RING_BUFFER_MAX_SIZE;
-
   playbin->force_aspect_ratio = TRUE;
 
   playbin->multiview_mode = GST_VIDEO_MULTIVIEW_FRAME_PACKING_NONE;
@@ -1250,9 +1117,6 @@ gst_play_bin3_finalize (GObject * object)
 
   playbin = GST_PLAY_BIN3 (object);
 
-  free_group (playbin, &playbin->groups[0]);
-  free_group (playbin, &playbin->groups[1]);
-
   /* Setting states to NULL is safe here because playsink
    * will already be gone and none of these sinks will be
    * a child of playsink
@@ -1290,12 +1154,8 @@ gst_play_bin3_finalize (GObject * object)
   g_ptr_array_free (playbin->combiner[PLAYBIN_STREAM_TEXT].streams, TRUE);
   g_ptr_array_free (playbin->combiner[PLAYBIN_STREAM_TEXT].inputpads, TRUE);
 
-  g_rec_mutex_clear (&playbin->activation_lock);
   g_rec_mutex_clear (&playbin->lock);
 
-  g_mutex_clear (&playbin->buffering_post_lock);
-  g_mutex_clear (&playbin->dyn_lock);
-
   G_OBJECT_CLASS (parent_class)->finalize (object);
 }
 
@@ -1330,9 +1190,12 @@ invalid:
 static void
 gst_play_bin3_set_uri (GstPlayBin3 * playbin, const gchar * uri)
 {
-  GstSourceGroup *group;
+  if (uri == NULL) {
+    g_warning ("cannot set NULL uri");
+    return;
+  }
 
-  if (uri && !gst_playbin_uri_is_valid (playbin, uri)) {
+  if (!gst_playbin_uri_is_valid (playbin, uri)) {
     if (g_str_has_prefix (uri, "file:")) {
       GST_WARNING_OBJECT (playbin, "not entirely correct file URI '%s' - make "
           "sure to escape spaces and non-ASCII characters properly and specify "
@@ -1343,41 +1206,17 @@ gst_play_bin3_set_uri (GstPlayBin3 * playbin, const gchar * uri)
     }
   }
 
-  GST_PLAY_BIN3_LOCK (playbin);
-  group = playbin->next_group;
-
-  GST_SOURCE_GROUP_LOCK (group);
-  /* store the uri in the next group we will play */
-  g_free (group->uri);
-  if (uri) {
-    group->uri = g_strdup (uri);
-    group->valid = TRUE;
-  } else {
-    group->uri = NULL;
-    group->valid = FALSE;
-  }
-  GST_SOURCE_GROUP_UNLOCK (group);
+  g_object_set (playbin->uridecodebin, "uri", uri, NULL);
 
   GST_DEBUG ("set new uri to %s", GST_STR_NULL (uri));
-  GST_PLAY_BIN3_UNLOCK (playbin);
 }
 
 static void
 gst_play_bin3_set_suburi (GstPlayBin3 * playbin, const gchar * suburi)
 {
-  GstSourceGroup *group;
-
-  GST_PLAY_BIN3_LOCK (playbin);
-  group = playbin->next_group;
-
-  GST_SOURCE_GROUP_LOCK (group);
-  g_free (group->suburi);
-  group->suburi = g_strdup (suburi);
-  GST_SOURCE_GROUP_UNLOCK (group);
+  g_object_set (playbin->uridecodebin, "suburi", suburi, NULL);
 
   GST_DEBUG ("setting new .sub uri to %s", suburi);
-
-  GST_PLAY_BIN3_UNLOCK (playbin);
 }
 
 static void
@@ -1390,32 +1229,12 @@ gst_play_bin3_set_flags (GstPlayBin3 * playbin, GstPlayFlags flags)
     gst_play_sink_set_flags (playbin->playsink, flags);
     gst_play_sink_reconfigure (playbin->playsink);
   }
+  g_object_set (playbin->uridecodebin, "download",
+      ((flags & GST_PLAY_FLAG_DOWNLOAD) != 0),
+      /* configure buffering of demuxed/parsed data */
+      "use-buffering", ((flags & GST_PLAY_FLAG_BUFFERING) != 0), NULL);
 }
 
-static GstPlayFlags
-gst_play_bin3_get_flags (GstPlayBin3 * playbin)
-{
-  GstPlayFlags flags;
-
-  flags = gst_play_sink_get_flags (playbin->playsink);
-
-  return flags;
-}
-
-/* get the currently playing group or if nothing is playing, the next
- * group. Must be called with the PLAY_BIN_LOCK. */
-static GstSourceGroup *
-get_group (GstPlayBin3 * playbin)
-{
-  GstSourceGroup *result;
-
-  if (!(result = playbin->curr_group))
-    result = playbin->next_group;
-
-  return result;
-}
-
-
 static GstSample *
 gst_play_bin3_convert_sample (GstPlayBin3 * playbin, GstCaps * caps)
 {
@@ -1463,10 +1282,10 @@ gst_play_bin3_set_current_stream (GstPlayBin3 * playbin,
   GST_DEBUG_OBJECT (playbin, "Changing current %s stream %d -> %d",
       stream_type_names[stream_type], *current_value, stream);
 
-  if (combine->combiner == NULL || combine->is_concat) {
+  if (combine->combiner == NULL) {
     /* FIXME: Check that the current_value is within range */
     *current_value = stream;
-    do_stream_selection (playbin, playbin->curr_group);
+    do_stream_selection (playbin);
     GST_PLAY_BIN3_UNLOCK (playbin);
     return TRUE;
   }
@@ -1618,14 +1437,6 @@ gst_play_bin3_set_property (GObject * object, guint prop_id,
       break;
     case PROP_FLAGS:
       gst_play_bin3_set_flags (playbin, g_value_get_flags (value));
-      if (playbin->curr_group) {
-        GST_SOURCE_GROUP_LOCK (playbin->curr_group);
-        if (playbin->curr_group->uridecodebin) {
-          g_object_set (playbin->curr_group->uridecodebin, "download",
-              (g_value_get_flags (value) & GST_PLAY_FLAG_DOWNLOAD) != 0, NULL);
-        }
-        GST_SOURCE_GROUP_UNLOCK (playbin->curr_group);
-      }
       break;
     case PROP_SUBTITLE_ENCODING:
       gst_play_bin3_set_encoding (playbin, g_value_get_string (value));
@@ -1678,14 +1489,17 @@ gst_play_bin3_set_property (GObject * object, guint prop_id,
       break;
     case PROP_CONNECTION_SPEED:
       GST_PLAY_BIN3_LOCK (playbin);
-      playbin->connection_speed = g_value_get_uint64 (value) * 1000;
+      g_object_set_property ((GObject *) playbin->uridecodebin,
+          "connection-speed", value);
       GST_PLAY_BIN3_UNLOCK (playbin);
       break;
     case PROP_BUFFER_SIZE:
-      playbin->buffer_size = g_value_get_int (value);
+      g_object_set_property ((GObject *) playbin->uridecodebin, "buffer-size",
+          value);
       break;
     case PROP_BUFFER_DURATION:
-      playbin->buffer_duration = g_value_get_int64 (value);
+      g_object_set_property ((GObject *) playbin->uridecodebin,
+          "buffer-duration", value);
       break;
     case PROP_AV_OFFSET:
       gst_play_sink_set_av_offset (playbin->playsink,
@@ -1696,15 +1510,8 @@ gst_play_bin3_set_property (GObject * object, guint prop_id,
           g_value_get_int64 (value));
       break;
     case PROP_RING_BUFFER_MAX_SIZE:
-      playbin->ring_buffer_max_size = g_value_get_uint64 (value);
-      if (playbin->curr_group) {
-        GST_SOURCE_GROUP_LOCK (playbin->curr_group);
-        if (playbin->curr_group->uridecodebin) {
-          g_object_set (playbin->curr_group->uridecodebin,
-              "ring-buffer-max-size", playbin->ring_buffer_max_size, NULL);
-        }
-        GST_SOURCE_GROUP_UNLOCK (playbin->curr_group);
-      }
+      g_object_set_property ((GObject *) playbin->uridecodebin,
+          "ring-buffer-max-size", value);
       break;
     case PROP_FORCE_ASPECT_RATIO:
       g_object_set (playbin->playsink, "force-aspect-ratio",
@@ -1720,6 +1527,10 @@ gst_play_bin3_set_property (GObject * object, guint prop_id,
       playbin->multiview_flags = g_value_get_flags (value);
       GST_PLAY_BIN3_UNLOCK (playbin);
       break;
+    case PROP_INSTANT_URI:
+      g_object_set_property ((GObject *) playbin->uridecodebin,
+          "instant-uri", value);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -1746,25 +1557,6 @@ gst_play_bin3_get_current_sink (GstPlayBin3 * playbin, GstElement ** elem,
   return sink;
 }
 
-static GstElement *
-gst_play_bin3_get_current_stream_combiner (GstPlayBin3 * playbin,
-    GstElement ** elem, const gchar * dbg, int stream_type)
-{
-  GstElement *combiner;
-
-  GST_PLAY_BIN3_LOCK (playbin);
-  /* The special concat element should never be returned */
-  if (playbin->combiner[stream_type].is_concat)
-    combiner = NULL;
-  else if ((combiner = playbin->combiner[stream_type].combiner))
-    gst_object_ref (combiner);
-  else if ((combiner = *elem))
-    gst_object_ref (combiner);
-  GST_PLAY_BIN3_UNLOCK (playbin);
-
-  return combiner;
-}
-
 static void
 gst_play_bin3_get_property (GObject * object, guint prop_id, GValue * value,
     GParamSpec * pspec)
@@ -1774,46 +1566,37 @@ gst_play_bin3_get_property (GObject * object, guint prop_id, GValue * value,
   switch (prop_id) {
     case PROP_URI:
     {
-      GstSourceGroup *group;
-
       GST_PLAY_BIN3_LOCK (playbin);
-      group = playbin->next_group;
-      g_value_set_string (value, group->uri);
+      g_object_get_property ((GObject *) playbin->uridecodebin, "uri", value);
       GST_PLAY_BIN3_UNLOCK (playbin);
       break;
     }
     case PROP_CURRENT_URI:
     {
-      GstSourceGroup *group;
-
       GST_PLAY_BIN3_LOCK (playbin);
-      group = get_group (playbin);
-      g_value_set_string (value, group->uri);
+      g_object_get_property ((GObject *) playbin->uridecodebin, "current-uri",
+          value);
       GST_PLAY_BIN3_UNLOCK (playbin);
       break;
     }
     case PROP_SUBURI:
     {
-      GstSourceGroup *group;
-
       GST_PLAY_BIN3_LOCK (playbin);
-      group = playbin->next_group;
-      g_value_set_string (value, group->suburi);
+      g_object_get_property ((GObject *) playbin->uridecodebin, "suburi",
+          value);
       GST_PLAY_BIN3_UNLOCK (playbin);
       break;
     }
     case PROP_CURRENT_SUBURI:
     {
-      GstSourceGroup *group;
-
       GST_PLAY_BIN3_LOCK (playbin);
-      group = get_group (playbin);
-      g_value_set_string (value, group->suburi);
+      g_object_get_property ((GObject *) playbin->uridecodebin,
+          "current-suburi", value);
       GST_PLAY_BIN3_UNLOCK (playbin);
       break;
     }
     case PROP_FLAGS:
-      g_value_set_flags (value, gst_play_bin3_get_flags (playbin));
+      g_value_set_flags (value, gst_play_sink_get_flags (playbin->playsink));
       break;
     case PROP_SUBTITLE_ENCODING:
       GST_PLAY_BIN3_LOCK (playbin);
@@ -1851,19 +1634,13 @@ gst_play_bin3_get_property (GObject * object, guint prop_id, GValue * value,
               "text", GST_PLAY_SINK_TYPE_TEXT));
       break;
     case PROP_VIDEO_STREAM_COMBINER:
-      g_value_take_object (value,
-          gst_play_bin3_get_current_stream_combiner (playbin,
-              &playbin->video_stream_combiner, "video", PLAYBIN_STREAM_VIDEO));
+      g_value_set_object (value, playbin->video_stream_combiner);
       break;
     case PROP_AUDIO_STREAM_COMBINER:
-      g_value_take_object (value,
-          gst_play_bin3_get_current_stream_combiner (playbin,
-              &playbin->audio_stream_combiner, "audio", PLAYBIN_STREAM_AUDIO));
+      g_value_set_object (value, playbin->audio_stream_combiner);
       break;
     case PROP_TEXT_STREAM_COMBINER:
-      g_value_take_object (value,
-          gst_play_bin3_get_current_stream_combiner (playbin,
-              &playbin->text_stream_combiner, "text", PLAYBIN_STREAM_TEXT));
+      g_value_set_object (value, playbin->text_stream_combiner);
       break;
     case PROP_VOLUME:
       g_value_set_double (value, gst_play_sink_get_volume (playbin->playsink));
@@ -1881,17 +1658,20 @@ gst_play_bin3_get_property (GObject * object, guint prop_id, GValue * value,
       break;
     case PROP_CONNECTION_SPEED:
       GST_PLAY_BIN3_LOCK (playbin);
-      g_value_set_uint64 (value, playbin->connection_speed / 1000);
+      g_object_get_property ((GObject *) playbin->uridecodebin,
+          "connection-speed", value);
       GST_PLAY_BIN3_UNLOCK (playbin);
       break;
     case PROP_BUFFER_SIZE:
       GST_OBJECT_LOCK (playbin);
-      g_value_set_int (value, playbin->buffer_size);
+      g_object_get_property ((GObject *) playbin->uridecodebin, "buffer-size",
+          value);
       GST_OBJECT_UNLOCK (playbin);
       break;
     case PROP_BUFFER_DURATION:
       GST_OBJECT_LOCK (playbin);
-      g_value_set_int64 (value, playbin->buffer_duration);
+      g_object_get_property ((GObject *) playbin->uridecodebin,
+          "buffer-duration", value);
       GST_OBJECT_UNLOCK (playbin);
       break;
     case PROP_AV_OFFSET:
@@ -1903,7 +1683,8 @@ gst_play_bin3_get_property (GObject * object, guint prop_id, GValue * value,
           gst_play_sink_get_text_offset (playbin->playsink));
       break;
     case PROP_RING_BUFFER_MAX_SIZE:
-      g_value_set_uint64 (value, playbin->ring_buffer_max_size);
+      g_object_get_property ((GObject *) playbin->uridecodebin,
+          "ring-buffer-max-size", value);
       break;
     case PROP_FORCE_ASPECT_RATIO:{
       gboolean v;
@@ -1922,6 +1703,10 @@ gst_play_bin3_get_property (GObject * object, guint prop_id, GValue * value,
       g_value_set_flags (value, playbin->multiview_flags);
       GST_OBJECT_UNLOCK (playbin);
       break;
+    case PROP_INSTANT_URI:
+      g_object_get_property ((GObject *) playbin->uridecodebin,
+          "instant-uri", value);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -1983,8 +1768,7 @@ extend_list_of_streams (GstPlayBin3 * playbin, GstStreamType stype,
 }
 
 static GstEvent *
-update_select_streams_event (GstPlayBin3 * playbin, GstEvent * event,
-    GstSourceGroup * group)
+update_select_streams_event (GstPlayBin3 * playbin, GstEvent * event)
 {
   GList *streams = NULL;
   GList *to_use;
@@ -1998,9 +1782,9 @@ update_select_streams_event (GstPlayBin3 * playbin, GstEvent * event,
     return event;
   }
 
-  if (!group->collection) {
+  if (!playbin->collection) {
     GST_DEBUG_OBJECT (playbin,
-        "No stream collection for group, no need to modify SELECT_STREAMS event");
+        "No stream collection, no need to modify SELECT_STREAMS event");
     return event;
   }
 
@@ -2012,7 +1796,7 @@ update_select_streams_event (GstPlayBin3 * playbin, GstEvent * event,
   if (playbin->audio_stream_combiner) {
     to_use =
         extend_list_of_streams (playbin, GST_STREAM_TYPE_AUDIO, to_use,
-        group->collection);
+        playbin->collection);
     combine_id =
         get_combiner_stream_id (playbin,
         &playbin->combiner[PLAYBIN_STREAM_AUDIO], streams);
@@ -2022,7 +1806,7 @@ update_select_streams_event (GstPlayBin3 * playbin, GstEvent * event,
   if (playbin->video_stream_combiner) {
     to_use =
         extend_list_of_streams (playbin, GST_STREAM_TYPE_VIDEO, to_use,
-        group->collection);
+        playbin->collection);
     combine_id =
         get_combiner_stream_id (playbin,
         &playbin->combiner[PLAYBIN_STREAM_VIDEO], streams);
@@ -2032,7 +1816,7 @@ update_select_streams_event (GstPlayBin3 * playbin, GstEvent * event,
   if (playbin->text_stream_combiner) {
     to_use =
         extend_list_of_streams (playbin, GST_STREAM_TYPE_TEXT, to_use,
-        group->collection);
+        playbin->collection);
     combine_id =
         get_combiner_stream_id (playbin,
         &playbin->combiner[PLAYBIN_STREAM_TEXT], streams);
@@ -2051,57 +1835,6 @@ update_select_streams_event (GstPlayBin3 * playbin, GstEvent * event,
   return event;
 }
 
-/* Returns TRUE if the given list of streams belongs to the stream collection */
-static gboolean
-gst_streams_belong_to_collection (GList * streams,
-    GstStreamCollection * collection)
-{
-  GList *tmp;
-  guint i, nb;
-
-  if (streams == NULL || collection == NULL)
-    return FALSE;
-  nb = gst_stream_collection_get_size (collection);
-  if (nb == 0)
-    return FALSE;
-
-  for (tmp = streams; tmp; tmp = tmp->next) {
-    const gchar *cand = (const gchar *) tmp->data;
-    gboolean found = FALSE;
-
-    for (i = 0; i < nb; i++) {
-      GstStream *stream = gst_stream_collection_get_stream (collection, i);
-      if (!g_strcmp0 (cand, gst_stream_get_stream_id (stream))) {
-        found = TRUE;
-        break;
-      }
-    }
-    if (!found)
-      return FALSE;
-  }
-  return TRUE;
-}
-
-static GstSourceGroup *
-get_source_group_for_streams (GstPlayBin3 * playbin, GstEvent * event)
-{
-  GList *streams;
-  GstSourceGroup *res = NULL;
-
-  gst_event_parse_select_streams (event, &streams);
-  if (playbin->curr_group->collection &&
-      gst_streams_belong_to_collection (streams,
-          playbin->curr_group->collection))
-    res = playbin->curr_group;
-  else if (playbin->next_group->collection &&
-      gst_streams_belong_to_collection (streams,
-          playbin->next_group->collection))
-    res = playbin->next_group;
-  g_list_free_full (streams, g_free);
-
-  return res;
-}
-
 static gboolean
 gst_play_bin3_send_event (GstElement * element, GstEvent * event)
 {
@@ -2109,7 +1842,6 @@ gst_play_bin3_send_event (GstElement * element, GstEvent * event)
 
   if (GST_EVENT_TYPE (event) == GST_EVENT_SELECT_STREAMS) {
     gboolean res;
-    GstSourceGroup *group;
 
     GST_PLAY_BIN3_LOCK (playbin);
     GST_LOG_OBJECT (playbin,
@@ -2117,25 +1849,17 @@ gst_play_bin3_send_event (GstElement * element, GstEvent * event)
     /* This is probably already false, but it doesn't hurt to be sure */
     playbin->do_stream_selections = FALSE;
 
-    group = get_source_group_for_streams (playbin, event);
-    if (group == NULL) {
-      GST_WARNING_OBJECT (playbin,
-          "Can't figure out to which uridecodebin the select-streams event should be sent to");
-      GST_PLAY_BIN3_UNLOCK (playbin);
-      return FALSE;
-    }
-
     /* If we have custom combiners, we need to extend the selection with
      * the list of all streams for that given type since we will be handling
      * the selection with that combiner */
-    event = update_select_streams_event (playbin, event, group);
+    event = update_select_streams_event (playbin, event);
 
     /* Don't reconfigure playsink just yet, until the streams-selected
      * message(s) tell us as streams become active / available */
 
     /* Send this event directly to uridecodebin, so it works even
      * if uridecodebin didn't add any pads yet */
-    res = gst_element_send_event (group->uridecodebin, event);
+    res = gst_element_send_event (playbin->uridecodebin, event);
     GST_PLAY_BIN3_UNLOCK (playbin);
 
     return res;
@@ -2159,7 +1883,7 @@ gst_play_bin3_send_event (GstElement * element, GstEvent * event)
 
 /* Called with playbin lock held */
 static void
-do_stream_selection (GstPlayBin3 * playbin, GstSourceGroup * group)
+do_stream_selection (GstPlayBin3 * playbin)
 {
   GstStreamCollection *collection;
   guint i, nb_streams;
@@ -2167,10 +1891,7 @@ do_stream_selection (GstPlayBin3 * playbin, GstSourceGroup * group)
   gint nb_video = 0, nb_audio = 0, nb_text = 0;
   GstStreamType chosen_stream_types = 0;
 
-  if (group == NULL)
-    return;
-
-  collection = group->collection;
+  collection = playbin->collection;
   if (collection == NULL) {
     GST_LOG_OBJECT (playbin, "No stream collection. Not doing stream-select");
     return;
@@ -2236,168 +1957,43 @@ do_stream_selection (GstPlayBin3 * playbin, GstSourceGroup * group)
   }
 
   if (streams) {
-    if (group->uridecodebin) {
-      GstEvent *ev = gst_event_new_select_streams (streams);
-      gst_element_send_event (group->uridecodebin, ev);
-    }
+    GstEvent *ev = gst_event_new_select_streams (streams);
+    gst_element_send_event ((GstElement *) playbin->collection_source, ev);
     g_list_free (streams);
   }
 
-  group->selected_stream_types = chosen_stream_types;
-  /* Update global selected_stream_types */
-  playbin->selected_stream_types =
-      playbin->groups[0].selected_stream_types | playbin->groups[1].
-      selected_stream_types;
+  playbin->selected_stream_types = chosen_stream_types;
   if (playbin->active_stream_types != playbin->selected_stream_types)
     reconfigure_output (playbin);
 }
 
-/* Return the GstSourceGroup to which this element belongs
- * Can be NULL (if it belongs to playsink for example) */
-static GstSourceGroup *
-find_source_group_owner (GstPlayBin3 * playbin, GstObject * element)
-{
-  if (playbin->curr_group->uridecodebin
-      && gst_object_has_as_ancestor (element,
-          GST_OBJECT_CAST (playbin->curr_group->uridecodebin)))
-    return playbin->curr_group;
-  if (playbin->next_group->uridecodebin
-      && gst_object_has_as_ancestor (element,
-          GST_OBJECT_CAST (playbin->next_group->uridecodebin)))
-    return playbin->next_group;
-  return NULL;
-}
-
 static void
 gst_play_bin3_handle_message (GstBin * bin, GstMessage * msg)
 {
   GstPlayBin3 *playbin = GST_PLAY_BIN3 (bin);
   gboolean do_reset_time = FALSE;
 
-  if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_STREAM_START) {
-    GstSourceGroup *group = NULL, *other_group = NULL;
-    gboolean changed = FALSE;
-    guint group_id;
-    GstMessage *buffering_msg;
-
-    if (!gst_message_parse_group_id (msg, &group_id)) {
-      GST_ERROR_OBJECT (bin,
-          "Could not get group_id from STREAM_START message !");
-      goto beach;
-    }
-    GST_DEBUG_OBJECT (bin, "STREAM_START group_id:%u", group_id);
-
-    /* Figure out to which group this group_id corresponds */
-    GST_PLAY_BIN3_LOCK (playbin);
-    if (playbin->groups[0].group_id == group_id) {
-      group = &playbin->groups[0];
-      other_group = &playbin->groups[1];
-    } else if (playbin->groups[1].group_id == group_id) {
-      group = &playbin->groups[1];
-      other_group = &playbin->groups[0];
-    }
-    if (group == NULL) {
-      GST_ERROR_OBJECT (bin, "group_id %u is not provided by any group !",
-          group_id);
-      GST_PLAY_BIN3_UNLOCK (playbin);
-      goto beach;
-    }
-
-    debug_groups (playbin);
-
-    /* Do the switch now ! */
-    playbin->curr_group = group;
-    playbin->next_group = other_group;
-
-    /* we may need to serialise a buffering
-     * message, and need to take that lock
-     * before any source group lock, so
-     * do that now */
-    g_mutex_lock (&playbin->buffering_post_lock);
-
-    GST_SOURCE_GROUP_LOCK (group);
-    if (group->playing == FALSE)
-      changed = TRUE;
-    group->playing = TRUE;
-
-    buffering_msg = group->pending_buffering_msg;
-    group->pending_buffering_msg = NULL;
-
-    GST_SOURCE_GROUP_UNLOCK (group);
-
-    GST_SOURCE_GROUP_LOCK (other_group);
-    other_group->playing = FALSE;
-    GST_SOURCE_GROUP_UNLOCK (other_group);
-
-    debug_groups (playbin);
-    GST_PLAY_BIN3_UNLOCK (playbin);
-    if (changed)
-      gst_play_bin3_check_group_status (playbin);
-    else
-      GST_DEBUG_OBJECT (bin, "Groups didn't changed");
-
-    /* If there was a pending buffering message to send, do it now */
-    if (buffering_msg)
-      GST_BIN_CLASS (parent_class)->handle_message (bin, buffering_msg);
-
-    g_mutex_unlock (&playbin->buffering_post_lock);
-
-  } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_BUFFERING) {
-    GstSourceGroup *group;
-
-    /* Only post buffering messages for group which is currently playing */
-    GST_PLAY_BIN3_LOCK (playbin);
-    group = find_source_group_owner (playbin, msg->src);
-    if (group->active) {
-      g_mutex_lock (&playbin->buffering_post_lock);
-
-      GST_SOURCE_GROUP_LOCK (group);
-      GST_PLAY_BIN3_UNLOCK (playbin);
-
-      if (!group->playing) {
-        GST_DEBUG_OBJECT (playbin,
-            "Storing buffering message from pending group " "%p %"
-            GST_PTR_FORMAT, group, msg);
-        gst_message_replace (&group->pending_buffering_msg, msg);
-        gst_message_unref (msg);
-        msg = NULL;
-      } else {
-        /* Ensure there's no cached buffering message for this group */
-        gst_message_replace (&group->pending_buffering_msg, NULL);
-      }
-      GST_SOURCE_GROUP_UNLOCK (group);
-
-      if (msg != NULL) {
-        GST_BIN_CLASS (parent_class)->handle_message (bin, msg);
-        msg = NULL;
-      }
-      g_mutex_unlock (&playbin->buffering_post_lock);
-    } else {
-      GST_PLAY_BIN3_UNLOCK (playbin);
-    }
-  } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_STREAM_COLLECTION) {
+  if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_STREAM_COLLECTION) {
     GstStreamCollection *collection = NULL;
 
     gst_message_parse_stream_collection (msg, &collection);
 
     if (collection) {
       gboolean pstate = playbin->do_stream_selections;
-      GstSourceGroup *target_group = NULL;
 
       GST_PLAY_BIN3_LOCK (playbin);
       GST_DEBUG_OBJECT (playbin,
           "STREAM_COLLECTION: Got a collection from %" GST_PTR_FORMAT,
           msg->src);
-      target_group = find_source_group_owner (playbin, msg->src);
-      if (target_group)
-        gst_object_replace ((GstObject **) & target_group->collection,
-            (GstObject *) collection);
-      /* FIXME: Only do the following if it's the current group? */
-      if (target_group == playbin->curr_group)
-        update_combiner_info (playbin, target_group->collection);
+      gst_object_replace ((GstObject **) & playbin->collection,
+          (GstObject *) collection);
+      gst_object_replace ((GstObject **) & playbin->collection_source,
+          (GstObject *) GST_MESSAGE_SRC (msg));
+
+      update_combiner_info (playbin, playbin->collection);
       if (pstate)
         playbin->do_stream_selections = FALSE;
-      do_stream_selection (playbin, target_group);
+      do_stream_selection (playbin);
       if (pstate)
         playbin->do_stream_selections = TRUE;
       GST_PLAY_BIN3_UNLOCK (playbin);
@@ -2409,34 +2005,26 @@ gst_play_bin3_handle_message (GstBin * bin, GstMessage * msg)
       do_reset_time = TRUE;
     }
   } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_STREAMS_SELECTED) {
-    GstSourceGroup *target_group;
+    GstStreamType selected_types = 0;
+    guint i, nb;
 
     GST_PLAY_BIN3_LOCK (playbin);
 
-    target_group = find_source_group_owner (playbin, msg->src);
-    if (target_group) {
-      GstStreamType selected_types = 0;
-      guint i, nb;
-      nb = gst_message_streams_selected_get_size (msg);
-      for (i = 0; i < nb; i++) {
-        GstStream *stream = gst_message_streams_selected_get_stream (msg, i);
-        selected_types |= gst_stream_get_stream_type (stream);
-        gst_object_unref (stream);
-      }
-      target_group->selected_stream_types = selected_types;
-      playbin->selected_stream_types =
-          playbin->groups[0].selected_stream_types | playbin->groups[1].
-          selected_stream_types;
-      if (playbin->active_stream_types != playbin->selected_stream_types) {
-        GST_DEBUG_OBJECT (playbin,
-            "selected stream types changed, reconfiguring output");
-        reconfigure_output (playbin);
-      }
+    nb = gst_message_streams_selected_get_size (msg);
+    for (i = 0; i < nb; i++) {
+      GstStream *stream = gst_message_streams_selected_get_stream (msg, i);
+      selected_types |= gst_stream_get_stream_type (stream);
+      gst_object_unref (stream);
+    }
+    playbin->selected_stream_types = selected_types;
+    if (playbin->active_stream_types != playbin->selected_stream_types) {
+      GST_DEBUG_OBJECT (playbin,
+          "selected stream types changed, reconfiguring output");
+      reconfigure_output (playbin);
     }
     GST_PLAY_BIN3_UNLOCK (playbin);
   }
 
-beach:
   if (msg)
     GST_BIN_CLASS (parent_class)->handle_message (bin, msg);
 
@@ -2600,28 +2188,12 @@ update_video_multiview_caps (GstPlayBin3 * playbin, GstCaps * caps)
   return out_caps;
 }
 
-static void
-emit_about_to_finish (GstPlayBin3 * playbin)
-{
-  GST_DEBUG_OBJECT (playbin, "Emitting about-to-finish");
-
-  /* after this call, we should have a next group to activate or we EOS */
-  g_signal_emit (G_OBJECT (playbin),
-      gst_play_bin3_signals[SIGNAL_ABOUT_TO_FINISH], 0, NULL);
-
-  debug_groups (playbin);
-
-  /* now activate the next group. If the app did not set a uri, this will
-   * fail and we can do EOS */
-  setup_next_source (playbin);
-}
-
 static SourcePad *
-find_source_pad (GstSourceGroup * group, GstPad * target)
+find_source_pad (GstPlayBin3 * playbin, GstPad * target)
 {
   GList *tmp;
 
-  for (tmp = group->source_pads; tmp; tmp = tmp->next) {
+  for (tmp = playbin->source_pads; tmp; tmp = tmp->next) {
     SourcePad *res = (SourcePad *) tmp->data;
     if (res->pad == target)
       return res;
@@ -2630,11 +2202,10 @@ find_source_pad (GstSourceGroup * group, GstPad * target)
 }
 
 static GstPadProbeReturn
-_decodebin_event_probe (GstPad * pad, GstPadProbeInfo * info, gpointer udata)
+_decodebin_event_probe (GstPad * pad, GstPadProbeInfo * info,
+    GstPlayBin3 * playbin)
 {
   GstPadProbeReturn ret = GST_PAD_PROBE_OK;
-  GstSourceGroup *group = (GstSourceGroup *) udata;
-  GstPlayBin3 *playbin = group->playbin;
   GstEvent *event = GST_PAD_PROBE_INFO_DATA (info);
 
   switch (GST_EVENT_TYPE (event)) {
@@ -2658,21 +2229,6 @@ _decodebin_event_probe (GstPad * pad, GstPadProbeInfo * info, gpointer udata)
       }
       break;
     }
-    case GST_EVENT_STREAM_START:
-    {
-      guint group_id;
-      if (gst_event_parse_group_id (event, &group_id)) {
-        GST_LOG_OBJECT (pad, "STREAM_START group_id:%u", group_id);
-        if (group->group_id == GST_GROUP_ID_INVALID)
-          group->group_id = group_id;
-        else if (group->group_id != group_id) {
-          GST_DEBUG_OBJECT (pad, "group_id changing from %u to %u",
-              group->group_id, group_id);
-          group->group_id = group_id;
-        }
-      }
-      break;
-    }
     default:
       break;
   }
@@ -2681,7 +2237,7 @@ _decodebin_event_probe (GstPad * pad, GstPadProbeInfo * info, gpointer udata)
 }
 
 static void
-control_source_pad (GstSourceGroup * group, GstPad * pad,
+control_source_pad (GstPlayBin3 * playbin, GstPad * pad,
     GstPad * combine_pad, GstStreamType stream_type)
 {
   SourcePad *sourcepad = g_slice_new0 (SourcePad);
@@ -2689,10 +2245,10 @@ control_source_pad (GstSourceGroup * group, GstPad * pad,
   sourcepad->pad = pad;
   sourcepad->event_probe_id =
       gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
-      _decodebin_event_probe, group, NULL);
+      (GstPadProbeCallback) _decodebin_event_probe, playbin, NULL);
   sourcepad->stream_type = stream_type;
   sourcepad->combine_sinkpad = combine_pad;
-  group->source_pads = g_list_append (group->source_pads, sourcepad);
+  playbin->source_pads = g_list_append (playbin->source_pads, sourcepad);
 }
 
 static void
@@ -2740,32 +2296,21 @@ create_combiner (GstPlayBin3 * playbin, GstSourceCombine * combine)
   combine->combiner = custom_combiner;
 
   if (!combine->combiner) {
-    gchar *concat_name;
-    GST_DEBUG_OBJECT (playbin,
-        "No custom combiner requested, using 'concat' element");
-    concat_name =
-        g_strdup_printf ("%s-concat",
-        gst_stream_type_get_name (combine->stream_type));
-    combine->combiner = gst_element_factory_make ("concat", concat_name);
-    g_object_set (combine->combiner, "adjust-base", FALSE, NULL);
-    g_free (concat_name);
-    combine->is_concat = TRUE;
+    GST_DEBUG_OBJECT (playbin, "No custom combiner requested");
+    return;
   }
 
   combine->srcpad = gst_element_get_static_pad (combine->combiner, "src");
 
   /* We only want to use 'active-pad' if it's a regular combiner that
-   * will consume all streams, and not concat (which is just used for
-   * gapless) */
-  if (!combine->is_concat) {
-    combine->has_active_pad =
-        g_object_class_find_property (G_OBJECT_GET_CLASS (combine->combiner),
-        "active-pad") != NULL;
+   * will consume all streams */
+  combine->has_active_pad =
+      g_object_class_find_property (G_OBJECT_GET_CLASS (combine->combiner),
+      "active-pad") != NULL;
 
-    if (combine->has_active_pad)
-      g_signal_connect (combine->combiner, "notify::active-pad",
-          G_CALLBACK (combiner_active_pad_changed), playbin);
-  }
+  if (combine->has_active_pad)
+    g_signal_connect (combine->combiner, "notify::active-pad",
+        G_CALLBACK (combiner_active_pad_changed), playbin);
 
   GST_DEBUG_OBJECT (playbin, "adding new stream combiner %" GST_PTR_FORMAT,
       combine->combiner);
@@ -2848,17 +2393,13 @@ failed_combiner_link:
   return NULL;
 }
 
-
-/* Call after pad was unlinked from (potential) combiner */
 static void
-release_source_pad (GstPlayBin3 * playbin, GstSourceGroup * group,
+release_source_pad (GstPlayBin3 * playbin,
     GstSourceCombine * combine, GstPad * pad)
 {
   SourcePad *sourcepad;
-  GList *tmp;
-  GstStreamType alltype = 0;
 
-  sourcepad = find_source_pad (group, pad);
+  sourcepad = find_source_pad (playbin, pad);
   if (!sourcepad) {
     GST_DEBUG_OBJECT (playbin, "Not a pad controlled by us ?");
     return;
@@ -2876,32 +2417,22 @@ release_source_pad (GstPlayBin3 * playbin, GstSourceGroup * group,
   }
 
   /* Remove from list of controlled pads and check again for EOS status */
-  group->source_pads = g_list_remove (group->source_pads, sourcepad);
+  playbin->source_pads = g_list_remove (playbin->source_pads, sourcepad);
   g_slice_free (SourcePad, sourcepad);
-
-  /* Update present stream types */
-  for (tmp = group->source_pads; tmp; tmp = tmp->next) {
-    SourcePad *cand = (SourcePad *) tmp->data;
-    alltype |= cand->stream_type;
-  }
-  group->present_stream_types = alltype;
 }
 
 /* this function is called when a new pad is added to decodebin. We check the
  * type of the pad and add it to the combiner element
  */
 static void
-pad_added_cb (GstElement * uridecodebin, GstPad * pad, GstSourceGroup * group)
+pad_added_cb (GstElement * uridecodebin, GstPad * pad, GstPlayBin3 * playbin)
 {
   GstSourceCombine *combine = NULL;
   gint pb_stream_type = -1;
   gchar *pad_name;
-  GstPlayBin3 *playbin = group->playbin;
   GstPad *combine_pad;
   GstStreamType selected, active, cur;
 
-  GST_PLAY_BIN3_SHUTDOWN_LOCK (playbin, shutdown);
-
   pad_name = gst_object_get_name (GST_OBJECT (pad));
 
   GST_DEBUG_OBJECT (playbin, "decoded pad %s:%s added",
@@ -2925,7 +2456,6 @@ pad_added_cb (GstElement * uridecodebin, GstPad * pad, GstSourceGroup * group)
   /* no stream type found for the media type, don't bother linking it to a
    * combiner. This will leave the pad unlinked and thus ignored. */
   if (pb_stream_type < 0) {
-    GST_PLAY_BIN3_SHUTDOWN_UNLOCK (playbin);
     goto unknown_type;
   }
 
@@ -2946,54 +2476,27 @@ pad_added_cb (GstElement * uridecodebin, GstPad * pad, GstSourceGroup * group)
         GST_DEBUG_PAD_NAME (pad));
     playbin->selected_stream_types = selected;
     reconfigure_output (playbin);
-
-    /* shutdown state can be changed meantime then combiner will not be
-     * configured */
-    if (g_atomic_int_get (&playbin->shutdown)) {
-      GST_PLAY_BIN3_UNLOCK (playbin);
-      GST_PLAY_BIN3_SHUTDOWN_UNLOCK (playbin);
-      return;
-    }
   }
 
   combine_pad = combiner_control_pad (playbin, combine, pad);
+  control_source_pad (playbin, pad, combine_pad, combine->stream_type);
 
-  control_source_pad (group, pad, combine_pad, combine->stream_type);
-
-  /* Update present stream_types and check whether we should post a pending about-to-finish */
-  group->present_stream_types |= combine->stream_type;
-
-  if (group->playing && group->pending_about_to_finish
-      && group->present_stream_types == group->selected_stream_types) {
-    group->pending_about_to_finish = FALSE;
-    emit_about_to_finish (playbin);
-  }
   GST_PLAY_BIN3_UNLOCK (playbin);
 
-  GST_PLAY_BIN3_SHUTDOWN_UNLOCK (playbin);
-
   return;
 
   /* ERRORS */
 unknown_type:
   GST_DEBUG_OBJECT (playbin, "Ignoring pad with unknown type");
   return;
-
-shutdown:
-  {
-    GST_DEBUG ("ignoring, we are shutting down. Pad will be left unlinked");
-    /* not going to done as we didn't request the caps */
-    return;
-  }
 }
 
 /* called when a pad is removed from the decodebin. We unlink the pad from
  * the combiner. */
 static void
-pad_removed_cb (GstElement * decodebin, GstPad * pad, GstSourceGroup * group)
+pad_removed_cb (GstElement * decodebin, GstPad * pad, GstPlayBin3 * playbin)
 {
   GstSourceCombine *combine;
-  GstPlayBin3 *playbin = group->playbin;
 
   GST_DEBUG_OBJECT (playbin,
       "decoded pad %s:%s removed", GST_DEBUG_PAD_NAME (pad));
@@ -3010,7 +2513,7 @@ pad_removed_cb (GstElement * decodebin, GstPad * pad, GstSourceGroup * group)
   else
     goto done;
 
-  release_source_pad (playbin, group, combine, pad);
+  release_source_pad (playbin, combine, pad);
 
 done:
   GST_PLAY_BIN3_UNLOCK (playbin);
@@ -3019,11 +2522,10 @@ done:
 
 static gint
 select_stream_cb (GstElement * decodebin, GstStreamCollection * collection,
-    GstStream * stream, GstSourceGroup * group)
+    GstStream * stream, GstPlayBin3 * playbin)
 {
   GstStreamType stype = gst_stream_get_stream_type (stream);
   GstElement *combiner = NULL;
-  GstPlayBin3 *playbin = group->playbin;
 
   if (stype & GST_STREAM_TYPE_AUDIO)
     combiner = playbin->audio_stream_combiner;
@@ -3101,16 +2603,11 @@ reconfigure_output (GstPlayBin3 * playbin)
       }
 
       /* Release combiner */
-      GST_FIXME_OBJECT (playbin, "Release combiner");
       remove_combiner (playbin, combine);
     } else if (!is_active && is_selected) {
       GST_DEBUG_OBJECT (playbin, "Stream type '%s' is now requested",
           gst_stream_type_get_name (combine->stream_type));
 
-      /* If we are shutting down, do *not* add more combiners */
-      if (g_atomic_int_get (&playbin->shutdown))
-        continue;
-
       g_assert (combine->sinkpad == NULL);
 
       /* Request playsink sink pad */
@@ -3151,601 +2648,40 @@ reconfigure_output (GstPlayBin3 * playbin)
 }
 
 static void
-about_to_finish_cb (GstElement * uridecodebin, GstSourceGroup * group)
+about_to_finish_cb (GstElement * uridecodebin, GstPlayBin3 * playbin)
 {
-  GstPlayBin3 *playbin = group->playbin;
-  GST_DEBUG_OBJECT (playbin, "about to finish in group %p", group);
+  GST_DEBUG_OBJECT (playbin, "about to finish");
 
   GST_LOG_OBJECT (playbin, "selected_stream_types:%" STREAM_TYPES_FORMAT,
-      STREAM_TYPES_ARGS (group->selected_stream_types));
-  GST_LOG_OBJECT (playbin, "present_stream_types:%" STREAM_TYPES_FORMAT,
-      STREAM_TYPES_ARGS (group->present_stream_types));
-
-  if (group->selected_stream_types == 0
-      || (group->selected_stream_types != group->present_stream_types)) {
-    GST_LOG_OBJECT (playbin,
-        "Delaying emission of signal until this group is ready");
-    group->pending_about_to_finish = TRUE;
-  } else
-    emit_about_to_finish (playbin);
-}
-
-static GstBusSyncReply
-activate_sink_bus_handler (GstBus * bus, GstMessage * msg,
-    GstPlayBin3 * playbin)
-{
-  if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) {
-    /* Only proxy errors from a fixed sink. If that fails we can just error out
-     * early as stuff will fail later anyway */
-    if (playbin->audio_sink
-        && gst_object_has_as_ancestor (GST_MESSAGE_SRC (msg),
-            GST_OBJECT_CAST (playbin->audio_sink)))
-      gst_element_post_message (GST_ELEMENT_CAST (playbin), msg);
-    else if (playbin->video_sink
-        && gst_object_has_as_ancestor (GST_MESSAGE_SRC (msg),
-            GST_OBJECT_CAST (playbin->video_sink)))
-      gst_element_post_message (GST_ELEMENT_CAST (playbin), msg);
-    else if (playbin->text_sink
-        && gst_object_has_as_ancestor (GST_MESSAGE_SRC (msg),
-            GST_OBJECT_CAST (playbin->text_sink)))
-      gst_element_post_message (GST_ELEMENT_CAST (playbin), msg);
-    else
-      gst_message_unref (msg);
-  } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_HAVE_CONTEXT) {
-    GstContext *context;
-
-    gst_message_parse_have_context (msg, &context);
-    gst_element_set_context (GST_ELEMENT_CAST (playbin), context);
-    gst_context_unref (context);
-    gst_element_post_message (GST_ELEMENT_CAST (playbin), msg);
-  } else {
-    gst_element_post_message (GST_ELEMENT_CAST (playbin), msg);
-  }
-
-  /* Doesn't really matter, nothing is using this bus */
-  return GST_BUS_DROP;
-}
-
-static gboolean
-activate_sink (GstPlayBin3 * playbin, GstElement * sink, gboolean * activated)
-{
-  GstState state;
-  GstBus *bus = NULL;
-  GstStateChangeReturn sret;
-  gboolean ret = FALSE;
-
-  if (activated)
-    *activated = FALSE;
-
-  GST_OBJECT_LOCK (sink);
-  state = GST_STATE (sink);
-  GST_OBJECT_UNLOCK (sink);
-  if (state >= GST_STATE_READY) {
-    ret = TRUE;
-    goto done;
-  }
-
-  if (!GST_OBJECT_PARENT (sink)) {
-    bus = gst_bus_new ();
-    gst_bus_set_sync_handler (bus,
-        (GstBusSyncHandler) activate_sink_bus_handler, playbin, NULL);
-    gst_element_set_bus (sink, bus);
-  }
-
-  sret = gst_element_set_state (sink, GST_STATE_READY);
-  if (sret == GST_STATE_CHANGE_FAILURE)
-    goto done;
-
-  if (activated)
-    *activated = TRUE;
-  ret = TRUE;
-
-done:
-  if (bus) {
-    gst_element_set_bus (sink, NULL);
-    gst_object_unref (bus);
-  }
-
-  return ret;
-}
-
-/* must be called with the group lock */
-static gboolean
-group_set_locked_state_unlocked (GstPlayBin3 * playbin, GstSourceGroup * group,
-    gboolean locked)
-{
-  GST_DEBUG_OBJECT (playbin, "locked_state %d on group %p", locked, group);
-
-  if (group->uridecodebin)
-    gst_element_set_locked_state (group->uridecodebin, locked);
-
-  return TRUE;
-}
+      STREAM_TYPES_ARGS (playbin->selected_stream_types));
 
-static gboolean
-make_or_reuse_element (GstPlayBin3 * playbin, const gchar * name,
-    GstElement ** elem)
-{
-  if (*elem) {
-    GST_DEBUG_OBJECT (playbin, "reusing existing %s", name);
-    gst_element_set_state (*elem, GST_STATE_READY);
-    /* no need to take extra ref, we already have one
-     * and the bin will add one since it is no longer floating,
-     * as we added a non-floating ref when removing it from the
-     * bin earlier */
-  } else {
-    GstElement *new_elem;
-    GST_DEBUG_OBJECT (playbin, "making new %s", name);
-    new_elem = gst_element_factory_make (name, NULL);
-    if (!new_elem)
-      return FALSE;
-    *elem = gst_object_ref (new_elem);
-  }
+  GST_DEBUG_OBJECT (playbin, "Emitting about-to-finish");
 
-  if (GST_OBJECT_PARENT (*elem) != GST_OBJECT_CAST (playbin))
-    gst_bin_add (GST_BIN_CAST (playbin), *elem);
-  return TRUE;
+  /* after this call, we should have a next group to activate or we EOS */
+  g_signal_emit (G_OBJECT (playbin),
+      gst_play_bin3_signals[SIGNAL_ABOUT_TO_FINISH], 0, NULL);
 }
 
-
 static void
 source_setup_cb (GstElement * element, GstElement * source,
-    GstSourceGroup * group)
+    GstPlayBin3 * playbin)
 {
-  g_signal_emit (group->playbin, gst_play_bin3_signals[SIGNAL_SOURCE_SETUP], 0,
+  g_signal_emit (playbin, gst_play_bin3_signals[SIGNAL_SOURCE_SETUP], 0,
       source);
 }
 
-/* must be called with PLAY_BIN_LOCK */
-static GstStateChangeReturn
-activate_group (GstPlayBin3 * playbin, GstSourceGroup * group)
-{
-  GstElement *uridecodebin = NULL;
-  GstPlayFlags flags;
-  gboolean audio_sink_activated = FALSE;
-  gboolean video_sink_activated = FALSE;
-  gboolean text_sink_activated = FALSE;
-  GstStateChangeReturn state_ret;
-
-  g_return_val_if_fail (group->valid, GST_STATE_CHANGE_FAILURE);
-  g_return_val_if_fail (!group->active, GST_STATE_CHANGE_FAILURE);
-
-  GST_DEBUG_OBJECT (playbin, "activating group %p", group);
-
-  GST_SOURCE_GROUP_LOCK (group);
-
-  /* First set up the custom sinks */
-  if (playbin->audio_sink)
-    group->audio_sink = gst_object_ref (playbin->audio_sink);
-  else
-    group->audio_sink =
-        gst_play_sink_get_sink (playbin->playsink, GST_PLAY_SINK_TYPE_AUDIO);
-
-  if (group->audio_sink) {
-    if (!activate_sink (playbin, group->audio_sink, &audio_sink_activated)) {
-      if (group->audio_sink == playbin->audio_sink) {
-        goto sink_failure;
-      } else {
-        gst_object_unref (group->audio_sink);
-        group->audio_sink = NULL;
-      }
-    }
-  }
-
-  if (playbin->video_sink)
-    group->video_sink = gst_object_ref (playbin->video_sink);
-  else
-    group->video_sink =
-        gst_play_sink_get_sink (playbin->playsink, GST_PLAY_SINK_TYPE_VIDEO);
-
-  if (group->video_sink) {
-    if (!activate_sink (playbin, group->video_sink, &video_sink_activated)) {
-      if (group->video_sink == playbin->video_sink) {
-        goto sink_failure;
-      } else {
-        gst_object_unref (group->video_sink);
-        group->video_sink = NULL;
-      }
-    }
-  }
-
-  if (playbin->text_sink)
-    group->text_sink = gst_object_ref (playbin->text_sink);
-  else
-    group->text_sink =
-        gst_play_sink_get_sink (playbin->playsink, GST_PLAY_SINK_TYPE_TEXT);
-
-  if (group->text_sink) {
-    if (!activate_sink (playbin, group->text_sink, &text_sink_activated)) {
-      if (group->text_sink == playbin->text_sink) {
-        goto sink_failure;
-      } else {
-        gst_object_unref (group->text_sink);
-        group->text_sink = NULL;
-      }
-    }
-  }
-
-
-  if (!make_or_reuse_element (playbin, "uridecodebin3", &group->uridecodebin))
-    goto no_uridecodebin;
-  uridecodebin = group->uridecodebin;
-
-  flags = gst_play_sink_get_flags (playbin->playsink);
-
-  g_object_set (uridecodebin,
-      /* configure connection speed */
-      "connection-speed", playbin->connection_speed / 1000,
-      /* configure uri */
-      "uri", group->uri,
-      /* configure download buffering */
-      "download", ((flags & GST_PLAY_FLAG_DOWNLOAD) != 0),
-      /* configure buffering of demuxed/parsed data */
-      "use-buffering", ((flags & GST_PLAY_FLAG_BUFFERING) != 0),
-      /* configure buffering parameters */
-      "buffer-duration", playbin->buffer_duration,
-      "buffer-size", playbin->buffer_size,
-      "ring-buffer-max-size", playbin->ring_buffer_max_size, NULL);
-
-  group->pad_added_id = g_signal_connect (uridecodebin, "pad-added",
-      G_CALLBACK (pad_added_cb), group);
-  group->pad_removed_id = g_signal_connect (uridecodebin,
-      "pad-removed", G_CALLBACK (pad_removed_cb), group);
-  group->select_stream_id = g_signal_connect (uridecodebin, "select-stream",
-      G_CALLBACK (select_stream_cb), group);
-  group->source_setup_id = g_signal_connect (uridecodebin, "source-setup",
-      G_CALLBACK (source_setup_cb), group);
-  group->about_to_finish_id =
-      g_signal_connect (uridecodebin, "about-to-finish",
-      G_CALLBACK (about_to_finish_cb), group);
-
-  if (group->suburi)
-    g_object_set (group->uridecodebin, "suburi", group->suburi, NULL);
-
-  /* release the group lock before setting the state of the source bins, they
-   * might fire signals in this thread that we need to handle with the
-   * group_lock taken. */
-  GST_SOURCE_GROUP_UNLOCK (group);
-
-  if ((state_ret =
-          gst_element_set_state (uridecodebin,
-              GST_STATE_PAUSED)) == GST_STATE_CHANGE_FAILURE)
-    goto uridecodebin_failure;
-
-  GST_SOURCE_GROUP_LOCK (group);
-  /* allow state changes of the playbin affect the group elements now */
-  group_set_locked_state_unlocked (playbin, group, FALSE);
-  group->active = TRUE;
-  GST_SOURCE_GROUP_UNLOCK (group);
-
-  return state_ret;
-
-  /* ERRORS */
-no_uridecodebin:
-  {
-    GstMessage *msg;
-
-    GST_SOURCE_GROUP_UNLOCK (group);
-    msg =
-        gst_missing_element_message_new (GST_ELEMENT_CAST (playbin),
-        "uridecodebin3");
-    gst_element_post_message (GST_ELEMENT_CAST (playbin), msg);
-
-    GST_ELEMENT_ERROR (playbin, CORE, MISSING_PLUGIN,
-        (_("Could not create \"uridecodebin3\" element.")), (NULL));
-
-    GST_SOURCE_GROUP_LOCK (group);
-
-    goto error_cleanup;
-  }
-uridecodebin_failure:
-  {
-    GST_DEBUG_OBJECT (playbin, "failed state change of uridecodebin");
-    GST_SOURCE_GROUP_LOCK (group);
-    goto error_cleanup;
-  }
-sink_failure:
-  {
-    GST_ERROR_OBJECT (playbin, "failed to activate sinks");
-    goto error_cleanup;
-  }
-
-error_cleanup:
-  {
-    group->selected_stream_types = 0;
-
-    /* delete any custom sinks we might have */
-    if (group->audio_sink) {
-      /* If this is a automatically created sink set it to NULL */
-      if (audio_sink_activated)
-        gst_element_set_state (group->audio_sink, GST_STATE_NULL);
-      gst_object_unref (group->audio_sink);
-    }
-    group->audio_sink = NULL;
-
-    if (group->video_sink) {
-      /* If this is a automatically created sink set it to NULL */
-      if (video_sink_activated)
-        gst_element_set_state (group->video_sink, GST_STATE_NULL);
-      gst_object_unref (group->video_sink);
-    }
-    group->video_sink = NULL;
-
-    if (group->text_sink) {
-      /* If this is a automatically created sink set it to NULL */
-      if (text_sink_activated)
-        gst_element_set_state (group->text_sink, GST_STATE_NULL);
-      gst_object_unref (group->text_sink);
-    }
-    group->text_sink = NULL;
-
-    if (uridecodebin) {
-      REMOVE_SIGNAL (group->uridecodebin, group->pad_added_id);
-      REMOVE_SIGNAL (group->uridecodebin, group->pad_removed_id);
-      REMOVE_SIGNAL (group->uridecodebin, group->select_stream_id);
-      REMOVE_SIGNAL (group->uridecodebin, group->source_setup_id);
-      REMOVE_SIGNAL (group->uridecodebin, group->about_to_finish_id);
-
-      gst_element_set_state (uridecodebin, GST_STATE_NULL);
-      gst_bin_remove (GST_BIN_CAST (playbin), uridecodebin);
-    }
-
-    GST_SOURCE_GROUP_UNLOCK (group);
-
-    return GST_STATE_CHANGE_FAILURE;
-  }
-}
-
-/* must be called with PLAY_BIN_LOCK, which is dropped temporarily
- * if changing states */
-static gboolean
-deactivate_group (GstPlayBin3 * playbin, GstSourceGroup * group)
-{
-  g_return_val_if_fail (group->active, FALSE);
-  g_return_val_if_fail (group->valid, FALSE);
-
-  GST_DEBUG_OBJECT (playbin, "unlinking group %p", group);
-
-  GST_SOURCE_GROUP_LOCK (group);
-  group->active = FALSE;
-  group->playing = FALSE;
-  group->group_id = GST_GROUP_ID_INVALID;
-
-  group->selected_stream_types = 0;
-  /* Update global selected_stream_types */
-  playbin->selected_stream_types =
-      playbin->groups[0].selected_stream_types | playbin->groups[1].
-      selected_stream_types;
-  if (playbin->active_stream_types != playbin->selected_stream_types)
-    reconfigure_output (playbin);
-
-  if (group->uridecodebin) {
-    REMOVE_SIGNAL (group->uridecodebin, group->select_stream_id);
-    REMOVE_SIGNAL (group->uridecodebin, group->source_setup_id);
-    REMOVE_SIGNAL (group->uridecodebin, group->about_to_finish_id);
-
-    GST_PLAY_BIN3_UNLOCK (playbin);
-    gst_element_set_state (group->uridecodebin, GST_STATE_NULL);
-    gst_bin_remove (GST_BIN_CAST (playbin), group->uridecodebin);
-    GST_PLAY_BIN3_LOCK (playbin);
-
-    REMOVE_SIGNAL (group->uridecodebin, group->pad_added_id);
-    REMOVE_SIGNAL (group->uridecodebin, group->pad_removed_id);
-  }
-
-  GST_SOURCE_GROUP_UNLOCK (group);
-
-  GST_DEBUG_OBJECT (playbin, "Done");
-
-  return TRUE;
-}
-
-/* setup the next group to play, this assumes the next_group is valid and
- * configured. It swaps out the current_group and activates the valid
- * next_group. */
-static GstStateChangeReturn
-setup_next_source (GstPlayBin3 * playbin)
-{
-  GstSourceGroup *new_group;
-  GstStateChangeReturn state_ret;
-
-  GST_DEBUG_OBJECT (playbin, "setup next source");
-
-  debug_groups (playbin);
-
-  /* see if there is a next group */
-  GST_PLAY_BIN3_LOCK (playbin);
-  new_group = playbin->next_group;
-  if (!new_group || !new_group->valid || new_group->active)
-    goto no_next_group;
-
-  /* activate the new group */
-  state_ret = activate_group (playbin, new_group);
-  if (state_ret == GST_STATE_CHANGE_FAILURE)
-    goto activate_failed;
-
-  GST_PLAY_BIN3_UNLOCK (playbin);
-
-  debug_groups (playbin);
-
-  return state_ret;
-
-  /* ERRORS */
-no_next_group:
-  {
-    GST_DEBUG_OBJECT (playbin, "no next group");
-    GST_PLAY_BIN3_UNLOCK (playbin);
-    return GST_STATE_CHANGE_FAILURE;
-  }
-activate_failed:
-  {
-    new_group->stream_changed_pending = FALSE;
-    GST_DEBUG_OBJECT (playbin, "activate failed");
-    new_group->valid = FALSE;
-    GST_PLAY_BIN3_UNLOCK (playbin);
-    return GST_STATE_CHANGE_FAILURE;
-  }
-}
-
-/* The group that is currently playing is copied again to the
- * next_group so that it will start playing the next time.
- */
-static gboolean
-save_current_group (GstPlayBin3 * playbin)
-{
-  GstSourceGroup *curr_group;
-  gboolean swapped = FALSE;
-
-  GST_DEBUG_OBJECT (playbin, "save current group");
-
-  /* see if there is a current group */
-  GST_PLAY_BIN3_LOCK (playbin);
-  curr_group = playbin->curr_group;
-  if (curr_group && curr_group->valid && curr_group->active) {
-    swapped = TRUE;
-  }
-  /* swap old and new */
-  playbin->curr_group = playbin->next_group;
-  playbin->next_group = curr_group;
-
-  if (swapped) {
-    /* unlink our pads with the sink */
-    deactivate_group (playbin, curr_group);
-  }
-  GST_PLAY_BIN3_UNLOCK (playbin);
-
-  return TRUE;
-}
-
-/* clear the locked state from all groups. This function is called before a
- * state change to NULL is performed on them. */
-static gboolean
-groups_set_locked_state (GstPlayBin3 * playbin, gboolean locked)
-{
-  GST_DEBUG_OBJECT (playbin, "setting locked state to %d on all groups",
-      locked);
-
-  GST_PLAY_BIN3_LOCK (playbin);
-  GST_SOURCE_GROUP_LOCK (playbin->curr_group);
-  group_set_locked_state_unlocked (playbin, playbin->curr_group, locked);
-  GST_SOURCE_GROUP_UNLOCK (playbin->curr_group);
-  GST_SOURCE_GROUP_LOCK (playbin->next_group);
-  group_set_locked_state_unlocked (playbin, playbin->next_group, locked);
-  GST_SOURCE_GROUP_UNLOCK (playbin->next_group);
-  GST_PLAY_BIN3_UNLOCK (playbin);
-
-  return TRUE;
-}
-
-static void
-gst_play_bin3_check_group_status (GstPlayBin3 * playbin)
-{
-  if (playbin->activation_task)
-    gst_task_start (playbin->activation_task);
-}
-
-static void
-gst_play_bin3_activation_thread (GstPlayBin3 * playbin)
-{
-  GST_DEBUG_OBJECT (playbin, "starting");
-
-  debug_groups (playbin);
-
-  /* Check if next_group needs to be deactivated */
-  GST_PLAY_BIN3_LOCK (playbin);
-  if (playbin->next_group->active) {
-    deactivate_group (playbin, playbin->next_group);
-    playbin->next_group->valid = FALSE;
-  }
-
-  /* Is there a pending about-to-finish to be emitted ? */
-  GST_SOURCE_GROUP_LOCK (playbin->curr_group);
-  if (playbin->curr_group->pending_about_to_finish) {
-    GST_LOG_OBJECT (playbin, "Propagating about-to-finish");
-    playbin->curr_group->pending_about_to_finish = FALSE;
-    GST_SOURCE_GROUP_UNLOCK (playbin->curr_group);
-    /* This will activate the next source afterwards */
-    emit_about_to_finish (playbin);
-  } else
-    GST_SOURCE_GROUP_UNLOCK (playbin->curr_group);
-
-  GST_LOG_OBJECT (playbin, "Pausing task");
-  if (playbin->activation_task)
-    gst_task_pause (playbin->activation_task);
-  GST_PLAY_BIN3_UNLOCK (playbin);
-
-  GST_DEBUG_OBJECT (playbin, "done");
-  return;
-}
-
 static gboolean
 gst_play_bin3_start (GstPlayBin3 * playbin)
 {
   GST_DEBUG_OBJECT (playbin, "starting");
 
   GST_PLAY_BIN3_LOCK (playbin);
-
-  if (playbin->activation_task == NULL) {
-    playbin->activation_task =
-        gst_task_new ((GstTaskFunction) gst_play_bin3_activation_thread,
-        playbin, NULL);
-    if (playbin->activation_task == NULL)
-      goto task_error;
-    gst_task_set_lock (playbin->activation_task, &playbin->activation_lock);
-  }
-  GST_LOG_OBJECT (playbin, "clearing shutdown flag");
-  g_atomic_int_set (&playbin->shutdown, 0);
+  playbin->active_stream_types = 0;
+  playbin->selected_stream_types = 0;
   do_async_start (playbin);
-
   GST_PLAY_BIN3_UNLOCK (playbin);
 
   return TRUE;
-
-task_error:
-  {
-    GST_PLAY_BIN3_UNLOCK (playbin);
-    GST_ERROR_OBJECT (playbin, "Failed to create task");
-    return FALSE;
-  }
-}
-
-static void
-gst_play_bin3_stop (GstPlayBin3 * playbin)
-{
-  GstTask *task;
-
-  GST_DEBUG_OBJECT (playbin, "stopping");
-
-  /* FIXME unlock our waiting groups */
-  GST_LOG_OBJECT (playbin, "setting shutdown flag");
-  g_atomic_int_set (&playbin->shutdown, 1);
-
-  /* wait for all callbacks to end by taking the lock.
-   * No dynamic (critical) new callbacks will
-   * be able to happen as we set the shutdown flag. */
-  GST_PLAY_BIN3_DYN_LOCK (playbin);
-  GST_LOG_OBJECT (playbin, "dynamic lock taken, we can continue shutdown");
-  GST_PLAY_BIN3_DYN_UNLOCK (playbin);
-
-  /* Stop the activation task */
-  GST_PLAY_BIN3_LOCK (playbin);
-  if ((task = playbin->activation_task)) {
-    playbin->activation_task = NULL;
-    GST_PLAY_BIN3_UNLOCK (playbin);
-
-    gst_task_stop (task);
-
-    /* Make sure task is not running */
-    g_rec_mutex_lock (&playbin->activation_lock);
-    g_rec_mutex_unlock (&playbin->activation_lock);
-
-    /* Wait for task to finish and unref it */
-    gst_task_join (task);
-    gst_object_unref (task);
-
-    GST_PLAY_BIN3_LOCK (playbin);
-  }
-  GST_PLAY_BIN3_UNLOCK (playbin);
 }
 
 static GstStateChangeReturn
@@ -3753,7 +2689,6 @@ gst_play_bin3_change_state (GstElement * element, GstStateChange transition)
 {
   GstStateChangeReturn ret;
   GstPlayBin3 *playbin;
-  gboolean do_save = FALSE;
 
   playbin = GST_PLAY_BIN3 (element);
 
@@ -3762,24 +2697,6 @@ gst_play_bin3_change_state (GstElement * element, GstStateChange transition)
       if (!gst_play_bin3_start (playbin))
         return GST_STATE_CHANGE_FAILURE;
       break;
-    case GST_STATE_CHANGE_PAUSED_TO_READY:
-    async_down:
-      gst_play_bin3_stop (playbin);
-      if (!do_save)
-        break;
-    case GST_STATE_CHANGE_READY_TO_NULL:
-      /* we go async to PAUSED, so if that fails, we never make it to PAUSED
-       * and we will never be called with the GST_STATE_CHANGE_PAUSED_TO_READY.
-       * Make sure we do go through the same steps (see above) for
-       * proper cleanup */
-      if (!g_atomic_int_get (&playbin->shutdown)) {
-        do_save = TRUE;
-        goto async_down;
-      }
-
-      /* unlock so that all groups go to NULL */
-      groups_set_locked_state (playbin, FALSE);
-      break;
     default:
       break;
   }
@@ -3790,45 +2707,22 @@ gst_play_bin3_change_state (GstElement * element, GstStateChange transition)
 
   switch (transition) {
     case GST_STATE_CHANGE_READY_TO_PAUSED:
-      if ((ret = setup_next_source (playbin)) == GST_STATE_CHANGE_FAILURE)
-        goto failure;
       if (ret == GST_STATE_CHANGE_SUCCESS)
         ret = GST_STATE_CHANGE_ASYNC;
-
       break;
     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
       do_async_done (playbin);
-      /* FIXME Release audio device when we implement that */
       break;
     case GST_STATE_CHANGE_PAUSED_TO_READY:
       playbin->is_live = FALSE;
-      save_current_group (playbin);
+      /* Make sure we reset our state  */
+      if (playbin->selected_stream_types) {
+        playbin->selected_stream_types = 0;
+        reconfigure_output (playbin);
+      }
       break;
     case GST_STATE_CHANGE_READY_TO_NULL:
     {
-      guint i;
-
-      /* also do missed state change down to READY */
-      if (do_save)
-        save_current_group (playbin);
-      /* Deactivate the groups, set uridecodebin to NULL and unref it */
-      GST_PLAY_BIN3_LOCK (playbin);
-      for (i = 0; i < 2; i++) {
-        if (playbin->groups[i].active && playbin->groups[i].valid) {
-          deactivate_group (playbin, &playbin->groups[i]);
-          playbin->groups[i].valid = FALSE;
-        }
-
-        if (playbin->groups[i].uridecodebin) {
-          gst_element_set_state (playbin->groups[i].uridecodebin,
-              GST_STATE_NULL);
-          gst_object_unref (playbin->groups[i].uridecodebin);
-          playbin->groups[i].uridecodebin = NULL;
-        }
-
-      }
-      GST_PLAY_BIN3_UNLOCK (playbin);
-
       /* Set our sinks back to NULL, they might not be child of playbin */
       if (playbin->audio_sink)
         gst_element_set_state (playbin->audio_sink, GST_STATE_NULL);
@@ -3844,9 +2738,9 @@ gst_play_bin3_change_state (GstElement * element, GstStateChange transition)
       if (playbin->text_stream_combiner)
         gst_element_set_state (playbin->text_stream_combiner, GST_STATE_NULL);
 
-      /* make sure the groups don't perform a state change anymore until we
-       * enable them again */
-      groups_set_locked_state (playbin, TRUE);
+      gst_object_replace ((GstObject **) & playbin->collection, NULL);
+      gst_object_replace ((GstObject **) & playbin->collection_source, NULL);
+
       break;
     }
     default:
@@ -3865,27 +2759,6 @@ gst_play_bin3_change_state (GstElement * element, GstStateChange transition)
 failure:
   {
     do_async_done (playbin);
-
-    if (transition == GST_STATE_CHANGE_READY_TO_PAUSED) {
-      GstSourceGroup *curr_group;
-
-      GST_PLAY_BIN3_LOCK (playbin);
-
-      curr_group = playbin->curr_group;
-      if (curr_group) {
-        if (curr_group->active && curr_group->valid) {
-          /* unlink our pads with the sink */
-          deactivate_group (playbin, curr_group);
-        }
-        curr_group->valid = FALSE;
-      }
-
-      /* Swap current and next group back */
-      playbin->curr_group = playbin->next_group;
-      playbin->next_group = curr_group;
-
-      GST_PLAY_BIN3_UNLOCK (playbin);
-    }
     return ret;
   }
 }
index d6fce6f..390b51b 100644 (file)
  *    post the (last) buffering messages.
  *    If no group_id is being outputted (still prerolling), then output
  *    the messages directly
- *
- * * ASYNC HANDLING
- * ** URIDECODEBIN3 is not async-aware.
- *
- * * GAPLESS HANDLING
- * ** Correlate group_id and URI to know when/which stream is being outputted/started
  */
 
 /**
@@ -74,16 +68,19 @@ typedef struct _GstSourceGroup GstSourceGroup;
 typedef struct _GstURIDecodeBin3 GstURIDecodeBin3;
 typedef struct _GstURIDecodeBin3Class GstURIDecodeBin3Class;
 
-#define GST_URI_DECODE_BIN3_LOCK(dec) (g_mutex_lock(&((GstURIDecodeBin3*)(dec))->lock))
-#define GST_URI_DECODE_BIN3_UNLOCK(dec) (g_mutex_unlock(&((GstURIDecodeBin3*)(dec))->lock))
-
 typedef struct _GstPlayItem GstPlayItem;
 typedef struct _GstSourceItem GstSourceItem;
 typedef struct _GstSourceHandler GstSourceHandler;
+typedef struct _GstSourcePad GstSourcePad;
 typedef struct _OutputPad OutputPad;
 
 /* A structure describing a play item, which travels through the elements
- * over time. */
+ * over time.
+ *
+ * All source items in this play item will be played together. Corresponds to an
+ * end-user "play item" (ex: one item from a playlist, even though it might be
+ * using a main content and subtitle content).
+ */
 struct _GstPlayItem
 {
   GstURIDecodeBin3 *uridecodebin;
@@ -101,11 +98,25 @@ struct _GstPlayItem
    * The urisourcebin-specific group_id is located in GstSourceItem */
   guint group_id;
 
-  /* Is this play item the one being currently outputted by decodebin3
-   * and on our source ghostpads */
-  gboolean currently_outputted;
+  /* The two following variables are required for gapless, since there could be
+   * a play item which is started which is different from the one currently
+   * being outputted */
+
+  /* active: TRUE if the backing urisourcebin were created */
+  gboolean active;
+
+  /* Whether about-to-finish was already posted for this play item */
+  gboolean posted_about_to_finish;
+
+  /* Whether about-to-finish should be posted once this play item becomes the
+   * current input item */
+  gboolean pending_about_to_finish;
 };
 
+/* The actual "source" component of a "play item"
+ *
+ * This is defined by having a URI, is backed by a `GstSourceHandler`.
+ */
 struct _GstSourceItem
 {
   /* The GstPlayItem to which this GstSourceItem belongs to */
@@ -116,17 +127,13 @@ struct _GstSourceItem
   /* The urisourcebin controlling this uri
    * Can be NULL */
   GstSourceHandler *handler;
-
-  /* The groupid created by urisourcebin for this uri */
-  guint internal_groupid;
-
-  /* FIXME : Add tag lists and other uri-specific items here ? */
 };
 
 /* Structure wrapping everything related to a urisourcebin */
 struct _GstSourceHandler
 {
   GstURIDecodeBin3 *uridecodebin;
+  GstPlayItem *play_item;
 
   GstElement *urisourcebin;
 
@@ -139,19 +146,46 @@ struct _GstSourceHandler
   /* TRUE if the controlled urisourcebin was added to uridecodebin */
   gboolean active;
 
-  /* whether urisourcebin is drained or not.
-   * Reset if/when setting a new URI */
-  gboolean drained;
-
-  /* Whether urisourcebin posted EOS on all pads and
-   * there is no pending entry */
-  gboolean is_eos;
-
   /* TRUE if the urisourcebin handles main item */
   gboolean is_main_source;
 
   /* buffering message stored for after switching */
   GstMessage *pending_buffering_msg;
+
+  /* Number of expected sourcepads. Default 1, else it's the number of streams
+   * specified by GST_MESSAGE_SELECTED_STREAMS from the source */
+  guint expected_pads;
+
+  /* List of GstSourcePad */
+  GList *sourcepads;
+};
+
+/* Structure wrapping everything related to a urisourcebin pad */
+struct _GstSourcePad
+{
+  GstSourceHandler *handler;
+
+  GstPad *src_pad;
+
+  /* GstStream (if present) */
+  GstStream *stream;
+
+  /* Decodebin3 pad to which src_pad is linked to */
+  GstPad *db3_sink_pad;
+
+  /* TRUE if db3_sink_pad is a request pad */
+  gboolean db3_pad_is_request;
+
+  /* TRUE if EOS went through the source pad. Marked as TRUE if decodebin3
+   * notified `about-to-finish` for pull mode */
+  gboolean saw_eos;
+
+  /* Downstream blocking probe id. Only set/valid if we need to block this
+   * pad */
+  gulong block_probe_id;
+
+  /* Downstream event probe id */
+  gulong event_probe_id;
 };
 
 /* Controls an output source pad */
@@ -165,14 +199,23 @@ struct _OutputPad
   /* Downstream event probe id */
   gulong probe_id;
 
-  /* TRUE if the pad saw EOS. Reset to FALSE on STREAM_START */
-  gboolean is_eos;
-
   /* The last seen (i.e. current) group_id
    * Can be (guint)-1 if no group_id was seen yet */
   guint current_group_id;
 };
 
+#define PLAY_ITEMS_GET_LOCK(d) (&(GST_URI_DECODE_BIN3_CAST(d)->play_items_lock))
+#define PLAY_ITEMS_LOCK(d) G_STMT_START { \
+    GST_TRACE("Locking play_items from thread %p", g_thread_self()); \
+    g_mutex_lock (PLAY_ITEMS_GET_LOCK (d)); \
+    GST_TRACE("Locked play_items from thread %p", g_thread_self()); \
+ } G_STMT_END
+
+#define PLAY_ITEMS_UNLOCK(d) G_STMT_START { \
+    GST_TRACE("Unlocking play_items from thread %p", g_thread_self()); \
+    g_mutex_unlock (PLAY_ITEMS_GET_LOCK (d)); \
+ } G_STMT_END
+
 /**
  * GstURIDecodeBin3
  *
@@ -182,8 +225,6 @@ struct _GstURIDecodeBin3
 {
   GstBin parent_instance;
 
-  GMutex lock;                  /* lock for constructing */
-
   /* Properties */
   GstElement *source;
   guint64 connection_speed;     /* In bits/sec (0 = unknown) */
@@ -193,26 +234,23 @@ struct _GstURIDecodeBin3
   gboolean download;
   gboolean use_buffering;
   guint64 ring_buffer_max_size;
+  gboolean instant_uri;         /* Whether URI changes should be applied immediately or not */
 
-  GList *play_items;            /* List of GstPlayItem ordered by time of
-                                 * creation. Head of list is therefore the
-                                 * current (or pending if initial) one being
-                                 * outputted */
-  GstPlayItem *current;         /* Currently active GstPlayItem. Can be NULL
-                                 * if no entry is active yet (i.e. no source
-                                 * pads) */
-
-  /* sources.
-   * FIXME : Replace by a more modular system later on */
-  GstSourceHandler *main_handler;
-  GstSourceHandler *sub_handler;
-
-  /* URI handling
-   * FIXME : Switch to a playlist-based API */
-  gchar *uri;
-  gboolean uri_changed;         /* TRUE if uri changed */
-  gchar *suburi;
-  gboolean suburi_changed;      /* TRUE if suburi changed */
+  /* Mutex to protect play_items/input_item/output_item */
+  GMutex play_items_lock;
+
+  /* Notify that the input_item sources have all drained */
+  GCond input_source_drained;
+
+  /* List of GstPlayItem ordered by time of creation (first is oldest, new ones
+   * are appended) */
+  GList *play_items;
+
+  /* Play item currently feeding decodebin3. */
+  GstPlayItem *input_item;
+
+  /* Play item currently outputted by decodebin3 */
+  GstPlayItem *output_item;
 
   /* A global decodebin3 that's used to actually do decoding */
   GstElement *decodebin;
@@ -223,15 +261,14 @@ struct _GstURIDecodeBin3
   gulong db_select_stream_id;
   gulong db_about_to_finish_id;
 
-  GList *output_pads;           /* List of OutputPad */
-
-  GList *source_handlers;       /* List of SourceHandler */
+  /* 1 if shutting down */
+  gint shutdown;
 
-  /* Whether we already signalled about-to-finish or not
-   * FIXME: Track this by group-id ! */
-  gboolean posted_about_to_finish;
+  GList *output_pads;           /* List of OutputPad */
 };
 
+static GstStateChangeReturn activate_play_item (GstPlayItem * item);
+
 static gint
 gst_uridecodebin3_select_stream (GstURIDecodeBin3 * dbin,
     GstStreamCollection * collection, GstStream * stream)
@@ -261,11 +298,6 @@ enum
   LAST_SIGNAL
 };
 
-#if 0
-static GstStaticCaps raw_audio_caps = GST_STATIC_CAPS ("audio/x-raw(ANY)");
-static GstStaticCaps raw_video_caps = GST_STATIC_CAPS ("video/x-raw(ANY)");
-#endif
-
 /* properties */
 #define DEFAULT_PROP_URI            NULL
 #define DEFAULT_PROP_SUBURI            NULL
@@ -276,6 +308,7 @@ static GstStaticCaps raw_video_caps = GST_STATIC_CAPS ("video/x-raw(ANY)");
 #define DEFAULT_DOWNLOAD            FALSE
 #define DEFAULT_USE_BUFFERING       FALSE
 #define DEFAULT_RING_BUFFER_MAX_SIZE 0
+#define DEFAULT_INSTANT_URI         FALSE
 
 enum
 {
@@ -291,7 +324,8 @@ enum
   PROP_DOWNLOAD,
   PROP_USE_BUFFERING,
   PROP_RING_BUFFER_MAX_SIZE,
-  PROP_CAPS
+  PROP_CAPS,
+  PROP_INSTANT_URI
 };
 
 static guint gst_uri_decode_bin3_signals[LAST_SIGNAL] = { 0 };
@@ -341,14 +375,28 @@ static void gst_uri_decode_bin3_set_property (GObject * object, guint prop_id,
     const GValue * value, GParamSpec * pspec);
 static void gst_uri_decode_bin3_get_property (GObject * object, guint prop_id,
     GValue * value, GParamSpec * pspec);
-static void gst_uri_decode_bin3_finalize (GObject * obj);
+static void gst_uri_decode_bin3_dispose (GObject * obj);
 static GstSourceHandler *new_source_handler (GstURIDecodeBin3 * uridecodebin,
-    gboolean is_main);
+    GstPlayItem * item, gboolean is_main);
+static void free_source_item (GstURIDecodeBin3 * uridecodebin,
+    GstSourceItem * item);
+
+static GstPlayItem *new_play_item (GstURIDecodeBin3 * dec);
+static void free_play_item (GstURIDecodeBin3 * dec, GstPlayItem * item);
+static gboolean play_item_is_eos (GstPlayItem * item);
+static void play_item_set_eos (GstPlayItem * item);
+static gboolean play_item_has_all_pads (GstPlayItem * item);
+
+static void gst_uri_decode_bin3_set_uri (GstURIDecodeBin3 * dec,
+    const gchar * uri);
+static void gst_uri_decode_bin3_set_suburi (GstURIDecodeBin3 * dec,
+    const gchar * uri);
 
 static GstStateChangeReturn gst_uri_decode_bin3_change_state (GstElement *
     element, GstStateChange transition);
 static gboolean gst_uri_decodebin3_send_event (GstElement * element,
     GstEvent * event);
+static void gst_uri_decode_bin3_handle_message (GstBin * bin, GstMessage * msg);
 
 static gboolean
 _gst_int_accumulator (GSignalInvocationHint * ihint,
@@ -370,13 +418,15 @@ gst_uri_decode_bin3_class_init (GstURIDecodeBin3Class * klass)
 {
   GObjectClass *gobject_class;
   GstElementClass *gstelement_class;
+  GstBinClass *gstbin_class;
 
   gobject_class = G_OBJECT_CLASS (klass);
   gstelement_class = GST_ELEMENT_CLASS (klass);
+  gstbin_class = GST_BIN_CLASS (klass);
 
   gobject_class->set_property = gst_uri_decode_bin3_set_property;
   gobject_class->get_property = gst_uri_decode_bin3_get_property;
-  gobject_class->finalize = gst_uri_decode_bin3_finalize;
+  gobject_class->dispose = gst_uri_decode_bin3_dispose;
 
   g_object_class_install_property (gobject_class, PROP_URI,
       g_param_spec_string ("uri", "URI", "URI to decode",
@@ -460,6 +510,19 @@ gst_uri_decode_bin3_class_init (GstURIDecodeBin3Class * klass)
           GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
   /**
+   * GstURIDecodeBin3:instant-uri:
+   *
+   * Changes to uri are applied immediately (instead of on EOS or when the
+   * element is set back to PLAYING.
+   *
+   * Since: 1.22
+   */
+  g_object_class_install_property (gobject_class, PROP_INSTANT_URI,
+      g_param_spec_boolean ("instant-uri", "Instantaneous URI change",
+          "When enabled, URI changes are applied immediately",
+          DEFAULT_INSTANT_URI, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
    * GstURIDecodebin3::select-stream
    * @decodebin: a #GstURIDecodebin3
    * @collection: a #GstStreamCollection
@@ -521,18 +584,93 @@ gst_uri_decode_bin3_class_init (GstURIDecodeBin3Class * klass)
   gstelement_class->send_event =
       GST_DEBUG_FUNCPTR (gst_uri_decodebin3_send_event);
 
+  gstbin_class->handle_message = gst_uri_decode_bin3_handle_message;
+
   klass->select_stream = gst_uridecodebin3_select_stream;
 }
 
+static void
+check_output_group_id (GstURIDecodeBin3 * dec)
+{
+  GList *iter;
+  guint common_group_id = GST_GROUP_ID_INVALID;
+
+  PLAY_ITEMS_LOCK (dec);
+
+  for (iter = dec->output_pads; iter; iter = iter->next) {
+    OutputPad *pad = iter->data;
+
+    if (common_group_id == GST_GROUP_ID_INVALID)
+      common_group_id = pad->current_group_id;
+    else if (common_group_id != pad->current_group_id) {
+      GST_DEBUG_OBJECT (dec, "transitioning output play item");
+      PLAY_ITEMS_UNLOCK (dec);
+      return;
+    }
+  }
+
+  if (dec->output_item->group_id == common_group_id) {
+    GST_DEBUG_OBJECT (dec, "Output play item %d fully active", common_group_id);
+  } else if (dec->output_item->group_id == GST_GROUP_ID_INVALID) {
+    /* This can happen for pull-based situations */
+    GST_DEBUG_OBJECT (dec, "Assigning group id %u to current output play item",
+        common_group_id);
+    dec->output_item->group_id = common_group_id;
+  } else if (common_group_id != GST_GROUP_ID_INVALID &&
+      dec->output_item->group_id != common_group_id) {
+    GstPlayItem *previous_item = dec->output_item;
+    GST_DEBUG_OBJECT (dec, "Output play item %d fully active", common_group_id);
+    if (g_list_length (dec->play_items) > 1) {
+      dec->play_items = g_list_remove (dec->play_items, previous_item);
+      dec->output_item = dec->play_items->data;
+      dec->output_item->group_id = common_group_id;
+      free_play_item (dec, previous_item);
+    }
+  }
+
+  PLAY_ITEMS_UNLOCK (dec);
+}
+
 static GstPadProbeReturn
 db_src_probe (GstPad * pad, GstPadProbeInfo * info, OutputPad * output)
 {
-  /* FIXME : IMPLEMENT */
+  GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
+  GstURIDecodeBin3 *uridecodebin = output->uridecodebin;
+
+  GST_DEBUG_OBJECT (pad, "event %" GST_PTR_FORMAT, event);
 
   /* EOS : Mark pad as EOS */
 
-  /* STREAM_START : Store group_id and check if currently active
-   *  PlayEntry changed */
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_EOS:
+    {
+      /* If there is a next input, drop the EOS event */
+      if (uridecodebin->input_item != uridecodebin->output_item ||
+          uridecodebin->input_item !=
+          g_list_last (uridecodebin->play_items)->data) {
+        GST_DEBUG_OBJECT (uridecodebin,
+            "Dropping EOS event because in gapless mode");
+        return GST_PAD_PROBE_DROP;
+      }
+      break;
+    }
+    case GST_EVENT_STREAM_START:
+    {
+      /* STREAM_START : Store group_id and check if currently active
+       *  PlayEntry changed */
+      if (gst_event_parse_group_id (event, &output->current_group_id)) {
+        GST_DEBUG_OBJECT (pad, "current group id %" G_GUINT32_FORMAT,
+            output->current_group_id);
+        /* Check if we switched over to a new output */
+        check_output_group_id (uridecodebin);
+      }
+
+      break;
+    }
+    default:
+      break;
+  }
+
 
   return GST_PAD_PROBE_OK;
 }
@@ -617,12 +755,12 @@ db_pad_removed_cb (GstElement * element, GstPad * pad, GstURIDecodeBin3 * dec)
     gst_ghost_pad_set_target ((GstGhostPad *) output->ghost_pad, NULL);
     gst_element_remove_pad ((GstElement *) dec, output->ghost_pad);
 
-    /* FIXME : Update global/current PlayEntry group_id (did we switch ?) */
-
     /* Remove event probe */
     gst_pad_remove_probe (output->target_pad, output->probe_id);
 
     g_slice_free (OutputPad, output);
+
+    check_output_group_id (dec);
   }
 }
 
@@ -639,23 +777,88 @@ db_select_stream_cb (GstElement * decodebin,
   return response;
 }
 
+static gboolean
+check_pad_mode (GstElement * src, GstPad * pad, gpointer udata)
+{
+  GstPadMode curmode = GST_PAD_MODE (pad);
+  GstPadMode *retmode = (GstPadMode *) udata;
+
+  /* We don't care if pads aren't activated */
+  if (curmode == GST_PAD_MODE_NONE)
+    return TRUE;
+
+  if (*retmode == GST_PAD_MODE_NONE) {
+    *retmode = curmode;
+  } else if (*retmode != curmode) {
+    GST_ERROR_OBJECT (src, "source has different scheduling mode ?");
+  }
+
+  return TRUE;
+}
+
+static gboolean
+play_item_is_pull_based (GstPlayItem * item)
+{
+  GstElement *src;
+  GstPadMode mode = GST_PAD_MODE_NONE;
+
+  g_assert (item->main_item && item->main_item->handler
+      && item->main_item->handler->urisourcebin);
+
+  src = item->main_item->handler->urisourcebin;
+  gst_element_foreach_src_pad (src, check_pad_mode, &mode);
+
+  return (mode == GST_PAD_MODE_PULL);
+}
+
 static void
-db_about_to_finish_cb (GstElement * decodebin, GstURIDecodeBin3 * uridecodebin)
+emit_and_handle_about_to_finish (GstURIDecodeBin3 * uridecodebin,
+    GstPlayItem * item)
 {
-  if (!uridecodebin->posted_about_to_finish) {
-    uridecodebin->posted_about_to_finish = TRUE;
-    g_signal_emit (uridecodebin,
-        gst_uri_decode_bin3_signals[SIGNAL_ABOUT_TO_FINISH], 0, NULL);
+  GST_DEBUG_OBJECT (uridecodebin, "output %d , posted_about_to_finish:%d",
+      item->group_id, item->posted_about_to_finish);
+
+  if (item->posted_about_to_finish) {
+    GST_DEBUG_OBJECT (uridecodebin,
+        "already handling about-to-finish for this play item");
+    return;
+  }
+
+  if (item != uridecodebin->input_item) {
+    GST_DEBUG_OBJECT (uridecodebin, "Postponing about-to-finish propagation");
+    item->pending_about_to_finish = TRUE;
+    return;
   }
+
+  /* If the input entry is pull-based, mark all the source pads as EOS */
+  if (play_item_is_pull_based (item)) {
+    GST_DEBUG_OBJECT (uridecodebin, "Marking play item as EOS");
+    play_item_set_eos (item);
+  }
+
+  item->posted_about_to_finish = TRUE;
+  GST_DEBUG_OBJECT (uridecodebin, "Posting about-to-finish");
+  g_signal_emit (uridecodebin,
+      gst_uri_decode_bin3_signals[SIGNAL_ABOUT_TO_FINISH], 0, NULL);
+
+  /* Note : Activation of the (potential) next entry is handled in
+   * gst_uri_decode_bin3_set_uri */
+}
+
+static void
+db_about_to_finish_cb (GstElement * decodebin, GstURIDecodeBin3 * uridecodebin)
+{
+  GST_LOG_OBJECT (uridecodebin, "about to finish from %s",
+      GST_OBJECT_NAME (decodebin));
+
+  emit_and_handle_about_to_finish (uridecodebin, uridecodebin->output_item);
 }
 
 static void
 gst_uri_decode_bin3_init (GstURIDecodeBin3 * dec)
 {
-  g_mutex_init (&dec->lock);
+  GstPlayItem *item;
 
-  dec->uri = DEFAULT_PROP_URI;
-  dec->suburi = DEFAULT_PROP_SUBURI;
   dec->connection_speed = DEFAULT_CONNECTION_SPEED;
   dec->caps = DEFAULT_CAPS;
   dec->buffer_duration = DEFAULT_BUFFER_DURATION;
@@ -664,6 +867,9 @@ gst_uri_decode_bin3_init (GstURIDecodeBin3 * dec)
   dec->use_buffering = DEFAULT_USE_BUFFERING;
   dec->ring_buffer_max_size = DEFAULT_RING_BUFFER_MAX_SIZE;
 
+  g_mutex_init (&dec->play_items_lock);
+  g_cond_init (&dec->input_source_drained);
+
   dec->decodebin = gst_element_factory_make ("decodebin3", NULL);
   gst_bin_add (GST_BIN_CAST (dec), dec->decodebin);
   dec->db_pad_added_id =
@@ -682,18 +888,32 @@ gst_uri_decode_bin3_init (GstURIDecodeBin3 * dec)
   GST_OBJECT_FLAG_SET (dec, GST_ELEMENT_FLAG_SOURCE);
   gst_bin_set_suppressed_flags (GST_BIN (dec),
       GST_ELEMENT_FLAG_SOURCE | GST_ELEMENT_FLAG_SINK);
+
+  item = new_play_item (dec);
+  dec->play_items = g_list_append (dec->play_items, item);
+  /* The initial play item is automatically the input and output one */
+  dec->input_item = dec->output_item = item;
 }
 
 static void
-gst_uri_decode_bin3_finalize (GObject * obj)
+gst_uri_decode_bin3_dispose (GObject * obj)
 {
   GstURIDecodeBin3 *dec = GST_URI_DECODE_BIN3 (obj);
+  GList *iter;
 
-  g_mutex_clear (&dec->lock);
-  g_free (dec->uri);
-  g_free (dec->suburi);
+  GST_DEBUG_OBJECT (obj, "Disposing");
 
-  G_OBJECT_CLASS (parent_class)->finalize (obj);
+  /* Free all play items */
+  for (iter = dec->play_items; iter; iter = iter->next) {
+    GstPlayItem *item = iter->data;
+    free_play_item (dec, item);
+  }
+  g_list_free (dec->play_items);
+  dec->play_items = NULL;
+
+  g_mutex_clear (&dec->play_items_lock);
+
+  G_OBJECT_CLASS (parent_class)->dispose (obj);
 }
 
 static GstStateChangeReturn
@@ -709,31 +929,19 @@ activate_source_item (GstSourceItem * item)
   g_object_set (handler->urisourcebin, "uri", item->uri, NULL);
   if (!handler->active) {
     gst_bin_add ((GstBin *) handler->uridecodebin, handler->urisourcebin);
-    /* if (!gst_element_sync_state_with_parent (handler->urisourcebin)) */
-    /*   return GST_STATE_CHANGE_FAILURE; */
     handler->active = TRUE;
   }
 
+  gst_element_sync_state_with_parent (handler->urisourcebin);
+
   return GST_STATE_CHANGE_SUCCESS;
 }
 
 static void
-src_pad_added_cb (GstElement * element, GstPad * pad,
-    GstSourceHandler * handler)
+link_src_pad_to_db3 (GstURIDecodeBin3 * uridecodebin, GstSourcePad * spad)
 {
-  GstURIDecodeBin3 *uridecodebin;
+  GstSourceHandler *handler = spad->handler;
   GstPad *sinkpad = NULL;
-  GstPadLinkReturn res;
-  GstPlayItem *current_play_item;
-  GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
-
-  uridecodebin = handler->uridecodebin;
-  current_play_item = uridecodebin->current;
-
-  GST_DEBUG_OBJECT (uridecodebin,
-      "New pad %" GST_PTR_FORMAT " from source %" GST_PTR_FORMAT, pad, element);
-
-  /* FIXME: Add probe to unify group_id and detect EOS */
 
   /* Try to link to main sink pad only if it's from a main handler */
   if (handler->is_main_source) {
@@ -744,44 +952,371 @@ src_pad_added_cb (GstElement * element, GstPad * pad,
     }
   }
 
-  if (sinkpad == NULL)
+  if (sinkpad == NULL) {
     sinkpad =
         gst_element_request_pad_simple (uridecodebin->decodebin, "sink_%u");
+    spad->db3_pad_is_request = TRUE;
+  }
 
   if (sinkpad) {
+    GstPadLinkReturn res;
     GST_DEBUG_OBJECT (uridecodebin,
-        "Linking %" GST_PTR_FORMAT " to %" GST_PTR_FORMAT, pad, sinkpad);
-    res = gst_pad_link (pad, sinkpad);
+        "Linking %" GST_PTR_FORMAT " to %" GST_PTR_FORMAT, spad->src_pad,
+        sinkpad);
+    res = gst_pad_link (spad->src_pad, sinkpad);
     gst_object_unref (sinkpad);
-    if (GST_PAD_LINK_FAILED (res))
-      goto link_failed;
+    if (GST_PAD_LINK_FAILED (res)) {
+      GST_ERROR_OBJECT (uridecodebin,
+          "failed to link pad %s:%s to decodebin, reason %s (%d)",
+          GST_DEBUG_PAD_NAME (spad->src_pad), gst_pad_link_get_name (res), res);
+      return;
+    }
+  } else {
+    GST_ERROR_OBJECT (uridecodebin, "Could not get a sinkpad from decodebin3");
+    return;
   }
 
+  spad->db3_sink_pad = sinkpad;
+
   /* Activate sub_item after the main source activation was finished */
-  if (handler->is_main_source && current_play_item->sub_item
-      && !current_play_item->sub_item->handler) {
-    current_play_item->sub_item->handler =
-        new_source_handler (uridecodebin, FALSE);
-    ret = activate_source_item (current_play_item->sub_item);
+  if (handler->is_main_source && handler->play_item->sub_item
+      && !handler->play_item->sub_item->handler) {
+    GstStateChangeReturn ret;
+    handler->play_item->sub_item->handler =
+        new_source_handler (uridecodebin, handler->play_item, FALSE);
+    ret = activate_source_item (handler->play_item->sub_item);
     if (ret == GST_STATE_CHANGE_FAILURE)
       goto sub_item_activation_failed;
   }
 
   return;
 
-link_failed:
+sub_item_activation_failed:
   {
     GST_ERROR_OBJECT (uridecodebin,
-        "failed to link pad %s:%s to decodebin, reason %s (%d)",
-        GST_DEBUG_PAD_NAME (pad), gst_pad_link_get_name (res), res);
+        "failed to activate subtitle playback item");
     return;
   }
-sub_item_activation_failed:
+}
+
+static GList *
+get_all_play_item_source_pads (GstPlayItem * item)
+{
+  GList *ret = NULL;
+
+  if (item->main_item && item->main_item->handler) {
+    ret = g_list_copy (item->main_item->handler->sourcepads);
+  }
+
+  if (item->sub_item && item->sub_item->handler) {
+    ret =
+        g_list_concat (ret, g_list_copy (item->sub_item->handler->sourcepads));
+  }
+
+  return ret;
+}
+
+static GstSourcePad *
+find_matching_source_pad (GList * candidates, GstSourcePad * target)
+{
+  GList *iter;
+  GstStream *stream = target->stream;
+
+  GST_DEBUG_OBJECT (target->src_pad, "Find match for stream %" GST_PTR_FORMAT,
+      stream);
+
+  for (iter = candidates; iter; iter = iter->next) {
+    GstSourcePad *cand = iter->data;
+
+    if (!cand->db3_sink_pad)
+      continue;
+
+    /* Target doesn't have a specific GstStream, return the first result */
+    if (!stream)
+      return cand;
+
+    if (gst_stream_get_stream_type (cand->stream) ==
+        gst_stream_get_stream_type (stream))
+      return cand;
+  }
+
+  return NULL;
+}
+
+/* PLAY_ITEMS_LOCK held
+ *
+ * Switch the input play item to the next one
+ */
+static void
+switch_and_activate_input_locked (GstURIDecodeBin3 * uridecodebin,
+    GstPlayItem * new_item)
+{
+  GList *new_pads = get_all_play_item_source_pads (new_item);
+  GList *old_pads = get_all_play_item_source_pads (uridecodebin->input_item);
+  GList *to_activate = NULL;
+  GList *iternew, *iterold;
+
+  /* Deactivate old urisourcebins first ? Problem is they might remove the pads */
+
+  /* Go over new item source pads and figure out a candidate replacement in  */
+  /* Figure out source pad matches */
+  for (iternew = new_pads; iternew; iternew = iternew->next) {
+    GstSourcePad *new_spad = iternew->data;
+    GstSourcePad *old_spad = find_matching_source_pad (old_pads, new_spad);
+
+    if (old_spad) {
+      GST_DEBUG_OBJECT (uridecodebin, "Relinking %s:%s from %s:%s to %s:%s",
+          GST_DEBUG_PAD_NAME (old_spad->db3_sink_pad),
+          GST_DEBUG_PAD_NAME (old_spad->src_pad),
+          GST_DEBUG_PAD_NAME (new_spad->src_pad));
+      gst_pad_unlink (old_spad->src_pad, old_spad->db3_sink_pad);
+      new_spad->db3_sink_pad = old_spad->db3_sink_pad;
+      new_spad->db3_pad_is_request = old_spad->db3_pad_is_request;
+      old_spad->db3_sink_pad = NULL;
+
+      gst_pad_link (new_spad->src_pad, new_spad->db3_sink_pad);
+      old_pads = g_list_remove (old_pads, old_spad);
+    } else {
+      GST_DEBUG_OBJECT (new_spad->src_pad, "Needs a new pad");
+      to_activate = g_list_append (to_activate, new_spad);
+    }
+  }
+
+  /* Remove unmatched old source pads */
+  for (iterold = old_pads; iterold; iterold = iterold->next) {
+    GstSourcePad *old_spad = iterold->data;
+    if (old_spad->db3_sink_pad && old_spad->db3_pad_is_request) {
+      GST_DEBUG_OBJECT (uridecodebin, "Releasing no longer used db3 pad");
+      gst_element_release_request_pad (uridecodebin->decodebin,
+          old_spad->db3_sink_pad);
+      old_spad->db3_sink_pad = NULL;
+    }
+  }
+
+  /* Link new source pads */
+  for (iternew = to_activate; iternew; iternew = iternew->next) {
+    GstSourcePad *new_spad = iternew->data;
+    link_src_pad_to_db3 (uridecodebin, new_spad);
+  }
+
+  /* Unblock all new item source pads */
+  for (iternew = new_pads; iternew; iternew = iternew->next) {
+    GstSourcePad *new_spad = iternew->data;
+    if (new_spad->block_probe_id) {
+      gst_pad_remove_probe (new_spad->src_pad, new_spad->block_probe_id);
+      new_spad->block_probe_id = 0;
+    }
+  }
+  g_list_free (new_pads);
+  g_list_free (old_pads);
+
+  /* Deactivate old input item (by removing the source components). The final
+   * removal of this play item will be done once decodebin3 starts output the
+   * content of the new play item. */
+  if (uridecodebin->input_item->main_item) {
+    free_source_item (uridecodebin, uridecodebin->input_item->main_item);
+    uridecodebin->input_item->main_item = NULL;
+  }
+  if (uridecodebin->input_item->sub_item) {
+    free_source_item (uridecodebin, uridecodebin->input_item->sub_item);
+    uridecodebin->input_item->sub_item = NULL;
+  }
+
+  /* and set new one as input item */
+  uridecodebin->input_item = new_item;
+
+  if (new_item->main_item->handler->pending_buffering_msg) {
+    GstMessage *msg = new_item->main_item->handler->pending_buffering_msg;
+    new_item->main_item->handler->pending_buffering_msg = NULL;
+    PLAY_ITEMS_UNLOCK (uridecodebin);
+    GST_BIN_CLASS (parent_class)->handle_message ((GstBin *) uridecodebin, msg);
+    PLAY_ITEMS_LOCK (uridecodebin);
+  }
+}
+
+static GstPadProbeReturn
+uri_src_probe (GstPad * pad, GstPadProbeInfo * info, GstSourcePad * srcpad)
+{
+  GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
+  GstSourceHandler *handler = srcpad->handler;
+  GstPadProbeReturn ret = GST_PAD_PROBE_OK;
+
+  GST_DEBUG_OBJECT (pad, "event %" GST_PTR_FORMAT, event);
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_EOS:
+    {
+      GstPad *peer;
+      /* Propagate the EOS *before* triggering any potential switch */
+      peer = gst_pad_get_peer (pad);
+      if (peer) {
+        gst_pad_send_event (peer, event);
+        gst_object_unref (peer);
+      }
+
+      PLAY_ITEMS_LOCK (handler->uridecodebin);
+      /* EOS : Mark pad as EOS */
+      srcpad->saw_eos = TRUE;
+      /* Check if the input play item is fully EOS. If yes and there is a
+       * pending play item, switch to it */
+      if (handler->play_item == handler->uridecodebin->input_item &&
+          play_item_is_eos (handler->play_item)) {
+        g_cond_signal (&handler->uridecodebin->input_source_drained);
+      }
+      PLAY_ITEMS_UNLOCK (handler->uridecodebin);
+      ret = GST_PAD_PROBE_HANDLED;
+      break;
+    }
+    case GST_EVENT_STREAM_START:
+    {
+      GstStream *stream = NULL;
+      srcpad->saw_eos = FALSE;
+      gst_event_parse_stream (event, &stream);
+      if (stream) {
+        GST_DEBUG_OBJECT (srcpad->src_pad, "Got GstStream %" GST_PTR_FORMAT,
+            stream);
+        if (srcpad->stream)
+          gst_object_unref (srcpad->stream);
+        srcpad->stream = stream;
+      }
+      break;
+    }
+    case GST_EVENT_SEGMENT:
+    {
+      srcpad->saw_eos = FALSE;
+      break;
+    }
+    default:
+      break;
+  }
+
+  return ret;
+}
+
+static GstPadProbeReturn
+uri_src_block_probe (GstPad * pad, GstPadProbeInfo * info,
+    GstSourcePad * srcpad)
+{
+  GstPadProbeReturn ret = GST_PAD_PROBE_OK;
+  GstSourceHandler *handler = srcpad->handler;
+  GST_DEBUG_OBJECT (pad, "blocked");
+
+  /* We only block on buffers, buffer list and gap events. Everything else is
+   * dropped (sticky events will be propagated later) */
+  if (GST_IS_EVENT (GST_PAD_PROBE_INFO_DATA (info)) &&
+      GST_EVENT_TYPE (GST_PAD_PROBE_INFO_EVENT (info)) != GST_EVENT_GAP) {
+    GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
+    if (GST_EVENT_TYPE (event) == GST_EVENT_STREAM_START) {
+      GstStream *stream = NULL;
+      gst_event_parse_stream (event, &stream);
+      if (stream) {
+        GST_DEBUG_OBJECT (srcpad->src_pad, "Got GstStream %" GST_PTR_FORMAT,
+            stream);
+        if (srcpad->stream)
+          gst_object_unref (srcpad->stream);
+        srcpad->stream = stream;
+      }
+    }
+    GST_LOG_OBJECT (pad, "Skiping %" GST_PTR_FORMAT, event);
+    return GST_PAD_PROBE_DROP;
+  }
+
+  PLAY_ITEMS_LOCK (handler->uridecodebin);
+  if (play_item_is_eos (handler->uridecodebin->input_item)) {
+    GST_DEBUG_OBJECT (handler->uridecodebin,
+        "We can switch over to the next input item");
+    switch_and_activate_input_locked (handler->uridecodebin,
+        handler->play_item);
+    ret = GST_PAD_PROBE_REMOVE;
+  } else if (play_item_has_all_pads (handler->play_item)) {
+    /* We have all expected pads for this play item but the current input
+     * play item isn't done yet, wait for it */
+    g_cond_wait (&handler->uridecodebin->input_source_drained,
+        &handler->uridecodebin->play_items_lock);
+    if (g_atomic_int_get (&handler->uridecodebin->shutdown))
+      goto shutdown;
+    if (play_item_is_eos (handler->uridecodebin->input_item)) {
+      GST_DEBUG_OBJECT (handler->uridecodebin,
+          "We can switch over to the next input item");
+      switch_and_activate_input_locked (handler->uridecodebin,
+          handler->play_item);
+      ret = GST_PAD_PROBE_REMOVE;
+    }
+  }
+
+  PLAY_ITEMS_UNLOCK (handler->uridecodebin);
+
+  return ret;
+
+  /* ERRORS */
+shutdown:
   {
-    GST_ERROR_OBJECT (uridecodebin,
-        "failed to activate subtitle playback item");
+    GST_LOG_OBJECT (pad, "Shutting down");
+    PLAY_ITEMS_UNLOCK (handler->uridecodebin);
+    return GST_PAD_PROBE_REMOVE;
+  }
+}
+
+static void
+src_pad_added_cb (GstElement * element, GstPad * pad,
+    GstSourceHandler * handler)
+{
+  GstSourcePad *spad = g_slice_new0 (GstSourcePad);
+  GstURIDecodeBin3 *uridecodebin;
+
+  uridecodebin = handler->uridecodebin;
+
+  PLAY_ITEMS_LOCK (uridecodebin);
+
+  GST_DEBUG_OBJECT (uridecodebin,
+      "New pad %" GST_PTR_FORMAT " from source %" GST_PTR_FORMAT, pad, element);
+
+  /* Register the new pad information with the source handler */
+  spad->handler = handler;
+  spad->src_pad = pad;
+  spad->event_probe_id =
+      gst_pad_add_probe (pad,
+      GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, (GstPadProbeCallback) uri_src_probe,
+      spad, NULL);
+
+  handler->sourcepads = g_list_append (handler->sourcepads, spad);
+
+  /* Can the pad be linked straight away to db3 ?
+   * This can happen if:
+   * * It is the initial play item
+   * * It is part of the current input item
+   */
+  if (handler->play_item == uridecodebin->input_item) {
+    GST_DEBUG_OBJECT (uridecodebin,
+        "Pad is part of current input item, linking");
+
+    link_src_pad_to_db3 (uridecodebin, spad);
+    PLAY_ITEMS_UNLOCK (uridecodebin);
     return;
   }
+
+  /* This pad is not from the current input item. We add a blocking probe to
+   * wait until we block on the new urisourcebin streaming thread and can
+   * switch */
+  GST_DEBUG_OBJECT (uridecodebin, "Blocking input pad");
+  spad->block_probe_id =
+      gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM,
+      (GstPadProbeCallback) uri_src_block_probe, spad, NULL);
+  PLAY_ITEMS_UNLOCK (uridecodebin);
+}
+
+static GstSourcePad *
+handler_get_source_pad (GstSourceHandler * handler, GstPad * srcpad)
+{
+  GList *iter;
+
+  for (iter = handler->sourcepads; iter; iter = iter->next) {
+    GstSourcePad *spad = iter->data;
+    if (spad->src_pad == srcpad)
+      return spad;
+  }
+
+  return NULL;
 }
 
 static void
@@ -789,26 +1324,21 @@ src_pad_removed_cb (GstElement * element, GstPad * pad,
     GstSourceHandler * handler)
 {
   GstURIDecodeBin3 *uridecodebin = handler->uridecodebin;
-  GstPad *peer_pad = gst_pad_get_peer (pad);
+  GstSourcePad *spad = handler_get_source_pad (handler, pad);
 
-  if (peer_pad) {
-    GstPadTemplate *templ = gst_pad_get_pad_template (peer_pad);
+  if (!spad)
+    return;
 
-    GST_DEBUG_OBJECT (uridecodebin,
-        "Source %" GST_PTR_FORMAT " removed pad %" GST_PTR_FORMAT " peer %"
-        GST_PTR_FORMAT, element, pad, peer_pad);
+  GST_DEBUG_OBJECT (uridecodebin,
+      "Source %" GST_PTR_FORMAT " removed pad %" GST_PTR_FORMAT " peer %"
+      GST_PTR_FORMAT, element, pad, spad->db3_sink_pad);
 
-    if (templ) {
-      if (GST_PAD_TEMPLATE_PRESENCE (templ) == GST_PAD_REQUEST) {
-        GST_DEBUG_OBJECT (uridecodebin,
-            "Releasing decodebin pad %" GST_PTR_FORMAT, peer_pad);
-        gst_element_release_request_pad (uridecodebin->decodebin, peer_pad);
-      }
-      gst_object_unref (templ);
-    }
+  if (spad->db3_sink_pad && spad->db3_pad_is_request)
+    gst_element_release_request_pad (uridecodebin->decodebin,
+        spad->db3_sink_pad);
 
-    gst_object_unref (peer_pad);
-  }
+  handler->sourcepads = g_list_remove (handler->sourcepads, spad);
+  g_slice_free (GstSourcePad, spad);
 }
 
 static void
@@ -822,22 +1352,22 @@ src_source_setup_cb (GstElement * element, GstElement * source,
 static void
 src_about_to_finish_cb (GstElement * element, GstSourceHandler * handler)
 {
-  /* FIXME : check if all sources are done */
-  if (!handler->uridecodebin->posted_about_to_finish) {
-    handler->uridecodebin->posted_about_to_finish = TRUE;
-    g_signal_emit (handler->uridecodebin,
-        gst_uri_decode_bin3_signals[SIGNAL_ABOUT_TO_FINISH], 0, NULL);
-  }
+  GST_LOG_OBJECT (handler->uridecodebin, "about to finish from %s",
+      GST_OBJECT_NAME (element));
+
+  emit_and_handle_about_to_finish (handler->uridecodebin, handler->play_item);
 }
 
 static GstSourceHandler *
-new_source_handler (GstURIDecodeBin3 * uridecodebin, gboolean is_main)
+new_source_handler (GstURIDecodeBin3 * uridecodebin, GstPlayItem * item,
+    gboolean is_main)
 {
   GstSourceHandler *handler;
 
   handler = g_slice_new0 (GstSourceHandler);
 
   handler->uridecodebin = uridecodebin;
+  handler->play_item = item;
   handler->is_main_source = is_main;
   handler->urisourcebin = gst_element_factory_make ("urisourcebin", NULL);
   /* Set pending properties */
@@ -863,12 +1393,38 @@ new_source_handler (GstURIDecodeBin3 * uridecodebin, gboolean is_main)
       g_signal_connect (handler->urisourcebin, "about-to-finish",
       (GCallback) src_about_to_finish_cb, handler);
 
-  uridecodebin->source_handlers =
-      g_list_append (uridecodebin->source_handlers, handler);
+  handler->expected_pads = 1;
 
   return handler;
 }
 
+static gboolean
+source_handler_is_eos (GstSourceHandler * handler)
+{
+  GList *iter;
+
+  for (iter = handler->sourcepads; iter; iter = iter->next) {
+    GstSourcePad *spad = iter->data;
+
+    if (!spad->saw_eos)
+      return FALSE;
+  }
+
+  return TRUE;
+}
+
+static void
+source_handler_set_eos (GstSourceHandler * handler)
+{
+  GList *iter;
+
+  for (iter = handler->sourcepads; iter; iter = iter->next) {
+    GstSourcePad *spad = iter->data;
+
+    spad->saw_eos = TRUE;
+  }
+}
+
 static void
 gst_uri_decode_bin3_set_property (GObject * object, guint prop_id,
     const GValue * value, GParamSpec * pspec)
@@ -877,19 +1433,15 @@ gst_uri_decode_bin3_set_property (GObject * object, guint prop_id,
 
   switch (prop_id) {
     case PROP_URI:
-      if (dec->uri)
-        g_free (dec->uri);
-      dec->uri = g_value_dup_string (value);
+      gst_uri_decode_bin3_set_uri (dec, g_value_get_string (value));
       break;
     case PROP_SUBURI:
-      if (dec->suburi)
-        g_free (dec->suburi);
-      dec->suburi = g_value_dup_string (value);
+      gst_uri_decode_bin3_set_suburi (dec, g_value_get_string (value));
       break;
     case PROP_CONNECTION_SPEED:
-      GST_URI_DECODE_BIN3_LOCK (dec);
+      GST_OBJECT_LOCK (dec);
       dec->connection_speed = g_value_get_uint64 (value) * 1000;
-      GST_URI_DECODE_BIN3_UNLOCK (dec);
+      GST_OBJECT_UNLOCK (dec);
       break;
     case PROP_BUFFER_SIZE:
       dec->buffer_size = g_value_get_int (value);
@@ -913,6 +1465,11 @@ gst_uri_decode_bin3_set_property (GObject * object, guint prop_id,
       dec->caps = g_value_dup_boxed (value);
       GST_OBJECT_UNLOCK (dec);
       break;
+    case PROP_INSTANT_URI:
+      GST_OBJECT_LOCK (dec);
+      dec->instant_uri = g_value_get_boolean (value);
+      GST_OBJECT_UNLOCK (dec);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -928,13 +1485,18 @@ gst_uri_decode_bin3_get_property (GObject * object, guint prop_id,
   switch (prop_id) {
     case PROP_URI:
     {
-      g_value_set_string (value, dec->uri);
+      GstPlayItem *item = dec->play_items->data;
+      /* Return from the head */
+      if (item->main_item)
+        g_value_set_string (value, item->main_item->uri);
+      else
+        g_value_set_string (value, NULL);
       break;
     }
     case PROP_CURRENT_URI:
     {
-      if (dec->current && dec->current->main_item) {
-        g_value_set_string (value, dec->current->main_item->uri);
+      if (dec->output_item && dec->output_item->main_item) {
+        g_value_set_string (value, dec->output_item->main_item->uri);
       } else {
         g_value_set_string (value, NULL);
       }
@@ -942,13 +1504,18 @@ gst_uri_decode_bin3_get_property (GObject * object, guint prop_id,
     }
     case PROP_SUBURI:
     {
-      g_value_set_string (value, dec->suburi);
+      GstPlayItem *item = dec->play_items->data;
+      /* Return from the head */
+      if (item->sub_item)
+        g_value_set_string (value, item->sub_item->uri);
+      else
+        g_value_set_string (value, NULL);
       break;
     }
     case PROP_CURRENT_SUBURI:
     {
-      if (dec->current && dec->current->sub_item) {
-        g_value_set_string (value, dec->current->sub_item->uri);
+      if (dec->output_item && dec->output_item->sub_item) {
+        g_value_set_string (value, dec->output_item->sub_item->uri);
       } else {
         g_value_set_string (value, NULL);
       }
@@ -962,9 +1529,9 @@ gst_uri_decode_bin3_get_property (GObject * object, guint prop_id,
       break;
     }
     case PROP_CONNECTION_SPEED:
-      GST_URI_DECODE_BIN3_LOCK (dec);
+      GST_OBJECT_LOCK (dec);
       g_value_set_uint64 (value, dec->connection_speed / 1000);
-      GST_URI_DECODE_BIN3_UNLOCK (dec);
+      GST_OBJECT_UNLOCK (dec);
       break;
     case PROP_BUFFER_SIZE:
       GST_OBJECT_LOCK (dec);
@@ -990,6 +1557,11 @@ gst_uri_decode_bin3_get_property (GObject * object, guint prop_id,
       g_value_set_boxed (value, dec->caps);
       GST_OBJECT_UNLOCK (dec);
       break;
+    case PROP_INSTANT_URI:
+      GST_OBJECT_LOCK (dec);
+      g_value_set_boolean (value, dec->instant_uri);
+      GST_OBJECT_UNLOCK (dec);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -1002,13 +1574,22 @@ free_source_handler (GstURIDecodeBin3 * uridecodebin,
 {
   GST_LOG_OBJECT (uridecodebin, "source handler %p", handler);
   if (handler->active) {
+    GList *iter;
+    GST_STATE_LOCK (uridecodebin);
     GST_LOG_OBJECT (uridecodebin, "Removing %" GST_PTR_FORMAT,
         handler->urisourcebin);
+    for (iter = handler->sourcepads; iter; iter = iter->next) {
+      GstSourcePad *spad = iter->data;
+      if (spad->block_probe_id)
+        gst_pad_remove_probe (spad->src_pad, spad->block_probe_id);
+    }
     gst_element_set_state (handler->urisourcebin, GST_STATE_NULL);
     gst_bin_remove ((GstBin *) uridecodebin, handler->urisourcebin);
+    GST_STATE_UNLOCK (uridecodebin);
+    g_list_free (handler->sourcepads);
   }
-  uridecodebin->source_handlers =
-      g_list_remove (uridecodebin->source_handlers, handler);
+  if (handler->pending_buffering_msg)
+    gst_message_unref (handler->pending_buffering_msg);
   g_slice_free (GstSourceHandler, handler);
 }
 
@@ -1029,18 +1610,28 @@ free_source_item (GstURIDecodeBin3 * uridecodebin, GstSourceItem * item)
   GST_LOG_OBJECT (uridecodebin, "source item %p", item);
   if (item->handler)
     free_source_handler (uridecodebin, item->handler);
+  g_free (item->uri);
   g_slice_free (GstSourceItem, item);
 }
 
+static void
+source_item_set_uri (GstSourceItem * item, const gchar * uri)
+{
+  if (item->uri)
+    g_free (item->uri);
+  item->uri = g_strdup (uri);
+  if (item->handler) {
+    g_object_set (item->handler->urisourcebin, "uri", uri, NULL);
+  }
+}
+
 static GstPlayItem *
-new_play_item (GstURIDecodeBin3 * dec, gchar * uri, gchar * suburi)
+new_play_item (GstURIDecodeBin3 * dec)
 {
   GstPlayItem *item = g_slice_new0 (GstPlayItem);
 
   item->uridecodebin = dec;
-  item->main_item = new_source_item (dec, item, uri);
-  if (suburi)
-    item->sub_item = new_source_item (dec, item, suburi);
+  item->group_id = GST_GROUP_ID_INVALID;
 
   return item;
 }
@@ -1057,6 +1648,170 @@ free_play_item (GstURIDecodeBin3 * dec, GstPlayItem * item)
   g_slice_free (GstPlayItem, item);
 }
 
+static void
+play_item_set_uri (GstPlayItem * item, const gchar * uri)
+{
+  if (!item->main_item) {
+    item->main_item =
+        new_source_item (item->uridecodebin, item, g_strdup (uri));
+  } else {
+    source_item_set_uri (item->main_item, uri);
+  }
+}
+
+static void
+play_item_set_suburi (GstPlayItem * item, const gchar * uri)
+{
+  if (!item->sub_item) {
+    item->sub_item = new_source_item (item->uridecodebin, item, g_strdup (uri));
+  } else {
+    source_item_set_uri (item->sub_item, uri);
+  }
+}
+
+static gboolean
+play_item_is_eos (GstPlayItem * item)
+{
+  if (item->main_item && item->main_item->handler) {
+    if (!source_handler_is_eos (item->main_item->handler))
+      return FALSE;
+  }
+  if (item->sub_item && item->sub_item->handler) {
+    if (!source_handler_is_eos (item->sub_item->handler))
+      return FALSE;
+  }
+
+  return TRUE;
+}
+
+/* Mark all sourcepads of a play item as EOS. Used in pull-mode */
+static void
+play_item_set_eos (GstPlayItem * item)
+{
+  if (item->main_item && item->main_item->handler)
+    source_handler_set_eos (item->main_item->handler);
+
+  if (item->sub_item && item->sub_item->handler)
+    source_handler_set_eos (item->sub_item->handler);
+}
+
+static gboolean
+play_item_has_all_pads (GstPlayItem * item)
+{
+  GstSourceHandler *handler;
+
+  if (item->main_item && item->main_item->handler) {
+    handler = item->main_item->handler;
+    if (handler->expected_pads != g_list_length (handler->sourcepads))
+      return FALSE;
+  }
+
+  if (item->sub_item && item->sub_item->handler) {
+    handler = item->sub_item->handler;
+    if (handler->expected_pads != g_list_length (handler->sourcepads))
+      return FALSE;
+  }
+
+  return TRUE;
+}
+
+/* Returns the next inactive play item. If none available, it will create one
+ * and add it to the list of play items */
+static GstPlayItem *
+next_inactive_play_item (GstURIDecodeBin3 * dec)
+{
+  GstPlayItem *res;
+  GList *iter;
+
+  for (iter = dec->play_items; iter; iter = iter->next) {
+    res = iter->data;
+    if (!res->active)
+      return res;
+  }
+
+  GST_DEBUG_OBJECT (dec, "No inactive play items, creating a new one");
+  res = new_play_item (dec);
+  dec->play_items = g_list_append (dec->play_items, res);
+
+  return res;
+}
+
+static GstPadProbeReturn
+uri_src_ignore_block_probe (GstPad * pad, GstPadProbeInfo * info,
+    GstSourcePad * srcpad)
+{
+  GST_DEBUG_OBJECT (pad, "blocked");
+  return GST_PAD_PROBE_OK;
+}
+
+static void
+gst_uri_decode_bin3_set_uri (GstURIDecodeBin3 * dec, const gchar * uri)
+{
+  GstPlayItem *item;
+  gboolean start_item = FALSE;
+
+  GST_DEBUG_OBJECT (dec, "uri: %s", uri);
+
+  item = next_inactive_play_item (dec);
+  play_item_set_uri (item, uri);
+  if (dec->instant_uri && item != dec->input_item) {
+    GList *old_pads = get_all_play_item_source_pads (dec->input_item);
+    GList *iter;
+
+    /* Switch immediately if not the current input item */
+    GST_DEBUG_OBJECT (dec, "Switching immediately");
+
+    /* FLUSH START all input pads */
+    for (iter = old_pads; iter; iter = iter->next) {
+      GstSourcePad *spad = iter->data;
+      if (spad->db3_sink_pad) {
+        /* Mark all input pads as EOS */
+        gst_pad_send_event (spad->db3_sink_pad, gst_event_new_flush_start ());
+      }
+      /* Block all input source pads */
+      spad->block_probe_id =
+          gst_pad_add_probe (spad->src_pad, GST_PAD_PROBE_TYPE_IDLE,
+          (GstPadProbeCallback) uri_src_ignore_block_probe, spad, NULL);
+      spad->saw_eos = TRUE;
+    }
+    for (iter = old_pads; iter; iter = iter->next) {
+      /* FLUSH_STOP all current input pads */
+      GstSourcePad *spad = iter->data;
+      if (spad->db3_sink_pad) {
+        gst_pad_send_event (spad->db3_sink_pad,
+            gst_event_new_flush_stop (TRUE));
+      }
+    }
+    start_item = TRUE;
+  } else if (dec->input_item->posted_about_to_finish) {
+    GList *iter = g_list_find (dec->play_items, dec->input_item);
+    /* If the current item is finishing and the new item is the one just after,
+     *  we need to activate it */
+    if (iter && iter->next && iter->next->data == item) {
+      GST_DEBUG_OBJECT (dec, "Starting new entry (gapless mode)");
+      start_item = TRUE;
+    }
+  }
+
+  if (start_item) {
+    /* Start new item */
+    activate_play_item (item);
+  }
+}
+
+static void
+gst_uri_decode_bin3_set_suburi (GstURIDecodeBin3 * dec, const gchar * uri)
+{
+  GstPlayItem *item;
+  GST_DEBUG_OBJECT (dec, "suburi: %s", uri);
+
+  /* FIXME : Handle instant-uri-change. Should we just apply it automatically to
+   * the current input item ? */
+
+  item = next_inactive_play_item (dec);
+  play_item_set_suburi (item, uri);
+}
+
 /* Sync source handlers for the given play item. Might require creating/removing some
  * and/or configure the handlers accordingly */
 static GstStateChangeReturn
@@ -1064,12 +1819,12 @@ assign_handlers_to_item (GstURIDecodeBin3 * dec, GstPlayItem * item)
 {
   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
 
-  /* FIXME : Go over existing handlers to see if we can assign some to the
-   * given item */
+  if (item->main_item == NULL)
+    return GST_STATE_CHANGE_FAILURE;
 
   /* Create missing handlers */
   if (item->main_item->handler == NULL) {
-    item->main_item->handler = new_source_handler (dec, TRUE);
+    item->main_item->handler = new_source_handler (dec, item, TRUE);
     ret = activate_source_item (item->main_item);
     if (ret == GST_STATE_CHANGE_FAILURE)
       return ret;
@@ -1080,40 +1835,37 @@ assign_handlers_to_item (GstURIDecodeBin3 * dec, GstPlayItem * item)
 
 /* Called to activate the next play item */
 static GstStateChangeReturn
-activate_next_play_item (GstURIDecodeBin3 * dec)
+activate_play_item (GstPlayItem * item)
 {
-  GstPlayItem *item;
   GstStateChangeReturn ret;
 
-  /* If there is no current play entry, create one from the uri/suburi
-   * FIXME : Use a playlist API in the future */
-  item = new_play_item (dec, dec->uri, dec->suburi);
+  GST_DEBUG_OBJECT (item->uridecodebin, "Activating play item");
 
-  ret = assign_handlers_to_item (dec, item);
-  if (ret == GST_STATE_CHANGE_FAILURE) {
-    free_play_item (dec, item);
-    return ret;
+  ret = assign_handlers_to_item (item->uridecodebin, item);
+  if (ret != GST_STATE_CHANGE_FAILURE) {
+    item->active = TRUE;
   }
 
-  dec->play_items = g_list_append (dec->play_items, item);
-  dec->current = dec->play_items->data;
-
   return ret;
 }
 
+/* Remove all but the last play item */
 static void
-free_play_items (GstURIDecodeBin3 * dec)
+purge_play_items (GstURIDecodeBin3 * dec)
 {
-  GList *tmp;
+  GST_DEBUG_OBJECT (dec, "Purging play items");
 
-  for (tmp = dec->play_items; tmp; tmp = tmp->next) {
-    GstPlayItem *item = (GstPlayItem *) tmp->data;
+  PLAY_ITEMS_LOCK (dec);
+  g_cond_signal (&dec->input_source_drained);
+  while (dec->play_items && dec->play_items->next) {
+    GstPlayItem *item = dec->play_items->data;
+    dec->play_items = g_list_remove (dec->play_items, item);
     free_play_item (dec, item);
   }
 
-  g_list_free (dec->play_items);
-  dec->play_items = NULL;
-  dec->current = NULL;
+  dec->output_item = dec->input_item = dec->play_items->data;
+  dec->output_item->posted_about_to_finish = FALSE;
+  PLAY_ITEMS_UNLOCK (dec);
 }
 
 static GstStateChangeReturn
@@ -1128,9 +1880,17 @@ gst_uri_decode_bin3_change_state (GstElement * element,
       g_object_set (uridecodebin->decodebin, "caps", uridecodebin->caps, NULL);
       break;
     case GST_STATE_CHANGE_READY_TO_PAUSED:
-      ret = activate_next_play_item (uridecodebin);
+      g_atomic_int_set (&uridecodebin->shutdown, 0);
+      ret = activate_play_item (uridecodebin->input_item);
       if (ret == GST_STATE_CHANGE_FAILURE)
         goto failure;
+      break;
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+      PLAY_ITEMS_LOCK (uridecodebin);
+      g_atomic_int_set (&uridecodebin->shutdown, 1);
+      g_cond_signal (&uridecodebin->input_source_drained);
+      PLAY_ITEMS_UNLOCK (uridecodebin);
+      break;
     default:
       break;
   }
@@ -1141,10 +1901,9 @@ gst_uri_decode_bin3_change_state (GstElement * element,
 
   switch (transition) {
     case GST_STATE_CHANGE_PAUSED_TO_READY:
-      /* FIXME: Cleanup everything */
-      free_play_items (uridecodebin);
-      /* Free play item */
-      uridecodebin->posted_about_to_finish = FALSE;
+      /* Remove all play items *but* the last one, which becomes the current entry */
+      purge_play_items (uridecodebin);
+      uridecodebin->input_item->active = FALSE;
       break;
     default:
       break;
@@ -1156,11 +1915,93 @@ gst_uri_decode_bin3_change_state (GstElement * element,
 failure:
   {
     if (transition == GST_STATE_CHANGE_READY_TO_PAUSED)
-      free_play_items (uridecodebin);
+      purge_play_items (uridecodebin);
     return ret;
   }
 }
 
+static GstSourceHandler *
+find_source_handler_for_element (GstURIDecodeBin3 * uridecodebin,
+    GstObject * element)
+{
+  GList *iter;
+
+  for (iter = uridecodebin->play_items; iter; iter = iter->next) {
+    GstPlayItem *item = iter->data;
+
+    if (item->main_item && item->main_item->handler) {
+      GstSourceHandler *handler = item->main_item->handler;
+
+      if (gst_object_has_as_ancestor (element,
+              (GstObject *) handler->urisourcebin))
+        return handler;
+    }
+    if (item->sub_item && item->sub_item->handler) {
+      GstSourceHandler *handler = item->sub_item->handler;
+
+      if (gst_object_has_as_ancestor (element,
+              (GstObject *) handler->urisourcebin))
+        return handler;
+    }
+  }
+
+  return NULL;
+}
+
+static void
+gst_uri_decode_bin3_handle_message (GstBin * bin, GstMessage * msg)
+{
+  GstURIDecodeBin3 *uridecodebin = (GstURIDecodeBin3 *) bin;
+
+  switch (GST_MESSAGE_TYPE (msg)) {
+    case GST_MESSAGE_STREAMS_SELECTED:
+    {
+      GstSourceHandler *handler;
+      GST_DEBUG_OBJECT (uridecodebin, "Handle streams selected");
+      PLAY_ITEMS_LOCK (uridecodebin);
+      /* Find the matching handler (if any) */
+      if ((handler = find_source_handler_for_element (uridecodebin, msg->src))) {
+        handler->expected_pads = gst_message_streams_selected_get_size (msg);
+        GST_DEBUG_OBJECT (uridecodebin,
+            "Got streams-selected for %s with %d streams selected",
+            GST_ELEMENT_NAME (handler->urisourcebin), handler->expected_pads);
+      }
+      PLAY_ITEMS_UNLOCK (uridecodebin);
+      break;
+    }
+    case GST_MESSAGE_BUFFERING:
+    {
+      GstSourceHandler *handler;
+      GST_DEBUG_OBJECT (uridecodebin, "Handle buffering message");
+      PLAY_ITEMS_LOCK (uridecodebin);
+      /* Find the matching handler (if any) */
+      handler = find_source_handler_for_element (uridecodebin, msg->src);
+      if (!handler || !uridecodebin->input_item->main_item) {
+        gst_message_unref (msg);
+        msg = NULL;
+      } else if (handler != uridecodebin->input_item->main_item->handler) {
+        /* Store the message for a later time */
+        if (handler->pending_buffering_msg)
+          gst_message_unref (handler->pending_buffering_msg);
+        handler->pending_buffering_msg = msg;
+        msg = NULL;
+      } else {
+        /* This is the active main input item, we can forward directly */
+        GST_DEBUG_OBJECT (uridecodebin,
+            "Forwarding message for active input item");
+      }
+      PLAY_ITEMS_UNLOCK (uridecodebin);
+      break;
+
+    }
+    default:
+      break;
+  }
+
+  if (msg)
+    GST_BIN_CLASS (parent_class)->handle_message (bin, msg);
+}
+
 static gboolean
 gst_uri_decodebin3_send_event (GstElement * element, GstEvent * event)
 {
index c66c77a..11378ba 100644 (file)
@@ -104,6 +104,7 @@ typedef struct
 
   gboolean buffering;
   gboolean is_live;
+  gboolean initial_file;
 
   GstState desired_state;       /* as per user interaction, PAUSED or PLAYING */
 
@@ -111,6 +112,7 @@ typedef struct
 
   /* configuration */
   gboolean gapless;
+  gboolean instant_uri;
 
   GstPlayTrickMode trick_mode;
   gdouble rate;
@@ -166,8 +168,9 @@ gst_play_printf (const gchar * format, ...)
 
 static GstPlay *
 play_new (gchar ** uris, const gchar * audio_sink, const gchar * video_sink,
-    gboolean gapless, gdouble initial_volume, gboolean verbose,
-    const gchar * flags_string, gboolean use_playbin3, gdouble start_position)
+    gboolean gapless, gboolean instant_uri, gdouble initial_volume,
+    gboolean verbose, const gchar * flags_string, gboolean use_playbin3,
+    gdouble start_position)
 {
   GstElement *sink, *playbin;
   GstPlay *play;
@@ -262,6 +265,11 @@ play_new (gchar ** uris, const gchar * audio_sink, const gchar * video_sink,
         G_CALLBACK (play_about_to_finish), play);
   }
 
+  play->initial_file = TRUE;
+  if (use_playbin3) {
+    play->instant_uri = instant_uri;
+    g_object_set (G_OBJECT (play->playbin), "instant-uri", instant_uri, NULL);
+  }
   if (initial_volume != -1)
     play_set_relative_volume (play, initial_volume - 1.0);
 
@@ -745,7 +753,8 @@ play_uri (GstPlay * play, const gchar * next_uri)
 {
   gchar *loc;
 
-  gst_element_set_state (play->playbin, GST_STATE_READY);
+  if (!play->instant_uri || play->initial_file)
+    gst_element_set_state (play->playbin, GST_STATE_READY);
   play_reset (play);
 
   loc = play_uri_get_display_name (play, next_uri);
@@ -754,23 +763,26 @@ play_uri (GstPlay * play, const gchar * next_uri)
 
   g_object_set (play->playbin, "uri", next_uri, NULL);
 
-  switch (gst_element_set_state (play->playbin, GST_STATE_PAUSED)) {
-    case GST_STATE_CHANGE_FAILURE:
-      /* ignore, we should get an error message posted on the bus */
-      break;
-    case GST_STATE_CHANGE_NO_PREROLL:
-      gst_print ("Pipeline is live.\n");
-      play->is_live = TRUE;
-      break;
-    case GST_STATE_CHANGE_ASYNC:
-      gst_print ("Prerolling...\r");
-      break;
-    default:
-      break;
-  }
+  if (!play->instant_uri || play->initial_file) {
+    switch (gst_element_set_state (play->playbin, GST_STATE_PAUSED)) {
+      case GST_STATE_CHANGE_FAILURE:
+        /* ignore, we should get an error message posted on the bus */
+        break;
+      case GST_STATE_CHANGE_NO_PREROLL:
+        gst_print ("Pipeline is live.\n");
+        play->is_live = TRUE;
+        break;
+      case GST_STATE_CHANGE_ASYNC:
+        gst_print ("Prerolling...\r");
+        break;
+      default:
+        break;
+    }
 
-  if (play->desired_state != GST_STATE_PAUSED)
-    gst_element_set_state (play->playbin, play->desired_state);
+    if (play->desired_state != GST_STATE_PAUSED)
+      gst_element_set_state (play->playbin, play->desired_state);
+  }
+  play->initial_file = FALSE;
 }
 
 /* returns FALSE if we have reached the end of the playlist */
@@ -1589,6 +1601,7 @@ main (int argc, char **argv)
   gboolean print_version = FALSE;
   gboolean interactive = TRUE;
   gboolean gapless = FALSE;
+  gboolean instant_uri = FALSE;
   gboolean shuffle = FALSE;
   gdouble volume = -1;
   gdouble start_position = 0;
@@ -1619,6 +1632,8 @@ main (int argc, char **argv)
         N_("Audio sink to use (default is autoaudiosink)"), NULL},
     {"gapless", 0, 0, G_OPTION_ARG_NONE, &gapless,
         N_("Enable gapless playback"), NULL},
+    {"instant-uri", 0, 0, G_OPTION_ARG_NONE, &instant_uri,
+        N_("Enable instantaneous uri changes (only with playbin3)"), NULL},
     {"shuffle", 0, 0, G_OPTION_ARG_NONE, &shuffle,
         N_("Shuffle playlist"), NULL},
     {"no-interactive", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,
@@ -1754,8 +1769,9 @@ main (int argc, char **argv)
     shuffle_uris (uris, num);
 
   /* prepare */
-  play = play_new (uris, audio_sink, video_sink, gapless, volume, verbose,
-      flags, use_playbin3, start_position);
+  play =
+      play_new (uris, audio_sink, video_sink, gapless, instant_uri, volume,
+      verbose, flags, use_playbin3, start_position);
 
   if (play == NULL) {
     gst_printerr