Merge branch 'upstream/1.16' into tizen_gst_1.16.2
[platform/upstream/gst-plugins-good.git] / gst / isomp4 / qtdemux.c
index b43cfc9..6e4b71c 100644 (file)
@@ -56,6 +56,8 @@
 #include <gst/tag/tag.h>
 #include <gst/audio/audio.h>
 #include <gst/video/video.h>
+#include <gst/riff/riff.h>
+#include <gst/pbutils/pbutils.h>
 
 #include "qtatomparser.h"
 #include "qtdemux_types.h"
 #include "qtdemux.h"
 #include "qtpalette.h"
 
-#include "gst/riff/riff-media.h"
-#include "gst/riff/riff-read.h"
-
-#include <gst/pbutils/pbutils.h>
-
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -86,7 +83,7 @@
 #define QTDEMUX_MAX_ATOM_SIZE (25*1024*1024)
 
 /* if the sample index is larger than this, something is likely wrong */
-#define QTDEMUX_MAX_SAMPLE_INDEX_SIZE (50*1024*1024)
+#define QTDEMUX_MAX_SAMPLE_INDEX_SIZE (200*1024*1024)
 
 /* For converting qt creation times to unix epoch times */
 #define QTDEMUX_SECONDS_PER_DAY (60 * 60 * 24)
 
 #define QTDEMUX_TREE_NODE_FOURCC(n) (QT_FOURCC(((guint8 *) (n)->data) + 4))
 
-#define STREAM_IS_EOS(s) (s->time_position == GST_CLOCK_TIME_NONE)
+#define STREAM_IS_EOS(s) ((s)->time_position == GST_CLOCK_TIME_NONE)
 
 #define ABSDIFF(x, y) ( (x) > (y) ? ((x) - (y)) : ((y) - (x)) )
 
+#define QTDEMUX_STREAM(s) ((QtDemuxStream *)(s))
+#define QTDEMUX_N_STREAMS(demux) ((demux)->active_streams->len)
+#define QTDEMUX_NTH_STREAM(demux,idx) \
+   QTDEMUX_STREAM(g_ptr_array_index((demux)->active_streams,idx))
+#define QTDEMUX_NTH_OLD_STREAM(demux,idx) \
+   QTDEMUX_STREAM(g_ptr_array_index((demux)->old_streams,idx))
+
 GST_DEBUG_CATEGORY (qtdemux_debug);
+#define GST_CAT_DEFAULT qtdemux_debug
 
 typedef struct _QtDemuxSegment QtDemuxSegment;
 typedef struct _QtDemuxSample QtDemuxSample;
@@ -163,6 +168,18 @@ struct _QtDemuxSphericalMetadata
 
 #define QTSAMPLE_KEYFRAME(stream,sample) ((stream)->all_keyframe || (sample)->keyframe)
 
+#define QTDEMUX_EXPOSE_GET_LOCK(demux) (&((demux)->expose_lock))
+#define QTDEMUX_EXPOSE_LOCK(demux) G_STMT_START { \
+    GST_TRACE("Locking from thread %p", g_thread_self()); \
+    g_mutex_lock (QTDEMUX_EXPOSE_GET_LOCK (demux)); \
+    GST_TRACE("Locked from thread %p", g_thread_self()); \
+ } G_STMT_END
+
+#define QTDEMUX_EXPOSE_UNLOCK(demux) G_STMT_START { \
+    GST_TRACE("Unlocking from thread %p", g_thread_self()); \
+    g_mutex_unlock (QTDEMUX_EXPOSE_GET_LOCK (demux)); \
+ } G_STMT_END
+
 /*
  * Quicktime has tracks and segments. A track is a continuous piece of
  * multimedia content. The track is not always played from start to finish but
@@ -287,6 +304,9 @@ struct _QtDemuxStream
 {
   GstPad *pad;
 
+  GstQTDemux *demux;
+  gchar *stream_id;
+
   QtDemuxStreamStsdEntry *stsd_entries;
   guint stsd_entries_length;
   guint cur_stsd_entry_index;
@@ -321,8 +341,6 @@ struct _QtDemuxStream
   guint32 n_samples;
   QtDemuxSample *samples;
   gboolean all_keyframe;        /* TRUE when all samples are keyframes (no stss) */
-  guint32 first_duration;       /* duration in timescale of first sample, used for figuring out
-                                   the framerate */
   guint32 n_samples_moof;       /* sample count in a moof */
   guint64 duration_moof;        /* duration in timescale of a moof, used for figure out
                                  * the framerate of fragmented format stream */
@@ -359,6 +377,8 @@ struct _QtDemuxStream
 
   /* buffer needs some custom processing, e.g. subtitles */
   gboolean need_process;
+  /* buffer needs potentially be split, e.g. CEA608 subtitles */
+  gboolean need_split;
 
   /* current position */
   guint32 segment_index;
@@ -459,6 +479,8 @@ struct _QtDemuxStream
   guint32 protection_scheme_version;
   gpointer protection_scheme_info;      /* specific to the protection scheme */
   GQueue protection_scheme_event_queue;
+
+  gint ref_count;               /* atomic */
 };
 
 /* Contains properties and cryptographic info for a set of samples from a
@@ -497,6 +519,8 @@ static GNode *qtdemux_tree_get_sibling_by_type_full (GNode * node,
 
 static GstFlowReturn qtdemux_add_fragmented_samples (GstQTDemux * qtdemux);
 
+static void gst_qtdemux_check_send_pending_segment (GstQTDemux * demux);
+
 static GstStaticPadTemplate gst_qtdemux_sink_template =
     GST_STATIC_PAD_TEMPLATE ("sink",
     GST_PAD_SINK,
@@ -541,6 +565,8 @@ static GstIndex *gst_qtdemux_get_index (GstElement * element);
 #endif
 static GstStateChangeReturn gst_qtdemux_change_state (GstElement * element,
     GstStateChange transition);
+static void gst_qtdemux_set_context (GstElement * element,
+    GstContext * context);
 static gboolean qtdemux_sink_activate (GstPad * sinkpad, GstObject * parent);
 static gboolean qtdemux_sink_activate_mode (GstPad * sinkpad,
     GstObject * parent, GstPadMode mode, gboolean active);
@@ -550,6 +576,8 @@ static GstFlowReturn gst_qtdemux_chain (GstPad * sinkpad, GstObject * parent,
     GstBuffer * inbuf);
 static gboolean gst_qtdemux_handle_sink_event (GstPad * pad, GstObject * parent,
     GstEvent * event);
+static gboolean gst_qtdemux_handle_sink_query (GstPad * pad, GstObject * parent,
+    GstQuery * query);
 static gboolean gst_qtdemux_setcaps (GstQTDemux * qtdemux, GstCaps * caps);
 static gboolean gst_qtdemux_configure_stream (GstQTDemux * qtdemux,
     QtDemuxStream * stream);
@@ -558,6 +586,8 @@ static void gst_qtdemux_stream_check_and_change_stsd_index (GstQTDemux * demux,
 static GstFlowReturn gst_qtdemux_process_adapter (GstQTDemux * demux,
     gboolean force);
 
+static void gst_qtdemux_check_seekability (GstQTDemux * demux);
+
 static gboolean qtdemux_parse_moov (GstQTDemux * qtdemux,
     const guint8 * buffer, guint length);
 static gboolean qtdemux_parse_node (GstQTDemux * qtdemux, GNode * node,
@@ -585,14 +615,12 @@ static GstCaps *qtdemux_generic_caps (GstQTDemux * qtdemux,
 static gboolean qtdemux_parse_samples (GstQTDemux * qtdemux,
     QtDemuxStream * stream, guint32 n);
 static GstFlowReturn qtdemux_expose_streams (GstQTDemux * qtdemux);
-static void gst_qtdemux_stream_free (GstQTDemux * qtdemux,
-    QtDemuxStream * stream);
-static void gst_qtdemux_stream_clear (GstQTDemux * qtdemux,
-    QtDemuxStream * stream);
-static void gst_qtdemux_remove_stream (GstQTDemux * qtdemux, int index);
+static QtDemuxStream *gst_qtdemux_stream_ref (QtDemuxStream * stream);
+static void gst_qtdemux_stream_unref (QtDemuxStream * stream);
+static void gst_qtdemux_stream_clear (QtDemuxStream * stream);
 static GstFlowReturn qtdemux_prepare_streams (GstQTDemux * qtdemux);
-static void qtdemux_do_allocation (GstQTDemux * qtdemux,
-    QtDemuxStream * stream);
+static void qtdemux_do_allocation (QtDemuxStream * stream,
+    GstQTDemux * qtdemux);
 static gboolean gst_qtdemux_activate_segment (GstQTDemux * qtdemux,
     QtDemuxStream * stream, guint32 seg_idx, GstClockTime offset);
 static gboolean gst_qtdemux_stream_update_segment (GstQTDemux * qtdemux,
@@ -611,6 +639,7 @@ static GstStructure *qtdemux_get_cenc_sample_properties (GstQTDemux * qtdemux,
 static void gst_qtdemux_append_protection_system_id (GstQTDemux * qtdemux,
     const gchar * id);
 static void qtdemux_gst_structure_free (GstStructure * gststructure);
+static void gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard);
 
 #ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
 static void gst_tag_register_spherical_tags (void);
@@ -634,6 +663,7 @@ gst_qtdemux_class_init (GstQTDemuxClass * klass)
   gstelement_class->set_index = GST_DEBUG_FUNCPTR (gst_qtdemux_set_index);
   gstelement_class->get_index = GST_DEBUG_FUNCPTR (gst_qtdemux_get_index);
 #endif
+  gstelement_class->set_context = GST_DEBUG_FUNCPTR (gst_qtdemux_set_context);
 
   gst_tag_register_musicbrainz_tags ();
 
@@ -655,7 +685,7 @@ gst_qtdemux_class_init (GstQTDemuxClass * klass)
       "David Schleef <ds@schleef.org>, Wim Taymans <wim@fluendo.com>");
 
   GST_DEBUG_CATEGORY_INIT (qtdemux_debug, "qtdemux", 0, "qtdemux plugin");
-
+  gst_riff_init ();
 }
 
 static void
@@ -668,39 +698,18 @@ gst_qtdemux_init (GstQTDemux * qtdemux)
       qtdemux_sink_activate_mode);
   gst_pad_set_chain_function (qtdemux->sinkpad, gst_qtdemux_chain);
   gst_pad_set_event_function (qtdemux->sinkpad, gst_qtdemux_handle_sink_event);
+  gst_pad_set_query_function (qtdemux->sinkpad, gst_qtdemux_handle_sink_query);
   gst_element_add_pad (GST_ELEMENT_CAST (qtdemux), qtdemux->sinkpad);
 
-  qtdemux->state = QTDEMUX_STATE_INITIAL;
-  qtdemux->pullbased = FALSE;
-  qtdemux->posted_redirect = FALSE;
-  qtdemux->neededbytes = 16;
-  qtdemux->todrop = 0;
   qtdemux->adapter = gst_adapter_new ();
-  qtdemux->offset = 0;
-  qtdemux->first_mdat = -1;
-  qtdemux->got_moov = FALSE;
-  qtdemux->mdatoffset = -1;
-  qtdemux->mdatbuffer = NULL;
-  qtdemux->restoredata_buffer = NULL;
-  qtdemux->restoredata_offset = -1;
-  qtdemux->fragment_start = -1;
-  qtdemux->fragment_start_offset = -1;
-  qtdemux->media_caps = NULL;
-  qtdemux->exposed = FALSE;
-  qtdemux->mss_mode = FALSE;
-  qtdemux->pending_newsegment = NULL;
-  qtdemux->upstream_format_is_time = FALSE;
-  qtdemux->have_group_id = FALSE;
-  qtdemux->group_id = G_MAXUINT;
-  qtdemux->cenc_aux_info_offset = 0;
-  qtdemux->cenc_aux_info_sizes = NULL;
-  qtdemux->cenc_aux_sample_count = 0;
-  qtdemux->protection_system_ids = NULL;
   g_queue_init (&qtdemux->protection_event_queue);
-  gst_segment_init (&qtdemux->segment, GST_FORMAT_TIME);
-  qtdemux->tag_list = gst_tag_list_new_empty ();
-  gst_tag_list_set_scope (qtdemux->tag_list, GST_TAG_SCOPE_GLOBAL);
   qtdemux->flowcombiner = gst_flow_combiner_new ();
+  g_mutex_init (&qtdemux->expose_lock);
+
+  qtdemux->active_streams = g_ptr_array_new_with_free_func
+      ((GDestroyNotify) gst_qtdemux_stream_unref);
+  qtdemux->old_streams = g_ptr_array_new_with_free_func
+      ((GDestroyNotify) gst_qtdemux_stream_unref);
 
 #ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
   qtdemux->spherical_metadata = (QtDemuxSphericalMetadata *)
@@ -730,6 +739,8 @@ gst_qtdemux_init (GstQTDemux * qtdemux)
 #endif /* TIZEN_FEATURE_QTDEMUX_MODIFICATION */
 
   GST_OBJECT_FLAG_SET (qtdemux, GST_ELEMENT_FLAG_INDEXABLE);
+
+  gst_qtdemux_reset (qtdemux, TRUE);
 }
 
 static void
@@ -763,6 +774,10 @@ gst_qtdemux_dispose (GObject * object)
 
   g_free (qtdemux->cenc_aux_info_sizes);
   qtdemux->cenc_aux_info_sizes = NULL;
+  g_mutex_clear (&qtdemux->expose_lock);
+
+  g_ptr_array_free (qtdemux->active_streams, TRUE);
+  g_ptr_array_free (qtdemux->old_streams, TRUE);
 
   G_OBJECT_CLASS (parent_class)->dispose (object);
 }
@@ -983,10 +998,9 @@ gst_qtdemux_handle_src_query (GstPad * pad, GstObject * parent,
 
       res = gst_qtdemux_src_convert (qtdemux, pad,
           src_fmt, src_value, dest_fmt, &dest_value);
-      if (res) {
+      if (res)
         gst_query_set_convert (query, src_fmt, src_value, dest_fmt, dest_value);
-        res = TRUE;
-      }
+
       break;
     }
     case GST_QUERY_FORMATS:
@@ -1086,17 +1100,17 @@ gst_qtdemux_push_tags (GstQTDemux * qtdemux, QtDemuxStream * stream)
 static void
 gst_qtdemux_push_event (GstQTDemux * qtdemux, GstEvent * event)
 {
-  guint n;
   gboolean has_valid_stream = FALSE;
   GstEventType etype = GST_EVENT_TYPE (event);
+  guint i;
 
   GST_DEBUG_OBJECT (qtdemux, "pushing %s event on all source pads",
       GST_EVENT_TYPE_NAME (event));
 
-  for (n = 0; n < qtdemux->n_streams; n++) {
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
     GstPad *pad;
-    QtDemuxStream *stream = qtdemux->streams[n];
-    GST_DEBUG_OBJECT (qtdemux, "pushing on pad %i", n);
+    QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
+    GST_DEBUG_OBJECT (qtdemux, "pushing on track-id %u", stream->track_id);
 
     if ((pad = stream->pad)) {
       has_valid_stream = TRUE;
@@ -1120,16 +1134,6 @@ gst_qtdemux_push_event (GstQTDemux * qtdemux, GstEvent * event)
   }
 }
 
-/* push a pending newsegment event, if any from the streaming thread */
-static void
-gst_qtdemux_push_pending_newsegment (GstQTDemux * qtdemux)
-{
-  if (qtdemux->pending_newsegment) {
-    gst_qtdemux_push_event (qtdemux, qtdemux->pending_newsegment);
-    qtdemux->pending_newsegment = NULL;
-  }
-}
-
 typedef struct
 {
   guint64 media_time;
@@ -1138,9 +1142,9 @@ typedef struct
 static gint
 find_func (QtDemuxSample * s1, gint64 * media_time, gpointer user_data)
 {
-  if ((gint64) s1->timestamp + s1->pts_offset > *media_time)
+  if ((gint64) s1->timestamp > *media_time)
     return 1;
-  if ((gint64) s1->timestamp + s1->pts_offset == *media_time)
+  if ((gint64) s1->timestamp == *media_time)
     return 0;
 
   return -1;
@@ -1149,7 +1153,7 @@ find_func (QtDemuxSample * s1, gint64 * media_time, gpointer user_data)
 /* find the index of the sample that includes the data for @media_time using a
  * binary search.  Only to be called in optimized cases of linear search below.
  *
- * Returns the index of the sample.
+ * Returns the index of the sample with the corresponding *DTS*.
  */
 static guint32
 gst_qtdemux_find_index (GstQTDemux * qtdemux, QtDemuxStream * str,
@@ -1239,20 +1243,31 @@ gst_qtdemux_find_index_linear (GstQTDemux * qtdemux, QtDemuxStream * str,
 
   /* use faster search if requested time in already parsed range */
   sample = str->samples + str->stbl_index;
-  if (str->stbl_index >= 0 &&
-      mov_time <= (sample->timestamp + sample->pts_offset))
-    return gst_qtdemux_find_index (qtdemux, str, media_time);
+  if (str->stbl_index >= 0 && mov_time <= sample->timestamp) {
+    index = gst_qtdemux_find_index (qtdemux, str, media_time);
+    sample = str->samples + index;
+  } else {
+    while (index < str->n_samples - 1) {
+      if (!qtdemux_parse_samples (qtdemux, str, index + 1))
+        goto parse_failed;
 
-  while (index < str->n_samples - 1) {
-    if (!qtdemux_parse_samples (qtdemux, str, index + 1))
-      goto parse_failed;
+      sample = str->samples + index + 1;
+      if (mov_time < sample->timestamp) {
+        sample = str->samples + index;
+        break;
+      }
 
-    sample = str->samples + index + 1;
-    if (mov_time < (sample->timestamp + sample->pts_offset))
-      break;
+      index++;
+    }
+  }
 
-    index++;
+  /* sample->timestamp is now <= media_time, need to find the corresponding
+   * PTS now by looking backwards */
+  while (index > 0 && sample->timestamp + sample->pts_offset > mov_time) {
+    index--;
+    sample = str->samples + index;
   }
+
   return index;
 
   /* ERRORS */
@@ -1391,13 +1406,13 @@ gst_qtdemux_adjust_seek (GstQTDemux * qtdemux, gint64 desired_time,
 {
   guint64 min_offset;
   gint64 min_byte_offset = -1;
-  gint n;
+  guint i;
 
   min_offset = desired_time;
 
   /* for each stream, find the index of the sample in the segment
    * and move back to the previous keyframe. */
-  for (n = 0; n < qtdemux->n_streams; n++) {
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
     QtDemuxStream *str;
     guint32 index, kindex;
     guint32 seg_idx;
@@ -1407,7 +1422,7 @@ gst_qtdemux_adjust_seek (GstQTDemux * qtdemux, gint64 desired_time,
     QtDemuxSegment *seg;
     gboolean empty_segment = FALSE;
 
-    str = qtdemux->streams[n];
+    str = QTDEMUX_NTH_STREAM (qtdemux, i);
 
     if (CUR_STREAM (str)->sparse && !use_sparse)
       continue;
@@ -1588,6 +1603,7 @@ gst_qtdemux_do_push_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event)
   }
   GST_OBJECT_UNLOCK (qtdemux);
 
+  qtdemux->segment_seqnum = seqnum;
   /* BYTE seek event */
   event = gst_event_new_seek (rate, GST_FORMAT_BYTES, flags, cur_type, byte_cur,
       stop_type, stop);
@@ -1636,7 +1652,7 @@ gst_qtdemux_perform_seek (GstQTDemux * qtdemux, GstSegment * segment,
     guint32 seqnum, GstSeekFlags flags)
 {
   gint64 desired_offset;
-  gint n;
+  guint i;
 
   desired_offset = segment->position;
 
@@ -1666,8 +1682,8 @@ gst_qtdemux_perform_seek (GstQTDemux * qtdemux, GstSegment * segment,
   /* and set all streams to the final position */
   gst_flow_combiner_reset (qtdemux->flowcombiner);
   qtdemux->segment_seqnum = seqnum;
-  for (n = 0; n < qtdemux->n_streams; n++) {
-    QtDemuxStream *stream = qtdemux->streams[n];
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+    QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
 
     stream->time_position = desired_offset;
     stream->accumulated_base = 0;
@@ -1709,7 +1725,7 @@ gst_qtdemux_do_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event)
   gboolean flush;
   gboolean update;
   GstSegment seeksegment;
-  guint32 seqnum = 0;
+  guint32 seqnum = GST_SEQNUM_INVALID;
   GstEvent *flush_event;
   gboolean ret;
 
@@ -1737,7 +1753,7 @@ gst_qtdemux_do_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event)
   /* stop streaming, either by flushing or by pausing the task */
   if (flush) {
     flush_event = gst_event_new_flush_start ();
-    if (seqnum)
+    if (seqnum != GST_SEQNUM_INVALID)
       gst_event_set_seqnum (flush_event, seqnum);
     /* unlock upstream pull_range */
     gst_pad_push_event (qtdemux->sinkpad, gst_event_ref (flush_event));
@@ -1774,7 +1790,7 @@ gst_qtdemux_do_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event)
   /* prepare for streaming again */
   if (flush) {
     flush_event = gst_event_new_flush_stop (TRUE);
-    if (seqnum)
+    if (seqnum != GST_SEQNUM_INVALID)
       gst_event_set_seqnum (flush_event, seqnum);
 
     gst_pad_push_event (qtdemux->sinkpad, gst_event_ref (flush_event));
@@ -1787,7 +1803,7 @@ gst_qtdemux_do_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event)
   if (qtdemux->segment.flags & GST_SEEK_FLAG_SEGMENT) {
     GstMessage *msg = gst_message_new_segment_start (GST_OBJECT_CAST (qtdemux),
         qtdemux->segment.format, qtdemux->segment.position);
-    if (seqnum)
+    if (seqnum != GST_SEQNUM_INVALID)
       gst_message_set_seqnum (msg, seqnum);
     gst_element_post_message (GST_ELEMENT_CAST (qtdemux), msg);
   }
@@ -1816,21 +1832,18 @@ qtdemux_ensure_index (GstQTDemux * qtdemux)
   GST_DEBUG_OBJECT (qtdemux, "collecting all metadata for all streams");
 
   /* Build complete index */
-  for (i = 0; i < qtdemux->n_streams; i++) {
-    QtDemuxStream *stream = qtdemux->streams[i];
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+    QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
 
-    if (!qtdemux_parse_samples (qtdemux, stream, stream->n_samples - 1))
-      goto parse_error;
+    if (!qtdemux_parse_samples (qtdemux, stream, stream->n_samples - 1)) {
+      GST_LOG_OBJECT (qtdemux,
+          "Building complete index of track-id %u for seeking failed!",
+          stream->track_id);
+      return FALSE;
+    }
   }
-  return TRUE;
 
-  /* ERRORS */
-parse_error:
-  {
-    GST_LOG_OBJECT (qtdemux,
-        "Building complete index of stream %u for seeking failed!", i);
-    return FALSE;
-  }
+  return TRUE;
 }
 
 static gboolean
@@ -1848,6 +1861,8 @@ gst_qtdemux_handle_src_event (GstPad * pad, GstObject * parent,
 #endif
       guint32 seqnum = gst_event_get_seqnum (event);
 
+      qtdemux->received_seek = TRUE;
+
       if (seqnum == qtdemux->segment_seqnum) {
         GST_LOG_OBJECT (pad,
             "Drop duplicated SEEK event seqnum %" G_GUINT32_FORMAT, seqnum);
@@ -1878,7 +1893,8 @@ gst_qtdemux_handle_src_event (GstPad * pad, GstObject * parent,
       } else if (gst_pad_push_event (qtdemux->sinkpad, gst_event_ref (event))) {
         GST_DEBUG_OBJECT (qtdemux, "Upstream successfully seeked");
         res = TRUE;
-      } else if (qtdemux->state == QTDEMUX_STATE_MOVIE && qtdemux->n_streams
+      } else if (qtdemux->state == QTDEMUX_STATE_MOVIE
+          && QTDEMUX_N_STREAMS (qtdemux)
           && !qtdemux->fragmented) {
         res = gst_qtdemux_do_push_seek (qtdemux, pad, event);
       } else {
@@ -1907,27 +1923,38 @@ index_failed:
   }
 }
 
-/* stream/index return sample that is min/max w.r.t. byte position,
- * time is min/max w.r.t. time of samples,
- * the latter need not be time of the former sample */
+/* Find, for each track, the first sample in coding order that has a file offset >= @byte_pos.
+ *
+ * If @fw is false, the coding order is explored backwards.
+ *
+ * If @set is true, each stream will be moved to its matched sample, or EOS if no matching
+ * sample is found for that track.
+ *
+ * The stream and sample index of the sample with the minimum offset in the direction explored
+ * (see @fw) is returned in the output parameters @_stream and @_index respectively.
+ *
+ * @_time is set to the QTSAMPLE_PTS of the matched sample with the minimum QTSAMPLE_PTS in the
+ * direction explored, which may not always match the QTSAMPLE_PTS of the sample returned in
+ * @_stream and @_index. */
 static void
 gst_qtdemux_find_sample (GstQTDemux * qtdemux, gint64 byte_pos, gboolean fw,
     gboolean set, QtDemuxStream ** _stream, gint * _index, gint64 * _time)
 {
-  gint i, n, index;
+  gint i, index;
   gint64 time, min_time;
   QtDemuxStream *stream;
+  gint iter;
 
   min_time = -1;
   stream = NULL;
   index = -1;
 
-  for (n = 0; n < qtdemux->n_streams; ++n) {
+  for (iter = 0; iter < QTDEMUX_N_STREAMS (qtdemux); iter++) {
     QtDemuxStream *str;
     gint inc;
     gboolean set_sample;
 
-    str = qtdemux->streams[n];
+    str = QTDEMUX_NTH_STREAM (qtdemux, iter);
     set_sample = !set;
 
     if (fw) {
@@ -1987,12 +2014,60 @@ gst_qtdemux_find_sample (GstQTDemux * qtdemux, gint64 byte_pos, gboolean fw,
     *_index = index;
 }
 
+/* Copied from mpegtsbase code */
+/* FIXME: replace this function when we add new util function for stream-id creation */
+static gchar *
+_get_upstream_id (GstQTDemux * demux)
+{
+  gchar *upstream_id = gst_pad_get_stream_id (demux->sinkpad);
+
+  if (!upstream_id) {
+    /* Try to create one from the upstream URI, else use a randome number */
+    GstQuery *query;
+    gchar *uri = NULL;
+
+    /* Try to generate one from the URI query and
+     * if it fails take a random number instead */
+    query = gst_query_new_uri ();
+    if (gst_element_query (GST_ELEMENT_CAST (demux), query)) {
+      gst_query_parse_uri (query, &uri);
+    }
+
+    if (uri) {
+      GChecksum *cs;
+
+      /* And then generate an SHA256 sum of the URI */
+      cs = g_checksum_new (G_CHECKSUM_SHA256);
+      g_checksum_update (cs, (const guchar *) uri, strlen (uri));
+      g_free (uri);
+      upstream_id = g_strdup (g_checksum_get_string (cs));
+      g_checksum_free (cs);
+    } else {
+      /* Just get some random number if the URI query fails */
+      GST_FIXME_OBJECT (demux, "Creating random stream-id, consider "
+          "implementing a deterministic way of creating a stream-id");
+      upstream_id =
+          g_strdup_printf ("%08x%08x%08x%08x", g_random_int (), g_random_int (),
+          g_random_int (), g_random_int ());
+    }
+
+    gst_query_unref (query);
+  }
+  return upstream_id;
+}
+
 static QtDemuxStream *
-_create_stream (void)
+_create_stream (GstQTDemux * demux, guint32 track_id)
 {
   QtDemuxStream *stream;
+  gchar *upstream_id;
 
   stream = g_new0 (QtDemuxStream, 1);
+  stream->demux = demux;
+  stream->track_id = track_id;
+  upstream_id = _get_upstream_id (demux);
+  stream->stream_id = g_strdup_printf ("%s/%03u", upstream_id, track_id);
+  g_free (upstream_id);
   /* new streams always need a discont */
   stream->discont = TRUE;
   /* we enable clipping for raw audio/video streams */
@@ -2016,6 +2091,9 @@ _create_stream (void)
   stream->stream_tags = gst_tag_list_new_empty ();
   gst_tag_list_set_scope (stream->stream_tags, GST_TAG_SCOPE_STREAM);
   g_queue_init (&stream->protection_scheme_event_queue);
+  stream->ref_count = 1;
+  /* consistent default for push based mode */
+  gst_segment_init (&stream->segment, GST_FORMAT_TIME);
   return stream;
 }
 
@@ -2038,7 +2116,7 @@ gst_qtdemux_setcaps (GstQTDemux * demux, GstCaps * caps)
     demux->fragmented = TRUE;
     demux->mss_mode = TRUE;
 
-    if (demux->n_streams > 1) {
+    if (QTDEMUX_N_STREAMS (demux) > 1) {
       /* can't do this, we can only renegotiate for another mss format */
       return FALSE;
     }
@@ -2050,16 +2128,15 @@ gst_qtdemux_setcaps (GstQTDemux * demux, GstCaps * caps)
 
       /* TODO update when stream changes during playback */
 
-      if (demux->n_streams == 0) {
-        stream = _create_stream ();
-        demux->streams[demux->n_streams] = stream;
-        demux->n_streams = 1;
+      if (QTDEMUX_N_STREAMS (demux) == 0) {
+        stream = _create_stream (demux, 1);
+        g_ptr_array_add (demux->active_streams, stream);
         /* mss has no stsd/stsd entry, use id 0 as default */
         stream->stsd_entries_length = 1;
         stream->stsd_sample_description_id = stream->cur_stsd_entry_index = 0;
         stream->stsd_entries = g_new0 (QtDemuxStreamStsdEntry, 1);
       } else {
-        stream = demux->streams[0];
+        stream = QTDEMUX_NTH_STREAM (demux, 0);
       }
 
       timescale_v = gst_structure_get_value (structure, "timescale");
@@ -2108,7 +2185,7 @@ gst_qtdemux_setcaps (GstQTDemux * demux, GstCaps * caps)
 static void
 gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard)
 {
-  gint n;
+  gint i;
 
   GST_DEBUG_OBJECT (qtdemux, "Resetting demux");
   gst_pad_stop_task (qtdemux->sinkpad);
@@ -2154,9 +2231,6 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard)
     qtdemux->element_index = NULL;
 #endif
     qtdemux->major_brand = 0;
-    if (qtdemux->pending_newsegment)
-      gst_event_unref (qtdemux->pending_newsegment);
-    qtdemux->pending_newsegment = NULL;
     qtdemux->upstream_format_is_time = FALSE;
     qtdemux->upstream_seekable = FALSE;
     qtdemux->upstream_size = 0;
@@ -2172,18 +2246,19 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard)
     g_queue_foreach (&qtdemux->protection_event_queue, (GFunc) gst_event_unref,
         NULL);
     g_queue_clear (&qtdemux->protection_event_queue);
+
+    qtdemux->received_seek = FALSE;
+    qtdemux->first_moof_already_parsed = FALSE;
   }
   qtdemux->offset = 0;
   gst_adapter_clear (qtdemux->adapter);
   gst_segment_init (&qtdemux->segment, GST_FORMAT_TIME);
-  qtdemux->segment_seqnum = 0;
+  qtdemux->need_segment = TRUE;
 
   if (hard) {
-    for (n = 0; n < qtdemux->n_streams; n++) {
-      gst_qtdemux_stream_free (qtdemux, qtdemux->streams[n]);
-      qtdemux->streams[n] = NULL;
-    }
-    qtdemux->n_streams = 0;
+    qtdemux->segment_seqnum = GST_SEQNUM_INVALID;
+    g_ptr_array_set_size (qtdemux->active_streams, 0);
+    g_ptr_array_set_size (qtdemux->old_streams, 0);
     qtdemux->n_video_streams = 0;
     qtdemux->n_audio_streams = 0;
     qtdemux->n_sub_streams = 0;
@@ -2193,26 +2268,32 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard)
     gst_caps_replace (&qtdemux->media_caps, NULL);
     qtdemux->timescale = 0;
     qtdemux->got_moov = FALSE;
+    qtdemux->cenc_aux_info_offset = 0;
+    qtdemux->cenc_aux_info_sizes = NULL;
+    qtdemux->cenc_aux_sample_count = 0;
     if (qtdemux->protection_system_ids) {
       g_ptr_array_free (qtdemux->protection_system_ids, TRUE);
       qtdemux->protection_system_ids = NULL;
     }
+    qtdemux->streams_aware = GST_OBJECT_PARENT (qtdemux)
+        && GST_OBJECT_FLAG_IS_SET (GST_OBJECT_PARENT (qtdemux),
+        GST_BIN_FLAG_STREAMS_AWARE);
+
+    if (qtdemux->preferred_protection_system_id) {
+      g_free (qtdemux->preferred_protection_system_id);
+      qtdemux->preferred_protection_system_id = NULL;
+    }
   } else if (qtdemux->mss_mode) {
     gst_flow_combiner_reset (qtdemux->flowcombiner);
-    for (n = 0; n < qtdemux->n_streams; n++)
-      gst_qtdemux_stream_clear (qtdemux, qtdemux->streams[n]);
+    g_ptr_array_foreach (qtdemux->active_streams,
+        (GFunc) gst_qtdemux_stream_clear, NULL);
   } else {
     gst_flow_combiner_reset (qtdemux->flowcombiner);
-    for (n = 0; n < qtdemux->n_streams; n++) {
-      qtdemux->streams[n]->sent_eos = FALSE;
-      qtdemux->streams[n]->time_position = 0;
-      qtdemux->streams[n]->accumulated_base = 0;
-    }
-    if (!qtdemux->pending_newsegment) {
-      qtdemux->pending_newsegment = gst_event_new_segment (&qtdemux->segment);
-      if (qtdemux->segment_seqnum)
-        gst_event_set_seqnum (qtdemux->pending_newsegment,
-            qtdemux->segment_seqnum);
+    for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+      QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
+      stream->sent_eos = FALSE;
+      stream->time_position = 0;
+      stream->accumulated_base = 0;
     }
   }
 }
@@ -2228,10 +2309,10 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard)
 static void
 gst_qtdemux_map_and_push_segments (GstQTDemux * qtdemux, GstSegment * segment)
 {
-  gint n, i;
+  gint i, iter;
 
-  for (n = 0; n < qtdemux->n_streams; n++) {
-    QtDemuxStream *stream = qtdemux->streams[n];
+  for (iter = 0; iter < QTDEMUX_N_STREAMS (qtdemux); iter++) {
+    QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, iter);
 
     stream->time_position = segment->start;
 
@@ -2240,12 +2321,18 @@ gst_qtdemux_map_and_push_segments (GstQTDemux * qtdemux, GstSegment * segment)
      * supported and are discarded when parsing the edts */
     for (i = 0; i < stream->n_segments; i++) {
       if (stream->segments[i].stop_time > segment->start) {
+        /* push the empty segment and move to the next one */
         gst_qtdemux_activate_segment (qtdemux, stream, i,
             stream->time_position);
         if (QTSEGMENT_IS_EMPTY (&stream->segments[i])) {
-          /* push the empty segment and move to the next one */
           gst_qtdemux_send_gap_for_segment (qtdemux, stream, i,
               stream->time_position);
+
+          /* accumulate previous segments */
+          if (GST_CLOCK_TIME_IS_VALID (stream->segment.stop))
+            stream->accumulated_base +=
+                (stream->segment.stop -
+                stream->segment.start) / ABS (stream->segment.rate);
           continue;
         }
 
@@ -2255,6 +2342,31 @@ gst_qtdemux_map_and_push_segments (GstQTDemux * qtdemux, GstSegment * segment)
   }
 }
 
+static void
+gst_qtdemux_stream_concat (GstQTDemux * qtdemux, GPtrArray * dest,
+    GPtrArray * src)
+{
+  guint i;
+  guint len;
+
+  len = src->len;
+
+  if (len == 0)
+    return;
+
+  for (i = 0; i < len; i++) {
+    QtDemuxStream *stream = g_ptr_array_index (src, i);
+
+#ifndef GST_DISABLE_GST_DEBUG
+    GST_DEBUG_OBJECT (qtdemux, "Move stream %p (stream-id %s) to %p",
+        stream, GST_STR_NULL (stream->stream_id), dest);
+#endif
+    g_ptr_array_add (dest, gst_qtdemux_stream_ref (stream));
+  }
+
+  g_ptr_array_set_size (src, 0);
+}
+
 static gboolean
 gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent,
     GstEvent * event)
@@ -2277,19 +2389,15 @@ gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent,
       GST_DEBUG_OBJECT (demux, "received newsegment %" GST_SEGMENT_FORMAT,
           &segment);
 
-      /* erase any previously set segment */
-      gst_event_replace (&demux->pending_newsegment, NULL);
-
       if (segment.format == GST_FORMAT_TIME) {
-        GST_DEBUG_OBJECT (demux, "new pending_newsegment");
-        gst_event_replace (&demux->pending_newsegment, event);
         demux->upstream_format_is_time = TRUE;
+        demux->segment_seqnum = gst_event_get_seqnum (event);
       } else {
         GST_DEBUG_OBJECT (demux, "Not storing upstream newsegment, "
             "not in time format");
 
         /* chain will send initial newsegment after pads have been added */
-        if (demux->state != QTDEMUX_STATE_MOVIE || !demux->n_streams) {
+        if (demux->state != QTDEMUX_STATE_MOVIE || !QTDEMUX_N_STREAMS (demux)) {
           GST_DEBUG_OBJECT (demux, "still starting, eating event");
           goto exit;
         }
@@ -2357,17 +2465,9 @@ gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent,
       GST_DEBUG_OBJECT (demux, "Pushing newseg %" GST_SEGMENT_FORMAT, &segment);
 
       /* map segment to internal qt segments and push on each stream */
-      if (demux->n_streams) {
-        if (demux->fragmented) {
-          GstEvent *segment_event = gst_event_new_segment (&segment);
-
-          gst_event_replace (&demux->pending_newsegment, NULL);
-          gst_event_set_seqnum (segment_event, demux->segment_seqnum);
-          gst_qtdemux_push_event (demux, segment_event);
-        } else {
-          gst_event_replace (&demux->pending_newsegment, NULL);
-          gst_qtdemux_map_and_push_segments (demux, &segment);
-        }
+      if (QTDEMUX_N_STREAMS (demux)) {
+        demux->need_segment = TRUE;
+        gst_qtdemux_check_send_pending_segment (demux);
       }
 
       /* clear leftover in current segment, if any */
@@ -2403,7 +2503,10 @@ gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent,
         gst_event_unref (event);
         goto drop;
       }
-      break;
+      QTDEMUX_EXPOSE_LOCK (demux);
+      res = gst_pad_event_default (demux->sinkpad, parent, event);
+      QTDEMUX_EXPOSE_UNLOCK (demux);
+      goto drop;
     }
     case GST_EVENT_FLUSH_STOP:
     {
@@ -2425,8 +2528,8 @@ gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent,
       if (!demux->pullbased) {
         gint i;
         gboolean has_valid_stream = FALSE;
-        for (i = 0; i < demux->n_streams; i++) {
-          if (demux->streams[i]->pad != NULL) {
+        for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
+          if (QTDEMUX_NTH_STREAM (demux, i)->pad != NULL) {
             has_valid_stream = TRUE;
             break;
           }
@@ -2466,6 +2569,22 @@ gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent,
       res = TRUE;
       goto drop;
     }
+    case GST_EVENT_STREAM_START:
+    {
+      res = TRUE;
+      gst_event_unref (event);
+
+      /* Drain all the buffers */
+      gst_qtdemux_process_adapter (demux, TRUE);
+      gst_qtdemux_reset (demux, FALSE);
+      /* We expect new moov box after new stream-start event */
+      if (demux->exposed) {
+        gst_qtdemux_stream_concat (demux,
+            demux->old_streams, demux->active_streams);
+      }
+
+      goto drop;
+    }
     default:
       break;
   }
@@ -2476,6 +2595,46 @@ drop:
   return res;
 }
 
+static gboolean
+gst_qtdemux_handle_sink_query (GstPad * pad, GstObject * parent,
+    GstQuery * query)
+{
+  GstQTDemux *demux = GST_QTDEMUX (parent);
+  gboolean res = FALSE;
+
+  switch (GST_QUERY_TYPE (query)) {
+    case GST_QUERY_BITRATE:
+    {
+      GstClockTime duration;
+
+      /* populate demux->upstream_size if not done yet */
+      gst_qtdemux_check_seekability (demux);
+
+      if (demux->upstream_size != -1
+          && gst_qtdemux_get_duration (demux, &duration)) {
+        guint bitrate =
+            gst_util_uint64_scale (8 * demux->upstream_size, GST_SECOND,
+            duration);
+
+        GST_LOG_OBJECT (demux, "bitrate query byte length: %" G_GUINT64_FORMAT
+            " duration %" GST_TIME_FORMAT " resulting a bitrate of %u",
+            demux->upstream_size, GST_TIME_ARGS (duration), bitrate);
+
+        /* TODO: better results based on ranges/index tables */
+        gst_query_set_bitrate (query, bitrate);
+        res = TRUE;
+      }
+      break;
+    }
+    default:
+      res = gst_pad_query_default (pad, (GstObject *) demux, query);
+      break;
+  }
+
+  return res;
+}
+
+
 #if 0
 static void
 gst_qtdemux_set_index (GstElement * element, GstIndex * index)
@@ -2535,8 +2694,7 @@ gst_qtdemux_stbl_free (QtDemuxStream * stream)
 }
 
 static void
-gst_qtdemux_stream_flush_segments_data (GstQTDemux * qtdemux,
-    QtDemuxStream * stream)
+gst_qtdemux_stream_flush_segments_data (QtDemuxStream * stream)
 {
   g_free (stream->segments);
   stream->segments = NULL;
@@ -2545,8 +2703,7 @@ gst_qtdemux_stream_flush_segments_data (GstQTDemux * qtdemux,
 }
 
 static void
-gst_qtdemux_stream_flush_samples_data (GstQTDemux * qtdemux,
-    QtDemuxStream * stream)
+gst_qtdemux_stream_flush_samples_data (QtDemuxStream * stream)
 {
   g_free (stream->samples);
   stream->samples = NULL;
@@ -2568,7 +2725,7 @@ gst_qtdemux_stream_flush_samples_data (GstQTDemux * qtdemux,
 }
 
 static void
-gst_qtdemux_stream_clear (GstQTDemux * qtdemux, QtDemuxStream * stream)
+gst_qtdemux_stream_clear (QtDemuxStream * stream)
 {
   gint i;
   if (stream->allocator)
@@ -2586,7 +2743,9 @@ gst_qtdemux_stream_clear (GstQTDemux * qtdemux, QtDemuxStream * stream)
     entry->sparse = FALSE;
   }
 
-  gst_tag_list_unref (stream->stream_tags);
+  if (stream->stream_tags)
+    gst_tag_list_unref (stream->stream_tags);
+
   stream->stream_tags = gst_tag_list_new_empty ();
   gst_tag_list_set_scope (stream->stream_tags, GST_TAG_SCOPE_STREAM);
   g_free (stream->redirect_uri);
@@ -2610,15 +2769,15 @@ gst_qtdemux_stream_clear (GstQTDemux * qtdemux, QtDemuxStream * stream)
   g_queue_foreach (&stream->protection_scheme_event_queue,
       (GFunc) gst_event_unref, NULL);
   g_queue_clear (&stream->protection_scheme_event_queue);
-  gst_qtdemux_stream_flush_segments_data (qtdemux, stream);
-  gst_qtdemux_stream_flush_samples_data (qtdemux, stream);
+  gst_qtdemux_stream_flush_segments_data (stream);
+  gst_qtdemux_stream_flush_samples_data (stream);
 }
 
 static void
-gst_qtdemux_stream_free (GstQTDemux * qtdemux, QtDemuxStream * stream)
+gst_qtdemux_stream_reset (QtDemuxStream * stream)
 {
   gint i;
-  gst_qtdemux_stream_clear (qtdemux, stream);
+  gst_qtdemux_stream_clear (stream);
   for (i = 0; i < stream->stsd_entries_length; i++) {
     QtDemuxStreamStsdEntry *entry = &stream->stsd_entries[i];
     if (entry->caps) {
@@ -2626,24 +2785,33 @@ gst_qtdemux_stream_free (GstQTDemux * qtdemux, QtDemuxStream * stream)
       entry->caps = NULL;
     }
   }
-  gst_tag_list_unref (stream->stream_tags);
-  if (stream->pad) {
-    gst_element_remove_pad (GST_ELEMENT_CAST (qtdemux), stream->pad);
-    gst_flow_combiner_remove_pad (qtdemux->flowcombiner, stream->pad);
-  }
   g_free (stream->stsd_entries);
-  g_free (stream);
+  stream->stsd_entries = NULL;
+  stream->stsd_entries_length = 0;
 }
 
-static void
-gst_qtdemux_remove_stream (GstQTDemux * qtdemux, int i)
+static QtDemuxStream *
+gst_qtdemux_stream_ref (QtDemuxStream * stream)
 {
-  g_assert (i >= 0 && i < qtdemux->n_streams && qtdemux->streams[i] != NULL);
+  g_atomic_int_add (&stream->ref_count, 1);
+
+  return stream;
+}
 
-  gst_qtdemux_stream_free (qtdemux, qtdemux->streams[i]);
-  qtdemux->streams[i] = qtdemux->streams[qtdemux->n_streams - 1];
-  qtdemux->streams[qtdemux->n_streams - 1] = NULL;
-  qtdemux->n_streams--;
+static void
+gst_qtdemux_stream_unref (QtDemuxStream * stream)
+{
+  if (g_atomic_int_dec_and_test (&stream->ref_count)) {
+    gst_qtdemux_stream_reset (stream);
+    gst_tag_list_unref (stream->stream_tags);
+    if (stream->pad) {
+      GstQTDemux *demux = stream->demux;
+      gst_element_remove_pad (GST_ELEMENT_CAST (demux), stream->pad);
+      gst_flow_combiner_remove_pad (demux->flowcombiner, stream->pad);
+    }
+    g_free (stream->stream_id);
+    g_free (stream);
+  }
 }
 
 static GstStateChangeReturn
@@ -2653,7 +2821,8 @@ gst_qtdemux_change_state (GstElement * element, GstStateChange transition)
   GstStateChangeReturn result = GST_STATE_CHANGE_FAILURE;
 
   switch (transition) {
-    case GST_STATE_CHANGE_PAUSED_TO_READY:
+    case GST_STATE_CHANGE_READY_TO_PAUSED:
+      gst_qtdemux_reset (qtdemux, TRUE);
       break;
     default:
       break;
@@ -2674,6 +2843,28 @@ gst_qtdemux_change_state (GstElement * element, GstStateChange transition)
 }
 
 static void
+gst_qtdemux_set_context (GstElement * element, GstContext * context)
+{
+  GstQTDemux *qtdemux = GST_QTDEMUX (element);
+
+  g_return_if_fail (GST_IS_CONTEXT (context));
+
+  if (gst_context_has_context_type (context,
+          "drm-preferred-decryption-system-id")) {
+    const GstStructure *s;
+
+    s = gst_context_get_structure (context);
+    g_free (qtdemux->preferred_protection_system_id);
+    qtdemux->preferred_protection_system_id =
+        g_strdup (gst_structure_get_string (s, "decryption-system-id"));
+    GST_DEBUG_OBJECT (element, "set preferred decryption system to %s",
+        qtdemux->preferred_protection_system_id);
+  }
+
+  GST_ELEMENT_CLASS (parent_class)->set_context (element, context);
+}
+
+static void
 qtdemux_parse_ftyp (GstQTDemux * qtdemux, const guint8 * buffer, gint length)
 {
   /* counts as header data */
@@ -3184,6 +3375,62 @@ qtdemux_parse_SA3D (GstQTDemux * qtdemux, const guint8 * buffer, gint length)
 #endif /* TIZEN_FEATURE_QTDEMUX_MODIFICATION */
 
 static void
+qtdemux_update_default_sample_encryption_settings (GstQTDemux * qtdemux,
+    QtDemuxCencSampleSetInfo * info, guint32 is_encrypted, guint8 iv_size,
+    const guint8 * kid)
+{
+  GstBuffer *kid_buf = gst_buffer_new_allocate (NULL, 16, NULL);
+  gst_buffer_fill (kid_buf, 0, kid, 16);
+  if (info->default_properties)
+    gst_structure_free (info->default_properties);
+  info->default_properties =
+      gst_structure_new ("application/x-cenc",
+      "iv_size", G_TYPE_UINT, iv_size,
+      "encrypted", G_TYPE_BOOLEAN, (is_encrypted == 1),
+      "kid", GST_TYPE_BUFFER, kid_buf, NULL);
+  GST_DEBUG_OBJECT (qtdemux, "default sample properties: "
+      "is_encrypted=%u, iv_size=%u", is_encrypted, iv_size);
+  gst_buffer_unref (kid_buf);
+}
+
+static gboolean
+qtdemux_update_default_piff_encryption_settings (GstQTDemux * qtdemux,
+    QtDemuxCencSampleSetInfo * info, GstByteReader * br)
+{
+  guint32 algorithm_id = 0;
+  const guint8 *kid;
+  gboolean is_encrypted = TRUE;
+  guint8 iv_size = 8;
+
+  if (!gst_byte_reader_get_uint24_le (br, &algorithm_id)) {
+    GST_ERROR_OBJECT (qtdemux, "Error getting box's algorithm ID field");
+    return FALSE;
+  }
+
+  algorithm_id >>= 8;
+  if (algorithm_id == 0) {
+    is_encrypted = FALSE;
+  } else if (algorithm_id == 1) {
+    GST_DEBUG_OBJECT (qtdemux, "AES 128-bits CTR encrypted stream");
+  } else if (algorithm_id == 2) {
+    GST_DEBUG_OBJECT (qtdemux, "AES 128-bits CBC encrypted stream");
+  }
+
+  if (!gst_byte_reader_get_uint8 (br, &iv_size))
+    return FALSE;
+
+  if (!gst_byte_reader_get_data (br, 16, &kid))
+    return FALSE;
+
+  qtdemux_update_default_sample_encryption_settings (qtdemux, info,
+      is_encrypted, iv_size, kid);
+  gst_structure_set (info->default_properties, "piff_algorithm_id",
+      G_TYPE_UINT, algorithm_id, NULL);
+  return TRUE;
+}
+
+
+static void
 qtdemux_parse_piff (GstQTDemux * qtdemux, const guint8 * buffer, gint length,
     guint offset)
 {
@@ -3191,17 +3438,18 @@ qtdemux_parse_piff (GstQTDemux * qtdemux, const guint8 * buffer, gint length,
   guint8 version;
   guint32 flags = 0;
   guint i;
-  guint8 iv_size = 8;
+  guint iv_size = 8;
   QtDemuxStream *stream;
   GstStructure *structure;
   QtDemuxCencSampleSetInfo *ss_info = NULL;
   const gchar *system_id;
   gboolean uses_sub_sample_encryption = FALSE;
+  guint32 sample_count;
 
-  if (qtdemux->n_streams == 0)
+  if (QTDEMUX_N_STREAMS (qtdemux) == 0)
     return;
 
-  stream = qtdemux->streams[0];
+  stream = QTDEMUX_NTH_STREAM (qtdemux, 0);
 
   structure = gst_caps_get_structure (CUR_STREAM (stream)->caps, 0);
   if (!gst_structure_has_name (structure, "application/x-cenc")) {
@@ -3221,13 +3469,13 @@ qtdemux_parse_piff (GstQTDemux * qtdemux, const guint8 * buffer, gint length,
     stream->protection_scheme_info = g_new0 (QtDemuxCencSampleSetInfo, 1);
 
   ss_info = (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info;
+  if (!ss_info->default_properties) {
+    ss_info->default_properties =
+        gst_structure_new ("application/x-cenc",
+        "iv_size", G_TYPE_UINT, iv_size, "encrypted", G_TYPE_BOOLEAN, TRUE,
+        NULL);
 
-  if (ss_info->default_properties)
-    gst_structure_free (ss_info->default_properties);
-
-  ss_info->default_properties =
-      gst_structure_new ("application/x-cenc",
-      "iv_size", G_TYPE_UINT, iv_size, "encrypted", G_TYPE_BOOLEAN, TRUE, NULL);
+  }
 
   if (ss_info->crypto_info) {
     GST_LOG_OBJECT (qtdemux, "unreffing existing crypto_info");
@@ -3249,59 +3497,29 @@ qtdemux_parse_piff (GstQTDemux * qtdemux, const guint8 * buffer, gint length,
   }
 
   if ((flags & 0x000001)) {
-    guint32 algorithm_id = 0;
-    const guint8 *kid;
-    GstBuffer *kid_buf;
-    gboolean is_encrypted = TRUE;
-
-    if (!gst_byte_reader_get_uint24_le (&br, &algorithm_id)) {
-      GST_ERROR_OBJECT (qtdemux, "Error getting box's algorithm ID field");
-      return;
-    }
-
-    algorithm_id >>= 8;
-    if (algorithm_id == 0) {
-      is_encrypted = FALSE;
-    } else if (algorithm_id == 1) {
-      /* FIXME: maybe store this in properties? */
-      GST_DEBUG_OBJECT (qtdemux, "AES 128-bits CTR encrypted stream");
-    } else if (algorithm_id == 2) {
-      /* FIXME: maybe store this in properties? */
-      GST_DEBUG_OBJECT (qtdemux, "AES 128-bits CBC encrypted stream");
-    }
-
-    if (!gst_byte_reader_get_uint8 (&br, &iv_size))
-      return;
-
-    if (!gst_byte_reader_get_data (&br, 16, &kid))
+    if (!qtdemux_update_default_piff_encryption_settings (qtdemux, ss_info,
+            &br))
       return;
-
-    kid_buf = gst_buffer_new_allocate (NULL, 16, NULL);
-    gst_buffer_fill (kid_buf, 0, kid, 16);
-    if (ss_info->default_properties)
-      gst_structure_free (ss_info->default_properties);
-    ss_info->default_properties =
-        gst_structure_new ("application/x-cenc",
-        "iv_size", G_TYPE_UINT, iv_size,
-        "encrypted", G_TYPE_BOOLEAN, is_encrypted,
-        "kid", GST_TYPE_BUFFER, kid_buf, NULL);
-    GST_DEBUG_OBJECT (qtdemux, "default sample properties: "
-        "is_encrypted=%u, iv_size=%u", is_encrypted, iv_size);
-    gst_buffer_unref (kid_buf);
   } else if ((flags & 0x000002)) {
     uses_sub_sample_encryption = TRUE;
   }
 
-  if (!gst_byte_reader_get_uint32_be (&br, &qtdemux->cenc_aux_sample_count)) {
+  if (!gst_structure_get_uint (ss_info->default_properties, "iv_size",
+          &iv_size)) {
+    GST_ERROR_OBJECT (qtdemux, "Error getting encryption IV size field");
+    return;
+  }
+
+  if (!gst_byte_reader_get_uint32_be (&br, &sample_count)) {
     GST_ERROR_OBJECT (qtdemux, "Error getting box's sample count field");
     return;
   }
 
   ss_info->crypto_info =
-      g_ptr_array_new_full (qtdemux->cenc_aux_sample_count,
+      g_ptr_array_new_full (sample_count,
       (GDestroyNotify) qtdemux_gst_structure_free);
 
-  for (i = 0; i < qtdemux->cenc_aux_sample_count; ++i) {
+  for (i = 0; i < sample_count; ++i) {
     GstStructure *properties;
     guint8 *data;
     GstBuffer *buf;
@@ -3309,12 +3527,14 @@ qtdemux_parse_piff (GstQTDemux * qtdemux, const guint8 * buffer, gint length,
     properties = qtdemux_get_cenc_sample_properties (qtdemux, stream, i);
     if (properties == NULL) {
       GST_ERROR_OBJECT (qtdemux, "failed to get properties for sample %u", i);
+      qtdemux->cenc_aux_sample_count = i;
       return;
     }
 
     if (!gst_byte_reader_dup_data (&br, iv_size, &data)) {
       GST_ERROR_OBJECT (qtdemux, "IV data not present for sample %u", i);
       gst_structure_free (properties);
+      qtdemux->cenc_aux_sample_count = i;
       return;
     }
     buf = gst_buffer_new_wrapped (data, iv_size);
@@ -3323,12 +3543,14 @@ qtdemux_parse_piff (GstQTDemux * qtdemux, const guint8 * buffer, gint length,
 
     if (uses_sub_sample_encryption) {
       guint16 n_subsamples;
+      const GValue *kid_buf_value;
 
       if (!gst_byte_reader_get_uint16_be (&br, &n_subsamples)
           || n_subsamples == 0) {
         GST_ERROR_OBJECT (qtdemux,
             "failed to get subsample count for sample %u", i);
         gst_structure_free (properties);
+        qtdemux->cenc_aux_sample_count = i;
         return;
       }
       GST_LOG_OBJECT (qtdemux, "subsample count: %u", n_subsamples);
@@ -3336,12 +3558,18 @@ qtdemux_parse_piff (GstQTDemux * qtdemux, const guint8 * buffer, gint length,
         GST_ERROR_OBJECT (qtdemux, "failed to get subsample data for sample %u",
             i);
         gst_structure_free (properties);
+        qtdemux->cenc_aux_sample_count = i;
         return;
       }
       buf = gst_buffer_new_wrapped (data, n_subsamples * 6);
+
+      kid_buf_value =
+          gst_structure_get_value (ss_info->default_properties, "kid");
+
       gst_structure_set (properties,
           "subsample_count", G_TYPE_UINT, n_subsamples,
           "subsamples", GST_TYPE_BUFFER, buf, NULL);
+      gst_structure_set_value (properties, "kid", kid_buf_value);
       gst_buffer_unref (buf);
     } else {
       gst_structure_set (properties, "subsample_count", G_TYPE_UINT, 0, NULL);
@@ -3349,6 +3577,8 @@ qtdemux_parse_piff (GstQTDemux * qtdemux, const guint8 * buffer, gint length,
 
     g_ptr_array_add (ss_info->crypto_info, properties);
   }
+
+  qtdemux->cenc_aux_sample_count = sample_count;
 }
 
 static void
@@ -3619,34 +3849,38 @@ check_update_duration (GstQTDemux * qtdemux, GstClockTime duration)
       qtdemux->segment.stop = fixeddur;
     }
   }
-  for (i = 0; i < qtdemux->n_streams; i++) {
-    QtDemuxStream *stream = qtdemux->streams[i];
-    if (stream) {
-      movdur = GSTTIME_TO_QTSTREAMTIME (stream, duration);
-      if (movdur > stream->duration) {
-        GST_DEBUG_OBJECT (qtdemux,
-            "Updating stream #%d duration to %" GST_TIME_FORMAT, i,
-            GST_TIME_ARGS (duration));
-        stream->duration = movdur;
-        if (stream->dummy_segment) {
-          /* Update all dummy values to new duration */
-          stream->segments[0].stop_time = duration;
-          stream->segments[0].duration = duration;
-          stream->segments[0].media_stop = duration;
-
-          /* let downstream know we possibly have a new stop time */
-          if (stream->segment_index != -1) {
-            GstClockTime pos;
-
-            if (qtdemux->segment.rate >= 0) {
-              pos = stream->segment.start;
-            } else {
-              pos = stream->segment.stop;
-            }
 
-            gst_qtdemux_stream_update_segment (qtdemux, stream,
-                stream->segment_index, pos, NULL, NULL);
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+    QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
+
+    movdur = GSTTIME_TO_QTSTREAMTIME (stream, duration);
+    if (movdur > stream->duration) {
+      GST_DEBUG_OBJECT (qtdemux,
+          "Updating stream #%d duration to %" GST_TIME_FORMAT, i,
+          GST_TIME_ARGS (duration));
+      stream->duration = movdur;
+      /* internal duration tracking state has been updated above, so */
+      /* preserve an open-ended dummy segment rather than repeatedly updating
+       * it and spamming downstream accordingly with segment events */
+      if (stream->dummy_segment &&
+          GST_CLOCK_TIME_IS_VALID (stream->segments[0].duration)) {
+        /* Update all dummy values to new duration */
+        stream->segments[0].stop_time = duration;
+        stream->segments[0].duration = duration;
+        stream->segments[0].media_stop = duration;
+
+        /* let downstream know we possibly have a new stop time */
+        if (stream->segment_index != -1) {
+          GstClockTime pos;
+
+          if (qtdemux->segment.rate >= 0) {
+            pos = stream->segment.start;
+          } else {
+            pos = stream->segment.stop;
           }
+
+          gst_qtdemux_stream_update_segment (qtdemux, stream,
+              stream->segment_index, pos, NULL, NULL);
         }
       }
     }
@@ -3671,7 +3905,7 @@ qtdemux_parse_trun (GstQTDemux * qtdemux, GstByteReader * trun,
   gboolean ismv = FALSE;
   gint64 initial_offset;
 
-  GST_LOG_OBJECT (qtdemux, "parsing trun stream %d; "
+  GST_LOG_OBJECT (qtdemux, "parsing trun track-id %d; "
       "default dur %d, size %d, flags 0x%x, base offset %" G_GINT64_FORMAT ", "
       "decode ts %" G_GINT64_FORMAT, stream->track_id, d_sample_duration,
       d_sample_size, d_sample_flags, *base_offset, decode_ts);
@@ -3944,22 +4178,14 @@ qtdemux_find_stream (GstQTDemux * qtdemux, guint32 id)
     return NULL;
   }
 
-  /* try to get it fast and simple */
-  if (G_LIKELY (id <= qtdemux->n_streams)) {
-    stream = qtdemux->streams[id - 1];
-    if (G_LIKELY (stream->track_id == id))
-      return stream;
-  }
-
-  /* linear search otherwise */
-  for (i = 0; i < qtdemux->n_streams; i++) {
-    stream = qtdemux->streams[i];
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+    stream = QTDEMUX_NTH_STREAM (qtdemux, i);
     if (stream->track_id == id)
       return stream;
   }
   if (qtdemux->mss_mode) {
     /* mss should have only 1 stream anyway */
-    return qtdemux->streams[0];
+    return QTDEMUX_NTH_STREAM (qtdemux, 0);
   }
 
   return NULL;
@@ -4393,8 +4619,12 @@ qtdemux_parse_pssh (GstQTDemux * qtdemux, GNode * node)
   /* Push an event containing the pssh box onto the queues of all streams. */
   event = gst_event_new_protection (sysid_string, pssh,
       (parent_box_type == FOURCC_moov) ? "isobmff/moov" : "isobmff/moof");
-  for (i = 0; i < qtdemux->n_streams; ++i) {
-    g_queue_push_tail (&qtdemux->streams[i]->protection_scheme_event_queue,
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+    QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
+    GST_TRACE_OBJECT (qtdemux,
+        "adding protection event for stream %s and system %s",
+        stream->stream_id, sysid_string);
+    g_queue_push_tail (&stream->protection_scheme_event_queue,
         gst_event_ref (event));
   }
   g_free (sysid_string);
@@ -4415,6 +4645,7 @@ qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length,
   guint32 ds_size = 0, ds_duration = 0, ds_flags = 0;
   gint64 base_offset, running_offset;
   guint32 frag_num;
+  GstClockTime min_dts = GST_CLOCK_TIME_NONE;
 
   /* NOTE @stream ignored */
 
@@ -4535,8 +4766,19 @@ qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length,
     if (G_UNLIKELY (base_offset < -1))
       goto lost_offset;
 
-    if (qtdemux->upstream_format_is_time)
-      gst_qtdemux_stream_flush_samples_data (qtdemux, stream);
+    min_dts = MIN (min_dts, QTSTREAMTIME_TO_GSTTIME (stream, decode_time));
+
+    if (!qtdemux->pullbased) {
+      /* Sample tables can grow enough to be problematic if the system memory
+       * is very low (e.g. embedded devices) and the videos very long
+       * (~8 MiB/hour for 25-30 fps video + typical AAC audio frames).
+       * Fortunately, we can easily discard them for each new fragment when
+       * we know qtdemux will not receive seeks outside of the current fragment.
+       * adaptivedemux honors this assumption.
+       * This optimization is also useful for applications that use qtdemux as
+       * a push-based simple demuxer, like Media Source Extensions. */
+      gst_qtdemux_stream_flush_samples_data (stream);
+    }
 
     /* initialise moof sample data */
     stream->n_samples_moof = 0;
@@ -4585,6 +4827,38 @@ qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length,
     pssh_node = qtdemux_tree_get_sibling_by_type (pssh_node, FOURCC_pssh);
   }
 
+  if (!qtdemux->upstream_format_is_time && !qtdemux->first_moof_already_parsed
+      && !qtdemux->received_seek && GST_CLOCK_TIME_IS_VALID (min_dts)
+      && min_dts != 0) {
+    /* Unless the user has explictly requested another seek, perform an
+     * internal seek to the time specified in the tfdt.
+     *
+     * This way if the user opens a file where the first tfdt is 1 hour
+     * into the presentation, they will not have to wait 1 hour for run
+     * time to catch up and actual playback to start. */
+    gint i;
+
+    GST_DEBUG_OBJECT (qtdemux, "First fragment has a non-zero tfdt, "
+        "performing an internal seek to %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (min_dts));
+
+    qtdemux->segment.start = min_dts;
+    qtdemux->segment.time = qtdemux->segment.position = min_dts;
+
+    for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+      QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
+      stream->time_position = min_dts;
+    }
+
+    /* Before this code was run a segment was already sent when the moov was
+     * parsed... which is OK -- some apps (mostly tests) expect a segment to
+     * be emitted after a moov, and we can emit a second segment anyway for
+     * special cases like this. */
+    qtdemux->need_segment = TRUE;
+  }
+
+  qtdemux->first_moof_already_parsed = TRUE;
+
   g_node_destroy (moof_node);
   return TRUE;
 
@@ -4875,6 +5149,7 @@ gst_qtdemux_loop_state_header (GstQTDemux * qtdemux)
       break;
     case FOURCC_mdat:
     case FOURCC_free:
+    case FOURCC_skip:
     case FOURCC_wide:
     case FOURCC_PICT:
     case FOURCC_pnot:
@@ -5026,7 +5301,9 @@ beach:
       _send_spherical_metadata_msg_to_bus (qtdemux);
 #endif /* TIZEN_FEATURE_QTDEMUX_MODIFICATION */
     qtdemux_prepare_streams (qtdemux);
+    QTDEMUX_EXPOSE_LOCK (qtdemux);
     ret = qtdemux_expose_streams (qtdemux);
+    QTDEMUX_EXPOSE_UNLOCK (qtdemux);
 
     qtdemux->state = QTDEMUX_STATE_MOVIE;
     GST_DEBUG_OBJECT (qtdemux, "switching state to STATE_MOVIE (%d)",
@@ -5043,7 +5320,6 @@ beach:
 static GstFlowReturn
 gst_qtdemux_seek_to_previous_keyframe (GstQTDemux * qtdemux)
 {
-  guint8 n = 0;
   guint32 seg_idx = 0, k_index = 0;
   guint32 ref_seg_idx, ref_k_index;
   GstClockTime k_pos = 0, last_stop = 0;
@@ -5051,12 +5327,13 @@ gst_qtdemux_seek_to_previous_keyframe (GstQTDemux * qtdemux)
   QtDemuxStream *ref_str = NULL;
   guint64 seg_media_start_mov;  /* segment media start time in mov format */
   guint64 target_ts;
+  gint i;
 
   /* Now we choose an arbitrary stream, get the previous keyframe timestamp
    * and finally align all the other streams on that timestamp with their
    * respective keyframes */
-  for (n = 0; n < qtdemux->n_streams; n++) {
-    QtDemuxStream *str = qtdemux->streams[n];
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+    QtDemuxStream *str = QTDEMUX_NTH_STREAM (qtdemux, i);
 
     /* No candidate yet, take the first stream */
     if (!ref_str) {
@@ -5104,7 +5381,7 @@ gst_qtdemux_seek_to_previous_keyframe (GstQTDemux * qtdemux)
   seg_media_start_mov = seg->trak_media_start;
 
   GST_LOG_OBJECT (qtdemux, "keyframe index %u ts %" G_GUINT64_FORMAT
-      " seg start %" G_GUINT64_FORMAT " %" GST_TIME_FORMAT "\n",
+      " seg start %" G_GUINT64_FORMAT " %" GST_TIME_FORMAT,
       k_index, target_ts, seg_media_start_mov,
       GST_TIME_ARGS (seg->media_start));
 
@@ -5151,10 +5428,10 @@ gst_qtdemux_seek_to_previous_keyframe (GstQTDemux * qtdemux)
   ref_k_index = k_index;
 
   /* Align them all on this */
-  for (n = 0; n < qtdemux->n_streams; n++) {
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
     guint32 index = 0;
     GstClockTime seg_time = 0;
-    QtDemuxStream *str = qtdemux->streams[n];
+    QtDemuxStream *str = QTDEMUX_NTH_STREAM (qtdemux, i);
 
     /* aligning reference stream again might lead to backing up to yet another
      * keyframe (due to timestamp rounding issues),
@@ -5163,13 +5440,13 @@ gst_qtdemux_seek_to_previous_keyframe (GstQTDemux * qtdemux)
       seg_idx = ref_seg_idx;
       seg = &str->segments[seg_idx];
       k_index = ref_k_index;
-      GST_DEBUG_OBJECT (qtdemux, "reference stream %d segment %d, "
-          "sample at index %d", n, ref_str->segment_index, k_index);
+      GST_DEBUG_OBJECT (qtdemux, "reference track-id %u segment %d, "
+          "sample at index %d", str->track_id, ref_str->segment_index, k_index);
     } else {
       seg_idx = gst_qtdemux_find_segment (qtdemux, str, k_pos);
       GST_DEBUG_OBJECT (qtdemux,
-          "stream %d align segment %d for keyframe pos %" GST_TIME_FORMAT, n,
-          seg_idx, GST_TIME_ARGS (k_pos));
+          "track-id %u align segment %d for keyframe pos %" GST_TIME_FORMAT,
+          str->track_id, seg_idx, GST_TIME_ARGS (k_pos));
 
       /* get segment and time in the segment */
       seg = &str->segments[seg_idx];
@@ -5183,7 +5460,7 @@ gst_qtdemux_seek_to_previous_keyframe (GstQTDemux * qtdemux)
       /* get the index of the sample with media time */
       index = gst_qtdemux_find_index_linear (qtdemux, str, seg_time);
       GST_DEBUG_OBJECT (qtdemux,
-          "stream %d sample for %" GST_TIME_FORMAT " at %u", n,
+          "track-id %u sample for %" GST_TIME_FORMAT " at %u", str->track_id,
           GST_TIME_ARGS (seg_time), index);
 
       /* find previous keyframe */
@@ -5201,8 +5478,8 @@ gst_qtdemux_seek_to_previous_keyframe (GstQTDemux * qtdemux)
 
     /* Now seek back in time */
     gst_qtdemux_move_stream (qtdemux, str, k_index);
-    GST_DEBUG_OBJECT (qtdemux, "stream %d keyframe at %u, time position %"
-        GST_TIME_FORMAT " playing from sample %u to %u", n, k_index,
+    GST_DEBUG_OBJECT (qtdemux, "track-id %u keyframe at %u, time position %"
+        GST_TIME_FORMAT " playing from sample %u to %u", str->track_id, k_index,
         GST_TIME_ARGS (str->time_position), str->from_sample, str->to_sample);
   }
 
@@ -5347,7 +5624,7 @@ gst_qtdemux_stream_update_segment (GstQTDemux * qtdemux, QtDemuxStream * stream,
   /* now prepare and send the segment */
   if (stream->pad) {
     event = gst_event_new_segment (&stream->segment);
-    if (qtdemux->segment_seqnum) {
+    if (qtdemux->segment_seqnum != GST_SEQNUM_INVALID) {
       gst_event_set_seqnum (event, qtdemux->segment_seqnum);
     }
     gst_pad_push_event (stream->pad, event);
@@ -5394,9 +5671,15 @@ gst_qtdemux_activate_segment (GstQTDemux * qtdemux, QtDemuxStream * stream,
    * (FIXME: doesn't seem to work so well with ismv and wmv, as no parser; the
    * tfra entries tells us which trun/sample the key unit is in, but we don't
    * make use of this additional information at the moment) */
-  if (qtdemux->fragmented) {
+  if (qtdemux->fragmented && !qtdemux->fragmented_seek_pending) {
     stream->to_sample = G_MAXUINT32;
     return TRUE;
+  } else {
+    /* well, it will be taken care of below */
+    qtdemux->fragmented_seek_pending = FALSE;
+    /* FIXME ideally the do_fragmented_seek can be done right here,
+     * rather than at loop level
+     * (which might even allow handling edit lists in a fragmented file) */
   }
 
   /* We don't need to look for a sample in push-based */
@@ -5509,8 +5792,8 @@ gst_qtdemux_prepare_current_sample (GstQTDemux * qtdemux,
   if (G_UNLIKELY (stream->segment_index != seg_idx))
     gst_qtdemux_activate_segment (qtdemux, stream, seg_idx, time_position);
 
-  if (G_UNLIKELY (QTSEGMENT_IS_EMPTY (&stream->segments[stream->
-                  segment_index]))) {
+  if (G_UNLIKELY (QTSEGMENT_IS_EMPTY (&stream->
+              segments[stream->segment_index]))) {
     QtDemuxSegment *seg = &stream->segments[stream->segment_index];
 
     GST_LOG_OBJECT (qtdemux, "Empty segment activated,"
@@ -5664,14 +5947,14 @@ gst_qtdemux_sync_streams (GstQTDemux * demux)
 {
   gint i;
 
-  if (demux->n_streams <= 1)
+  if (QTDEMUX_N_STREAMS (demux) <= 1)
     return;
 
-  for (i = 0; i < demux->n_streams; i++) {
+  for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
     QtDemuxStream *stream;
     GstClockTime end_time;
 
-    stream = demux->streams[i];
+    stream = QTDEMUX_NTH_STREAM (demux, i);
 
     if (!stream->pad)
       continue;
@@ -5712,7 +5995,7 @@ gst_qtdemux_sync_streams (GstQTDemux * demux)
           GST_PAD_NAME (stream->pad));
       stream->sent_eos = TRUE;
       event = gst_event_new_eos ();
-      if (demux->segment_seqnum)
+      if (demux->segment_seqnum != GST_SEQNUM_INVALID)
         gst_event_set_seqnum (event, demux->segment_seqnum);
       gst_pad_push_event (stream->pad, event);
     }
@@ -5900,18 +6183,145 @@ gst_qtdemux_align_buffer (GstQTDemux * demux,
   return buffer;
 }
 
-/* the input buffer metadata must be writable,
- * but time/duration etc not yet set and need not be preserved */
-static GstBuffer *
-gst_qtdemux_process_buffer (GstQTDemux * qtdemux, QtDemuxStream * stream,
-    GstBuffer * buf)
+static guint8 *
+convert_to_s334_1a (const guint8 * ccpair, guint8 ccpair_size, guint field,
+    gsize * res)
+{
+  guint8 *storage;
+  gsize i;
+
+  /* We are converting from pairs to triplets */
+  *res = ccpair_size / 2 * 3;
+  storage = g_malloc (*res);
+  for (i = 0; i * 2 < ccpair_size; i += 1) {
+    /* FIXME: Use line offset 0 as we simply can't know here */
+    if (field == 1)
+      storage[i * 3] = 0x80 | 0x00;
+    else
+      storage[i * 3] = 0x00 | 0x00;
+    storage[i * 3 + 1] = ccpair[i * 2];
+    storage[i * 3 + 2] = ccpair[i * 2 + 1];
+  }
+
+  return storage;
+}
+
+static guint8 *
+extract_cc_from_data (QtDemuxStream * stream, const guint8 * data, gsize size,
+    gsize * cclen)
 {
-  GstMapInfo map;
-  guint nsize = 0;
-  gchar *str;
+  guint8 *res = NULL;
+  guint32 atom_length, fourcc;
+  QtDemuxStreamStsdEntry *stsd_entry;
 
-  /* not many cases for now */
-  if (G_UNLIKELY (CUR_STREAM (stream)->fourcc == FOURCC_mp4s)) {
+  GST_MEMDUMP ("caption atom", data, size);
+
+  /* There might be multiple atoms */
+
+  *cclen = 0;
+  if (size < 8)
+    goto invalid_cdat;
+  atom_length = QT_UINT32 (data);
+  fourcc = QT_FOURCC (data + 4);
+  if (G_UNLIKELY (atom_length > size || atom_length == 8))
+    goto invalid_cdat;
+
+  GST_DEBUG_OBJECT (stream->pad, "here");
+
+  /* Check if we have somethig compatible */
+  stsd_entry = CUR_STREAM (stream);
+  switch (stsd_entry->fourcc) {
+    case FOURCC_c608:{
+      guint8 *cdat = NULL, *cdt2 = NULL;
+      gsize cdat_size = 0, cdt2_size = 0;
+      /* Should be cdat or cdt2 */
+      if (fourcc != FOURCC_cdat && fourcc != FOURCC_cdt2) {
+        GST_WARNING_OBJECT (stream->pad,
+            "Unknown data atom (%" GST_FOURCC_FORMAT ") for CEA608",
+            GST_FOURCC_ARGS (fourcc));
+        goto invalid_cdat;
+      }
+
+      /* Convert to S334-1 Annex A byte triplet */
+      if (fourcc == FOURCC_cdat)
+        cdat = convert_to_s334_1a (data + 8, atom_length - 8, 1, &cdat_size);
+      else
+        cdt2 = convert_to_s334_1a (data + 8, atom_length - 8, 2, &cdt2_size);
+      GST_DEBUG_OBJECT (stream->pad, "size:%" G_GSIZE_FORMAT " atom_length:%u",
+          size, atom_length);
+
+      /* Check for another atom ? */
+      if (size > atom_length + 8) {
+        guint32 new_atom_length = QT_UINT32 (data + atom_length);
+        if (size >= atom_length + new_atom_length) {
+          fourcc = QT_FOURCC (data + atom_length + 4);
+          if (fourcc == FOURCC_cdat) {
+            if (cdat == NULL)
+              cdat =
+                  convert_to_s334_1a (data + atom_length + 8,
+                  new_atom_length - 8, 1, &cdat_size);
+            else
+              GST_WARNING_OBJECT (stream->pad,
+                  "Got multiple [cdat] atoms in a c608 sample. This is unsupported for now. Please file a bug");
+          } else {
+            if (cdt2 == NULL)
+              cdt2 =
+                  convert_to_s334_1a (data + atom_length + 8,
+                  new_atom_length - 8, 2, &cdt2_size);
+            else
+              GST_WARNING_OBJECT (stream->pad,
+                  "Got multiple [cdt2] atoms in a c608 sample. This is unsupported for now. Please file a bug");
+          }
+        }
+      }
+
+      *cclen = cdat_size + cdt2_size;
+      res = g_malloc (*cclen);
+      if (cdat_size)
+        memcpy (res, cdat, cdat_size);
+      if (cdt2_size)
+        memcpy (res + cdat_size, cdt2, cdt2_size);
+      g_free (cdat);
+      g_free (cdt2);
+    }
+      break;
+    case FOURCC_c708:
+      if (fourcc != FOURCC_ccdp) {
+        GST_WARNING_OBJECT (stream->pad,
+            "Unknown data atom (%" GST_FOURCC_FORMAT ") for CEA708",
+            GST_FOURCC_ARGS (fourcc));
+        goto invalid_cdat;
+      }
+      *cclen = atom_length - 8;
+      res = g_memdup (data + 8, *cclen);
+      break;
+    default:
+      /* Keep this here in case other closed caption formats are added */
+      g_assert_not_reached ();
+      break;
+  }
+
+  GST_MEMDUMP ("Output", res, *cclen);
+  return res;
+
+  /* Errors */
+invalid_cdat:
+  GST_WARNING ("[cdat] atom is too small or invalid");
+  return NULL;
+}
+
+/* the input buffer metadata must be writable,
+ * but time/duration etc not yet set and need not be preserved */
+static GstBuffer *
+gst_qtdemux_process_buffer (GstQTDemux * qtdemux, QtDemuxStream * stream,
+    GstBuffer * buf)
+{
+  GstMapInfo map;
+  guint nsize = 0;
+  gchar *str;
+
+  /* not many cases for now */
+  if (G_UNLIKELY (CUR_STREAM (stream)->fourcc == FOURCC_mp4s)) {
     /* send a one time dvd clut event */
     if (stream->pending_event && stream->pad)
       gst_pad_push_event (stream->pad, stream->pending_event);
@@ -5920,7 +6330,7 @@ gst_qtdemux_process_buffer (GstQTDemux * qtdemux, QtDemuxStream * stream,
 
   if (G_UNLIKELY (stream->subtype != FOURCC_text
           && stream->subtype != FOURCC_sbtl &&
-          stream->subtype != FOURCC_subp)) {
+          stream->subtype != FOURCC_subp && stream->subtype != FOURCC_clcp)) {
     return buf;
   }
 
@@ -5938,6 +6348,23 @@ gst_qtdemux_process_buffer (GstQTDemux * qtdemux, QtDemuxStream * stream,
     return buf;
   }
 
+  if (stream->subtype == FOURCC_clcp) {
+    guint8 *cc;
+    gsize cclen = 0;
+    /* For closed caption, we need to extract the information from the
+     * [cdat],[cdt2] or [ccdp] atom */
+    cc = extract_cc_from_data (stream, map.data, map.size, &cclen);
+    gst_buffer_unmap (buf, &map);
+    gst_buffer_unref (buf);
+    if (cc) {
+      buf = _gst_buffer_new_wrapped (cc, cclen, g_free);
+    } else {
+      /* Conversion failed or there's nothing */
+      buf = NULL;
+    }
+    return buf;
+  }
+
   nsize = GST_READ_UINT16_BE (map.data);
   nsize = MIN (nsize, map.size - 2);
 
@@ -5962,6 +6389,210 @@ gst_qtdemux_process_buffer (GstQTDemux * qtdemux, QtDemuxStream * stream,
   return buf;
 }
 
+static GstFlowReturn
+gst_qtdemux_push_buffer (GstQTDemux * qtdemux, QtDemuxStream * stream,
+    GstBuffer * buf)
+{
+  GstFlowReturn ret = GST_FLOW_OK;
+  GstClockTime pts, duration;
+
+  if (stream->need_clip)
+    buf = gst_qtdemux_clip_buffer (qtdemux, stream, buf);
+
+  if (G_UNLIKELY (buf == NULL))
+    goto exit;
+
+  if (G_UNLIKELY (stream->discont)) {
+    GST_LOG_OBJECT (qtdemux, "marking discont buffer");
+    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT);
+    stream->discont = FALSE;
+  } else {
+    GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT);
+  }
+
+  GST_LOG_OBJECT (qtdemux,
+      "Pushing buffer with dts %" GST_TIME_FORMAT ", pts %" GST_TIME_FORMAT
+      ", duration %" GST_TIME_FORMAT " on pad %s",
+      GST_TIME_ARGS (GST_BUFFER_DTS (buf)),
+      GST_TIME_ARGS (GST_BUFFER_PTS (buf)),
+      GST_TIME_ARGS (GST_BUFFER_DURATION (buf)), GST_PAD_NAME (stream->pad));
+
+  if (stream->protected && stream->protection_scheme_type == FOURCC_cenc) {
+    GstStructure *crypto_info;
+    QtDemuxCencSampleSetInfo *info =
+        (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info;
+    gint index;
+    GstEvent *event;
+
+    while ((event = g_queue_pop_head (&stream->protection_scheme_event_queue))) {
+      GST_TRACE_OBJECT (stream->pad, "pushing protection event: %"
+          GST_PTR_FORMAT, event);
+      gst_pad_push_event (stream->pad, event);
+    }
+
+    if (info->crypto_info == NULL) {
+      GST_DEBUG_OBJECT (qtdemux,
+          "cenc metadata hasn't been parsed yet, pushing buffer as if it wasn't encrypted");
+    } else {
+      /* The end of the crypto_info array matches our n_samples position,
+       * so count backward from there */
+      index = stream->sample_index - stream->n_samples + info->crypto_info->len;
+      if (G_LIKELY (index >= 0 && index < info->crypto_info->len)) {
+        /* steal structure from array */
+        crypto_info = g_ptr_array_index (info->crypto_info, index);
+        g_ptr_array_index (info->crypto_info, index) = NULL;
+        GST_LOG_OBJECT (qtdemux, "attaching cenc metadata [%u/%u]", index,
+            info->crypto_info->len);
+        if (!crypto_info || !gst_buffer_add_protection_meta (buf, crypto_info))
+          GST_ERROR_OBJECT (qtdemux,
+              "failed to attach cenc metadata to buffer");
+      } else {
+        GST_INFO_OBJECT (qtdemux, "No crypto info with index %d and sample %d",
+            index, stream->sample_index);
+      }
+    }
+  }
+
+  if (stream->alignment > 1)
+    buf = gst_qtdemux_align_buffer (qtdemux, buf, stream->alignment);
+
+  pts = GST_BUFFER_PTS (buf);
+  duration = GST_BUFFER_DURATION (buf);
+
+  ret = gst_pad_push (stream->pad, buf);
+
+  if (GST_CLOCK_TIME_IS_VALID (pts) && GST_CLOCK_TIME_IS_VALID (duration)) {
+    /* mark position in stream, we'll need this to know when to send GAP event */
+    stream->segment.position = pts + duration;
+  }
+
+exit:
+
+  return ret;
+}
+
+static GstFlowReturn
+gst_qtdemux_split_and_push_buffer (GstQTDemux * qtdemux, QtDemuxStream * stream,
+    GstBuffer * buf)
+{
+  GstFlowReturn ret = GST_FLOW_OK;
+
+  if (stream->subtype == FOURCC_clcp
+      && CUR_STREAM (stream)->fourcc == FOURCC_c608 && stream->need_split) {
+    GstMapInfo map;
+    guint n_output_buffers, n_field1 = 0, n_field2 = 0;
+    guint n_triplets, i;
+    guint field1_off = 0, field2_off = 0;
+
+    /* We have to split CEA608 buffers so that each outgoing buffer contains
+     * one byte pair per field according to the framerate of the video track.
+     *
+     * If there is only a single byte pair per field we don't have to do
+     * anything
+     */
+
+    gst_buffer_map (buf, &map, GST_MAP_READ);
+
+    n_triplets = map.size / 3;
+    for (i = 0; i < n_triplets; i++) {
+      if (map.data[3 * i] & 0x80)
+        n_field1++;
+      else
+        n_field2++;
+    }
+
+    g_assert (n_field1 || n_field2);
+
+    /* If there's more than 1 frame we have to split, otherwise we can just
+     * pass through */
+    if (n_field1 > 1 || n_field2 > 1) {
+      n_output_buffers =
+          gst_util_uint64_scale (GST_BUFFER_DURATION (buf),
+          CUR_STREAM (stream)->fps_n, GST_SECOND * CUR_STREAM (stream)->fps_d);
+
+      for (i = 0; i < n_output_buffers; i++) {
+        GstBuffer *outbuf =
+            gst_buffer_new_and_alloc ((n_field1 ? 3 : 0) + (n_field2 ? 3 : 0));
+        GstMapInfo outmap;
+        guint8 *outptr;
+
+        gst_buffer_map (outbuf, &outmap, GST_MAP_WRITE);
+        outptr = outmap.data;
+
+        if (n_field1) {
+          gboolean found = FALSE;
+
+          while (map.data + field1_off < map.data + map.size) {
+            if (map.data[field1_off] & 0x80) {
+              memcpy (outptr, &map.data[field1_off], 3);
+              field1_off += 3;
+              found = TRUE;
+              break;
+            }
+            field1_off += 3;
+          }
+
+          if (!found) {
+            const guint8 empty[] = { 0x80, 0x80, 0x80 };
+
+            memcpy (outptr, empty, 3);
+          }
+
+          outptr += 3;
+        }
+
+        if (n_field2) {
+          gboolean found = FALSE;
+
+          while (map.data + field2_off < map.data + map.size) {
+            if ((map.data[field2_off] & 0x80) == 0) {
+              memcpy (outptr, &map.data[field2_off], 3);
+              field2_off += 3;
+              found = TRUE;
+              break;
+            }
+            field2_off += 3;
+          }
+
+          if (!found) {
+            const guint8 empty[] = { 0x00, 0x80, 0x80 };
+
+            memcpy (outptr, empty, 3);
+          }
+
+          outptr += 3;
+        }
+
+        gst_buffer_unmap (outbuf, &outmap);
+
+        GST_BUFFER_PTS (outbuf) =
+            GST_BUFFER_PTS (buf) + gst_util_uint64_scale (i,
+            GST_SECOND * CUR_STREAM (stream)->fps_d,
+            CUR_STREAM (stream)->fps_n);
+        GST_BUFFER_DURATION (outbuf) =
+            gst_util_uint64_scale (GST_SECOND, CUR_STREAM (stream)->fps_d,
+            CUR_STREAM (stream)->fps_n);
+        GST_BUFFER_OFFSET (outbuf) = -1;
+        GST_BUFFER_OFFSET_END (outbuf) = -1;
+
+        ret = gst_qtdemux_push_buffer (qtdemux, stream, outbuf);
+
+        if (ret != GST_FLOW_OK && ret != GST_FLOW_NOT_LINKED)
+          break;
+      }
+      gst_buffer_unmap (buf, &map);
+      gst_buffer_unref (buf);
+    } else {
+      gst_buffer_unmap (buf, &map);
+      ret = gst_qtdemux_push_buffer (qtdemux, stream, buf);
+    }
+  } else {
+    ret = gst_qtdemux_push_buffer (qtdemux, stream, buf);
+  }
+
+  return ret;
+}
+
 /* Sets a buffer's attributes properly and pushes it downstream.
  * Also checks for additional actions and custom processing that may
  * need to be done first.
@@ -6044,6 +6675,13 @@ gst_qtdemux_decorate_and_push_buffer (GstQTDemux * qtdemux,
   GST_BUFFER_OFFSET (buf) = -1;
   GST_BUFFER_OFFSET_END (buf) = -1;
 
+  if (!keyframe) {
+    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
+    stream->on_keyframe = FALSE;
+  } else {
+    stream->on_keyframe = TRUE;
+  }
+
   if (G_UNLIKELY (CUR_STREAM (stream)->rgb8_palette))
     gst_buffer_append_memory (buf,
         gst_memory_ref (CUR_STREAM (stream)->rgb8_palette));
@@ -6071,77 +6709,7 @@ gst_qtdemux_decorate_and_push_buffer (GstQTDemux * qtdemux,
   }
 #endif
 
-  if (stream->need_clip)
-    buf = gst_qtdemux_clip_buffer (qtdemux, stream, buf);
-
-  if (G_UNLIKELY (buf == NULL))
-    goto exit;
-
-  if (G_UNLIKELY (stream->discont)) {
-    GST_LOG_OBJECT (qtdemux, "marking discont buffer");
-    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT);
-    stream->discont = FALSE;
-  } else {
-    GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT);
-  }
-
-  if (!keyframe) {
-    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
-    stream->on_keyframe = FALSE;
-  } else {
-    stream->on_keyframe = TRUE;
-  }
-
-
-  GST_LOG_OBJECT (qtdemux,
-      "Pushing buffer with dts %" GST_TIME_FORMAT ", pts %" GST_TIME_FORMAT
-      ", duration %" GST_TIME_FORMAT " on pad %s", GST_TIME_ARGS (dts),
-      GST_TIME_ARGS (pts), GST_TIME_ARGS (duration),
-      GST_PAD_NAME (stream->pad));
-
-  if (stream->protected && stream->protection_scheme_type == FOURCC_cenc) {
-    GstStructure *crypto_info;
-    QtDemuxCencSampleSetInfo *info =
-        (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info;
-    gint index;
-    GstEvent *event;
-
-    while ((event = g_queue_pop_head (&stream->protection_scheme_event_queue))) {
-      gst_pad_push_event (stream->pad, event);
-    }
-
-    if (info->crypto_info == NULL) {
-      GST_DEBUG_OBJECT (qtdemux, "cenc metadata hasn't been parsed yet");
-      gst_buffer_unref (buf);
-      goto exit;
-    }
-
-    /* The end of the crypto_info array matches our n_samples position,
-     * so count backward from there */
-    index = stream->sample_index - stream->n_samples + info->crypto_info->len;
-    if (G_LIKELY (index >= 0 && index < info->crypto_info->len)) {
-      /* steal structure from array */
-      crypto_info = g_ptr_array_index (info->crypto_info, index);
-      g_ptr_array_index (info->crypto_info, index) = NULL;
-      GST_LOG_OBJECT (qtdemux, "attaching cenc metadata [%u/%u]", index,
-          info->crypto_info->len);
-      if (!crypto_info || !gst_buffer_add_protection_meta (buf, crypto_info))
-        GST_ERROR_OBJECT (qtdemux, "failed to attach cenc metadata to buffer");
-    } else {
-      GST_INFO_OBJECT (qtdemux, "No crypto info with index %d and sample %d",
-          index, stream->sample_index);
-    }
-  }
-
-  if (stream->alignment > 1)
-    buf = gst_qtdemux_align_buffer (qtdemux, buf, stream->alignment);
-
-  ret = gst_pad_push (stream->pad, buf);
-
-  if (GST_CLOCK_TIME_IS_VALID (pts) && GST_CLOCK_TIME_IS_VALID (duration)) {
-    /* mark position in stream, we'll need this to know when to send GAP event */
-    stream->segment.position = pts + duration;
-  }
+  ret = gst_qtdemux_split_and_push_buffer (qtdemux, stream, buf);
 
 exit:
   return ret;
@@ -6176,36 +6744,20 @@ static gboolean
 gst_qtdemux_do_fragmented_seek (GstQTDemux * qtdemux)
 {
   const QtDemuxRandomAccessEntry *best_entry = NULL;
-  guint i;
+  gint i;
 
   GST_OBJECT_LOCK (qtdemux);
 
-  g_assert (qtdemux->n_streams > 0);
+  g_assert (QTDEMUX_N_STREAMS (qtdemux) > 0);
 
-  for (i = 0; i < qtdemux->n_streams; i++) {
+  /* first see if we can determine where to go to using mfra,
+   * before we start clearing things */
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
     const QtDemuxRandomAccessEntry *entry;
     QtDemuxStream *stream;
     gboolean is_audio_or_video;
 
-    stream = qtdemux->streams[i];
-
-    g_free (stream->samples);
-    stream->samples = NULL;
-    stream->n_samples = 0;
-    stream->stbl_index = -1;    /* no samples have yet been parsed */
-    stream->sample_index = -1;
-
-    if (stream->protection_scheme_info) {
-      /* Clear out any old cenc crypto info entries as we'll move to a new moof */
-      if (stream->protection_scheme_type == FOURCC_cenc) {
-        QtDemuxCencSampleSetInfo *info =
-            (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info;
-        if (info->crypto_info) {
-          g_ptr_array_free (info->crypto_info, TRUE);
-          info->crypto_info = NULL;
-        }
-      }
-    }
+    stream = QTDEMUX_NTH_STREAM (qtdemux, i);
 
     if (stream->ra_entries == NULL)
       continue;
@@ -6232,14 +6784,40 @@ gst_qtdemux_do_fragmented_seek (GstQTDemux * qtdemux)
       best_entry = entry;
   }
 
+  /* no luck, will handle seek otherwise */
   if (best_entry == NULL) {
     GST_OBJECT_UNLOCK (qtdemux);
     return FALSE;
   }
 
+  /* ok, now we can prepare for processing as of located moof */
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+    QtDemuxStream *stream;
+
+    stream = QTDEMUX_NTH_STREAM (qtdemux, i);
+
+    g_free (stream->samples);
+    stream->samples = NULL;
+    stream->n_samples = 0;
+    stream->stbl_index = -1;    /* no samples have yet been parsed */
+    stream->sample_index = -1;
+
+    if (stream->protection_scheme_info) {
+      /* Clear out any old cenc crypto info entries as we'll move to a new moof */
+      if (stream->protection_scheme_type == FOURCC_cenc) {
+        QtDemuxCencSampleSetInfo *info =
+            (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info;
+        if (info->crypto_info) {
+          g_ptr_array_free (info->crypto_info, TRUE);
+          info->crypto_info = NULL;
+        }
+      }
+    }
+  }
+
   GST_INFO_OBJECT (qtdemux, "seek to %" GST_TIME_FORMAT ", best fragment "
       "moof offset: %" G_GUINT64_FORMAT ", ts %" GST_TIME_FORMAT,
-      GST_TIME_ARGS (qtdemux->streams[0]->time_position),
+      GST_TIME_ARGS (QTDEMUX_NTH_STREAM (qtdemux, 0)->time_position),
       best_entry->moof_offset, GST_TIME_ARGS (best_entry->ts));
 
   qtdemux->moof_offset = best_entry->moof_offset;
@@ -6255,7 +6833,7 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux)
 {
   GstFlowReturn ret = GST_FLOW_OK;
   GstBuffer *buf = NULL;
-  QtDemuxStream *stream;
+  QtDemuxStream *stream, *target_stream = NULL;
   GstClockTime min_time;
   guint64 offset = 0;
   GstClockTime dts = GST_CLOCK_TIME_NONE;
@@ -6265,36 +6843,35 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux)
   guint sample_size = 0;
   gboolean empty = 0;
   guint size;
-  gint index;
   gint i;
 
-  gst_qtdemux_push_pending_newsegment (qtdemux);
-
   if (qtdemux->fragmented_seek_pending) {
     GST_INFO_OBJECT (qtdemux, "pending fragmented seek");
-    gst_qtdemux_do_fragmented_seek (qtdemux);
-    GST_INFO_OBJECT (qtdemux, "fragmented seek done!");
-    qtdemux->fragmented_seek_pending = FALSE;
+    if (gst_qtdemux_do_fragmented_seek (qtdemux)) {
+      GST_INFO_OBJECT (qtdemux, "fragmented seek done!");
+      qtdemux->fragmented_seek_pending = FALSE;
+    } else {
+      GST_INFO_OBJECT (qtdemux, "fragmented seek still pending");
+    }
   }
 
   /* Figure out the next stream sample to output, min_time is expressed in
    * global time and runs over the edit list segments. */
   min_time = G_MAXUINT64;
-  index = -1;
-  for (i = 0; i < qtdemux->n_streams; i++) {
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
     GstClockTime position;
 
-    stream = qtdemux->streams[i];
+    stream = QTDEMUX_NTH_STREAM (qtdemux, i);
     position = stream->time_position;
 
     /* position of -1 is EOS */
     if (position != GST_CLOCK_TIME_NONE && position < min_time) {
       min_time = position;
-      index = i;
+      target_stream = stream;
     }
   }
   /* all are EOS */
-  if (G_UNLIKELY (index == -1)) {
+  if (G_UNLIKELY (target_stream == NULL)) {
     GST_DEBUG_OBJECT (qtdemux, "all streams are EOS");
     goto eos;
   }
@@ -6304,15 +6881,15 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux)
           && ((qtdemux->segment.rate >= 0 && qtdemux->segment.stop <= min_time)
               || (qtdemux->segment.rate < 0
                   && qtdemux->segment.start > min_time))
-          && qtdemux->streams[index]->on_keyframe)) {
+          && target_stream->on_keyframe)) {
     GST_DEBUG_OBJECT (qtdemux, "we reached the end of our segment.");
-    qtdemux->streams[index]->time_position = GST_CLOCK_TIME_NONE;
+    target_stream->time_position = GST_CLOCK_TIME_NONE;
     goto eos_stream;
   }
 
   /* gap events for subtitle streams */
-  for (i = 0; i < qtdemux->n_streams; i++) {
-    stream = qtdemux->streams[i];
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+    stream = QTDEMUX_NTH_STREAM (qtdemux, i);
     if (stream->pad && (stream->subtype == FOURCC_subp
             || stream->subtype == FOURCC_text
             || stream->subtype == FOURCC_sbtl)) {
@@ -6329,7 +6906,7 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux)
     }
   }
 
-  stream = qtdemux->streams[index];
+  stream = target_stream;
   /* fetch info for the current sample of this stream */
   if (G_UNLIKELY (!gst_qtdemux_prepare_current_sample (qtdemux, stream, &empty,
               &offset, &sample_size, &dts, &pts, &duration, &keyframe)))
@@ -6338,27 +6915,31 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux)
   gst_qtdemux_stream_check_and_change_stsd_index (qtdemux, stream);
   if (stream->new_caps) {
     gst_qtdemux_configure_stream (qtdemux, stream);
-    qtdemux_do_allocation (qtdemux, stream);
+    qtdemux_do_allocation (stream, qtdemux);
   }
 
   /* If we're doing a keyframe-only trickmode, only push keyframes on video streams */
-  if (G_UNLIKELY (qtdemux->
-          segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS)) {
+  if (G_UNLIKELY (qtdemux->segment.
+          flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS)) {
     if (stream->subtype == FOURCC_vide && !keyframe) {
-      GST_LOG_OBJECT (qtdemux, "Skipping non-keyframe on stream %d", index);
+      GST_LOG_OBJECT (qtdemux, "Skipping non-keyframe on track-id %u",
+          stream->track_id);
       goto next;
     }
   }
 
   GST_DEBUG_OBJECT (qtdemux,
-      "pushing from stream %d, empty %d offset %" G_GUINT64_FORMAT
+      "pushing from track-id %u, empty %d offset %" G_GUINT64_FORMAT
       ", size %d, dts=%" GST_TIME_FORMAT ", pts=%" GST_TIME_FORMAT
-      ", duration %" GST_TIME_FORMAT, index, empty, offset, sample_size,
-      GST_TIME_ARGS (dts), GST_TIME_ARGS (pts), GST_TIME_ARGS (duration));
+      ", duration %" GST_TIME_FORMAT, stream->track_id, empty, offset,
+      sample_size, GST_TIME_ARGS (dts), GST_TIME_ARGS (pts),
+      GST_TIME_ARGS (duration));
 
   if (G_UNLIKELY (empty)) {
-    /* empty segment, push a gap and move to the next one */
-    gst_pad_push_event (stream->pad, gst_event_new_gap (pts, duration));
+    /* empty segment, push a gap if there's a second or more
+     * difference and move to the next one */
+    if ((pts + duration - stream->segment.position) >= GST_SECOND)
+      gst_pad_push_event (stream->pad, gst_event_new_gap (pts, duration));
     stream->segment.position = pts + duration;
     goto next;
   }
@@ -6548,7 +7129,7 @@ pause:
     /* fatal errors need special actions */
     /* check EOS */
     if (ret == GST_FLOW_EOS) {
-      if (qtdemux->n_streams == 0) {
+      if (QTDEMUX_N_STREAMS (qtdemux) == 0) {
         /* we have no streams, post an error */
         gst_qtdemux_post_no_playable_stream_error (qtdemux);
       }
@@ -6566,7 +7147,7 @@ pause:
           message = gst_message_new_segment_done (GST_OBJECT_CAST (qtdemux),
               GST_FORMAT_TIME, stop);
           event = gst_event_new_segment_done (GST_FORMAT_TIME, stop);
-          if (qtdemux->segment_seqnum) {
+          if (qtdemux->segment_seqnum != GST_SEQNUM_INVALID) {
             gst_message_set_seqnum (message, qtdemux->segment_seqnum);
             gst_event_set_seqnum (event, qtdemux->segment_seqnum);
           }
@@ -6582,7 +7163,7 @@ pause:
               GST_FORMAT_TIME, qtdemux->segment.start);
           event = gst_event_new_segment_done (GST_FORMAT_TIME,
               qtdemux->segment.start);
-          if (qtdemux->segment_seqnum) {
+          if (qtdemux->segment_seqnum != GST_SEQNUM_INVALID) {
             gst_message_set_seqnum (message, qtdemux->segment_seqnum);
             gst_event_set_seqnum (event, qtdemux->segment_seqnum);
           }
@@ -6594,7 +7175,7 @@ pause:
 
         GST_LOG_OBJECT (qtdemux, "Sending EOS at end of segment");
         event = gst_event_new_eos ();
-        if (qtdemux->segment_seqnum)
+        if (qtdemux->segment_seqnum != GST_SEQNUM_INVALID)
           gst_event_set_seqnum (event, qtdemux->segment_seqnum);
         gst_qtdemux_push_event (qtdemux, event);
       }
@@ -6615,12 +7196,12 @@ static gboolean
 has_next_entry (GstQTDemux * demux)
 {
   QtDemuxStream *stream;
-  int i;
+  gint i;
 
   GST_DEBUG_OBJECT (demux, "Checking if there are samples not played yet");
 
-  for (i = 0; i < demux->n_streams; i++) {
-    stream = demux->streams[i];
+  for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
+    stream = QTDEMUX_NTH_STREAM (demux, i);
 
     if (stream->sample_index == -1) {
       stream->sample_index = 0;
@@ -6628,7 +7209,7 @@ has_next_entry (GstQTDemux * demux)
     }
 
     if (stream->sample_index >= stream->n_samples) {
-      GST_LOG_OBJECT (demux, "stream %d samples exhausted", i);
+      GST_LOG_OBJECT (demux, "track-id %u samples exhausted", stream->track_id);
       continue;
     }
     GST_DEBUG_OBJECT (demux, "Found a sample");
@@ -6648,17 +7229,16 @@ has_next_entry (GstQTDemux * demux)
 static guint64
 next_entry_size (GstQTDemux * demux)
 {
-  QtDemuxStream *stream;
-  int i;
-  int smallidx = -1;
+  QtDemuxStream *stream, *target_stream = NULL;
   guint64 smalloffs = (guint64) - 1;
   QtDemuxSample *sample;
+  gint i;
 
   GST_LOG_OBJECT (demux, "Finding entry at offset %" G_GUINT64_FORMAT,
       demux->offset);
 
-  for (i = 0; i < demux->n_streams; i++) {
-    stream = demux->streams[i];
+  for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
+    stream = QTDEMUX_NTH_STREAM (demux, i);
 
     if (stream->sample_index == -1) {
       stream->sample_index = 0;
@@ -6666,7 +7246,7 @@ next_entry_size (GstQTDemux * demux)
     }
 
     if (stream->sample_index >= stream->n_samples) {
-      GST_LOG_OBJECT (demux, "stream %d samples exhausted", i);
+      GST_LOG_OBJECT (demux, "track-id %u samples exhausted", stream->track_id);
       continue;
     }
 
@@ -6679,25 +7259,25 @@ next_entry_size (GstQTDemux * demux)
     sample = &stream->samples[stream->sample_index];
 
     GST_LOG_OBJECT (demux,
-        "Checking Stream %d (sample_index:%d / offset:%" G_GUINT64_FORMAT
-        " / size:%" G_GUINT32_FORMAT ")", i, stream->sample_index,
-        sample->offset, sample->size);
+        "Checking track-id %u (sample_index:%d / offset:%" G_GUINT64_FORMAT
+        " / size:%" G_GUINT32_FORMAT ")", stream->track_id,
+        stream->sample_index, sample->offset, sample->size);
 
     if (((smalloffs == -1)
             || (sample->offset < smalloffs)) && (sample->size)) {
-      smallidx = i;
       smalloffs = sample->offset;
+      target_stream = stream;
     }
   }
 
-  GST_LOG_OBJECT (demux,
-      "stream %d offset %" G_GUINT64_FORMAT " demux->offset :%"
-      G_GUINT64_FORMAT, smallidx, smalloffs, demux->offset);
-
-  if (smallidx == -1)
+  if (!target_stream)
     return -1;
 
-  stream = demux->streams[smallidx];
+  GST_LOG_OBJECT (demux,
+      "track-id %u offset %" G_GUINT64_FORMAT " demux->offset :%"
+      G_GUINT64_FORMAT, target_stream->track_id, smalloffs, demux->offset);
+
+  stream = target_stream;
   sample = &stream->samples[stream->sample_index];
 
   if (sample->offset >= demux->offset) {
@@ -6736,7 +7316,7 @@ qtdemux_seek_offset (GstQTDemux * demux, guint64 offset)
   /* store seqnum to drop flush events, they don't need to reach downstream */
   demux->offset_seek_seqnum = gst_event_get_seqnum (event);
   res = gst_pad_push_event (demux->sinkpad, event);
-  demux->offset_seek_seqnum = 0;
+  demux->offset_seek_seqnum = GST_SEQNUM_INVALID;
 
   return res;
 }
@@ -6797,17 +7377,28 @@ gst_qtdemux_drop_data (GstQTDemux * demux, gint bytes)
   demux->todrop -= bytes;
 }
 
+/* PUSH-MODE only: Send a segment, if not done already. */
 static void
 gst_qtdemux_check_send_pending_segment (GstQTDemux * demux)
 {
-  if (G_UNLIKELY (demux->pending_newsegment)) {
+  if (G_UNLIKELY (demux->need_segment)) {
     gint i;
 
-    gst_qtdemux_push_pending_newsegment (demux);
+    if (!demux->upstream_format_is_time) {
+      gst_qtdemux_map_and_push_segments (demux, &demux->segment);
+    } else {
+      GstEvent *segment_event;
+      segment_event = gst_event_new_segment (&demux->segment);
+      if (demux->segment_seqnum != GST_SEQNUM_INVALID)
+        gst_event_set_seqnum (segment_event, demux->segment_seqnum);
+      gst_qtdemux_push_event (demux, segment_event);
+    }
+
+    demux->need_segment = FALSE;
+
     /* clear to send tags on all streams */
-    for (i = 0; i < demux->n_streams; i++) {
-      QtDemuxStream *stream;
-      stream = demux->streams[i];
+    for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
+      QtDemuxStream *stream = QTDEMUX_NTH_STREAM (demux, i);
       gst_qtdemux_push_tags (demux, stream);
       if (CUR_STREAM (stream)->sparse) {
         GST_INFO_OBJECT (demux, "Sending gap event on stream %d", i);
@@ -6818,46 +7409,29 @@ gst_qtdemux_check_send_pending_segment (GstQTDemux * demux)
   }
 }
 
+/* Used for push mode only. */
 static void
 gst_qtdemux_send_gap_for_segment (GstQTDemux * demux,
     QtDemuxStream * stream, gint segment_index, GstClockTime pos)
 {
   GstClockTime ts, dur;
-  GstEvent *gap;
 
   ts = pos;
   dur =
       stream->segments[segment_index].duration - (pos -
       stream->segments[segment_index].time);
-  gap = gst_event_new_gap (ts, dur);
   stream->time_position += dur;
 
-  GST_DEBUG_OBJECT (stream->pad, "Pushing gap for empty "
-      "segment: %" GST_PTR_FORMAT, gap);
-  gst_pad_push_event (stream->pad, gap);
-}
-
-static void
-gst_qtdemux_stream_send_initial_gap_segments (GstQTDemux * demux,
-    QtDemuxStream * stream)
-{
-  gint i;
+  /* Only gaps with a duration of at least one second are propagated.
+   * Same workaround as in pull mode.
+   * (See 2e45926a96ec5298c6ef29bf912e5e6a06dc3e0e) */
+  if (dur >= GST_SECOND) {
+    GstEvent *gap;
+    gap = gst_event_new_gap (ts, dur);
 
-  /* Push any initial gap segments before proceeding to the
-   * 'real' data */
-  for (i = 0; i < stream->n_segments; i++) {
-    gst_qtdemux_activate_segment (demux, stream, i, stream->time_position);
-
-    if (QTSEGMENT_IS_EMPTY (&stream->segments[i])) {
-      gst_qtdemux_send_gap_for_segment (demux, stream, i,
-          stream->time_position);
-    } else {
-      /* Only support empty segment at the beginning followed by
-       * one non-empty segment, this was checked when parsing the
-       * edts atom, arriving here is unexpected */
-      g_assert (i + 1 == stream->n_segments);
-      break;
-    }
+    GST_DEBUG_OBJECT (stream->pad, "Pushing gap for empty "
+        "segment: %" GST_PTR_FORMAT, gap);
+    gst_pad_push_event (stream->pad, gap);
   }
 }
 
@@ -6881,8 +7455,8 @@ gst_qtdemux_chain (GstPad * sinkpad, GstObject * parent, GstBuffer * inbuf)
 
     GST_DEBUG_OBJECT (demux, "Got DISCONT, marking all streams as DISCONT");
 
-    for (i = 0; i < demux->n_streams; i++) {
-      demux->streams[i]->discont = TRUE;
+    for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
+      QTDEMUX_NTH_STREAM (demux, i)->discont = TRUE;
     }
 
     /* Check if we can land back on our feet in the case where upstream is
@@ -6890,21 +7464,21 @@ gst_qtdemux_chain (GstPad * sinkpad, GstObject * parent, GstBuffer * inbuf)
      * in the case of trick-mode DASH for example) */
     if (demux->upstream_format_is_time
         && GST_BUFFER_OFFSET (inbuf) != GST_BUFFER_OFFSET_NONE) {
-      gint i;
-      for (i = 0; i < demux->n_streams; i++) {
+      for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
         guint32 res;
+        QtDemuxStream *stream = QTDEMUX_NTH_STREAM (demux, i);
         GST_LOG_OBJECT (demux,
-            "Stream #%d , checking if offset %" G_GUINT64_FORMAT
-            " is a sample start", i, GST_BUFFER_OFFSET (inbuf));
+            "track-id #%u , checking if offset %" G_GUINT64_FORMAT
+            " is a sample start", stream->track_id, GST_BUFFER_OFFSET (inbuf));
         res =
             gst_qtdemux_find_index_for_given_media_offset_linear (demux,
-            demux->streams[i], GST_BUFFER_OFFSET (inbuf));
+            stream, GST_BUFFER_OFFSET (inbuf));
         if (res != -1) {
-          QtDemuxSample *sample = &demux->streams[i]->samples[res];
+          QtDemuxSample *sample = &stream->samples[res];
           GST_LOG_OBJECT (demux,
-              "Checking if sample %d from stream %d is valid (offset:%"
-              G_GUINT64_FORMAT " size:%" G_GUINT32_FORMAT ")", res, i,
-              sample->offset, sample->size);
+              "Checking if sample %d from track-id %u is valid (offset:%"
+              G_GUINT64_FORMAT " size:%" G_GUINT32_FORMAT ")", res,
+              stream->track_id, sample->offset, sample->size);
           if (sample->offset == GST_BUFFER_OFFSET (inbuf)) {
             GST_LOG_OBJECT (demux,
                 "new buffer corresponds to a valid sample : %" G_GUINT32_FORMAT,
@@ -6913,9 +7487,9 @@ gst_qtdemux_chain (GstPad * sinkpad, GstObject * parent, GstBuffer * inbuf)
             /* We can go back to standard playback mode */
             demux->state = QTDEMUX_STATE_MOVIE;
             /* Remember which sample this stream is at */
-            demux->streams[i]->sample_index = res;
+            stream->sample_index = res;
             /* Finally update all push-based values to the expected values */
-            demux->neededbytes = demux->streams[i]->samples[res].size;
+            demux->neededbytes = stream->samples[res].size;
             demux->offset = GST_BUFFER_OFFSET (inbuf);
             demux->mdatleft =
                 demux->mdatsize - demux->offset + demux->mdatoffset;
@@ -6940,8 +7514,8 @@ gst_qtdemux_chain (GstPad * sinkpad, GstObject * parent, GstBuffer * inbuf)
      * previously received one. */
     if (!is_gap_input && demux->fragmented && demux->segment.rate < 0) {
       gst_qtdemux_process_adapter (demux, TRUE);
-      for (i = 0; i < demux->n_streams; i++)
-        gst_qtdemux_stream_flush_samples_data (demux, demux->streams[i]);
+      g_ptr_array_foreach (demux->active_streams,
+          (GFunc) gst_qtdemux_stream_flush_samples_data, NULL);
     }
   }
 
@@ -7010,7 +7584,8 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
         }
         if (fourcc == FOURCC_mdat) {
           gint next_entry = next_entry_size (demux);
-          if (demux->n_streams > 0 && (next_entry != -1 || !demux->fragmented)) {
+          if (QTDEMUX_N_STREAMS (demux) > 0 && (next_entry != -1
+                  || !demux->fragmented)) {
             /* we have the headers, start playback */
             demux->state = QTDEMUX_STATE_MOVIE;
             demux->neededbytes = next_entry;
@@ -7098,11 +7673,9 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
         extract_initial_length_and_fourcc (data, demux->neededbytes, NULL,
             &fourcc);
         if (fourcc == FOURCC_moov) {
-          gint n;
-
           /* in usual fragmented setup we could try to scan for more
            * and end up at the the moov (after mdat) again */
-          if (demux->got_moov && demux->n_streams > 0 &&
+          if (demux->got_moov && QTDEMUX_N_STREAMS (demux) > 0 &&
               (!demux->fragmented
                   || demux->last_moov_offset == demux->offset)) {
             GST_DEBUG_OBJECT (demux,
@@ -7122,44 +7695,25 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
               if (demux->moov_node)
                 g_node_destroy (demux->moov_node);
               demux->moov_node = NULL;
-            } else {
-              /* prepare newsegment to send when streaming actually starts */
-              if (!demux->pending_newsegment) {
-                demux->pending_newsegment =
-                    gst_event_new_segment (&demux->segment);
-                if (demux->segment_seqnum)
-                  gst_event_set_seqnum (demux->pending_newsegment,
-                      demux->segment_seqnum);
-              }
             }
 
             demux->last_moov_offset = demux->offset;
 
+            /* Update streams with new moov */
+            gst_qtdemux_stream_concat (demux,
+                demux->old_streams, demux->active_streams);
+
             qtdemux_parse_moov (demux, data, demux->neededbytes);
             qtdemux_node_dump (demux, demux->moov_node);
             qtdemux_parse_tree (demux);
             qtdemux_prepare_streams (demux);
-            if (!demux->got_moov)
-              qtdemux_expose_streams (demux);
-            else {
-
-              for (n = 0; n < demux->n_streams; n++) {
-                QtDemuxStream *stream = demux->streams[n];
-
-                gst_qtdemux_configure_stream (demux, stream);
-              }
-            }
+            QTDEMUX_EXPOSE_LOCK (demux);
+            qtdemux_expose_streams (demux);
+            QTDEMUX_EXPOSE_UNLOCK (demux);
 
             demux->got_moov = TRUE;
-            gst_qtdemux_check_send_pending_segment (demux);
 
-            /* fragmented streams headers shouldn't contain edts atoms */
-            if (!demux->fragmented) {
-              for (n = 0; n < demux->n_streams; n++) {
-                gst_qtdemux_stream_send_initial_gap_segments (demux,
-                    demux->streams[n]);
-              }
-            }
+            gst_qtdemux_check_send_pending_segment (demux);
 
             if (demux->moov_node_compressed) {
               g_node_destroy (demux->moov_node_compressed);
@@ -7242,18 +7796,15 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
               ret = GST_FLOW_ERROR;
               goto done;
             }
+
             /* in MSS we need to expose the pads after the first moof as we won't get a moov */
             if (demux->mss_mode && !demux->exposed) {
-              if (!demux->pending_newsegment) {
-                GST_DEBUG_OBJECT (demux, "new pending_newsegment");
-                demux->pending_newsegment =
-                    gst_event_new_segment (&demux->segment);
-                if (demux->segment_seqnum)
-                  gst_event_set_seqnum (demux->pending_newsegment,
-                      demux->segment_seqnum);
-              }
+              QTDEMUX_EXPOSE_LOCK (demux);
               qtdemux_expose_streams (demux);
+              QTDEMUX_EXPOSE_UNLOCK (demux);
             }
+
+            gst_qtdemux_check_send_pending_segment (demux);
           } else {
             GST_DEBUG_OBJECT (demux, "Discarding [moof]");
           }
@@ -7271,8 +7822,9 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
             case FOURCC_styp:
               /* [styp] is like a [ftyp], but in fragment header. We ignore it for now
                * FALLTHROUGH */
+            case FOURCC_skip:
             case FOURCC_free:
-              /* [free] is a padding atom */
+              /* [free] and [skip] are padding atoms */
               GST_DEBUG_OBJECT (demux,
                   "Skipping fourcc while parsing header : %" GST_FOURCC_FORMAT,
                   GST_FOURCC_ARGS (fourcc));
@@ -7288,12 +7840,12 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
         gst_adapter_unmap (demux->adapter);
         data = NULL;
 
-        if (demux->mdatbuffer && demux->n_streams) {
+        if (demux->mdatbuffer && QTDEMUX_N_STREAMS (demux)) {
           gsize remaining_data_size = 0;
 
           /* the mdat was before the header */
           GST_DEBUG_OBJECT (demux, "We have n_streams:%d and mdatbuffer:%p",
-              demux->n_streams, demux->mdatbuffer);
+              QTDEMUX_N_STREAMS (demux), demux->mdatbuffer);
           /* restore our adapter/offset view of things with upstream;
            * put preceding buffered data ahead of current moov data.
            * This should also handle evil mdat, moov, mdat cases and alike */
@@ -7368,9 +7920,9 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
       case QTDEMUX_STATE_MOVIE:{
         QtDemuxStream *stream = NULL;
         QtDemuxSample *sample;
-        int i = -1;
         GstClockTime dts, pts, duration;
         gboolean keyframe;
+        gint i;
 
         GST_DEBUG_OBJECT (demux,
             "BEGIN // in MOVIE for offset %" G_GUINT64_FORMAT, demux->offset);
@@ -7420,7 +7972,8 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
             GST_DEBUG_OBJECT (demux, "parsing cenc auxiliary info");
             data = gst_adapter_map (demux->adapter, demux->todrop);
             gst_byte_reader_init (&br, data + 8, demux->todrop);
-            if (!qtdemux_parse_cenc_aux_info (demux, demux->streams[0], &br,
+            if (!qtdemux_parse_cenc_aux_info (demux,
+                    QTDEMUX_NTH_STREAM (demux, 0), &br,
                     demux->cenc_aux_info_sizes, demux->cenc_aux_sample_count)) {
               GST_ERROR_OBJECT (demux, "failed to parse cenc auxiliary info");
               ret = GST_FLOW_ERROR;
@@ -7443,13 +7996,17 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
         gst_qtdemux_check_send_pending_segment (demux);
 
         /* Figure out which stream this packet belongs to */
-        for (i = 0; i < demux->n_streams; i++) {
-          stream = demux->streams[i];
-          if (stream->sample_index >= stream->n_samples)
+        for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
+          stream = QTDEMUX_NTH_STREAM (demux, i);
+          if (stream->sample_index >= stream->n_samples) {
+            /* reset to be checked below G_UNLIKELY (stream == NULL) */
+            stream = NULL;
             continue;
+          }
           GST_LOG_OBJECT (demux,
-              "Checking stream %d (sample_index:%d / offset:%" G_GUINT64_FORMAT
-              " / size:%d)", i, stream->sample_index,
+              "Checking track-id %u (sample_index:%d / offset:%"
+              G_GUINT64_FORMAT " / size:%d)", stream->track_id,
+              stream->sample_index,
               stream->samples[stream->sample_index].offset,
               stream->samples[stream->sample_index].size);
 
@@ -7457,7 +8014,7 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
             break;
         }
 
-        if (G_UNLIKELY (stream == NULL || i == demux->n_streams))
+        if (G_UNLIKELY (stream == NULL))
           goto unknown_stream;
 
         gst_qtdemux_stream_check_and_change_stsd_index (demux, stream);
@@ -7480,7 +8037,8 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
 
           /* check for segment end */
           if (G_UNLIKELY (demux->segment.stop != -1
-                  && demux->segment.stop <= pts && stream->on_keyframe)) {
+                  && demux->segment.stop <= pts && stream->on_keyframe)
+              && !(demux->upstream_format_is_time && demux->segment.rate < 0)) {
             GST_DEBUG_OBJECT (demux, "we reached the end of our segment.");
             stream->time_position = GST_CLOCK_TIME_NONE;        /* this means EOS */
 
@@ -7490,8 +8048,8 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
 
             /* check if all streams are eos */
             ret = GST_FLOW_EOS;
-            for (i = 0; i < demux->n_streams; i++) {
-              if (!STREAM_IS_EOS (demux->streams[i])) {
+            for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
+              if (!STREAM_IS_EOS (QTDEMUX_NTH_STREAM (demux, i))) {
                 ret = GST_FLOW_OK;
                 break;
               }
@@ -8053,7 +8611,27 @@ qtdemux_parse_node (GstQTDemux * qtdemux, GNode * node, const guint8 * buffer,
       case FOURCC_meta:
       {
         GST_DEBUG_OBJECT (qtdemux, "parsing meta atom");
-        qtdemux_parse_container (qtdemux, node, buffer + 12, end);
+
+        /* You are reading this correctly. QTFF specifies that the
+         * metadata atom is a short atom, whereas ISO BMFF specifies
+         * it's a full atom. But since so many people are doing things
+         * differently, we actually peek into the atom to see which
+         * variant it is */
+        if (length < 16) {
+          GST_LOG_OBJECT (qtdemux, "skipping small %" GST_FOURCC_FORMAT " box",
+              GST_FOURCC_ARGS (fourcc));
+          break;
+        }
+        if (QT_FOURCC (buffer + 12) == FOURCC_hdlr) {
+          /* Variant 1: What QTFF specifies. 'meta' is a short header which
+           * starts with a 'hdlr' atom */
+          qtdemux_parse_container (qtdemux, node, buffer + 8, end);
+        } else if (QT_UINT32 (buffer + 8) == 0x00000000) {
+          /* Variant 2: What ISO BMFF specifies. 'meta' is a _full_ atom
+           * with version/flags both set to zero */
+          qtdemux_parse_container (qtdemux, node, buffer + 12, end);
+        } else
+          GST_WARNING_OBJECT (qtdemux, "Unknown 'meta' atom format");
         break;
       }
       case FOURCC_mp4s:
@@ -8235,7 +8813,7 @@ qtdemux_tree_get_sibling_by_type (GNode * node, guint32 fourcc)
 }
 
 static void
-qtdemux_do_allocation (GstQTDemux * qtdemux, QtDemuxStream * stream)
+qtdemux_do_allocation (QtDemuxStream * stream, GstQTDemux * qtdemux)
 {
 /* FIXME: This can only reliably work if demuxers have a
  * separate streaming thread per srcpad. This should be
@@ -8272,11 +8850,147 @@ qtdemux_do_allocation (GstQTDemux * qtdemux, QtDemuxStream * stream)
 }
 
 static gboolean
+pad_query (const GValue * item, GValue * value, gpointer user_data)
+{
+  GstPad *pad = g_value_get_object (item);
+  GstQuery *query = user_data;
+  gboolean res;
+
+  res = gst_pad_peer_query (pad, query);
+
+  if (res) {
+    g_value_set_boolean (value, TRUE);
+    return FALSE;
+  }
+
+  GST_INFO_OBJECT (pad, "pad peer query failed");
+  return TRUE;
+}
+
+static gboolean
+gst_qtdemux_run_query (GstElement * element, GstQuery * query,
+    GstPadDirection direction)
+{
+  GstIterator *it;
+  GstIteratorFoldFunction func = pad_query;
+  GValue res = { 0, };
+
+  g_value_init (&res, G_TYPE_BOOLEAN);
+  g_value_set_boolean (&res, FALSE);
+
+  /* Ask neighbor */
+  if (direction == GST_PAD_SRC)
+    it = gst_element_iterate_src_pads (element);
+  else
+    it = gst_element_iterate_sink_pads (element);
+
+  while (gst_iterator_fold (it, func, &res, query) == GST_ITERATOR_RESYNC)
+    gst_iterator_resync (it);
+
+  gst_iterator_free (it);
+
+  return g_value_get_boolean (&res);
+}
+
+static void
+gst_qtdemux_request_protection_context (GstQTDemux * qtdemux,
+    QtDemuxStream * stream)
+{
+  GstQuery *query;
+  GstContext *ctxt;
+  GstElement *element = GST_ELEMENT (qtdemux);
+  GstStructure *st;
+  gchar **filtered_sys_ids;
+  GValue event_list = G_VALUE_INIT;
+  GList *walk;
+
+  /* 1. Check if we already have the context. */
+  if (qtdemux->preferred_protection_system_id != NULL) {
+    GST_LOG_OBJECT (element,
+        "already have the protection context, no need to request it again");
+    return;
+  }
+
+  g_ptr_array_add (qtdemux->protection_system_ids, NULL);
+  filtered_sys_ids = gst_protection_filter_systems_by_available_decryptors (
+      (const gchar **) qtdemux->protection_system_ids->pdata);
+
+  g_ptr_array_remove_index (qtdemux->protection_system_ids,
+      qtdemux->protection_system_ids->len - 1);
+  GST_TRACE_OBJECT (qtdemux, "detected %u protection systems, we have "
+      "decryptors for %u of them, running context request",
+      qtdemux->protection_system_ids->len,
+      filtered_sys_ids ? g_strv_length (filtered_sys_ids) : 0);
+
+
+  if (stream->protection_scheme_event_queue.length) {
+    GST_TRACE_OBJECT (qtdemux, "using stream event queue, length %u",
+        stream->protection_scheme_event_queue.length);
+    walk = stream->protection_scheme_event_queue.tail;
+  } else {
+    GST_TRACE_OBJECT (qtdemux, "using demuxer event queue, length %u",
+        qtdemux->protection_event_queue.length);
+    walk = qtdemux->protection_event_queue.tail;
+  }
+
+  g_value_init (&event_list, GST_TYPE_LIST);
+  for (; walk; walk = g_list_previous (walk)) {
+    GValue *event_value = g_new0 (GValue, 1);
+    g_value_init (event_value, GST_TYPE_EVENT);
+    g_value_set_boxed (event_value, walk->data);
+    gst_value_list_append_and_take_value (&event_list, event_value);
+  }
+
+  /*  2a) Query downstream with GST_QUERY_CONTEXT for the context and
+   *      check if downstream already has a context of the specific type
+   *  2b) Query upstream as above.
+   */
+  query = gst_query_new_context ("drm-preferred-decryption-system-id");
+  st = gst_query_writable_structure (query);
+  gst_structure_set (st, "track-id", G_TYPE_UINT, stream->track_id,
+      "available-stream-encryption-systems", G_TYPE_STRV, filtered_sys_ids,
+      NULL);
+  gst_structure_set_value (st, "stream-encryption-events", &event_list);
+  if (gst_qtdemux_run_query (element, query, GST_PAD_SRC)) {
+    gst_query_parse_context (query, &ctxt);
+    GST_INFO_OBJECT (element, "found context (%p) in downstream query", ctxt);
+    gst_element_set_context (element, ctxt);
+  } else if (gst_qtdemux_run_query (element, query, GST_PAD_SINK)) {
+    gst_query_parse_context (query, &ctxt);
+    GST_INFO_OBJECT (element, "found context (%p) in upstream query", ctxt);
+    gst_element_set_context (element, ctxt);
+  } else {
+    /* 3) Post a GST_MESSAGE_NEED_CONTEXT message on the bus with
+     *    the required context type and afterwards check if a
+     *    usable context was set now as in 1). The message could
+     *    be handled by the parent bins of the element and the
+     *    application.
+     */
+    GstMessage *msg;
+
+    GST_INFO_OBJECT (element, "posting need context message");
+    msg = gst_message_new_need_context (GST_OBJECT_CAST (element),
+        "drm-preferred-decryption-system-id");
+    st = (GstStructure *) gst_message_get_structure (msg);
+    gst_structure_set (st, "track-id", G_TYPE_UINT, stream->track_id,
+        "available-stream-encryption-systems", G_TYPE_STRV, filtered_sys_ids,
+        NULL);
+
+    gst_structure_set_value (st, "stream-encryption-events", &event_list);
+    gst_element_post_message (element, msg);
+  }
+
+  g_strfreev (filtered_sys_ids);
+  g_value_unset (&event_list);
+  gst_query_unref (query);
+}
+
+static gboolean
 gst_qtdemux_configure_protected_caps (GstQTDemux * qtdemux,
     QtDemuxStream * stream)
 {
   GstStructure *s;
-  const gchar *selected_system;
+  const gchar *selected_system = NULL;
 
   g_return_val_if_fail (qtdemux != NULL, FALSE);
   g_return_val_if_fail (stream != NULL, FALSE);
@@ -8284,7 +8998,9 @@ gst_qtdemux_configure_protected_caps (GstQTDemux * qtdemux,
       FALSE);
 
   if (stream->protection_scheme_type != FOURCC_cenc) {
-    GST_ERROR_OBJECT (qtdemux, "unsupported protection scheme");
+    GST_ERROR_OBJECT (qtdemux,
+        "unsupported protection scheme: %" GST_FOURCC_FORMAT,
+        GST_FOURCC_ARGS (stream->protection_scheme_type));
     return FALSE;
   }
   if (qtdemux->protection_system_ids == NULL) {
@@ -8292,17 +9008,41 @@ gst_qtdemux_configure_protected_caps (GstQTDemux * qtdemux,
         "cenc protection system information has been found");
     return FALSE;
   }
-  g_ptr_array_add (qtdemux->protection_system_ids, NULL);
-  selected_system = gst_protection_select_system ((const gchar **)
-      qtdemux->protection_system_ids->pdata);
-  g_ptr_array_remove_index (qtdemux->protection_system_ids,
-      qtdemux->protection_system_ids->len - 1);
+
+  gst_qtdemux_request_protection_context (qtdemux, stream);
+  if (qtdemux->preferred_protection_system_id != NULL) {
+    const gchar *preferred_system_array[] =
+        { qtdemux->preferred_protection_system_id, NULL };
+
+    selected_system = gst_protection_select_system (preferred_system_array);
+
+    if (selected_system) {
+      GST_TRACE_OBJECT (qtdemux, "selected preferred system %s",
+          qtdemux->preferred_protection_system_id);
+    } else {
+      GST_WARNING_OBJECT (qtdemux, "could not select preferred system %s "
+          "because there is no available decryptor",
+          qtdemux->preferred_protection_system_id);
+    }
+  }
+
+  if (!selected_system) {
+    g_ptr_array_add (qtdemux->protection_system_ids, NULL);
+    selected_system = gst_protection_select_system ((const gchar **)
+        qtdemux->protection_system_ids->pdata);
+    g_ptr_array_remove_index (qtdemux->protection_system_ids,
+        qtdemux->protection_system_ids->len - 1);
+  }
+
   if (!selected_system) {
     GST_ERROR_OBJECT (qtdemux, "stream is protected, but no "
         "suitable decryptor element has been found");
     return FALSE;
   }
 
+  GST_DEBUG_OBJECT (qtdemux, "selected protection system is %s",
+      selected_system);
+
   s = gst_caps_get_structure (CUR_STREAM (stream)->caps, 0);
   if (!gst_structure_has_name (s, "application/x-cenc")) {
     gst_structure_set (s,
@@ -8315,69 +9055,84 @@ gst_qtdemux_configure_protected_caps (GstQTDemux * qtdemux,
 }
 
 static gboolean
-gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream)
+gst_qtdemux_guess_framerate (GstQTDemux * qtdemux, QtDemuxStream * stream)
 {
-  if (stream->subtype == FOURCC_vide) {
-    /* fps is calculated base on the duration of the average framerate since
-     * qt does not have a fixed framerate. */
-    gboolean fps_available = TRUE;
+  /* fps is calculated base on the duration of the average framerate since
+   * qt does not have a fixed framerate. */
+  gboolean fps_available = TRUE;
+  guint32 first_duration = 0;
 
-    if ((stream->n_samples == 1) && (stream->first_duration == 0)) {
-      /* still frame */
-      CUR_STREAM (stream)->fps_n = 0;
+  if (stream->n_samples > 0)
+    first_duration = stream->samples[0].duration;
+
+  if ((stream->n_samples == 1 && first_duration == 0)
+      || (qtdemux->fragmented && stream->n_samples_moof == 1)) {
+    /* still frame */
+    CUR_STREAM (stream)->fps_n = 0;
+    CUR_STREAM (stream)->fps_d = 1;
+  } else {
+    if (stream->duration == 0 || stream->n_samples < 2) {
+      CUR_STREAM (stream)->fps_n = stream->timescale;
       CUR_STREAM (stream)->fps_d = 1;
+      fps_available = FALSE;
     } else {
-      if (stream->duration == 0 || stream->n_samples < 2) {
-        CUR_STREAM (stream)->fps_n = stream->timescale;
-        CUR_STREAM (stream)->fps_d = 1;
-        fps_available = FALSE;
+      GstClockTime avg_duration;
+      guint64 duration;
+      guint32 n_samples;
+
+      /* duration and n_samples can be updated for fragmented format
+       * so, framerate of fragmented format is calculated using data in a moof */
+      if (qtdemux->fragmented && stream->n_samples_moof > 0
+          && stream->duration_moof > 0) {
+        n_samples = stream->n_samples_moof;
+        duration = stream->duration_moof;
       } else {
-        GstClockTime avg_duration;
-        guint64 duration;
-        guint32 n_samples;
-
-        /* duration and n_samples can be updated for fragmented format
-         * so, framerate of fragmented format is calculated using data in a moof */
-        if (qtdemux->fragmented && stream->n_samples_moof > 0
-            && stream->duration_moof > 0) {
-          n_samples = stream->n_samples_moof;
-          duration = stream->duration_moof;
-        } else {
-          n_samples = stream->n_samples;
-          duration = stream->duration;
-        }
+        n_samples = stream->n_samples;
+        duration = stream->duration;
+      }
 
-        /* Calculate a framerate, ignoring the first sample which is sometimes truncated */
-        /* stream->duration is guint64, timescale, n_samples are guint32 */
-        avg_duration =
-            gst_util_uint64_scale_round (duration -
-            stream->first_duration, GST_SECOND,
-            (guint64) (stream->timescale) * (n_samples - 1));
+      /* Calculate a framerate, ignoring the first sample which is sometimes truncated */
+      /* stream->duration is guint64, timescale, n_samples are guint32 */
+      avg_duration =
+          gst_util_uint64_scale_round (duration -
+          first_duration, GST_SECOND,
+          (guint64) (stream->timescale) * (n_samples - 1));
 
-        GST_LOG_OBJECT (qtdemux,
-            "Calculating avg sample duration based on stream (or moof) duration %"
-            G_GUINT64_FORMAT
-            " minus first sample %u, leaving %d samples gives %"
-            GST_TIME_FORMAT, duration, stream->first_duration,
-            n_samples - 1, GST_TIME_ARGS (avg_duration));
+      GST_LOG_OBJECT (qtdemux,
+          "Calculating avg sample duration based on stream (or moof) duration %"
+          G_GUINT64_FORMAT
+          " minus first sample %u, leaving %d samples gives %"
+          GST_TIME_FORMAT, duration, first_duration,
+          n_samples - 1, GST_TIME_ARGS (avg_duration));
 
-        gst_video_guess_framerate (avg_duration, &CUR_STREAM (stream)->fps_n,
-            &CUR_STREAM (stream)->fps_d);
+      fps_available =
+          gst_video_guess_framerate (avg_duration,
+          &CUR_STREAM (stream)->fps_n, &CUR_STREAM (stream)->fps_d);
 
-        GST_DEBUG_OBJECT (qtdemux,
-            "Calculating framerate, timescale %u gave fps_n %d fps_d %d",
-            stream->timescale, CUR_STREAM (stream)->fps_n,
-            CUR_STREAM (stream)->fps_d);
-      }
+      GST_DEBUG_OBJECT (qtdemux,
+          "Calculating framerate, timescale %u gave fps_n %d fps_d %d",
+          stream->timescale, CUR_STREAM (stream)->fps_n,
+          CUR_STREAM (stream)->fps_d);
     }
+  }
+
+  return fps_available;
+}
+
+static gboolean
+gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream)
+{
+  if (stream->subtype == FOURCC_vide) {
+    gboolean fps_available = gst_qtdemux_guess_framerate (qtdemux, stream);
 
     if (CUR_STREAM (stream)->caps) {
       CUR_STREAM (stream)->caps =
           gst_caps_make_writable (CUR_STREAM (stream)->caps);
 
-      gst_caps_set_simple (CUR_STREAM (stream)->caps,
-          "width", G_TYPE_INT, CUR_STREAM (stream)->width,
-          "height", G_TYPE_INT, CUR_STREAM (stream)->height, NULL);
+      if (CUR_STREAM (stream)->width && CUR_STREAM (stream)->height)
+        gst_caps_set_simple (CUR_STREAM (stream)->caps,
+            "width", G_TYPE_INT, CUR_STREAM (stream)->width,
+            "height", G_TYPE_INT, CUR_STREAM (stream)->height, NULL);
 
       /* set framerate if calculated framerate is reliable */
       if (fps_available) {
@@ -8488,6 +9243,56 @@ gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream)
     }
   }
 
+  else if (stream->subtype == FOURCC_clcp && CUR_STREAM (stream)->caps) {
+    const GstStructure *s;
+    QtDemuxStream *fps_stream = NULL;
+    gboolean fps_available = FALSE;
+
+    /* CEA608 closed caption tracks are a bit special in that each sample
+     * can contain CCs for multiple frames, and CCs can be omitted and have to
+     * be inferred from the duration of the sample then.
+     *
+     * As such we take the framerate from the (first) video track here for
+     * CEA608 as there must be one CC byte pair for every video frame
+     * according to the spec.
+     *
+     * For CEA708 all is fine and there is one sample per frame.
+     */
+
+    s = gst_caps_get_structure (CUR_STREAM (stream)->caps, 0);
+    if (gst_structure_has_name (s, "closedcaption/x-cea-608")) {
+      gint i;
+
+      for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+        QtDemuxStream *tmp = QTDEMUX_NTH_STREAM (qtdemux, i);
+
+        if (tmp->subtype == FOURCC_vide) {
+          fps_stream = tmp;
+          break;
+        }
+      }
+
+      if (fps_stream) {
+        fps_available = gst_qtdemux_guess_framerate (qtdemux, fps_stream);
+        CUR_STREAM (stream)->fps_n = CUR_STREAM (fps_stream)->fps_n;
+        CUR_STREAM (stream)->fps_d = CUR_STREAM (fps_stream)->fps_d;
+      }
+    } else {
+      fps_available = gst_qtdemux_guess_framerate (qtdemux, stream);
+      fps_stream = stream;
+    }
+
+    CUR_STREAM (stream)->caps =
+        gst_caps_make_writable (CUR_STREAM (stream)->caps);
+
+    /* set framerate if calculated framerate is reliable */
+    if (fps_available) {
+      gst_caps_set_simple (CUR_STREAM (stream)->caps,
+          "framerate", GST_TYPE_FRACTION, CUR_STREAM (stream)->fps_n,
+          CUR_STREAM (stream)->fps_d, NULL);
+    }
+  }
+
   if (stream->pad) {
     GstCaps *prev_caps = NULL;
 
@@ -8509,7 +9314,6 @@ gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream)
     GST_DEBUG_OBJECT (qtdemux, "setting caps %" GST_PTR_FORMAT,
         CUR_STREAM (stream)->caps);
     if (stream->new_stream) {
-      gchar *stream_id;
       GstEvent *event;
       GstStreamFlags stream_flags = GST_STREAM_FLAG_NONE;
 
@@ -8529,10 +9333,7 @@ gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream)
       }
 
       stream->new_stream = FALSE;
-      stream_id =
-          gst_pad_create_stream_id_printf (stream->pad,
-          GST_ELEMENT_CAST (qtdemux), "%03u", stream->track_id);
-      event = gst_event_new_stream_start (stream_id);
+      event = gst_event_new_stream_start (stream->stream_id);
       if (qtdemux->have_group_id)
         gst_event_set_group_id (event, qtdemux->group_id);
       if (stream->disabled)
@@ -8544,7 +9345,6 @@ gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream)
       }
       gst_event_set_stream_flags (event, stream_flags);
       gst_pad_push_event (stream->pad, event);
-      g_free (stream_id);
     }
 
     prev_caps = gst_pad_get_current_caps (stream->pad);
@@ -8595,8 +9395,6 @@ gst_qtdemux_add_stream (GstQTDemux * qtdemux,
     QtDemuxStream * stream, GstTagList * list)
 {
   gboolean ret = TRUE;
-  /* consistent default for push based mode */
-  gst_segment_init (&stream->segment, GST_FORMAT_TIME);
 
   if (stream->subtype == FOURCC_vide) {
     gchar *name = g_strdup_printf ("video_%u", qtdemux->n_video_streams);
@@ -8629,7 +9427,8 @@ gst_qtdemux_add_stream (GstQTDemux * qtdemux,
   } else if (stream->subtype == FOURCC_strm) {
     GST_DEBUG_OBJECT (qtdemux, "stream type, not creating pad");
   } else if (stream->subtype == FOURCC_subp || stream->subtype == FOURCC_text
-      || stream->subtype == FOURCC_sbtl || stream->subtype == FOURCC_subt) {
+      || stream->subtype == FOURCC_sbtl || stream->subtype == FOURCC_subt
+      || stream->subtype == FOURCC_clcp) {
     gchar *name = g_strdup_printf ("subtitle_%u", qtdemux->n_sub_streams);
 
     stream->pad =
@@ -9029,6 +9828,18 @@ qtdemux_stbl_init (GstQTDemux * qtdemux, QtDemuxStream * stream, GNode * stbl)
 
         gst_byte_reader_skip_unchecked (&stream->ctts, 4);
         offset = gst_byte_reader_get_int32_be_unchecked (&stream->ctts);
+        /* HACK: if sample_offset is larger than 2 * duration, ignore the box.
+         * slightly inaccurate PTS could be more usable than corrupted one */
+        if (G_UNLIKELY ((ABS (offset) / 2) > stream->duration)) {
+          GST_WARNING_OBJECT (qtdemux,
+              "Ignore corrupted ctts, sample_offset %" G_GINT32_FORMAT
+              " larger than duration %" G_GUINT64_FORMAT,
+              offset, stream->duration);
+
+          stream->cslg_shift = 0;
+          stream->ctts_present = FALSE;
+          return TRUE;
+        }
 
         if (offset < cslg_least)
           cslg_least = offset;
@@ -9521,7 +10332,7 @@ qtdemux_parse_segments (GstQTDemux * qtdemux, QtDemuxStream * stream,
   if ((edts = qtdemux_tree_get_child_by_type (trak, FOURCC_edts))) {
     GNode *elst;
     gint n_segments;
-    gint i, count, entry_size;
+    gint segment_number, entry_size;
     guint64 time;
     GstClockTime stime;
     const guint8 *buffer;
@@ -9556,12 +10367,11 @@ qtdemux_parse_segments (GstQTDemux * qtdemux, QtDemuxStream * stream,
     /* segments always start from 0 */
     time = 0;
     stime = 0;
-    count = 0;
     buffer += 16;
-    for (i = 0; i < n_segments; i++) {
+    for (segment_number = 0; segment_number < n_segments; segment_number++) {
       guint64 duration;
       guint64 media_time;
-      gboolean time_valid = TRUE;
+      gboolean empty_edit = FALSE;
       QtDemuxSegment *segment;
       guint32 rate_int;
       GstClockTime media_start = GST_CLOCK_TIME_NONE;
@@ -9570,44 +10380,44 @@ qtdemux_parse_segments (GstQTDemux * qtdemux, QtDemuxStream * stream,
         media_time = QT_UINT64 (buffer + 8);
         duration = QT_UINT64 (buffer);
         if (media_time == G_MAXUINT64)
-          time_valid = FALSE;
+          empty_edit = TRUE;
       } else {
         media_time = QT_UINT32 (buffer + 4);
         duration = QT_UINT32 (buffer);
         if (media_time == G_MAXUINT32)
-          time_valid = FALSE;
+          empty_edit = TRUE;
       }
 
-      if (time_valid)
+      if (!empty_edit)
         media_start = QTSTREAMTIME_TO_GSTTIME (stream, media_time);
 
-      segment = &stream->segments[count++];
+      segment = &stream->segments[segment_number];
 
       /* time and duration expressed in global timescale */
       segment->time = stime;
-      /* add non scaled values so we don't cause roundoff errors */
-      if (duration || media_start == GST_CLOCK_TIME_NONE) {
+      if (duration != 0 || empty_edit) {
+        /* edge case: empty edits with duration=zero are treated here.
+         * (files should not have these anyway). */
+
+        /* add non scaled values so we don't cause roundoff errors */
         time += duration;
         stime = QTTIME_TO_GSTTIME (qtdemux, time);
         segment->duration = stime - segment->time;
       } else {
         /* zero duration does not imply media_start == media_stop
-         * but, only specify media_start.*/
-        stime = QTTIME_TO_GSTTIME (qtdemux, qtdemux->duration);
-        if (GST_CLOCK_TIME_IS_VALID (stime) && time_valid
-            && stime >= media_start) {
-          segment->duration = stime - media_start;
-        } else {
-          segment->duration = GST_CLOCK_TIME_NONE;
-        }
+         * but, only specify media_start. The edit ends with the track. */
+        stime = segment->duration = GST_CLOCK_TIME_NONE;
+        /* Don't allow more edits after this one. */
+        n_segments = segment_number + 1;
       }
       segment->stop_time = stime;
 
       segment->trak_media_start = media_time;
       /* media_time expressed in stream timescale */
-      if (time_valid) {
+      if (!empty_edit) {
         segment->media_start = media_start;
-        segment->media_stop = segment->media_start + segment->duration;
+        segment->media_stop = GST_CLOCK_TIME_IS_VALID (segment->duration)
+            ? segment->media_start + segment->duration : GST_CLOCK_TIME_NONE;
         media_segments_count++;
       } else {
         segment->media_start = GST_CLOCK_TIME_NONE;
@@ -9629,25 +10439,27 @@ qtdemux_parse_segments (GstQTDemux * qtdemux, QtDemuxStream * stream,
           ", duration %" GST_TIME_FORMAT ", media_start %" GST_TIME_FORMAT
           " (%" G_GUINT64_FORMAT ") , media_stop %" GST_TIME_FORMAT
           " stop_time %" GST_TIME_FORMAT " rate %g, (%d) timescale %u",
-          i, GST_TIME_ARGS (segment->time),
+          segment_number, GST_TIME_ARGS (segment->time),
           GST_TIME_ARGS (segment->duration),
           GST_TIME_ARGS (segment->media_start), media_time,
           GST_TIME_ARGS (segment->media_stop),
           GST_TIME_ARGS (segment->stop_time), segment->rate, rate_int,
           stream->timescale);
-      if (segment->stop_time > qtdemux->segment.stop) {
+      if (segment->stop_time > qtdemux->segment.stop &&
+          !qtdemux->upstream_format_is_time) {
         GST_WARNING_OBJECT (qtdemux, "Segment %d "
             " extends to %" GST_TIME_FORMAT
-            " past the end of the file duration %" GST_TIME_FORMAT
-            " it will be truncated", i, GST_TIME_ARGS (segment->stop_time),
+            " past the end of the declared movie duration %" GST_TIME_FORMAT
+            " movie segment will be extended", segment_number,
+            GST_TIME_ARGS (segment->stop_time),
             GST_TIME_ARGS (qtdemux->segment.stop));
-        qtdemux->segment.stop = segment->stop_time;
+        qtdemux->segment.stop = qtdemux->segment.duration = segment->stop_time;
       }
 
       buffer += entry_size;
     }
-    GST_DEBUG_OBJECT (qtdemux, "found %d segments", count);
-    stream->n_segments = count;
+    GST_DEBUG_OBJECT (qtdemux, "found %d segments", n_segments);
+    stream->n_segments = n_segments;
     if (media_segments_count != 1)
       allow_pushbased_edts = FALSE;
   }
@@ -10040,6 +10852,9 @@ qtdemux_parse_protection_scheme_info (GstQTDemux * qtdemux,
   GNode *frma;
   GNode *schm;
   GNode *schi;
+  QtDemuxCencSampleSetInfo *info;
+  GNode *tenc;
+  const guint8 *tenc_data;
 
   g_return_val_if_fail (qtdemux != NULL, FALSE);
   g_return_val_if_fail (stream != NULL, FALSE);
@@ -10086,20 +10901,24 @@ qtdemux_parse_protection_scheme_info (GstQTDemux * qtdemux,
     GST_DEBUG_OBJECT (qtdemux, "sinf box does not contain schi box");
     return FALSE;
   }
+  if (stream->protection_scheme_type != FOURCC_cenc &&
+      stream->protection_scheme_type != FOURCC_piff) {
+    GST_ERROR_OBJECT (qtdemux,
+        "Invalid protection_scheme_type: %" GST_FOURCC_FORMAT,
+        GST_FOURCC_ARGS (stream->protection_scheme_type));
+    return FALSE;
+  }
+
+  if (G_UNLIKELY (!stream->protection_scheme_info))
+    stream->protection_scheme_info =
+        g_malloc0 (sizeof (QtDemuxCencSampleSetInfo));
+
+  info = (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info;
+
   if (stream->protection_scheme_type == FOURCC_cenc) {
-    QtDemuxCencSampleSetInfo *info;
-    GNode *tenc;
-    const guint8 *tenc_data;
-    guint32 isEncrypted;
+    guint32 is_encrypted;
     guint8 iv_size;
     const guint8 *default_kid;
-    GstBuffer *kid_buf;
-
-    if (G_UNLIKELY (!stream->protection_scheme_info))
-      stream->protection_scheme_info =
-          g_malloc0 (sizeof (QtDemuxCencSampleSetInfo));
-
-    info = (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info;
 
     tenc = qtdemux_tree_get_child_by_type (schi, FOURCC_tenc);
     if (!tenc) {
@@ -10108,25 +10927,52 @@ qtdemux_parse_protection_scheme_info (GstQTDemux * qtdemux,
       return FALSE;
     }
     tenc_data = (const guint8 *) tenc->data + 12;
-    isEncrypted = QT_UINT24 (tenc_data);
+    is_encrypted = QT_UINT24 (tenc_data);
     iv_size = QT_UINT8 (tenc_data + 3);
     default_kid = (tenc_data + 4);
-    kid_buf = gst_buffer_new_allocate (NULL, 16, NULL);
-    gst_buffer_fill (kid_buf, 0, default_kid, 16);
-    if (info->default_properties)
-      gst_structure_free (info->default_properties);
-    info->default_properties =
-        gst_structure_new ("application/x-cenc",
-        "iv_size", G_TYPE_UINT, iv_size,
-        "encrypted", G_TYPE_BOOLEAN, (isEncrypted == 1),
-        "kid", GST_TYPE_BUFFER, kid_buf, NULL);
-    GST_DEBUG_OBJECT (qtdemux, "default sample properties: "
-        "is_encrypted=%u, iv_size=%u", isEncrypted, iv_size);
-    gst_buffer_unref (kid_buf);
+    qtdemux_update_default_sample_encryption_settings (qtdemux, info,
+        is_encrypted, iv_size, default_kid);
+  } else if (stream->protection_scheme_type == FOURCC_piff) {
+    GstByteReader br;
+    static const guint8 piff_track_encryption_uuid[] = {
+      0x89, 0x74, 0xdb, 0xce, 0x7b, 0xe7, 0x4c, 0x51,
+      0x84, 0xf9, 0x71, 0x48, 0xf9, 0x88, 0x25, 0x54
+    };
+
+    tenc = qtdemux_tree_get_child_by_type (schi, FOURCC_uuid);
+    if (!tenc) {
+      GST_ERROR_OBJECT (qtdemux, "schi box does not contain tenc box, "
+          "which is mandatory for Common Encryption");
+      return FALSE;
+    }
+
+    tenc_data = (const guint8 *) tenc->data + 8;
+    if (memcmp (tenc_data, piff_track_encryption_uuid, 16) != 0) {
+      gchar *box_uuid = qtdemux_uuid_bytes_to_string (tenc_data);
+      GST_ERROR_OBJECT (qtdemux,
+          "Unsupported track encryption box with uuid: %s", box_uuid);
+      g_free (box_uuid);
+      return FALSE;
+    }
+    tenc_data = (const guint8 *) tenc->data + 16 + 12;
+    gst_byte_reader_init (&br, tenc_data, 20);
+    if (!qtdemux_update_default_piff_encryption_settings (qtdemux, info, &br)) {
+      GST_ERROR_OBJECT (qtdemux, "PIFF track box parsing error");
+      return FALSE;
+    }
+    stream->protection_scheme_type = FOURCC_cenc;
   }
+
   return TRUE;
 }
 
+static gint
+qtdemux_track_id_compare_func (QtDemuxStream ** stream1,
+    QtDemuxStream ** stream2)
+{
+  return (gint) (*stream1)->track_id - (gint) (*stream2)->track_id;
+}
+
 /* parse the traks.
  * With each track we associate a new QtDemuxStream that contains all the info
  * about the trak.
@@ -10145,18 +10991,12 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
   GNode *stsd;
   GNode *mp4a;
   GNode *mp4v;
-  GNode *wave;
   GNode *esds;
-  GNode *pasp;
-  GNode *colr;
   GNode *tref;
   GNode *udta;
   GNode *svmi;
-  GNode *fiel;
 
   QtDemuxStream *stream = NULL;
-  gboolean new_stream = FALSE;
-  gchar *codec = NULL;
   const guint8 *stsd_data;
   const guint8 *stsd_entry_data;
   guint remaining_stsd_len;
@@ -10167,7 +11007,6 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
   guint32 tkhd_flags = 0;
   guint8 tkhd_version = 0;
   guint32 w = 0, h = 0;
-  guint32 fourcc;
   guint value_size, stsd_len, len;
   guint32 track_id;
   guint32 dummy;
@@ -10185,25 +11024,13 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
       !gst_byte_reader_get_uint32_be (&tkhd, &track_id))
     goto corrupt_file;
 
-  if (!qtdemux->got_moov) {
-    if (qtdemux_find_stream (qtdemux, track_id))
-      goto existing_stream;
-    stream = _create_stream ();
-    stream->track_id = track_id;
-    new_stream = TRUE;
-  } else {
-    stream = qtdemux_find_stream (qtdemux, track_id);
-    if (!stream) {
-      GST_WARNING_OBJECT (qtdemux, "Stream not found, going to ignore it");
-      goto skip_track;
-    }
+  /* Check if current moov has duplicated track_id */
+  if (qtdemux_find_stream (qtdemux, track_id))
+    goto existing_stream;
 
-    stream->stream_tags = gst_tag_list_make_writable (stream->stream_tags);
+  stream = _create_stream (qtdemux, track_id);
+  stream->stream_tags = gst_tag_list_make_writable (stream->stream_tags);
 
-    /* flush samples data from this track from previous moov */
-    gst_qtdemux_stream_flush_segments_data (qtdemux, stream);
-    gst_qtdemux_stream_flush_samples_data (qtdemux, stream);
-  }
   /* need defaults for fragments */
   qtdemux_parse_trex (qtdemux, stream, &dummy, &dummy, &dummy);
 
@@ -10227,11 +11054,11 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
   version = QT_UINT32 ((guint8 *) mdhd->data + 8);
   GST_LOG_OBJECT (qtdemux, "track version/flags: %08x", version);
   if (version == 0x01000000) {
-    if (len < 38)
+    if (len < 42)
       goto corrupt_file;
     stream->timescale = QT_UINT32 ((guint8 *) mdhd->data + 28);
     stream->duration = QT_UINT64 ((guint8 *) mdhd->data + 32);
-    lang_code = QT_UINT16 ((guint8 *) mdhd->data + 36);
+    lang_code = QT_UINT16 ((guint8 *) mdhd->data + 40);
   } else {
     if (len < 30)
       goto corrupt_file;
@@ -10296,8 +11123,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
           "found, assuming preview image or something; skipping track",
           stream->duration, stream->timescale, qtdemux->duration,
           qtdemux->timescale);
-      if (new_stream)
-        gst_qtdemux_stream_free (qtdemux, stream);
+      gst_qtdemux_stream_unref (stream);
       return TRUE;
     }
   }
@@ -10394,8 +11220,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
   if (stsd_len < 24) {
     /* .. but skip stream with empty stsd produced by some Vivotek cameras */
     if (stream->subtype == FOURCC_vivo) {
-      if (new_stream)
-        gst_qtdemux_stream_free (qtdemux, stream);
+      gst_qtdemux_stream_unref (stream);
       return TRUE;
     } else {
       goto corrupt_file;
@@ -10410,6 +11235,8 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
   stsd_entry_data = stsd_data + 16;
   remaining_stsd_len = stsd_len - 16;
   for (stsd_index = 0; stsd_index < stsd_entry_count; stsd_index++) {
+    guint32 fourcc;
+    gchar *codec = NULL;
     QtDemuxStreamStsdEntry *entry = &stream->stsd_entries[stsd_index];
 
     /* and that entry should fit within stsd */
@@ -10435,6 +11262,9 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
     }
 
     if (stream->subtype == FOURCC_vide) {
+      GNode *colr;
+      GNode *fiel;
+      GNode *pasp;
       gboolean gray;
       gint depth, palette_size, palette_count;
       guint32 *palette_data = NULL;
@@ -10606,14 +11436,17 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
       fiel = NULL;
       /* pick 'the' stsd child */
       mp4v = qtdemux_tree_get_child_by_index (stsd, stsd_index);
-      if (!stream->protected) {
-        if (QTDEMUX_TREE_NODE_FOURCC (mp4v) != fourcc) {
+      // We should skip parsing the stsd for non-protected streams if
+      // the entry doesn't match the fourcc, since they don't change
+      // format. However, for protected streams we can have partial
+      // encryption, where parts of the stream are encrypted and parts
+      // not. For both parts of such streams, we should ensure the
+      // esds overrides are parsed for both from the stsd.
+      if (QTDEMUX_TREE_NODE_FOURCC (mp4v) != fourcc) {
+        if (stream->protected && QTDEMUX_TREE_NODE_FOURCC (mp4v) != FOURCC_encv)
           mp4v = NULL;
-        }
-      } else {
-        if (QTDEMUX_TREE_NODE_FOURCC (mp4v) != FOURCC_encv) {
+        else if (!stream->protected)
           mp4v = NULL;
-        }
       }
 
       if (mp4v) {
@@ -10871,7 +11704,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
                   /* parse, if found */
                   GstBuffer *buf;
 
-                  GST_DEBUG_OBJECT (qtdemux, "found avcC codec_data in stsd");
+                  GST_DEBUG_OBJECT (qtdemux, "found hvcC codec_data in stsd");
 
                   /* First 4 bytes are the length of the atom, the next 4 bytes
                    * are the fourcc, the next 1 byte is the version, and the
@@ -11307,6 +12140,81 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
             }
             break;
           }
+          case FOURCC_av01:
+          {
+            gint len = QT_UINT32 (stsd_entry_data) - 0x56;
+            const guint8 *av1_data = stsd_entry_data + 0x56;
+
+            /* find av1C */
+            while (len >= 0x8) {
+              gint size;
+
+              if (QT_UINT32 (av1_data) <= len)
+                size = QT_UINT32 (av1_data) - 0x8;
+              else
+                size = len - 0x8;
+
+              if (size < 1)
+                /* No real data, so break out */
+                break;
+
+              switch (QT_FOURCC (av1_data + 0x4)) {
+                case FOURCC_av1C:
+                {
+                  /* parse, if found */
+                  GstBuffer *buf;
+                  guint8 pres_delay_field;
+
+                  GST_DEBUG_OBJECT (qtdemux,
+                      "found av1C codec_data in stsd of size %d", size);
+
+                  /* not enough data, just ignore and hope for the best */
+                  if (size < 5)
+                    break;
+
+                  /* Content is:
+                   * 4 bytes: atom length
+                   * 4 bytes: fourcc
+                   * 1 byte: version
+                   * 3 bytes: flags
+                   * 3 bits: reserved
+                   * 1 bits:  initial_presentation_delay_present
+                   * 4 bits: initial_presentation_delay (if present else reserved
+                   * rest: OBUs.
+                   */
+
+                  if (av1_data[9] != 0) {
+                    GST_WARNING ("Unknown version %d of av1C box", av1_data[9]);
+                    break;
+                  }
+
+                  /* We skip initial_presentation_delay* for now */
+                  pres_delay_field = *(av1_data + 12);
+                  if (pres_delay_field & (1 << 5)) {
+                    gst_caps_set_simple (entry->caps,
+                        "presentation-delay", G_TYPE_INT,
+                        (gint) (pres_delay_field & 0x0F) + 1, NULL);
+                  }
+                  if (size > 5) {
+                    buf = gst_buffer_new_and_alloc (size - 5);
+                    GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER);
+                    gst_buffer_fill (buf, 0, av1_data + 13, size - 5);
+                    gst_caps_set_simple (entry->caps,
+                        "codec_data", GST_TYPE_BUFFER, buf, NULL);
+                    gst_buffer_unref (buf);
+                  }
+                  break;
+                }
+                default:
+                  break;
+              }
+
+              len -= size + 8;
+              av1_data += size + 8;
+            }
+
+            break;
+          }
           default:
             break;
         }
@@ -11317,6 +12225,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
           GST_FOURCC_ARGS (fourcc), entry->caps);
 
     } else if (stream->subtype == FOURCC_soun) {
+      GNode *wave;
       int version, samplesize;
       guint16 compression_id;
       gboolean amrwb = FALSE;
@@ -11414,6 +12323,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
           case FOURCC_twos:
           case FOURCC_sowt:
           case FOURCC_raw_:
+          case FOURCC_lpcm:
             break;
           default:
           {
@@ -11720,20 +12630,11 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
       }
 
       mp4a = qtdemux_tree_get_child_by_index (stsd, stsd_index);
-      if (!stream->protected) {
-      } else {
-        if (QTDEMUX_TREE_NODE_FOURCC (mp4v) != FOURCC_encv) {
-          mp4v = NULL;
-        }
-      }
-      if (stream->protected && fourcc == FOURCC_mp4a) {
-        if (QTDEMUX_TREE_NODE_FOURCC (mp4a) != FOURCC_enca) {
+      if (QTDEMUX_TREE_NODE_FOURCC (mp4a) != fourcc) {
+        if (stream->protected && QTDEMUX_TREE_NODE_FOURCC (mp4a) != FOURCC_enca)
           mp4a = NULL;
-        }
-      } else {
-        if (QTDEMUX_TREE_NODE_FOURCC (mp4a) != FOURCC_mp4a) {
+        else if (!stream->protected)
           mp4a = NULL;
-        }
       }
 
       wave = NULL;
@@ -12036,6 +12937,9 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
             }
             break;
           }
+          case FOURCC_lpcm:
+            /* Fully handled elsewhere */
+            break;
           default:
             GST_INFO_OBJECT (qtdemux,
                 "unhandled type %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (fourcc));
@@ -12056,7 +12960,8 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
       }
       entry->sampled = TRUE;
     } else if (stream->subtype == FOURCC_subp || stream->subtype == FOURCC_text
-        || stream->subtype == FOURCC_sbtl || stream->subtype == FOURCC_subt) {
+        || stream->subtype == FOURCC_sbtl || stream->subtype == FOURCC_subt
+        || stream->subtype == FOURCC_clcp) {
 
       entry->sampled = TRUE;
       entry->sparse = TRUE;
@@ -12184,39 +13089,29 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
     qtdemux_parse_udta (qtdemux, stream->stream_tags, udta);
   }
 
-  /* now we are ready to add the stream */
-  if (qtdemux->n_streams >= GST_QTDEMUX_MAX_STREAMS)
-    goto too_many_streams;
-
-  if (!qtdemux->got_moov) {
-    qtdemux->streams[qtdemux->n_streams] = stream;
-    qtdemux->n_streams++;
-    GST_DEBUG_OBJECT (qtdemux, "n_streams is now %d", qtdemux->n_streams);
-  }
+  /* Insert and sort new stream in track-id order.
+   * This will help in comparing old/new streams during stream update check */
+  g_ptr_array_add (qtdemux->active_streams, stream);
+  g_ptr_array_sort (qtdemux->active_streams,
+      (GCompareFunc) qtdemux_track_id_compare_func);
+  GST_DEBUG_OBJECT (qtdemux, "n_streams is now %d",
+      QTDEMUX_N_STREAMS (qtdemux));
 
   return TRUE;
 
 /* ERRORS */
-skip_track:
-  {
-    GST_INFO_OBJECT (qtdemux, "skip disabled track");
-    if (new_stream)
-      gst_qtdemux_stream_free (qtdemux, stream);
-    return TRUE;
-  }
 corrupt_file:
   {
     GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX,
         (_("This file is corrupt and cannot be played.")), (NULL));
-    if (new_stream)
-      gst_qtdemux_stream_free (qtdemux, stream);
+    if (stream)
+      gst_qtdemux_stream_unref (stream);
     return FALSE;
   }
 error_encrypted:
   {
     GST_ELEMENT_ERROR (qtdemux, STREAM, DECRYPT, (NULL), (NULL));
-    if (new_stream)
-      gst_qtdemux_stream_free (qtdemux, stream);
+    gst_qtdemux_stream_unref (stream);
     return FALSE;
   }
 samples_failed:
@@ -12225,31 +13120,20 @@ segments_failed:
     /* we posted an error already */
     /* free stbl sub-atoms */
     gst_qtdemux_stbl_free (stream);
-    if (new_stream)
-      gst_qtdemux_stream_free (qtdemux, stream);
+    gst_qtdemux_stream_unref (stream);
     return FALSE;
   }
 existing_stream:
   {
     GST_INFO_OBJECT (qtdemux, "stream with track id %i already exists",
         track_id);
-    if (new_stream)
-      gst_qtdemux_stream_free (qtdemux, stream);
     return TRUE;
   }
 unknown_stream:
   {
     GST_INFO_OBJECT (qtdemux, "unknown subtype %" GST_FOURCC_FORMAT,
         GST_FOURCC_ARGS (stream->subtype));
-    if (new_stream)
-      gst_qtdemux_stream_free (qtdemux, stream);
-    return TRUE;
-  }
-too_many_streams:
-  {
-    GST_ELEMENT_WARNING (qtdemux, STREAM, DEMUX,
-        (_("This file contains too many streams. Only playing first %d"),
-            GST_QTDEMUX_MAX_STREAMS), (NULL));
+    gst_qtdemux_stream_unref (stream);
     return TRUE;
   }
 }
@@ -12265,8 +13149,8 @@ gst_qtdemux_guess_bitrate (GstQTDemux * qtdemux)
   QtDemuxStream *stream = NULL;
   gint64 size, sys_bitrate, sum_bitrate = 0;
   GstClockTime duration;
-  gint i;
   guint bitrate;
+  gint i;
 
   if (qtdemux->fragmented)
     return;
@@ -12294,24 +13178,25 @@ gst_qtdemux_guess_bitrate (GstQTDemux * qtdemux)
     return;
   }
 
-  for (i = 0; i < qtdemux->n_streams; i++) {
-    switch (qtdemux->streams[i]->subtype) {
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+    QtDemuxStream *str = QTDEMUX_NTH_STREAM (qtdemux, i);
+    switch (str->subtype) {
       case FOURCC_soun:
       case FOURCC_vide:
         GST_DEBUG_OBJECT (qtdemux, "checking bitrate for %" GST_PTR_FORMAT,
-            CUR_STREAM (qtdemux->streams[i])->caps);
+            CUR_STREAM (str)->caps);
         /* retrieve bitrate, prefer avg then max */
         bitrate = 0;
-        if (qtdemux->streams[i]->stream_tags) {
-          gst_tag_list_get_uint (qtdemux->streams[i]->stream_tags,
-              GST_TAG_MAXIMUM_BITRATE, &bitrate);
-          GST_DEBUG_OBJECT (qtdemux, "max-bitrate: %u", bitrate);
-          gst_tag_list_get_uint (qtdemux->streams[i]->stream_tags,
-              GST_TAG_NOMINAL_BITRATE, &bitrate);
-          GST_DEBUG_OBJECT (qtdemux, "nominal-bitrate: %u", bitrate);
-          gst_tag_list_get_uint (qtdemux->streams[i]->stream_tags,
-              GST_TAG_BITRATE, &bitrate);
-          GST_DEBUG_OBJECT (qtdemux, "bitrate: %u", bitrate);
+        if (str->stream_tags) {
+          if (gst_tag_list_get_uint (str->stream_tags,
+                  GST_TAG_MAXIMUM_BITRATE, &bitrate))
+            GST_DEBUG_OBJECT (qtdemux, "max-bitrate: %u", bitrate);
+          if (gst_tag_list_get_uint (str->stream_tags,
+                  GST_TAG_NOMINAL_BITRATE, &bitrate))
+            GST_DEBUG_OBJECT (qtdemux, "nominal-bitrate: %u", bitrate);
+          if (gst_tag_list_get_uint (str->stream_tags,
+                  GST_TAG_BITRATE, &bitrate))
+            GST_DEBUG_OBJECT (qtdemux, "bitrate: %u", bitrate);
         }
         if (bitrate)
           sum_bitrate += bitrate;
@@ -12321,7 +13206,7 @@ gst_qtdemux_guess_bitrate (GstQTDemux * qtdemux)
                 ">1 stream with unknown bitrate - bailing");
             return;
           } else
-            stream = qtdemux->streams[i];
+            stream = str;
         }
 
       default:
@@ -12361,17 +13246,17 @@ gst_qtdemux_guess_bitrate (GstQTDemux * qtdemux)
 static GstFlowReturn
 qtdemux_prepare_streams (GstQTDemux * qtdemux)
 {
-  gint i;
   GstFlowReturn ret = GST_FLOW_OK;
+  gint i;
 
   GST_DEBUG_OBJECT (qtdemux, "prepare streams");
 
-  for (i = 0; ret == GST_FLOW_OK && i < qtdemux->n_streams; i++) {
-    QtDemuxStream *stream = qtdemux->streams[i];
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+    QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
     guint32 sample_num = 0;
 
-    GST_DEBUG_OBJECT (qtdemux, "stream %d, id %d, fourcc %" GST_FOURCC_FORMAT,
-        i, stream->track_id, GST_FOURCC_ARGS (CUR_STREAM (stream)->fourcc));
+    GST_DEBUG_OBJECT (qtdemux, "track-id %u, fourcc %" GST_FOURCC_FORMAT,
+        stream->track_id, GST_FOURCC_ARGS (CUR_STREAM (stream)->fourcc));
 
     if (qtdemux->fragmented) {
       /* need all moov samples first */
@@ -12394,7 +13279,14 @@ qtdemux_prepare_streams (GstQTDemux * qtdemux)
      * in push mode, we'll just have to deal with it */
     if (G_UNLIKELY (qtdemux->pullbased && !stream->n_samples)) {
       GST_DEBUG_OBJECT (qtdemux, "no samples for stream; discarding");
-      gst_qtdemux_remove_stream (qtdemux, i);
+      g_ptr_array_remove_index (qtdemux->active_streams, i);
+      i--;
+      continue;
+    } else if (stream->track_id == qtdemux->chapters_track_id &&
+        (stream->subtype == FOURCC_text || stream->subtype == FOURCC_sbtl)) {
+      /* TODO - parse chapters track and expose it as GstToc; For now just ignore it
+         so that it doesn't look like a subtitle track */
+      g_ptr_array_remove_index (qtdemux->active_streams, i);
       i--;
       continue;
     }
@@ -12405,90 +13297,213 @@ qtdemux_prepare_streams (GstQTDemux * qtdemux)
         break;
       ++sample_num;
     }
-    if (stream->n_samples > 0 && stream->stbl_index >= 0) {
-      stream->first_duration = stream->samples[0].duration;
-      GST_LOG_OBJECT (qtdemux, "stream %d first duration %u",
-          stream->track_id, stream->first_duration);
-    }
   }
 
   return ret;
 }
 
+static gboolean
+_stream_equal_func (const QtDemuxStream * stream, const gchar * stream_id)
+{
+  return g_strcmp0 (stream->stream_id, stream_id) == 0;
+}
+
+static gboolean
+qtdemux_is_streams_update (GstQTDemux * qtdemux)
+{
+  gint i;
+
+  /* Different length, updated */
+  if (QTDEMUX_N_STREAMS (qtdemux) != qtdemux->old_streams->len)
+    return TRUE;
+
+  /* streams in list are sorted in track-id order */
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+    /* Different stream-id, updated */
+    if (g_strcmp0 (QTDEMUX_NTH_STREAM (qtdemux, i)->stream_id,
+            QTDEMUX_NTH_OLD_STREAM (qtdemux, i)->stream_id))
+      return TRUE;
+  }
+
+  return FALSE;
+}
+
+static gboolean
+qtdemux_reuse_and_configure_stream (GstQTDemux * qtdemux,
+    QtDemuxStream * oldstream, QtDemuxStream * newstream)
+{
+  /* Connect old stream's srcpad to new stream */
+  newstream->pad = oldstream->pad;
+  oldstream->pad = NULL;
+
+  /* unset new_stream to prevent stream-start event */
+  newstream->new_stream = FALSE;
+
+  return gst_qtdemux_configure_stream (qtdemux, newstream);
+}
+
+/* g_ptr_array_find_with_equal_func is available since 2.54,
+ * replacement until we can depend unconditionally on the real one in GLib */
+#if !GLIB_CHECK_VERSION(2,54,0)
+#define g_ptr_array_find_with_equal_func qtdemux_ptr_array_find_with_equal_func
+static gboolean
+qtdemux_ptr_array_find_with_equal_func (GPtrArray * haystack,
+    gconstpointer needle, GEqualFunc equal_func, guint * index_)
+{
+  guint i;
+
+  g_return_val_if_fail (haystack != NULL, FALSE);
+
+  if (equal_func == NULL)
+    equal_func = g_direct_equal;
+
+  for (i = 0; i < haystack->len; i++) {
+    if (equal_func (g_ptr_array_index (haystack, i), needle)) {
+      if (index_ != NULL)
+        *index_ = i;
+      return TRUE;
+    }
+  }
+
+  return FALSE;
+}
+#endif
+
+static gboolean
+qtdemux_update_streams (GstQTDemux * qtdemux)
+{
+  gint i;
+  g_assert (qtdemux->streams_aware);
+
+  /* At below, figure out which stream in active_streams has identical stream-id
+   * with that of in old_streams. If there is matching stream-id,
+   * corresponding newstream will not be exposed again,
+   * but demux will reuse srcpad of matched old stream
+   *
+   * active_streams : newly created streams from the latest moov
+   * old_streams : existing streams (belong to previous moov)
+   */
+
+  for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+    QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
+    QtDemuxStream *oldstream = NULL;
+    guint target;
+
+    GST_DEBUG_OBJECT (qtdemux, "track-id %u, fourcc %" GST_FOURCC_FORMAT,
+        stream->track_id, GST_FOURCC_ARGS (CUR_STREAM (stream)->fourcc));
+
+    if (g_ptr_array_find_with_equal_func (qtdemux->old_streams,
+            stream->stream_id, (GEqualFunc) _stream_equal_func, &target)) {
+      oldstream = QTDEMUX_NTH_OLD_STREAM (qtdemux, target);
+
+      /* null pad stream cannot be reused */
+      if (oldstream->pad == NULL)
+        oldstream = NULL;
+    }
+
+    if (oldstream) {
+      GST_DEBUG_OBJECT (qtdemux, "Reuse track-id %d", oldstream->track_id);
+
+      if (!qtdemux_reuse_and_configure_stream (qtdemux, oldstream, stream))
+        return FALSE;
+
+      /* we don't need to preserve order of old streams */
+      g_ptr_array_remove_fast (qtdemux->old_streams, oldstream);
+    } else {
+      GstTagList *list;
+
+      /* now we have all info and can expose */
+      list = stream->stream_tags;
+      stream->stream_tags = NULL;
+      if (!gst_qtdemux_add_stream (qtdemux, stream, list))
+        return FALSE;
+    }
+  }
+
+  return TRUE;
+}
+
+/* Must be called with expose lock */
 static GstFlowReturn
 qtdemux_expose_streams (GstQTDemux * qtdemux)
 {
   gint i;
-  GSList *oldpads = NULL;
-  GSList *iter;
 
   GST_DEBUG_OBJECT (qtdemux, "exposing streams");
 
-  for (i = 0; i < qtdemux->n_streams; i++) {
-    QtDemuxStream *stream = qtdemux->streams[i];
-    GstPad *oldpad = stream->pad;
-    GstTagList *list;
+  if (!qtdemux_is_streams_update (qtdemux)) {
+    GST_DEBUG_OBJECT (qtdemux, "Reuse all streams");
+    for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+      QtDemuxStream *new_stream = QTDEMUX_NTH_STREAM (qtdemux, i);
+      QtDemuxStream *old_stream = QTDEMUX_NTH_OLD_STREAM (qtdemux, i);
+      if (!qtdemux_reuse_and_configure_stream (qtdemux, old_stream, new_stream))
+        return GST_FLOW_ERROR;
+    }
 
-    GST_DEBUG_OBJECT (qtdemux, "stream %d, id %d, fourcc %" GST_FOURCC_FORMAT,
-        i, stream->track_id, GST_FOURCC_ARGS (CUR_STREAM (stream)->fourcc));
+    g_ptr_array_set_size (qtdemux->old_streams, 0);
+    qtdemux->need_segment = TRUE;
 
-    if ((stream->subtype == FOURCC_text || stream->subtype == FOURCC_sbtl) &&
-        stream->track_id == qtdemux->chapters_track_id) {
-      /* TODO - parse chapters track and expose it as GstToc; For now just ignore it
-         so that it doesn't look like a subtitle track */
-      gst_qtdemux_remove_stream (qtdemux, i);
-      i--;
-      continue;
-    }
+    return GST_FLOW_OK;
+  }
 
-    /* now we have all info and can expose */
-    list = stream->stream_tags;
-    stream->stream_tags = NULL;
-    if (oldpad)
-      oldpads = g_slist_prepend (oldpads, oldpad);
-    if (!gst_qtdemux_add_stream (qtdemux, stream, list))
+  if (qtdemux->streams_aware) {
+    if (!qtdemux_update_streams (qtdemux))
       return GST_FLOW_ERROR;
+  } else {
+    for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
+      QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
+      GstTagList *list;
+
+      /* now we have all info and can expose */
+      list = stream->stream_tags;
+      stream->stream_tags = NULL;
+      if (!gst_qtdemux_add_stream (qtdemux, stream, list))
+        return GST_FLOW_ERROR;
+
+    }
   }
 
   gst_qtdemux_guess_bitrate (qtdemux);
 
   gst_element_no_more_pads (GST_ELEMENT_CAST (qtdemux));
 
-  for (iter = oldpads; iter; iter = g_slist_next (iter)) {
-    GstPad *oldpad = iter->data;
-    GstEvent *event;
+  /* If we have still old_streams, it's no more used stream */
+  for (i = 0; i < qtdemux->old_streams->len; i++) {
+    QtDemuxStream *stream = QTDEMUX_NTH_OLD_STREAM (qtdemux, i);
 
-    event = gst_event_new_eos ();
-    if (qtdemux->segment_seqnum)
-      gst_event_set_seqnum (event, qtdemux->segment_seqnum);
+    if (stream->pad) {
+      GstEvent *event;
+
+      event = gst_event_new_eos ();
+      if (qtdemux->segment_seqnum)
+        gst_event_set_seqnum (event, qtdemux->segment_seqnum);
 
-    gst_pad_push_event (oldpad, event);
-    gst_pad_set_active (oldpad, FALSE);
-    gst_element_remove_pad (GST_ELEMENT (qtdemux), oldpad);
-    gst_flow_combiner_remove_pad (qtdemux->flowcombiner, oldpad);
-    gst_object_unref (oldpad);
+      gst_pad_push_event (stream->pad, event);
+    }
   }
 
+  g_ptr_array_set_size (qtdemux->old_streams, 0);
+
   /* check if we should post a redirect in case there is a single trak
    * and it is a redirecting trak */
-  if (qtdemux->n_streams == 1 && qtdemux->streams[0]->redirect_uri != NULL) {
+  if (QTDEMUX_N_STREAMS (qtdemux) == 1 &&
+      QTDEMUX_NTH_STREAM (qtdemux, 0)->redirect_uri != NULL) {
     GstMessage *m;
 
     GST_INFO_OBJECT (qtdemux, "Issuing a redirect due to a single track with "
         "an external content");
     m = gst_message_new_element (GST_OBJECT_CAST (qtdemux),
         gst_structure_new ("redirect",
-            "new-location", G_TYPE_STRING, qtdemux->streams[0]->redirect_uri,
-            NULL));
+            "new-location", G_TYPE_STRING,
+            QTDEMUX_NTH_STREAM (qtdemux, 0)->redirect_uri, NULL));
     gst_element_post_message (GST_ELEMENT_CAST (qtdemux), m);
     qtdemux->posted_redirect = TRUE;
   }
 
-  for (i = 0; i < qtdemux->n_streams; i++) {
-    QtDemuxStream *stream = qtdemux->streams[i];
+  g_ptr_array_foreach (qtdemux->active_streams,
+      (GFunc) qtdemux_do_allocation, qtdemux);
 
-    qtdemux_do_allocation (qtdemux, stream);
-  }
+  qtdemux->need_segment = TRUE;
 
   qtdemux->exposed = TRUE;
   return GST_FLOW_OK;
@@ -12989,6 +14004,7 @@ qtdemux_tag_add_date (GstQTDemux * qtdemux, GstTagList * taglist,
     const char *tag, const char *dummy, GNode * node)
 {
   GNode *data;
+  GstDateTime *datetime = NULL;
   char *s;
   int len;
   int type;
@@ -13003,6 +14019,13 @@ qtdemux_tag_add_date (GstQTDemux * qtdemux, GstTagList * taglist,
 
       s = g_strndup ((char *) data->data + 16, len - 16);
       GST_DEBUG_OBJECT (qtdemux, "adding date '%s'", s);
+      datetime = gst_date_time_new_from_iso8601_string (s);
+      if (datetime != NULL) {
+        gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, GST_TAG_DATE_TIME,
+            datetime, NULL);
+        gst_date_time_unref (datetime);
+      }
+
       ret = sscanf (s, "%u-%u-%u", &y, &m, &d);
       if (ret >= 1 && y > 1500 && y < 3000) {
         GDate *date;
@@ -13670,7 +14693,6 @@ qtdemux_parse_tree (GstQTDemux * qtdemux)
   GNode *trak;
   GNode *udta;
   GNode *mvex;
-  GstClockTime duration;
   GNode *pssh;
   guint64 creation_time;
   GstDateTime *datetime = NULL;
@@ -13750,9 +14772,13 @@ qtdemux_parse_tree (GstQTDemux * qtdemux)
       qtdemux_parse_mehd (qtdemux, &mehd_data);
   }
 
-  /* set duration in the segment info */
-  gst_qtdemux_get_duration (qtdemux, &duration);
-  if (duration) {
+  /* Update the movie segment duration, unless it was directly given to us
+   * by upstream. Otherwise let it as is, as we don't want to mangle the
+   * duration provided by upstream that may come e.g. from a MPD file. */
+  if (!qtdemux->upstream_format_is_time) {
+    GstClockTime duration;
+    /* set duration in the segment info */
+    gst_qtdemux_get_duration (qtdemux, &duration);
     qtdemux->segment.duration = duration;
     /* also do not exceed duration; stop is set that way post seek anyway,
      * and segment activation falls back to duration,
@@ -13822,6 +14848,81 @@ read_descr_size (guint8 * ptr, guint8 * end, guint8 ** end_out)
   return len;
 }
 
+static GList *
+parse_xiph_stream_headers (GstQTDemux * qtdemux, gpointer codec_data,
+    gsize codec_data_size)
+{
+  GList *list = NULL;
+  guint8 *p = codec_data;
+  gint i, offset, num_packets;
+  guint *length, last;
+
+  GST_MEMDUMP_OBJECT (qtdemux, "xiph codec data", codec_data, codec_data_size);
+
+  if (codec_data == NULL || codec_data_size == 0)
+    goto error;
+
+  /* start of the stream and vorbis audio or theora video, need to
+   * send the codec_priv data as first three packets */
+  num_packets = p[0] + 1;
+  GST_DEBUG_OBJECT (qtdemux,
+      "%u stream headers, total length=%" G_GSIZE_FORMAT " bytes",
+      (guint) num_packets, codec_data_size);
+
+  /* Let's put some limits, Don't think there even is a xiph codec
+   * with more than 3-4 headers */
+  if (G_UNLIKELY (num_packets > 16)) {
+    GST_WARNING_OBJECT (qtdemux,
+        "Unlikely number of xiph headers, most likely not valid");
+    goto error;
+  }
+
+  length = g_alloca (num_packets * sizeof (guint));
+  last = 0;
+  offset = 1;
+
+  /* first packets, read length values */
+  for (i = 0; i < num_packets - 1; i++) {
+    length[i] = 0;
+    while (offset < codec_data_size) {
+      length[i] += p[offset];
+      if (p[offset++] != 0xff)
+        break;
+    }
+    last += length[i];
+  }
+  if (offset + last > codec_data_size)
+    goto error;
+
+  /* last packet is the remaining size */
+  length[i] = codec_data_size - offset - last;
+
+  for (i = 0; i < num_packets; i++) {
+    GstBuffer *hdr;
+
+    GST_DEBUG_OBJECT (qtdemux, "buffer %d: %u bytes", i, (guint) length[i]);
+
+    if (offset + length[i] > codec_data_size)
+      goto error;
+
+    hdr = gst_buffer_new_wrapped (g_memdup (p + offset, length[i]), length[i]);
+    list = g_list_append (list, hdr);
+
+    offset += length[i];
+  }
+
+  return list;
+
+  /* ERRORS */
+error:
+  {
+    if (list != NULL)
+      g_list_free_full (list, (GDestroyNotify) gst_buffer_unref);
+    return NULL;
+  }
+
+}
+
 /* this can change the codec originally present in @list */
 static void
 gst_qtdemux_handle_esds (GstQTDemux * qtdemux, QtDemuxStream * stream,
@@ -13834,6 +14935,7 @@ gst_qtdemux_handle_esds (GstQTDemux * qtdemux, QtDemuxStream * stream,
   guint8 *data_ptr = NULL;
   int data_len = 0;
   guint8 object_type_id = 0;
+  guint8 stream_type = 0;
   const char *codec_name = NULL;
   GstCaps *caps = NULL;
 
@@ -13854,18 +14956,19 @@ gst_qtdemux_handle_esds (GstQTDemux * qtdemux, QtDemuxStream * stream,
 
     switch (tag) {
       case ES_DESCRIPTOR_TAG:
-        GST_DEBUG_OBJECT (qtdemux, "ID %04x", QT_UINT16 (ptr));
-        GST_DEBUG_OBJECT (qtdemux, "priority %04x", QT_UINT8 (ptr + 2));
+        GST_DEBUG_OBJECT (qtdemux, "ID 0x%04x", QT_UINT16 (ptr));
+        GST_DEBUG_OBJECT (qtdemux, "priority 0x%04x", QT_UINT8 (ptr + 2));
         ptr += 3;
         break;
       case DECODER_CONFIG_DESC_TAG:{
         guint max_bitrate, avg_bitrate;
 
         object_type_id = QT_UINT8 (ptr);
+        stream_type = QT_UINT8 (ptr + 1) >> 2;
         max_bitrate = QT_UINT32 (ptr + 5);
         avg_bitrate = QT_UINT32 (ptr + 9);
         GST_DEBUG_OBJECT (qtdemux, "object_type_id %02x", object_type_id);
-        GST_DEBUG_OBJECT (qtdemux, "stream_type %02x", QT_UINT8 (ptr + 1));
+        GST_DEBUG_OBJECT (qtdemux, "stream_type %02x", stream_type);
         GST_DEBUG_OBJECT (qtdemux, "buffer_size_db %02x", QT_UINT24 (ptr + 2));
         GST_DEBUG_OBJECT (qtdemux, "max bitrate %u", max_bitrate);
         GST_DEBUG_OBJECT (qtdemux, "avg bitrate %u", avg_bitrate);
@@ -14042,7 +15145,7 @@ gst_qtdemux_handle_esds (GstQTDemux * qtdemux, QtDemuxStream * stream,
     case 0x69:                 /* MPEG-2 BC audio */
     case 0x6B:                 /* MPEG-1 audio */
       caps = gst_caps_new_simple ("audio/mpeg",
-          "mpegversion", G_TYPE_INT, 1, "parsed", G_TYPE_BOOLEAN, TRUE, NULL);
+          "mpegversion", G_TYPE_INT, 1, NULL);
       codec_name = "MPEG-1 audio";
       break;
     case 0x6A:                 /* MPEG-1 */
@@ -14081,6 +15184,35 @@ gst_qtdemux_handle_esds (GstQTDemux * qtdemux, QtDemuxStream * stream,
       caps = gst_caps_new_simple ("audio/x-dts",
           "framed", G_TYPE_BOOLEAN, TRUE, NULL);
       break;
+    case 0xDD:
+      if (stream_type == 0x05 && data_ptr) {
+        GList *headers =
+            parse_xiph_stream_headers (qtdemux, data_ptr, data_len);
+        if (headers) {
+          GList *tmp;
+          GValue arr_val = G_VALUE_INIT;
+          GValue buf_val = G_VALUE_INIT;
+          GstStructure *s;
+
+          /* Let's assume it's vorbis if it's an audio stream of type 0xdd and we have codec data that extracts properly */
+          codec_name = "Vorbis";
+          caps = gst_caps_new_empty_simple ("audio/x-vorbis");
+          g_value_init (&arr_val, GST_TYPE_ARRAY);
+          g_value_init (&buf_val, GST_TYPE_BUFFER);
+          for (tmp = headers; tmp; tmp = tmp->next) {
+            g_value_set_boxed (&buf_val, (GstBuffer *) tmp->data);
+            gst_value_array_append_value (&arr_val, &buf_val);
+          }
+          s = gst_caps_get_structure (caps, 0);
+          gst_structure_take_value (s, "streamheader", &arr_val);
+          g_value_unset (&buf_val);
+          g_list_free (headers);
+
+          data_ptr = NULL;
+          data_len = 0;
+        }
+      }
+      break;
     case 0xE1:                 /* QCELP */
       /* QCELP, the codec_data is a riff tag (little endian) with
        * more info (http://ftp.3gpp2.org/TSGC/Working/2003/2003-05-SanDiego/TSG-C-2003-05-San%20Diego/WG1/SWG12/C12-20030512-006%20=%20C12-20030217-015_Draft_Baseline%20Text%20of%20FFMS_R2.doc). */
@@ -14152,7 +15284,7 @@ qtdemux_video_caps (GstQTDemux * qtdemux, QtDemuxStream * stream,
   GstVideoFormat format = GST_VIDEO_FORMAT_UNKNOWN;
 
   switch (fourcc) {
-    case GST_MAKE_FOURCC ('p', 'n', 'g', ' '):
+    case FOURCC_png:
       _codec ("PNG still images");
       caps = gst_caps_new_empty_simple ("image/png");
       break;
@@ -14493,9 +15625,14 @@ qtdemux_video_caps (GstQTDemux * qtdemux, QtDemuxStream * stream,
       caps = gst_caps_from_string ("video/x-dnxhd");
       break;
     case FOURCC_VP80:
+    case FOURCC_vp08:
       _codec ("On2 VP8");
       caps = gst_caps_from_string ("video/x-vp8");
       break;
+    case FOURCC_vp09:
+      _codec ("Google VP9");
+      caps = gst_caps_from_string ("video/x-vp9");
+      break;
     case FOURCC_apcs:
       _codec ("Apple ProRes LT");
       caps =
@@ -14542,6 +15679,10 @@ qtdemux_video_caps (GstQTDemux * qtdemux, QtDemuxStream * stream,
       caps = gst_caps_new_simple ("video/x-wmv",
           "wmvversion", G_TYPE_INT, 3, "format", G_TYPE_STRING, "WVC1", NULL);
       break;
+    case FOURCC_av01:
+      _codec ("AV1");
+      caps = gst_caps_new_empty_simple ("video/x-av1");
+      break;
     case GST_MAKE_FOURCC ('k', 'p', 'c', 'd'):
     default:
     {
@@ -14813,7 +15954,7 @@ qtdemux_audio_caps (GstQTDemux * qtdemux, QtDemuxStream * stream,
       _codec ("Opus");
       caps = gst_caps_new_empty_simple ("audio/x-opus");
       break;
-    case GST_MAKE_FOURCC ('l', 'p', 'c', 'm'):
+    case FOURCC_lpcm:
     {
       guint32 flags = 0;
       guint32 depth = 0;
@@ -14830,23 +15971,29 @@ qtdemux_audio_caps (GstQTDemux * qtdemux, QtDemuxStream * stream,
       };
       _codec ("Raw LPCM audio");
 
-      if (data && len >= 56) {
-        depth = QT_UINT32 (data + 40);
-        flags = QT_UINT32 (data + 44);
-        width = QT_UINT32 (data + 48) * 8 / entry->n_channels;
+      if (data && len >= 36) {
+        depth = QT_UINT32 (data + 24);
+        flags = QT_UINT32 (data + 28);
+        width = QT_UINT32 (data + 32) * 8 / entry->n_channels;
       }
       if ((flags & FLAG_IS_FLOAT) == 0) {
         if (depth == 0)
           depth = 16;
         if (width == 0)
           width = 16;
+        if ((flags & FLAG_IS_ALIGNED_HIGH))
+          depth = width;
+
         format = gst_audio_format_build_integer ((flags & FLAG_IS_SIGNED) ?
             TRUE : FALSE, (flags & FLAG_IS_BIG_ENDIAN) ?
             G_BIG_ENDIAN : G_LITTLE_ENDIAN, width, depth);
         caps = gst_caps_new_simple ("audio/x-raw",
-            "format", G_TYPE_STRING, gst_audio_format_to_string (format),
-            "layout", G_TYPE_STRING, (flags & FLAG_IS_NON_INTERLEAVED) ?
-            "non-interleaved" : "interleaved", NULL);
+            "format", G_TYPE_STRING,
+            format !=
+            GST_AUDIO_FORMAT_UNKNOWN ? gst_audio_format_to_string (format) :
+            "UNKNOWN", "layout", G_TYPE_STRING,
+            (flags & FLAG_IS_NON_INTERLEAVED) ? "non-interleaved" :
+            "interleaved", NULL);
         stream->alignment = GST_ROUND_UP_8 (depth);
         stream->alignment = round_up_pow2 (stream->alignment);
       } else {
@@ -14930,6 +16077,22 @@ qtdemux_sub_caps (GstQTDemux * qtdemux, QtDemuxStream * stream,
       _codec ("XML subtitles");
       caps = gst_caps_new_empty_simple ("application/ttml+xml");
       break;
+    case FOURCC_c608:
+      _codec ("CEA 608 Closed Caption");
+      caps =
+          gst_caps_new_simple ("closedcaption/x-cea-608", "format",
+          G_TYPE_STRING, "s334-1a", NULL);
+      stream->need_process = TRUE;
+      stream->need_split = TRUE;
+      break;
+    case FOURCC_c708:
+      _codec ("CEA 708 Closed Caption");
+      caps =
+          gst_caps_new_simple ("closedcaption/x-cea-708", "format",
+          G_TYPE_STRING, "cdp", NULL);
+      stream->need_process = TRUE;
+      break;
+
     default:
     {
       caps = _get_unknown_codec_name ("text", fourcc);