adaptivedemux: apply 1.14.4 upstream code
authorEunhae Choi <eunhae1.choi@samsung.com>
Tue, 19 Feb 2019 07:05:19 +0000 (16:05 +0900)
committerEunhae Choi <eunhae1.choi@samsung.com>
Tue, 19 Feb 2019 07:07:10 +0000 (16:07 +0900)
- apply the upstream 1.14.4 release code of
  adaptivedemux, dashdemux, hlsdemux and uridownloader
  to use latest playback code

Change-Id: If3270872c26059b81c9eab8fbbe239f251d9e675

28 files changed:
configure.ac
ext/dash/Makefile.am
ext/dash/gstdashdemux.c
ext/dash/gstdashdemux.h
ext/dash/gstmpdparser.c
ext/dash/gstmpdparser.h
ext/hls/Makefile.am
ext/hls/gsthlsdemux.c
ext/hls/gsthlsdemux.h
ext/hls/gsthlsplugin.c
ext/hls/gsthlssink2.c [new file with mode: 0644]
ext/hls/gsthlssink2.h [new file with mode: 0644]
ext/hls/m3u8.c
gst-libs/gst/Makefile.am
gst-libs/gst/adaptivedemux/Makefile.am
gst-libs/gst/adaptivedemux/adaptive-demux-prelude.h [new file with mode: 0644]
gst-libs/gst/adaptivedemux/gstadaptivedemux.c
gst-libs/gst/adaptivedemux/gstadaptivedemux.h
gst-libs/gst/isoff/Makefile.am [new file with mode: 0644]
gst-libs/gst/isoff/gstisoff.c [moved from ext/dash/gstisoff.c with 57% similarity]
gst-libs/gst/isoff/gstisoff.h [moved from ext/dash/gstisoff.h with 75% similarity]
gst-libs/gst/isoff/meson.build [new file with mode: 0644]
gst-libs/gst/uridownloader/Makefile.am
gst-libs/gst/uridownloader/gstfragment.h
gst-libs/gst/uridownloader/gsturidownloader.c
gst-libs/gst/uridownloader/gsturidownloader.h
gst-libs/gst/uridownloader/uridownloader-prelude.h [new file with mode: 0644]
packaging/gst-plugins-bad.spec

index 3cf76f9..2bc75e3 100644 (file)
@@ -3601,6 +3601,7 @@ gst-libs/gst/gl/x11/Makefile
 gst-libs/gst/gl/viv-fb/Makefile
 gst-libs/gst/insertbin/Makefile
 gst-libs/gst/interfaces/Makefile
+gst-libs/gst/isoff/Makefile
 gst-libs/gst/codecparsers/Makefile
 gst-libs/gst/mpegts/Makefile
 gst-libs/gst/uridownloader/Makefile
index 5197c34..5109e9e 100644 (file)
@@ -4,14 +4,12 @@ plugin_LTLIBRARIES = libgstdashdemux.la
 libgstdashdemux_la_SOURCES =                   \
        gstmpdparser.c                          \
        gstdashdemux.c                          \
-       gstisoff.c                              \
        gstplugin.c
 
 # headers we need but don't want installed
 noinst_HEADERS =        \
         gstmpdparser.h \
        gstdashdemux.h  \
-       gstisoff.h      \
        gstdash_debug.h
 
 # compiler and linker flags used to compile this plugin, set in configure.ac
@@ -24,6 +22,7 @@ libgstdashdemux_la_CFLAGS = $(GST_PLUGINS_BAD_CFLAGS) \
 libgstdashdemux_la_LIBADD = \
        $(top_builddir)/gst-libs/gst/uridownloader/libgsturidownloader-@GST_API_VERSION@.la \
        $(top_builddir)/gst-libs/gst/adaptivedemux/libgstadaptivedemux-@GST_API_VERSION@.la \
+       $(top_builddir)/gst-libs/gst/isoff/libgstisoff-@GST_API_VERSION@.la \
        $(GST_PLUGINS_BASE_LIBS) \
        -lgsttag-$(GST_API_VERSION) \
        $(GST_BASE_LIBS) \
index 2436502..2e4923c 100644 (file)
  * adapt quickly to bandwidth changes, we will not be able to rely
  * on downstream buffering, and will instead manage an internal queue.
  *
+ *
+ * Keyframe trick-mode implementation:
+ *
+ * When requested (with GST_SEEK_FLAG_TRICKMODE_KEY_UNIT) and if the format
+ * is supported (ISOBMFF profiles), dashdemux can download only keyframes
+ * in order to provide fast forward/reverse playback without exceeding the
+ * available bandwith/cpu/memory usage.
+ *
+ * This is done in two parts:
+ * 1) Parsing ISOBMFF atoms to detect the location of keyframes and only
+ *    download/push those.
+ * 2) Deciding what the ideal next keyframe to download is in order to
+ *    provide as many keyframes as possible without rebuffering.
+ *
+ * * Keyframe-only downloads:
+ *
+ * For each beginning of fragment, the fragment header will be parsed in
+ * gst_dash_demux_parse_isobmff() and then the information (offset, pts...)
+ * of each keyframe will be stored in moof_sync_samples.
+ *
+ * gst_dash_demux_stream_update_fragment_info() will specify the range
+ * start and end of the current keyframe, which will cause GstAdaptiveDemux
+ * to do a new upstream range request.
+ *
+ * When advancing, if there are still some keyframes in the current
+ * fragment, gst_dash_demux_stream_advance_fragment() will call
+ * gst_dash_demux_stream_advance_sync_sample() which decides what the next
+ * keyframe to get will be (it can be in reverse order for example, or
+ * might not be the *next* keyframe but one further as explained below).
+ *
+ * If no more keyframes are available in the current fragment, dash will
+ * advance to the next fragment (just like in the normal case) or to a
+ * fragment much further away (as explained below).
+ *
+ *
+ * * Deciding the optimal "next" keyframe/fragment to download:
+ *
+ * The main reason for doing keyframe-only downloads is for trick-modes
+ * (i.e. being able to do fast reverse/forward playback with limited
+ * bandwith/cpu/memory).
+ *
+ * Downloading all keyframes might not be the optimal solution, especially
+ * at high playback rates, since the time taken to download the keyframe
+ * might exceed the available running time between two displayed frames
+ * (i.e. all frames would end up arriving late). This would cause severe
+ * rebuffering.
+ *
+ * Note: The values specified below can be in either the segment running
+ * time or in absolute values. Where position values need to be converted
+ * to segment running time the "running_time(val)" notation is used, and
+ * where running time need ot be converted to segment poisition the
+ * "position(val)" notation is used.
+ *
+ * The goal instead is to be able to download/display as many frames as
+ * possible for a given playback rate. For that the implementation will
+ * take into account:
+ *  * The requested playback rate and segment
+ *  * The average time to request and download a keyframe (in running time)
+ *  * The current position of dashdemux in the stream
+ *  * The current downstream (i.e. sink) position (in running time)
+ *
+ * To reach this goal we consider that there is some amount of buffering
+ * (in time) between dashdemux and the display sink. While we do not know
+ * the exact amount of buffering available, a safe and reasonable assertion
+ * is that there is at least a second (in running time).
+ *
+ * The average time to request and fully download a keyframe (with or
+ * without fragment header) is obtained by averaging the
+ * GstAdaptiveDemuxStream->last_download_time and is stored in
+ * GstDashDemuxStream->average_download_time. Those values include the
+ * network latency and full download time, which are more interesting and
+ * correct than just bitrates (with small download sizes, the impact of the
+ * network latency is much higher).
+ *
+ * The current position is calculated based on the fragment timestamp and
+ * the current keyframe index within that fragment. It is stored in
+ * GstDashDemuxStream->actual_position.
+ *
+ * The downstream position of the pipeline is obtained via QoS events and
+ * is stored in GstAdaptiveDemuxStream->qos_earliest_time (note: it's a
+ * running time value).
+ *
+ * The estimated buffering level between dashdemux and downstream is
+ * therefore:
+ *   buffering_level = running_time(actual_position) - qos_earliest_time
+ *
+ * In order to avoid rebuffering, we want to ensure that the next keyframe
+ * (including potential fragment header) we request will be download, demuxed
+ * and decoded in time so that it is not late. That next keyframe time is
+ * called the "target_time" and is calculated whenever we have finished
+ * pushing a keyframe downstream.
+ *
+ * One simple observation at this point is that we *need* to make sure that
+ * the target time is chosen such that:
+ *   running_time(target_time) > qos_earliest_time + average_download_time
+ *
+ * i.e. we chose a target time which will be greater than the time at which
+ * downstream will be once we request and download the keyframe (otherwise
+ * we're guaranteed to be late).
+ *
+ * This would provide the highest number of displayed frames per
+ * second, but it is just a *minimal* value and is not enough as-is,
+ * since it doesn't take into account the following items which could
+ * cause frames to arrive late (and therefore rebuffering):
+ * * Network jitter (i.e. by how much the download time can fluctuate)
+ * * Network stalling
+ * * Different keyframe sizes (and therefore download time)
+ * * Decoding speed
+ *
+ * Instead, we adjust the target time calculation based on the
+ * buffering_level.
+ *
+ * The smaller the buffering level is (i.e. the closer we are between
+ * current and downstream), the more aggresively we skip forward (and
+ * guarantee the keyframe will be downloaded, decoded and displayed in
+ * time). And the higher the buffering level, the least aggresivelly
+ * we need to skip forward (and therefore display more frames per
+ * second).
+ *
+ * Right now the threshold for agressive switching is set to 3
+ * average_download_time. Below that buffering level we set the target time
+ * to at least 3 average_download_time distance beyond the
+ * qos_earliest_time.
+ *
+ * If we are above that buffering level we set the target time to:
+ *      position(running_time(position) + average_download_time)
+ *
+ * The logic is therefore:
+ * WHILE(!EOS)
+ *   Calculate target_time
+ *   Advance to keyframe/fragment for that target_time
+ *   Adaptivedemux downloads that keyframe/fragment
+ *
  */
 
 #ifdef HAVE_CONFIG_H
@@ -734,9 +867,7 @@ gst_dash_demux_setup_all_streams (GstDashDemux * demux)
     stream->is_isobmff = gst_structure_has_name (s, "video/quicktime")
         || gst_structure_has_name (s, "audio/x-m4a");
     stream->first_sync_sample_always_after_moof = TRUE;
-    if (stream->is_isobmff
-        || gst_mpd_client_has_isoff_ondemand_profile (demux->client))
-      stream->adapter = gst_adapter_new ();
+    stream->adapter = gst_adapter_new ();
     gst_adaptive_demux_stream_set_caps (GST_ADAPTIVE_DEMUX_STREAM_CAST (stream),
         caps);
     if (tags)
@@ -745,6 +876,10 @@ gst_dash_demux_setup_all_streams (GstDashDemux * demux)
     stream->index = i;
     stream->pending_seek_ts = GST_CLOCK_TIME_NONE;
     stream->sidx_position = GST_CLOCK_TIME_NONE;
+    stream->actual_position = GST_CLOCK_TIME_NONE;
+    stream->target_time = GST_CLOCK_TIME_NONE;
+    /* Set a default average keyframe download time of a quarter of a second */
+    stream->average_download_time = 250 * GST_MSECOND;
     if (active_stream->cur_adapt_set &&
         active_stream->cur_adapt_set->RepresentationBase &&
         active_stream->cur_adapt_set->RepresentationBase->ContentProtection) {
@@ -1151,6 +1286,7 @@ gst_dash_demux_stream_update_fragment_info (GstAdaptiveDemuxStream * stream)
 
   /* Reset chunk size if any */
   stream->fragment.chunk_size = 0;
+  dashstream->current_fragment_keyframe_distance = GST_CLOCK_TIME_NONE;
 
   if (GST_ADAPTIVE_DEMUX_STREAM_NEED_HEADER (stream) && isombff) {
     gst_dash_demux_stream_update_headers_info (stream);
@@ -1164,8 +1300,7 @@ gst_dash_demux_stream_update_fragment_info (GstAdaptiveDemuxStream * stream)
   }
 
   if (dashstream->moof_sync_samples
-      && GST_ADAPTIVE_DEMUX (dashdemux)->
-      segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) {
+      && GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (dashdemux)) {
     GstDashStreamSyncSample *sync_sample =
         &g_array_index (dashstream->moof_sync_samples, GstDashStreamSyncSample,
         dashstream->current_sync_sample);
@@ -1173,12 +1308,39 @@ gst_dash_demux_stream_update_fragment_info (GstAdaptiveDemuxStream * stream)
     gst_mpd_client_get_next_fragment (dashdemux->client, dashstream->index,
         &fragment);
 
+    if (isombff && dashstream->sidx_position != GST_CLOCK_TIME_NONE
+        && SIDX (dashstream)->entries) {
+      GstSidxBoxEntry *entry = SIDX_CURRENT_ENTRY (dashstream);
+      dashstream->current_fragment_timestamp = fragment.timestamp = entry->pts;
+      dashstream->current_fragment_duration = fragment.duration =
+          entry->duration;
+    } else {
+      dashstream->current_fragment_timestamp = fragment.timestamp;
+      dashstream->current_fragment_duration = fragment.duration;
+    }
+
+    dashstream->current_fragment_keyframe_distance =
+        fragment.duration / dashstream->moof_sync_samples->len;
+    dashstream->actual_position =
+        fragment.timestamp +
+        dashstream->current_sync_sample *
+        dashstream->current_fragment_keyframe_distance;
+    if (stream->segment.rate < 0.0)
+      dashstream->actual_position +=
+          dashstream->current_fragment_keyframe_distance;
+    dashstream->actual_position =
+        MIN (dashstream->actual_position,
+        fragment.timestamp + fragment.duration);
+
     stream->fragment.uri = fragment.uri;
     stream->fragment.timestamp = GST_CLOCK_TIME_NONE;
     stream->fragment.duration = GST_CLOCK_TIME_NONE;
     stream->fragment.range_start = sync_sample->start_offset;
     stream->fragment.range_end = sync_sample->end_offset;
 
+    GST_DEBUG_OBJECT (stream->pad, "Actual position %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (dashstream->actual_position));
+
     return GST_FLOW_OK;
   }
 
@@ -1200,22 +1362,34 @@ gst_dash_demux_stream_update_fragment_info (GstAdaptiveDemuxStream * stream)
       GstSidxBoxEntry *entry = SIDX_CURRENT_ENTRY (dashstream);
       stream->fragment.range_start =
           dashstream->sidx_base_offset + entry->offset;
-      stream->fragment.timestamp = entry->pts;
-      stream->fragment.duration = entry->duration;
+      dashstream->actual_position = stream->fragment.timestamp = entry->pts;
+      dashstream->current_fragment_timestamp = stream->fragment.timestamp =
+          entry->pts;
+      dashstream->current_fragment_duration = stream->fragment.duration =
+          entry->duration;
       if (stream->demux->segment.rate < 0.0) {
         stream->fragment.range_end =
             stream->fragment.range_start + entry->size - 1;
+        dashstream->actual_position += entry->duration;
       } else {
         stream->fragment.range_end = fragment.range_end;
       }
     } else {
-      stream->fragment.timestamp = fragment.timestamp;
-      stream->fragment.duration = fragment.duration;
+      dashstream->actual_position = stream->fragment.timestamp =
+          fragment.timestamp;
+      dashstream->current_fragment_timestamp = fragment.timestamp;
+      dashstream->current_fragment_duration = stream->fragment.duration =
+          fragment.duration;
+      if (stream->demux->segment.rate < 0.0)
+        dashstream->actual_position += fragment.duration;
       stream->fragment.range_start =
           MAX (fragment.range_start, dashstream->sidx_base_offset);
       stream->fragment.range_end = fragment.range_end;
     }
 
+    GST_DEBUG_OBJECT (stream->pad, "Actual position %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (dashstream->actual_position));
+
     return GST_FLOW_OK;
   }
 
@@ -1334,6 +1508,7 @@ gst_dash_demux_stream_seek (GstAdaptiveDemuxStream * stream, gboolean forward,
     g_array_free (dashstream->moof_sync_samples, TRUE);
   dashstream->moof_sync_samples = NULL;
   dashstream->current_sync_sample = -1;
+  dashstream->target_time = GST_CLOCK_TIME_NONE;
 
   is_isobmff = gst_mpd_client_has_isoff_ondemand_profile (dashdemux->client);
 
@@ -1381,6 +1556,8 @@ gst_dash_demux_stream_seek (GstAdaptiveDemuxStream * stream, gboolean forward,
     }
   }
 
+  stream->discont = TRUE;
+
   return GST_FLOW_OK;
 }
 
@@ -1389,9 +1566,8 @@ gst_dash_demux_stream_has_next_sync_sample (GstAdaptiveDemuxStream * stream)
 {
   GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream;
 
-  if (dashstream->moof_sync_samples
-      && GST_ADAPTIVE_DEMUX (stream->demux)->
-      segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) {
+  if (dashstream->moof_sync_samples &&
+      GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (stream->demux)) {
     if (stream->demux->segment.rate > 0.0) {
       if (dashstream->current_sync_sample + 1 <
           dashstream->moof_sync_samples->len)
@@ -1423,32 +1599,100 @@ gst_dash_demux_stream_has_next_subfragment (GstAdaptiveDemuxStream * stream)
 }
 
 static gboolean
-gst_dash_demux_stream_advance_sync_sample (GstAdaptiveDemuxStream * stream)
+gst_dash_demux_stream_advance_sync_sample (GstAdaptiveDemuxStream * stream,
+    GstClockTime target_time)
 {
   GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream;
   gboolean fragment_finished = FALSE;
+  guint idx = -1;
+
+  if (GST_CLOCK_TIME_IS_VALID (target_time)) {
+    GST_LOG_OBJECT (stream->pad,
+        "target_time:%" GST_TIME_FORMAT " fragment ts %" GST_TIME_FORMAT
+        " average keyframe dist: %" GST_TIME_FORMAT
+        " current keyframe dist: %" GST_TIME_FORMAT
+        " fragment duration:%" GST_TIME_FORMAT,
+        GST_TIME_ARGS (target_time),
+        GST_TIME_ARGS (dashstream->current_fragment_timestamp),
+        GST_TIME_ARGS (dashstream->keyframe_average_distance),
+        GST_TIME_ARGS (dashstream->current_fragment_keyframe_distance),
+        GST_TIME_ARGS (stream->fragment.duration));
 
-  if (dashstream->moof_sync_samples
-      && GST_ADAPTIVE_DEMUX (stream->demux)->
-      segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) {
     if (stream->demux->segment.rate > 0.0) {
+      idx =
+          (target_time -
+          dashstream->current_fragment_timestamp) /
+          dashstream->current_fragment_keyframe_distance;
+
+      /* Prevent getting stuck in a loop due to rounding errors */
+      if (idx == dashstream->current_sync_sample)
+        idx++;
+    } else {
+      GstClockTime end_time =
+          dashstream->current_fragment_timestamp +
+          dashstream->current_fragment_duration;
+
+      if (end_time < target_time) {
+        idx = dashstream->moof_sync_samples->len;
+      } else {
+        idx =
+            (end_time -
+            target_time) / dashstream->current_fragment_keyframe_distance;
+        if (idx == dashstream->moof_sync_samples->len) {
+          dashstream->current_sync_sample = -1;
+          fragment_finished = TRUE;
+          goto beach;
+        }
+        idx = dashstream->moof_sync_samples->len - 1 - idx;
+      }
+
+      /* Prevent getting stuck in a loop due to rounding errors */
+      if (idx == dashstream->current_sync_sample) {
+        if (idx == 0) {
+          dashstream->current_sync_sample = -1;
+          fragment_finished = TRUE;
+          goto beach;
+        }
+
+        idx--;
+      }
+    }
+  }
+
+  GST_DEBUG_OBJECT (stream->pad,
+      "Advancing sync sample #%d target #%d",
+      dashstream->current_sync_sample, idx);
+
+  if (idx != -1 && idx >= dashstream->moof_sync_samples->len) {
+    dashstream->current_sync_sample = -1;
+    fragment_finished = TRUE;
+    goto beach;
+  }
+
+  if (stream->demux->segment.rate > 0.0) {
+    /* Try to get the sync sample for the target time */
+    if (idx != -1) {
+      dashstream->current_sync_sample = idx;
+    } else {
       dashstream->current_sync_sample++;
       if (dashstream->current_sync_sample >= dashstream->moof_sync_samples->len) {
         fragment_finished = TRUE;
       }
+    }
+  } else {
+    if (idx != -1) {
+      dashstream->current_sync_sample = idx;
+    } else if (dashstream->current_sync_sample == -1) {
+      dashstream->current_sync_sample = dashstream->moof_sync_samples->len - 1;
+    } else if (dashstream->current_sync_sample == 0) {
+      dashstream->current_sync_sample = -1;
+      fragment_finished = TRUE;
     } else {
-      if (dashstream->current_sync_sample == -1) {
-        dashstream->current_sync_sample =
-            dashstream->moof_sync_samples->len - 1;
-      } else if (dashstream->current_sync_sample == 0) {
-        dashstream->current_sync_sample = -1;
-        fragment_finished = TRUE;
-      } else {
-        dashstream->current_sync_sample--;
-      }
+      dashstream->current_sync_sample--;
     }
   }
 
+beach:
   GST_DEBUG_OBJECT (stream->pad,
       "Advancing sync sample #%d fragment_finished:%d",
       dashstream->current_sync_sample, fragment_finished);
@@ -1504,9 +1748,8 @@ gst_dash_demux_stream_has_next_fragment (GstAdaptiveDemuxStream * stream)
   GstDashDemux *dashdemux = GST_DASH_DEMUX_CAST (stream->demux);
   GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream;
 
-  if (dashstream->moof_sync_samples
-      && GST_ADAPTIVE_DEMUX (dashdemux)->
-      segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) {
+  if (dashstream->moof_sync_samples &&
+      GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (dashdemux)) {
     if (gst_dash_demux_stream_has_next_sync_sample (stream))
       return TRUE;
   }
@@ -1520,19 +1763,329 @@ gst_dash_demux_stream_has_next_fragment (GstAdaptiveDemuxStream * stream)
       dashstream->active_stream, stream->demux->segment.rate > 0.0);
 }
 
+/* The goal here is to figure out, once we have pushed a keyframe downstream,
+ * what the next ideal keyframe to download is.
+ * 
+ * This is done based on:
+ * * the current internal position (i.e. actual_position)
+ * * the reported downstream position (QoS feedback)
+ * * the average keyframe download time (average_download_time)
+ */
+static GstClockTime
+gst_dash_demux_stream_get_target_time (GstDashDemux * dashdemux,
+    GstAdaptiveDemuxStream * stream, GstClockTime cur_position,
+    GstClockTime min_skip)
+{
+  GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream;
+  GstClockTime cur_running, min_running, min_position;
+  GstClockTimeDiff diff;
+  GstClockTime ret = cur_position;
+  GstClockTime deadline;
+  GstClockTime earliest_time = GST_CLOCK_TIME_NONE;
+
+  g_assert (min_skip > 0);
+
+  /* minimum stream position we have to skip to */
+  if (stream->segment.rate > 0)
+    min_position = cur_position + min_skip;
+  else if (cur_position < min_skip)
+    min_position = 0;
+  else
+    min_position = cur_position - min_skip;
+
+  /* Use current clock time or the QoS earliest time, whichever is further in
+   * the future. The QoS time is only updated on every QoS event and
+   * especially not if e.g. a videodecoder or converter drops a frame further
+   * downstream.
+   *
+   * We only use the times if we ever received a QoS event since the last
+   * flush, as otherwise base_time and clock might not be correct because of a
+   * still pre-rolling sink
+   */
+  if (stream->qos_earliest_time != GST_CLOCK_TIME_NONE) {
+    GstClock *clock;
+
+    clock = gst_element_get_clock (GST_ELEMENT_CAST (dashdemux));
+
+    if (clock) {
+      GstClockTime base_time;
+      GstClockTime now_time;
+
+      base_time = gst_element_get_base_time (GST_ELEMENT_CAST (dashdemux));
+      now_time = gst_clock_get_time (clock);
+      if (now_time > base_time)
+        now_time -= base_time;
+      else
+        now_time = 0;
+
+      gst_object_unref (clock);
+
+      earliest_time = MAX (now_time, stream->qos_earliest_time);
+    } else {
+      earliest_time = stream->qos_earliest_time;
+    }
+  }
+
+  /* our current position in running time */
+  cur_running =
+      gst_segment_to_running_time (&stream->segment, GST_FORMAT_TIME,
+      cur_position);
+
+  /* the minimum position we have to skip to in running time */
+  min_running =
+      gst_segment_to_running_time (&stream->segment, GST_FORMAT_TIME,
+      min_position);
+
+  GST_DEBUG_OBJECT (stream->pad,
+      "position: current %" GST_TIME_FORMAT " min next %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (cur_position), GST_TIME_ARGS (min_position));
+  GST_DEBUG_OBJECT (stream->pad,
+      "running time: current %" GST_TIME_FORMAT " min next %" GST_TIME_FORMAT
+      " earliest %" GST_TIME_FORMAT, GST_TIME_ARGS (cur_running),
+      GST_TIME_ARGS (min_running), GST_TIME_ARGS (earliest_time));
+
+  /* Take configured maximum video bandwidth and framerate into account */
+  {
+    GstClockTime min_run_dist, min_frame_dist, diff = 0;
+    guint max_fps_n, max_fps_d;
+
+    min_run_dist = min_skip / ABS (stream->segment.rate);
+
+    if (dashdemux->max_video_framerate_n != 0) {
+      max_fps_n = dashdemux->max_video_framerate_n;
+      max_fps_d = dashdemux->max_video_framerate_d;
+    } else {
+      /* more than 10 fps is not very useful if we're skipping anyway */
+      max_fps_n = 10;
+      max_fps_d = 1;
+    }
+
+    min_frame_dist = gst_util_uint64_scale_ceil (GST_SECOND,
+        max_fps_d, max_fps_n);
+
+    GST_DEBUG_OBJECT (stream->pad,
+        "Have max framerate %d/%d - Min dist %" GST_TIME_FORMAT
+        ", min requested dist %" GST_TIME_FORMAT,
+        max_fps_n, max_fps_d,
+        GST_TIME_ARGS (min_run_dist), GST_TIME_ARGS (min_frame_dist));
+    if (min_frame_dist > min_run_dist)
+      diff = MAX (diff, min_frame_dist - min_run_dist);
+
+    if (dashdemux->max_bitrate != 0) {
+      guint64 max_bitrate = gst_util_uint64_scale_ceil (GST_SECOND,
+          8 * dashstream->keyframe_average_size,
+          dashstream->keyframe_average_distance) * ABS (stream->segment.rate);
+
+      if (max_bitrate > dashdemux->max_bitrate) {
+        min_frame_dist = gst_util_uint64_scale_ceil (GST_SECOND,
+            8 * dashstream->keyframe_average_size,
+            dashdemux->max_bitrate) * ABS (stream->segment.rate);
+
+        GST_DEBUG_OBJECT (stream->pad,
+            "Have max bitrate %u - Min dist %" GST_TIME_FORMAT
+            ", min requested dist %" GST_TIME_FORMAT, dashdemux->max_bitrate,
+            GST_TIME_ARGS (min_run_dist), GST_TIME_ARGS (min_frame_dist));
+        if (min_frame_dist > min_run_dist)
+          diff = MAX (diff, min_frame_dist - min_run_dist);
+      }
+    }
+
+    if (diff > 0) {
+      GST_DEBUG_OBJECT (stream->pad,
+          "Skipping further ahead by %" GST_TIME_FORMAT, GST_TIME_ARGS (diff));
+      min_running += diff;
+    }
+  }
+
+  if (earliest_time == GST_CLOCK_TIME_NONE) {
+    GstClockTime run_key_dist;
+
+    run_key_dist =
+        dashstream->keyframe_average_distance / ABS (stream->segment.rate);
+
+    /* If we don't have downstream information (such as at startup or
+     * without live sinks), just get the next time by taking the minimum
+     * amount we have to skip ahead
+     * Except if it takes us longer to download */
+    if (run_key_dist > dashstream->average_download_time)
+      ret =
+          gst_segment_position_from_running_time (&stream->segment,
+          GST_FORMAT_TIME, min_running);
+    else
+      ret = gst_segment_position_from_running_time (&stream->segment,
+          GST_FORMAT_TIME,
+          min_running - run_key_dist + dashstream->average_download_time);
+
+    GST_DEBUG_OBJECT (stream->pad,
+        "Advancing to %" GST_TIME_FORMAT " (was %" GST_TIME_FORMAT ")",
+        GST_TIME_ARGS (ret), GST_TIME_ARGS (min_position));
+
+    goto out;
+  }
+
+  /* Figure out the difference, in running time, between where we are and
+   * where downstream is */
+  diff = min_running - earliest_time;
+  GST_LOG_OBJECT (stream->pad,
+      "min_running %" GST_TIME_FORMAT " diff %" GST_STIME_FORMAT
+      " average_download %" GST_TIME_FORMAT, GST_TIME_ARGS (min_running),
+      GST_STIME_ARGS (diff), GST_TIME_ARGS (dashstream->average_download_time));
+
+  /* Have at least 500ms or 3 keyframes safety between current position and downstream */
+  deadline = MAX (500 * GST_MSECOND, 3 * dashstream->average_download_time);
+
+  /* The furthest away we are from the current position, the least we need to advance */
+  if (diff < 0 || diff < deadline) {
+    /* Force skipping (but not more than 1s ahead) */
+    ret =
+        gst_segment_position_from_running_time (&stream->segment,
+        GST_FORMAT_TIME, earliest_time + MIN (deadline, GST_SECOND));
+    GST_DEBUG_OBJECT (stream->pad,
+        "MUST SKIP to at least %" GST_TIME_FORMAT " (was %" GST_TIME_FORMAT ")",
+        GST_TIME_ARGS (ret), GST_TIME_ARGS (min_position));
+  } else if (diff < 4 * dashstream->average_download_time) {
+    /* Go forward a bit less aggresively (and at most 1s forward) */
+    ret = gst_segment_position_from_running_time (&stream->segment,
+        GST_FORMAT_TIME, min_running + MIN (GST_SECOND,
+            2 * dashstream->average_download_time));
+    GST_DEBUG_OBJECT (stream->pad,
+        "MUST SKIP to at least %" GST_TIME_FORMAT " (was %" GST_TIME_FORMAT ")",
+        GST_TIME_ARGS (ret), GST_TIME_ARGS (min_position));
+  } else {
+    /* Get the next position satisfying the download time */
+    ret = gst_segment_position_from_running_time (&stream->segment,
+        GST_FORMAT_TIME, min_running);
+    GST_DEBUG_OBJECT (stream->pad,
+        "Advance to %" GST_TIME_FORMAT " (was %" GST_TIME_FORMAT ")",
+        GST_TIME_ARGS (ret), GST_TIME_ARGS (min_position));
+  }
+
+out:
+
+  {
+    GstClockTime cur_skip =
+        (cur_position < ret) ? ret - cur_position : cur_position - ret;
+
+    if (dashstream->average_skip_size == 0) {
+      dashstream->average_skip_size = cur_skip;
+    } else {
+      dashstream->average_skip_size =
+          (cur_skip + 3 * dashstream->average_skip_size) / 4;
+    }
+
+    if (dashstream->average_skip_size >
+        cur_skip + dashstream->keyframe_average_distance
+        && dashstream->average_skip_size > min_skip) {
+      if (stream->segment.rate > 0)
+        ret = cur_position + dashstream->average_skip_size;
+      else if (cur_position > dashstream->average_skip_size)
+        ret = cur_position - dashstream->average_skip_size;
+      else
+        ret = 0;
+    }
+  }
+
+  return ret;
+}
+
 static GstFlowReturn
 gst_dash_demux_stream_advance_fragment (GstAdaptiveDemuxStream * stream)
 {
   GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream;
   GstDashDemux *dashdemux = GST_DASH_DEMUX_CAST (stream->demux);
+  GstClockTime target_time = GST_CLOCK_TIME_NONE;
+  GstClockTime previous_position;
+  GstFlowReturn ret;
 
   GST_DEBUG_OBJECT (stream->pad, "Advance fragment");
 
+  /* Update download statistics */
+  if (dashstream->moof_sync_samples &&
+      GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (dashdemux) &&
+      GST_CLOCK_TIME_IS_VALID (stream->last_download_time)) {
+    if (GST_CLOCK_TIME_IS_VALID (dashstream->average_download_time)) {
+      dashstream->average_download_time =
+          (3 * dashstream->average_download_time +
+          stream->last_download_time) / 4;
+    } else {
+      dashstream->average_download_time = stream->last_download_time;
+    }
+
+    GST_DEBUG_OBJECT (stream->pad,
+        "Download time last: %" GST_TIME_FORMAT " average: %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (stream->last_download_time),
+        GST_TIME_ARGS (dashstream->average_download_time));
+  }
+
+  previous_position = dashstream->actual_position;
+
+  /* Update internal position */
+  if (GST_CLOCK_TIME_IS_VALID (dashstream->actual_position)) {
+    GstClockTime dur;
+    if (dashstream->moof_sync_samples
+        && GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (dashdemux)) {
+      GST_LOG_OBJECT (stream->pad, "current sync sample #%d",
+          dashstream->current_sync_sample);
+      if (dashstream->current_sync_sample == -1) {
+        dur = 0;
+      } else if (dashstream->current_sync_sample <
+          dashstream->moof_sync_samples->len) {
+        dur = dashstream->current_fragment_keyframe_distance;
+      } else {
+        if (gst_mpd_client_has_isoff_ondemand_profile (dashdemux->client) &&
+            dashstream->sidx_position != GST_CLOCK_TIME_NONE
+            && SIDX (dashstream)->entries) {
+          GstSidxBoxEntry *entry = SIDX_CURRENT_ENTRY (dashstream);
+          dur = entry->duration;
+        } else {
+          dur =
+              dashstream->current_fragment_timestamp +
+              dashstream->current_fragment_duration -
+              dashstream->actual_position;
+        }
+      }
+    } else if (gst_mpd_client_has_isoff_ondemand_profile (dashdemux->client) &&
+        dashstream->sidx_position != GST_CLOCK_TIME_NONE
+        && SIDX (dashstream)->entries) {
+      GstSidxBoxEntry *entry = SIDX_CURRENT_ENTRY (dashstream);
+      dur = entry->duration;
+    } else {
+      dur = stream->fragment.duration;
+    }
+
+    if (dashstream->moof_sync_samples
+        && GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (dashdemux)) {
+      /* We just downloaded the header, we actually use the previous
+       * target_time now as it was not used up yet */
+      if (dashstream->current_sync_sample == -1)
+        target_time = dashstream->target_time;
+      else
+        target_time =
+            gst_dash_demux_stream_get_target_time (dashdemux, stream,
+            dashstream->actual_position, dur);
+      dashstream->actual_position = target_time;
+    } else {
+      /* Adjust based on direction */
+      if (stream->demux->segment.rate > 0.0)
+        dashstream->actual_position += dur;
+      else if (dashstream->actual_position >= dur)
+        dashstream->actual_position -= dur;
+      else
+        dashstream->actual_position = 0;
+    }
+
+    GST_DEBUG_OBJECT (stream->pad, "Actual position %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (dashstream->actual_position));
+  }
+  dashstream->target_time = target_time;
+
+  GST_DEBUG_OBJECT (stream->pad, "target_time: %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (target_time));
+
   /* If downloading only keyframes, switch to the next one or fall through */
-  if (dashstream->moof_sync_samples
-      && GST_ADAPTIVE_DEMUX (dashdemux)->
-      segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) {
-    if (gst_dash_demux_stream_advance_sync_sample (stream))
+  if (dashstream->moof_sync_samples &&
+      GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (dashdemux)) {
+    if (gst_dash_demux_stream_advance_sync_sample (stream, target_time))
       return GST_FLOW_OK;
   }
 
@@ -1548,21 +2101,81 @@ gst_dash_demux_stream_advance_fragment (GstAdaptiveDemuxStream * stream)
   dashstream->moof_sync_samples = NULL;
   dashstream->current_sync_sample = -1;
 
-  if (gst_mpd_client_has_isoff_ondemand_profile (dashdemux->client)) {
-    if (gst_dash_demux_stream_advance_subfragment (stream))
-      return GST_FLOW_OK;
-  }
+  /* Check if we just need to 'advance' to the next fragment, or if we
+   * need to skip by more. */
+  if (GST_CLOCK_TIME_IS_VALID (target_time)
+      && GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (stream->demux) &&
+      dashstream->active_stream->mimeType == GST_STREAM_VIDEO) {
+    GstClockTime actual_ts;
+    GstSeekFlags flags = 0;
+
+    /* Key-unit trick mode, seek to fragment containing target time
+     *
+     * We first try seeking without snapping. As above code to skip keyframes
+     * in the current fragment was not successful, we should go at least one
+     * fragment ahead. Due to rounding errors we could end up at the same
+     * fragment again here, in which case we retry seeking with the SNAP_AFTER
+     * flag.
+     *
+     * We don't always set that flag as we would then end up one further
+     * fragment in the future in all good cases.
+     */
+    while (TRUE) {
+      ret =
+          gst_dash_demux_stream_seek (stream, (stream->segment.rate > 0), flags,
+          target_time, &actual_ts);
+
+      if (ret != GST_FLOW_OK) {
+        GST_WARNING_OBJECT (stream->pad, "Failed to seek to %" GST_TIME_FORMAT,
+            GST_TIME_ARGS (target_time));
+        /* Give up */
+        if (flags != 0)
+          break;
 
-  gst_isoff_sidx_parser_clear (&dashstream->sidx_parser);
-  gst_isoff_sidx_parser_init (&dashstream->sidx_parser);
-  dashstream->sidx_base_offset = 0;
-  dashstream->sidx_position = GST_CLOCK_TIME_NONE;
-  dashstream->allow_sidx = TRUE;
-  if (dashstream->adapter)
-    gst_adapter_clear (dashstream->adapter);
+        /* Retry with skipping ahead */
+        flags |= GST_SEEK_FLAG_SNAP_AFTER;
+        continue;
+      }
 
-  return gst_mpd_client_advance_segment (dashdemux->client,
-      dashstream->active_stream, stream->demux->segment.rate > 0.0);
+      GST_DEBUG_OBJECT (stream->pad,
+          "Skipped to %" GST_TIME_FORMAT " (wanted %" GST_TIME_FORMAT ", was %"
+          GST_TIME_FORMAT ")", GST_TIME_ARGS (actual_ts),
+          GST_TIME_ARGS (target_time), GST_TIME_ARGS (previous_position));
+
+      if ((stream->segment.rate > 0 && actual_ts <= previous_position) ||
+          (stream->segment.rate < 0 && actual_ts >= previous_position)) {
+        /* Give up */
+        if (flags != 0)
+          break;
+
+        /* Retry with forcing skipping ahead */
+        flags |= GST_SEEK_FLAG_SNAP_AFTER;
+
+        continue;
+      }
+
+      /* All good */
+      break;
+    }
+  } else {
+    /* Normal mode, advance to the next fragment */
+    if (gst_mpd_client_has_isoff_ondemand_profile (dashdemux->client)) {
+      if (gst_dash_demux_stream_advance_subfragment (stream))
+        return GST_FLOW_OK;
+    }
+
+    if (dashstream->adapter)
+      gst_adapter_clear (dashstream->adapter);
+
+    gst_isoff_sidx_parser_clear (&dashstream->sidx_parser);
+    dashstream->sidx_base_offset = 0;
+    dashstream->sidx_position = GST_CLOCK_TIME_NONE;
+    dashstream->allow_sidx = TRUE;
+
+    ret = gst_mpd_client_advance_segment (dashdemux->client,
+        dashstream->active_stream, stream->demux->segment.rate > 0.0);
+  }
+  return ret;
 }
 
 static gboolean
@@ -1582,6 +2195,12 @@ gst_dash_demux_stream_select_bitrate (GstAdaptiveDemuxStream * stream,
     goto end;
   }
 
+  /* In key-frame trick mode don't change bitrates */
+  if (GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (demux)) {
+    GST_DEBUG_OBJECT (demux, "In key-frame trick mode, not changing bitrates");
+    goto end;
+  }
+
   /* retrieve representation list */
   if (active_stream->cur_adapt_set)
     rep_list = active_stream->cur_adapt_set->Representations;
@@ -1603,20 +2222,20 @@ gst_dash_demux_stream_select_bitrate (GstAdaptiveDemuxStream * stream,
       "Trying to change to bitrate under : %" G_GUINT64_FORMAT, bitrate);
 
   /* get representation index with current max_bandwidth */
-  if ((base_demux->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) ||
+  if (GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (base_demux) ||  
       ABS (base_demux->segment.rate) <= 1.0) {
     new_index =
-        gst_mpdparser_get_rep_idx_with_max_bandwidth (rep_list, (gint)bitrate,
+        gst_mpdparser_get_rep_idx_with_max_bandwidth (rep_list, bitrate,
         demux->max_video_width, demux->max_video_height);
   } else {
     new_index =
         gst_mpdparser_get_rep_idx_with_max_bandwidth (rep_list,
-        (gint)(bitrate / ABS (base_demux->segment.rate)), demux->max_video_width,
+        bitrate / ABS (base_demux->segment.rate), demux->max_video_width,
         demux->max_video_height);
   }
 #else
   /* get representation index with current max_bandwidth */
-  if ((base_demux->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) ||
+  if (GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (base_demux) ||
       ABS (base_demux->segment.rate) <= 1.0) {
     new_index =
         gst_mpdparser_get_rep_idx_with_max_bandwidth (rep_list, bitrate,
@@ -1694,6 +2313,7 @@ gst_dash_demux_stream_select_bitrate (GstAdaptiveDemuxStream * stream,
       g_array_free (dashstream->moof_sync_samples, TRUE);
     dashstream->moof_sync_samples = NULL;
     dashstream->current_sync_sample = -1;
+    dashstream->target_time = GST_CLOCK_TIME_NONE;
   }
 
 end:
@@ -1789,7 +2409,9 @@ gst_dash_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek)
   /* Update the current sequence on all streams */
   for (iter = streams; iter; iter = g_list_next (iter)) {
     GstAdaptiveDemuxStream *stream = iter->data;
+    GstDashDemuxStream *dashstream = iter->data;
 
+    dashstream->average_skip_size = 0;
     if (gst_dash_demux_stream_seek (stream, rate >= 0, 0, target_pos,
             NULL) != GST_FLOW_OK)
       return FALSE;
@@ -1905,6 +2527,12 @@ gst_dash_demux_update_manifest_data (GstAdaptiveDemux * demux,
          * 10 microseconds to get back to the correct segment. The errors are
          * usually on the order of nanoseconds so it should be enough.
          */
+
+        /* _get_next_fragment_timestamp() returned relative timestamp to
+         * corresponding period start, but _client_stream_seek expects absolute
+         * MPD time. */
+        ts += gst_mpd_parser_get_period_start_time (dashdemux->client);
+
         GST_DEBUG_OBJECT (GST_ADAPTIVE_DEMUX_STREAM_PAD (demux_stream),
             "Current position: %" GST_TIME_FORMAT ", updating to %"
             GST_TIME_FORMAT, GST_TIME_ARGS (ts),
@@ -2027,6 +2655,9 @@ gst_dash_demux_stream_fragment_start (GstAdaptiveDemux * demux,
   GstDashDemux *dashdemux = GST_DASH_DEMUX_CAST (demux);
   GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream;
 
+  GST_LOG_OBJECT (stream->pad, "Actual position %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (dashstream->actual_position));
+
   dashstream->current_index_header_or_data = 0;
   dashstream->current_offset = -1;
 
@@ -2037,7 +2668,7 @@ gst_dash_demux_stream_fragment_start (GstAdaptiveDemux * demux,
    * buffer. We need offsets to be consistent between moof and mdat
    */
   if (dashstream->is_isobmff && dashdemux->allow_trickmode_key_units
-      && (demux->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS)
+      && GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (demux)
       && dashstream->active_stream->mimeType == GST_STREAM_VIDEO)
     stream->discont = TRUE;
 
@@ -2058,15 +2689,14 @@ gst_dash_demux_stream_fragment_finished (GstAdaptiveDemux * demux,
    * buffer. We need offsets to be consistent between moof and mdat
    */
   if (dashstream->is_isobmff && dashdemux->allow_trickmode_key_units
-      && (demux->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS)
+      && GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (demux)
       && dashstream->active_stream->mimeType == GST_STREAM_VIDEO)
     stream->discont = TRUE;
 
   /* Only handle fragment advancing specifically for SIDX if we're not
    * in key unit mode */
   if (!(dashstream->moof_sync_samples
-          && GST_ADAPTIVE_DEMUX (dashdemux)->
-          segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS)
+          && GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (dashdemux))
       && gst_mpd_client_has_isoff_ondemand_profile (dashdemux->client)
       && dashstream->sidx_parser.status == GST_ISOFF_SIDX_PARSER_FINISHED) {
     /* fragment is advanced on data_received when byte limits are reached */
@@ -2096,8 +2726,7 @@ gst_dash_demux_need_another_chunk (GstAdaptiveDemuxStream * stream)
    * random guess for the moof size
    */
   if (dashstream->is_isobmff
-      && (GST_ADAPTIVE_DEMUX (stream->demux)->
-          segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS)
+      && GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (stream->demux)
       && dashstream->active_stream->mimeType == GST_STREAM_VIDEO
       && !stream->downloading_header && !stream->downloading_index
       && dashdemux->allow_trickmode_key_units) {
@@ -2108,20 +2737,41 @@ gst_dash_demux_need_another_chunk (GstAdaptiveDemuxStream * stream)
       /* Do we have the first fourcc already or are we in the middle */
       if (dashstream->isobmff_parser.current_fourcc == 0) {
         stream->fragment.chunk_size += dashstream->moof_average_size;
-        if (dashstream->first_sync_sample_always_after_moof)
-          stream->fragment.chunk_size +=
-              dashstream->first_sync_sample_average_size;
+        if (dashstream->first_sync_sample_always_after_moof) {
+          gboolean first = FALSE;
+          /* Check if we'll really need that first sample */
+          if (GST_CLOCK_TIME_IS_VALID (dashstream->target_time)) {
+            first =
+                ((dashstream->target_time -
+                    dashstream->current_fragment_timestamp) /
+                dashstream->keyframe_average_distance) == 0 ? TRUE : FALSE;
+          } else if (stream->segment.rate > 0) {
+            first = TRUE;
+          }
+
+          if (first)
+            stream->fragment.chunk_size += dashstream->keyframe_average_size;
+        }
       }
 
       if (gst_mpd_client_has_isoff_ondemand_profile (dashdemux->client) &&
           dashstream->sidx_parser.sidx.entries) {
-        guint64 sidx_end_offset =
+        guint64 sidx_start_offset =
             dashstream->sidx_base_offset +
-            SIDX_CURRENT_ENTRY (dashstream)->offset +
-            SIDX_CURRENT_ENTRY (dashstream)->size;
-        guint64 downloaded_end_offset =
-            dashstream->current_offset +
-            gst_adapter_available (dashstream->adapter);
+            SIDX_CURRENT_ENTRY (dashstream)->offset;
+        guint64 sidx_end_offset =
+            sidx_start_offset + SIDX_CURRENT_ENTRY (dashstream)->size;
+        guint64 downloaded_end_offset;
+
+        if (dashstream->current_offset == GST_CLOCK_TIME_NONE) {
+          downloaded_end_offset = sidx_start_offset;
+        } else {
+          downloaded_end_offset =
+              dashstream->current_offset +
+              gst_adapter_available (dashstream->adapter);
+        }
+
+        downloaded_end_offset = MAX (downloaded_end_offset, sidx_start_offset);
 
         if (stream->fragment.chunk_size +
             downloaded_end_offset > sidx_end_offset) {
@@ -2137,7 +2787,9 @@ gst_dash_demux_need_another_chunk (GstAdaptiveDemuxStream * stream)
             &g_array_index (dashstream->moof_sync_samples,
             GstDashStreamSyncSample, 0);
         guint64 end_offset = sync_sample->end_offset + 1;
-        guint64 downloaded_end_offset =
+        guint64 downloaded_end_offset;
+
+        downloaded_end_offset =
             dashstream->current_offset +
             gst_adapter_available (dashstream->adapter);
 
@@ -2170,11 +2822,11 @@ gst_dash_demux_need_another_chunk (GstAdaptiveDemuxStream * stream)
      * trickmodes while doing chunked downloading. In that case
      * just download from here to the end now */
     if (dashstream->moof
-        && (GST_ADAPTIVE_DEMUX (stream->demux)->
-            segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS))
+        && GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (stream->demux)) {
       stream->fragment.chunk_size = -1;
-    else
+    } else {
       stream->fragment.chunk_size = 0;
+    }
   }
 
   return stream->fragment.chunk_size != 0;
@@ -2568,31 +3220,80 @@ gst_dash_demux_find_sync_samples (GstAdaptiveDemux * demux,
   }
 
   {
-    GstDashStreamSyncSample *sync_sample =
-        &g_array_index (dash_stream->moof_sync_samples, GstDashStreamSyncSample,
-        0);
-    guint size = sync_sample->end_offset + 1 - sync_sample->start_offset;
-
-    if (dash_stream->first_sync_sample_average_size) {
-      if (dash_stream->first_sync_sample_average_size < size)
-        dash_stream->first_sync_sample_average_size =
-            (size * 3 + dash_stream->first_sync_sample_average_size) / 4;
-      else
-        dash_stream->first_sync_sample_average_size =
-            (size + dash_stream->first_sync_sample_average_size * 3) / 4;
-    } else {
-      dash_stream->first_sync_sample_average_size = size;
+    GstDashStreamSyncSample *sync_sample;
+    guint i;
+    guint size;
+    GstClockTime current_keyframe_distance;
+
+    for (i = 0; i < dash_stream->moof_sync_samples->len; i++) {
+      sync_sample =
+          &g_array_index (dash_stream->moof_sync_samples,
+          GstDashStreamSyncSample, i);
+      size = sync_sample->end_offset + 1 - sync_sample->start_offset;
+
+      if (dash_stream->keyframe_average_size) {
+        /* Over-estimate the keyframe size */
+        if (dash_stream->keyframe_average_size < size)
+          dash_stream->keyframe_average_size =
+              (size * 3 + dash_stream->keyframe_average_size) / 4;
+        else
+          dash_stream->keyframe_average_size =
+              (size + dash_stream->keyframe_average_size * 3) / 4;
+      } else {
+        dash_stream->keyframe_average_size = size;
+      }
+
+      if (i == 0) {
+        if (dash_stream->moof_offset + dash_stream->moof_size + 8 <
+            sync_sample->start_offset) {
+          dash_stream->first_sync_sample_after_moof = FALSE;
+          dash_stream->first_sync_sample_always_after_moof = FALSE;
+        } else {
+          dash_stream->first_sync_sample_after_moof =
+              (dash_stream->moof_sync_samples->len == 1
+              || demux->segment.rate > 0.0);
+        }
+      }
     }
 
-    if (dash_stream->moof_offset + dash_stream->moof_size + 8 <
-        sync_sample->start_offset) {
-      dash_stream->first_sync_sample_after_moof = FALSE;
-      dash_stream->first_sync_sample_always_after_moof = FALSE;
+    g_assert (stream->fragment.duration != 0);
+    g_assert (stream->fragment.duration != GST_CLOCK_TIME_NONE);
+
+    if (gst_mpd_client_has_isoff_ondemand_profile (dashdemux->client)
+        && dash_stream->sidx_position != GST_CLOCK_TIME_NONE
+        && SIDX (dash_stream)->entries) {
+      GstSidxBoxEntry *entry = SIDX_CURRENT_ENTRY (dash_stream);
+      current_keyframe_distance =
+          entry->duration / dash_stream->moof_sync_samples->len;
     } else {
-      dash_stream->first_sync_sample_after_moof =
-          (dash_stream->moof_sync_samples->len == 1
-          || demux->segment.rate > 0.0);
+      current_keyframe_distance =
+          stream->fragment.duration / dash_stream->moof_sync_samples->len;
     }
+    dash_stream->current_fragment_keyframe_distance = current_keyframe_distance;
+
+    if (dash_stream->keyframe_average_distance) {
+      /* Under-estimate the keyframe distance */
+      if (dash_stream->keyframe_average_distance > current_keyframe_distance)
+        dash_stream->keyframe_average_distance =
+            (dash_stream->keyframe_average_distance * 3 +
+            current_keyframe_distance) / 4;
+      else
+        dash_stream->keyframe_average_distance =
+            (dash_stream->keyframe_average_distance +
+            current_keyframe_distance * 3) / 4;
+    } else {
+      dash_stream->keyframe_average_distance = current_keyframe_distance;
+    }
+
+    GST_DEBUG_OBJECT (stream->pad,
+        "average keyframe sample size: %" G_GUINT64_FORMAT,
+        dash_stream->keyframe_average_size);
+    GST_DEBUG_OBJECT (stream->pad,
+        "average keyframe distance: %" GST_TIME_FORMAT " (%" GST_TIME_FORMAT
+        ")", GST_TIME_ARGS (dash_stream->keyframe_average_distance),
+        GST_TIME_ARGS (current_keyframe_distance));
+    GST_DEBUG_OBJECT (stream->pad, "first sync sample after moof: %d",
+        dash_stream->first_sync_sample_after_moof);
   }
 
   return TRUE;
@@ -2634,15 +3335,29 @@ gst_dash_demux_handle_isobmff (GstAdaptiveDemux * demux,
      * reuse the data if the sync sample follows the moof */
     if (dash_stream->active_stream->mimeType == GST_STREAM_VIDEO
         && gst_dash_demux_find_sync_samples (demux, stream) &&
-        GST_ADAPTIVE_DEMUX (stream->demux)->
-        segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) {
+        GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (stream->demux)) {
+      guint idx = -1;
+
+      if (GST_CLOCK_TIME_IS_VALID (dash_stream->target_time)) {
+        idx =
+            (dash_stream->target_time -
+            dash_stream->current_fragment_timestamp) /
+            dash_stream->current_fragment_keyframe_distance;
+      } else if (stream->segment.rate > 0) {
+        idx = 0;
+      }
+
+      GST_DEBUG_OBJECT (stream->pad, "target %" GST_TIME_FORMAT " idx %d",
+          GST_TIME_ARGS (dash_stream->target_time), idx);
+      /* Figure out target time */
 
-      if (dash_stream->first_sync_sample_after_moof) {
+      if (dash_stream->first_sync_sample_after_moof && idx == 0) {
         /* If we're here, don't throw away data but collect sync
          * sample while we're at it below. We're doing chunked
          * downloading so might need to adjust the next chunk size for
          * the remainder */
         dash_stream->current_sync_sample = 0;
+        GST_DEBUG_OBJECT (stream->pad, "Using first keyframe after header");
       }
     }
 
@@ -2701,9 +3416,7 @@ gst_dash_demux_handle_isobmff (GstAdaptiveDemux * demux,
   /* We're actually running in key-units trick mode */
   if (dash_stream->active_stream->mimeType == GST_STREAM_VIDEO
       && dash_stream->moof_sync_samples
-      && GST_ADAPTIVE_DEMUX (stream->demux)->
-      segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) {
-
+      && GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (stream->demux)) {
     if (dash_stream->current_sync_sample == -1) {
       /* We're doing chunked downloading and wait for finishing the current
        * chunk so we can jump to the first keyframe */
@@ -2989,7 +3702,7 @@ struct Rfc5322TimeZone
 
 /*
  Parse an RFC5322 (section 3.3) date-time from the Date: field in the
- HTTP response.
+ HTTP response. 
  See https://tools.ietf.org/html/rfc5322#section-3.3
 */
 static GstDateTime *
index d547d29..fdbbf31 100644 (file)
@@ -35,7 +35,7 @@
 #include <gst/base/gstadapter.h>
 #include <gst/base/gstdataqueue.h>
 #include "gstmpdparser.h"
-#include "gstisoff.h"
+#include <gst/isoff/gstisoff.h>
 #include <gst/uridownloader/gsturidownloader.h>
 
 G_BEGIN_DECLS
@@ -94,8 +94,24 @@ struct _GstDashDemuxStream
   GArray *moof_sync_samples;
   guint current_sync_sample;
 
-  guint64 moof_average_size, first_sync_sample_average_size;
+  guint64 moof_average_size;
+  guint64 keyframe_average_size;
+  guint64 keyframe_average_distance;
   gboolean first_sync_sample_after_moof, first_sync_sample_always_after_moof;
+
+  /* Internal position value, at the keyframe/entry level */
+  GstClockTime actual_position;
+  /* Timestamp of the beginning of the current fragment */
+  GstClockTime current_fragment_timestamp;
+  GstClockTime current_fragment_duration;
+  GstClockTime current_fragment_keyframe_distance;
+
+  /* Average keyframe download time (only in trickmode-key-units) */
+  GstClockTime average_download_time;
+  /* Cached target time (only in trickmode-key-units) */
+  GstClockTime target_time;
+  /* Average skip-ahead time (only in trickmode-key-units) */
+  GstClockTime average_skip_size;
 };
 
 /**
@@ -119,7 +135,7 @@ struct _GstDashDemux
 
   /* Properties */
   GstClockTime max_buffering_time;      /* Maximum buffering time accumulated during playback */
-  guint64 max_bitrate;          /* max of bitrate supported by target decoder         */
+  guint max_bitrate;          /* max of bitrate supported by target decoder         */
   gint max_video_width, max_video_height;
   gint max_video_framerate_n, max_video_framerate_d;
   gchar* default_presentation_delay; /* presentation time delay if MPD@suggestedPresentationDelay is not present */
index 5188feb..7604606 100644 (file)
@@ -849,7 +849,7 @@ error:
 
   The dateTime data type is used to specify a date and a time.
 
-  The dateTime is specified in the following form "YYYY-MM-DDThh:mm:ss" where:
+  The lexical form of xs:dateTime is YYYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm]
 
     * YYYY indicates the year
     * MM indicates the month
@@ -859,7 +859,7 @@ error:
     * mm indicates the minute
     * ss indicates the second
 
-  Note: All components are required!
+  The time zone may be specified as Z (UTC) or (+|-)hh:mm
 */
 static gboolean
 gst_mpdparser_get_xml_prop_dateTime (xmlNode * a_node,
@@ -871,6 +871,8 @@ gst_mpdparser_get_xml_prop_dateTime (xmlNode * a_node,
   gint year, month, day, hour, minute;
   gdouble second;
   gboolean exists = FALSE;
+  gfloat tzoffset = 0.0;
+  gint gmt_offset_hour = -99, gmt_offset_min = -99;
 
   prop_string = xmlGetProp (a_node, (const xmlChar *) property_name);
   if (prop_string) {
@@ -920,9 +922,50 @@ gst_mpdparser_get_xml_prop_dateTime (xmlNode * a_node,
     GST_LOG (" - %s: %4d/%02d/%02d %02d:%02d:%09.6lf", property_name,
         year, month, day, hour, minute, second);
 
+    if (strrchr (str, '+') || strrchr (str, '-')){
+      /* reuse some code from gst-plugins-base/gst-libs/gst/tag/gstxmptag.c */
+      gint gmt_offset = -1;
+      gchar *plus_pos = NULL;
+      gchar *neg_pos = NULL;
+      gchar *pos = NULL;
+
+      GST_LOG ("Checking for timezone information");
+
+      /* check if there is timezone info */
+      plus_pos = strrchr (str, '+');
+      neg_pos = strrchr (str, '-');
+      if (plus_pos)
+        pos = plus_pos + 1;
+      else if (neg_pos)
+        pos = neg_pos + 1;
+
+      if (pos && strlen (pos) >= 3) {
+        gint ret_tz;
+        if (pos[2] == ':')
+          ret_tz = sscanf (pos, "%d:%d", &gmt_offset_hour, &gmt_offset_min);
+        else
+          ret_tz = sscanf (pos, "%02d%02d", &gmt_offset_hour, &gmt_offset_min);
+
+        GST_DEBUG ("Parsing timezone: %s", pos);
+
+        if (ret_tz == 2) {
+          if (neg_pos != NULL && neg_pos + 1 == pos) {
+            gmt_offset_hour *= -1;
+            gmt_offset_min *= -1;
+          }
+          gmt_offset = gmt_offset_hour * 60 + gmt_offset_min;
+
+          tzoffset = gmt_offset / 60.0;
+
+          GST_LOG ("Timezone offset: %f (%d minutes)", tzoffset, gmt_offset);
+        } else
+          GST_WARNING ("Failed to parse timezone information");
+      }
+    }
+
     exists = TRUE;
     *property_value =
-        gst_date_time_new (0, year, month, day, hour, minute, second);
+        gst_date_time_new (tzoffset, year, month, day, hour, minute, second);
     xmlFree (prop_string);
   }
 
@@ -935,6 +978,7 @@ error:
   return FALSE;
 }
 
+
 /*
   Duration Data Type
 
@@ -2434,7 +2478,7 @@ gst_mpdparser_representation_get_mimetype (GstAdaptationSetNode * adapt_set,
     return GST_STREAM_AUDIO;
   if (strncmp_ext (mime, "video") == 0)
     return GST_STREAM_VIDEO;
-  if (strncmp_ext (mime, "application") == 0)
+  if (strncmp_ext (mime, "application") == 0 || strncmp_ext (mime, "text") == 0)
     return GST_STREAM_APPLICATION;
 
   return GST_STREAM_UNKNOWN;
@@ -2556,13 +2600,13 @@ gst_mpdparser_get_rep_idx_with_min_bandwidth (GList * Representations)
 #ifdef TIZEN_FEATURE_ADAPTIVE_MODIFICATION
 gint
 gst_mpdparser_get_rep_idx_with_max_bandwidth (GList * Representations,
-    gint max_bandwidth, gint max_video_width, gint max_video_height)
+    gint64 max_bandwidth, gint max_video_width, gint max_video_height)
 {
   GList *list = NULL, *best = NULL, *min_rep = NULL;
   GstRepresentationNode *representation;
   gint best_bandwidth = 0, min_bandwidth = 0;
 
-  GST_DEBUG ("[b]%d [w]%d [h]%d", max_bandwidth, max_video_width, max_video_height);
+  GST_DEBUG ("[b]%"G_GINT64_FORMAT" [w]%d [h]%d", max_bandwidth, max_video_width, max_video_height);
 
   if (Representations == NULL)
     return -1;
@@ -2601,14 +2645,14 @@ gst_mpdparser_get_rep_idx_with_max_bandwidth (GList * Representations,
 #else
 gint
 gst_mpdparser_get_rep_idx_with_max_bandwidth (GList * Representations,
-    gint max_bandwidth, gint max_video_width, gint max_video_height, gint
+    gint64 max_bandwidth, gint max_video_width, gint max_video_height, gint
     max_video_framerate_n, gint max_video_framerate_d)
 {
   GList *list = NULL, *best = NULL;
   GstRepresentationNode *representation;
   gint best_bandwidth = 0;
 
-  GST_DEBUG ("max_bandwidth = %i", max_bandwidth);
+  GST_DEBUG ("max_bandwidth = %" G_GINT64_FORMAT, max_bandwidth);
 
   if (Representations == NULL)
     return -1;
@@ -3816,8 +3860,8 @@ gst_mpd_client_fetch_on_load_external_resources (GstMpdClient * client)
             gst_mpd_client_fetch_external_adaptation_set (client, period,
             adapt_set);
 
-        prev = l->prev;
-        period->AdaptationSets = g_list_delete_link (period->AdaptationSets, l);
+        prev = m->prev;
+        period->AdaptationSets = g_list_delete_link (period->AdaptationSets, m);
         gst_mpdparser_free_adaptation_set_node (adapt_set);
         adapt_set = NULL;
 
@@ -3837,9 +3881,9 @@ gst_mpd_client_fetch_on_load_external_resources (GstMpdClient * client)
 
         /* Update our iterator to the first new adapt_set if any, or the next */
         if (prev)
-          l = prev->next;
+          m = prev->next;
         else
-          l = period->AdaptationSets;
+          m = period->AdaptationSets;
 
         continue;
       }
@@ -4846,10 +4890,6 @@ gst_mpd_client_setup_streaming (GstMpdClient * client,
 
   GST_DEBUG ("0. Current stream %p", stream);
 
-  /* retrieve representation list */
-  if (stream->cur_adapt_set != NULL)
-    rep_list = stream->cur_adapt_set->Representations;
-
 #if 0
   /* fast start */
   representation =
@@ -4868,14 +4908,14 @@ gst_mpd_client_setup_streaming (GstMpdClient * client,
 
   if (!representation) {
     GST_WARNING ("No valid representation in the MPD file, aborting...");
-    g_slice_free (GstActiveStream, stream);
+    gst_mpdparser_free_active_stream (stream);
     return FALSE;
   }
   stream->mimeType =
       gst_mpdparser_representation_get_mimetype (adapt_set, representation);
   if (stream->mimeType == GST_STREAM_UNKNOWN) {
     GST_WARNING ("Unknown mime type in the representation, aborting...");
-    g_slice_free (GstActiveStream, stream);
+    gst_mpdparser_free_active_stream (stream);
     return FALSE;
   }
 
@@ -5859,7 +5899,8 @@ gst_mpd_client_active_stream_contains_subtitles (GstActiveStream * stream)
   if (!mimeType)
     mimeType = stream->cur_adapt_set->RepresentationBase->mimeType;
 
-  if (g_strcmp0 (mimeType, "application/ttml+xml") == 0)
+  if (g_strcmp0 (mimeType, "application/ttml+xml") == 0 ||
+      g_strcmp0 (mimeType, "text/vtt") == 0)
     return TRUE;
 
   adapt_set_codecs = stream->cur_adapt_set->RepresentationBase->codecs;
@@ -5880,6 +5921,8 @@ gst_mpdparser_mimetype_to_caps (const gchar * mimeType)
     return "video/quicktime";
   } else if (strcmp (mimeType, "audio/mp4") == 0) {
     return "audio/x-m4a";
+  } else if (strcmp (mimeType, "text/vtt") == 0) {
+    return "application/x-subtitle-vtt";
   } else
     return mimeType;
 }
index cf06e00..85eae03 100644 (file)
@@ -572,9 +572,9 @@ gboolean gst_mpd_client_has_previous_period (GstMpdClient * client);
 
 /* Representation selection */
 #ifdef TIZEN_FEATURE_ADAPTIVE_MODIFICATION
-gint gst_mpdparser_get_rep_idx_with_max_bandwidth (GList *Representations, gint max_bandwidth, gint max_video_width, gint max_video_height);
+gint gst_mpdparser_get_rep_idx_with_max_bandwidth (GList *Representations, gint64 max_bandwidth, gint max_video_width, gint max_video_height);
 #else
-gint gst_mpdparser_get_rep_idx_with_max_bandwidth (GList *Representations, gint max_bandwidth, gint max_video_width, gint max_video_height, gint max_video_framerate_n, gint max_video_framerate_d);
+gint gst_mpdparser_get_rep_idx_with_max_bandwidth (GList *Representations, gint64 max_bandwidth, gint max_video_width, gint max_video_height, gint max_video_framerate_n, gint max_video_framerate_d);
 #endif
 gint gst_mpdparser_get_rep_idx_with_min_bandwidth (GList * Representations);
 
index 8314ff0..111e9d4 100644 (file)
@@ -7,6 +7,7 @@ libgsthls_la_SOURCES =                  \
        gsthlsdemux-util.c  \
        gsthlsplugin.c                  \
        gsthlssink.c                            \
+       gsthlssink2.c                           \
        gstm3u8playlist.c
 
 libgsthls_la_CFLAGS = $(GST_PLUGINS_BAD_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) $(LIBGCRYPT_CFLAGS) $(NETTLE_CFLAGS) $(OPENSSL_CFLAGS)
@@ -23,5 +24,6 @@ noinst_HEADERS =                      \
        gsthls.h                        \
        gsthlsdemux.h                   \
        gsthlssink.h                    \
+       gsthlssink2.h                   \
        gstm3u8playlist.h               \
        m3u8.h
index 1f7bf4e..8482a5a 100644 (file)
@@ -915,19 +915,17 @@ gst_hls_demux_handle_buffer (GstAdaptiveDemux * demux,
 {
   GstHLSDemuxStream *hls_stream = GST_HLS_DEMUX_STREAM_CAST (stream);   // FIXME: pass HlsStream into function
   GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux);
-  GstMapInfo info;
   GstClockTime first_pcr, last_pcr;
   GstTagList *tags;
 
   if (buffer == NULL)
     return GST_FLOW_OK;
 
-  gst_buffer_map (buffer, &info, GST_MAP_READ);
-
   if (G_UNLIKELY (hls_stream->do_typefind)) {
     GstCaps *caps = NULL;
     guint buffer_size;
     GstTypeFindProbability prob = GST_TYPE_FIND_NONE;
+    GstMapInfo info;
 
     if (hls_stream->pending_typefind_buffer)
       buffer = gst_buffer_append (hls_stream->pending_typefind_buffer, buffer);
@@ -979,11 +977,11 @@ gst_hls_demux_handle_buffer (GstAdaptiveDemux * demux,
     gst_adaptive_demux_stream_set_caps (stream, caps);
 #endif
     hls_stream->do_typefind = FALSE;
+
+    gst_buffer_unmap (buffer, &info);
   }
   g_assert (hls_stream->pending_typefind_buffer == NULL);
 
-  gst_buffer_unmap (buffer, &info);
-
   // Accumulate this buffer
   if (hls_stream->pending_pcr_buffer) {
     buffer = gst_buffer_append (hls_stream->pending_pcr_buffer, buffer);
@@ -1819,11 +1817,17 @@ static gboolean
 gst_hls_demux_stream_decrypt_start (GstHLSDemuxStream * stream,
     const guint8 * key_data, const guint8 * iv_data)
 {
+  EVP_CIPHER_CTX *ctx;
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
   EVP_CIPHER_CTX_init (&stream->aes_ctx);
-  if (!EVP_DecryptInit_ex (&stream->aes_ctx, EVP_aes_128_cbc (), NULL, key_data,
-          iv_data))
+  ctx = &stream->aes_ctx;
+#else
+  stream->aes_ctx = EVP_CIPHER_CTX_new ();
+  ctx = stream->aes_ctx;
+#endif
+  if (!EVP_DecryptInit_ex (ctx, EVP_aes_128_cbc (), NULL, key_data, iv_data))
     return FALSE;
-  EVP_CIPHER_CTX_set_padding (&stream->aes_ctx, 0);
+  EVP_CIPHER_CTX_set_padding (ctx, 0);
   return TRUE;
 }
 
@@ -1832,15 +1836,21 @@ decrypt_fragment (GstHLSDemuxStream * stream, gsize length,
     const guint8 * encrypted_data, guint8 * decrypted_data)
 {
   int len, flen = 0;
+  EVP_CIPHER_CTX *ctx;
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+  ctx = &stream->aes_ctx;
+#else
+  ctx = stream->aes_ctx;
+#endif
 
   if (G_UNLIKELY (length > G_MAXINT || length % 16 != 0))
     return FALSE;
 
   len = (int) length;
-  if (!EVP_DecryptUpdate (&stream->aes_ctx, decrypted_data, &len,
-          encrypted_data, len))
+  if (!EVP_DecryptUpdate (ctx, decrypted_data, &len, encrypted_data, len))
     return FALSE;
-  EVP_DecryptFinal_ex (&stream->aes_ctx, decrypted_data + len, &flen);
+  EVP_DecryptFinal_ex (ctx, decrypted_data + len, &flen);
   g_return_val_if_fail (len + flen == length, FALSE);
   return TRUE;
 }
@@ -1848,7 +1858,12 @@ decrypt_fragment (GstHLSDemuxStream * stream, gsize length,
 static void
 gst_hls_demux_stream_decrypt_end (GstHLSDemuxStream * stream)
 {
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
   EVP_CIPHER_CTX_cleanup (&stream->aes_ctx);
+#else
+  EVP_CIPHER_CTX_free (stream->aes_ctx);
+  stream->aes_ctx = NULL;
+#endif
 }
 
 #elif defined(HAVE_NETTLE)
index 2007489..bb04c82 100644 (file)
@@ -104,7 +104,11 @@ struct _GstHLSDemuxStream
 
   /* decryption tooling */
 #if defined(HAVE_OPENSSL)
+# if OPENSSL_VERSION_NUMBER < 0x10100000L
   EVP_CIPHER_CTX aes_ctx;
+# else
+  EVP_CIPHER_CTX *aes_ctx;
+# endif
 #elif defined(HAVE_NETTLE)
   struct CBC_CTX (struct aes_ctx, AES_BLOCK_SIZE) aes_ctx;
 #else
@@ -144,6 +148,7 @@ struct _GstHLSDemux
   /* FIXME: check locking, protected automatically by manifest_lock already? */
   /* The master playlist with the available variant streams */
   GstHLSMasterPlaylist *master;
+
   GstHLSVariantStream  *current_variant;
 };
 
index 552f514..fc26588 100644 (file)
@@ -7,6 +7,7 @@
 #include "gsthls.h"
 #include "gsthlsdemux.h"
 #include "gsthlssink.h"
+#include "gsthlssink2.h"
 
 GST_DEBUG_CATEGORY (hls_debug);
 
@@ -22,6 +23,9 @@ hls_init (GstPlugin * plugin)
   if (!gst_hls_sink_plugin_init (plugin))
     return FALSE;
 
+  if (!gst_hls_sink2_plugin_init (plugin))
+    return FALSE;
+
   return TRUE;
 }
 
diff --git a/ext/hls/gsthlssink2.c b/ext/hls/gsthlssink2.c
new file mode 100644 (file)
index 0000000..64a1642
--- /dev/null
@@ -0,0 +1,478 @@
+/* GStreamer
+ * Copyright (C) 2011 Alessandro Decina <alessandro.d@gmail.com>
+ * Copyright (C) 2017 Sebastian Dröge <sebastian@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:element-hlssink
+ * @title: hlssink
+ *
+ * HTTP Live Streaming sink/server
+ *
+ * ## Example launch line
+ * |[
+ * gst-launch-1.0 videotestsrc is-live=true ! x264enc ! hlssink max-files=5
+ * ]|
+ *
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gsthlssink2.h"
+#include <gst/pbutils/pbutils.h>
+#include <gst/video/video.h>
+#include <glib/gstdio.h>
+#include <memory.h>
+
+
+GST_DEBUG_CATEGORY_STATIC (gst_hls_sink2_debug);
+#define GST_CAT_DEFAULT gst_hls_sink2_debug
+
+#define DEFAULT_LOCATION "segment%05d.ts"
+#define DEFAULT_PLAYLIST_LOCATION "playlist.m3u8"
+#define DEFAULT_PLAYLIST_ROOT NULL
+#define DEFAULT_MAX_FILES 10
+#define DEFAULT_TARGET_DURATION 15
+#define DEFAULT_PLAYLIST_LENGTH 5
+
+#define GST_M3U8_PLAYLIST_VERSION 3
+
+enum
+{
+  PROP_0,
+  PROP_LOCATION,
+  PROP_PLAYLIST_LOCATION,
+  PROP_PLAYLIST_ROOT,
+  PROP_MAX_FILES,
+  PROP_TARGET_DURATION,
+  PROP_PLAYLIST_LENGTH
+};
+
+static GstStaticPadTemplate video_template = GST_STATIC_PAD_TEMPLATE ("video",
+    GST_PAD_SINK,
+    GST_PAD_REQUEST,
+    GST_STATIC_CAPS_ANY);
+static GstStaticPadTemplate audio_template = GST_STATIC_PAD_TEMPLATE ("audio",
+    GST_PAD_SINK,
+    GST_PAD_REQUEST,
+    GST_STATIC_CAPS_ANY);
+
+#define gst_hls_sink2_parent_class parent_class
+G_DEFINE_TYPE (GstHlsSink2, gst_hls_sink2, GST_TYPE_BIN);
+
+static void gst_hls_sink2_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * spec);
+static void gst_hls_sink2_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * spec);
+static void gst_hls_sink2_handle_message (GstBin * bin, GstMessage * message);
+static void gst_hls_sink2_reset (GstHlsSink2 * sink);
+static GstStateChangeReturn
+gst_hls_sink2_change_state (GstElement * element, GstStateChange trans);
+static GstPad *gst_hls_sink2_request_new_pad (GstElement * element,
+    GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
+static void gst_hls_sink2_release_pad (GstElement * element, GstPad * pad);
+
+static void
+gst_hls_sink2_dispose (GObject * object)
+{
+  GstHlsSink2 *sink = GST_HLS_SINK2_CAST (object);
+
+  G_OBJECT_CLASS (parent_class)->dispose ((GObject *) sink);
+}
+
+static void
+gst_hls_sink2_finalize (GObject * object)
+{
+  GstHlsSink2 *sink = GST_HLS_SINK2_CAST (object);
+
+  g_free (sink->location);
+  g_free (sink->playlist_location);
+  g_free (sink->playlist_root);
+  if (sink->playlist)
+    gst_m3u8_playlist_free (sink->playlist);
+
+  g_queue_foreach (&sink->old_locations, (GFunc) g_free, NULL);
+  g_queue_clear (&sink->old_locations);
+
+  G_OBJECT_CLASS (parent_class)->finalize ((GObject *) sink);
+}
+
+static void
+gst_hls_sink2_class_init (GstHlsSink2Class * klass)
+{
+  GObjectClass *gobject_class;
+  GstElementClass *element_class;
+  GstBinClass *bin_class;
+
+  gobject_class = (GObjectClass *) klass;
+  element_class = GST_ELEMENT_CLASS (klass);
+  bin_class = GST_BIN_CLASS (klass);
+
+  gst_element_class_add_static_pad_template (element_class, &video_template);
+  gst_element_class_add_static_pad_template (element_class, &audio_template);
+
+  gst_element_class_set_static_metadata (element_class,
+      "HTTP Live Streaming sink", "Sink", "HTTP Live Streaming sink",
+      "Alessandro Decina <alessandro.d@gmail.com>, "
+      "Sebastian Dröge <sebastian@centricular.com>");
+
+  element_class->change_state = GST_DEBUG_FUNCPTR (gst_hls_sink2_change_state);
+  element_class->request_new_pad =
+      GST_DEBUG_FUNCPTR (gst_hls_sink2_request_new_pad);
+  element_class->release_pad = GST_DEBUG_FUNCPTR (gst_hls_sink2_release_pad);
+
+  bin_class->handle_message = gst_hls_sink2_handle_message;
+
+  gobject_class->dispose = gst_hls_sink2_dispose;
+  gobject_class->finalize = gst_hls_sink2_finalize;
+  gobject_class->set_property = gst_hls_sink2_set_property;
+  gobject_class->get_property = gst_hls_sink2_get_property;
+
+  g_object_class_install_property (gobject_class, PROP_LOCATION,
+      g_param_spec_string ("location", "File Location",
+          "Location of the file to write", DEFAULT_LOCATION,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_PLAYLIST_LOCATION,
+      g_param_spec_string ("playlist-location", "Playlist Location",
+          "Location of the playlist to write", DEFAULT_PLAYLIST_LOCATION,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_PLAYLIST_ROOT,
+      g_param_spec_string ("playlist-root", "Playlist Root",
+          "Location of the playlist to write", DEFAULT_PLAYLIST_ROOT,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_MAX_FILES,
+      g_param_spec_uint ("max-files", "Max files",
+          "Maximum number of files to keep on disk. Once the maximum is reached,"
+          "old files start to be deleted to make room for new ones.",
+          0, G_MAXUINT, DEFAULT_MAX_FILES,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_TARGET_DURATION,
+      g_param_spec_uint ("target-duration", "Target duration",
+          "The target duration in seconds of a segment/file. "
+          "(0 - disabled, useful for management of segment duration by the "
+          "streaming server)",
+          0, G_MAXUINT, DEFAULT_TARGET_DURATION,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_PLAYLIST_LENGTH,
+      g_param_spec_uint ("playlist-length", "Playlist length",
+          "Length of HLS playlist. To allow players to conform to section 6.3.3 "
+          "of the HLS specification, this should be at least 3. If set to 0, "
+          "the playlist will be infinite.",
+          0, G_MAXUINT, DEFAULT_PLAYLIST_LENGTH,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gst_hls_sink2_init (GstHlsSink2 * sink)
+{
+  GstElement *mux;
+
+  sink->location = g_strdup (DEFAULT_LOCATION);
+  sink->playlist_location = g_strdup (DEFAULT_PLAYLIST_LOCATION);
+  sink->playlist_root = g_strdup (DEFAULT_PLAYLIST_ROOT);
+  sink->playlist_length = DEFAULT_PLAYLIST_LENGTH;
+  sink->max_files = DEFAULT_MAX_FILES;
+  sink->target_duration = DEFAULT_TARGET_DURATION;
+  g_queue_init (&sink->old_locations);
+
+  sink->splitmuxsink = gst_element_factory_make ("splitmuxsink", NULL);
+  gst_bin_add (GST_BIN (sink), sink->splitmuxsink);
+
+  mux = gst_element_factory_make ("mpegtsmux", NULL);
+  g_object_set (sink->splitmuxsink, "location", sink->location, "max-size-time",
+      ((GstClockTime) sink->target_duration * GST_SECOND),
+      "send-keyframe-requests", TRUE, "muxer", mux, NULL);
+
+  GST_OBJECT_FLAG_SET (sink, GST_ELEMENT_FLAG_SINK);
+
+  gst_hls_sink2_reset (sink);
+}
+
+static void
+gst_hls_sink2_reset (GstHlsSink2 * sink)
+{
+  sink->index = 0;
+
+  if (sink->playlist)
+    gst_m3u8_playlist_free (sink->playlist);
+  sink->playlist =
+      gst_m3u8_playlist_new (GST_M3U8_PLAYLIST_VERSION, sink->playlist_length,
+      FALSE);
+
+  g_queue_foreach (&sink->old_locations, (GFunc) g_free, NULL);
+  g_queue_clear (&sink->old_locations);
+}
+
+static void
+gst_hls_sink2_write_playlist (GstHlsSink2 * sink)
+{
+  char *playlist_content;
+  GError *error = NULL;
+
+  playlist_content = gst_m3u8_playlist_render (sink->playlist);
+  if (!g_file_set_contents (sink->playlist_location,
+          playlist_content, -1, &error)) {
+    GST_ERROR ("Failed to write playlist: %s", error->message);
+    GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
+        (("Failed to write playlist '%s'."), error->message), (NULL));
+    g_error_free (error);
+    error = NULL;
+  }
+  g_free (playlist_content);
+
+}
+
+static void
+gst_hls_sink2_handle_message (GstBin * bin, GstMessage * message)
+{
+  GstHlsSink2 *sink = GST_HLS_SINK2_CAST (bin);
+
+  switch (message->type) {
+    case GST_MESSAGE_ELEMENT:
+    {
+      const GstStructure *s = gst_message_get_structure (message);
+      if (message->src == GST_OBJECT_CAST (sink->splitmuxsink)) {
+        if (gst_structure_has_name (s, "splitmuxsink-fragment-opened")) {
+          g_free (sink->current_location);
+          sink->current_location =
+              g_strdup (gst_structure_get_string (s, "location"));
+          gst_structure_get_clock_time (s, "running-time",
+              &sink->current_running_time_start);
+        } else if (gst_structure_has_name (s, "splitmuxsink-fragment-closed")) {
+          GstClockTime running_time;
+          gchar *entry_location;
+
+          g_assert (strcmp (sink->current_location, gst_structure_get_string (s,
+                      "location")) == 0);
+
+          gst_structure_get_clock_time (s, "running-time", &running_time);
+
+          GST_INFO_OBJECT (sink, "COUNT %d", sink->index);
+          if (sink->playlist_root == NULL) {
+            entry_location = g_path_get_basename (sink->current_location);
+          } else {
+            gchar *name = g_path_get_basename (sink->current_location);
+            entry_location = g_build_filename (sink->playlist_root, name, NULL);
+            g_free (name);
+          }
+
+          gst_m3u8_playlist_add_entry (sink->playlist, entry_location,
+              NULL, running_time - sink->current_running_time_start,
+              sink->index++, FALSE);
+          g_free (entry_location);
+
+          gst_hls_sink2_write_playlist (sink);
+
+          g_queue_push_tail (&sink->old_locations,
+              g_strdup (sink->current_location));
+
+          while (g_queue_get_length (&sink->old_locations) >
+              g_queue_get_length (sink->playlist->entries)) {
+            gchar *old_location = g_queue_pop_head (&sink->old_locations);
+            g_remove (old_location);
+            g_free (old_location);
+          }
+        }
+      }
+      break;
+    }
+    case GST_MESSAGE_EOS:{
+      sink->playlist->end_list = TRUE;
+      gst_hls_sink2_write_playlist (sink);
+      break;
+    }
+    default:
+      break;
+  }
+
+  GST_BIN_CLASS (parent_class)->handle_message (bin, message);
+}
+
+static GstPad *
+gst_hls_sink2_request_new_pad (GstElement * element, GstPadTemplate * templ,
+    const gchar * name, const GstCaps * caps)
+{
+  GstHlsSink2 *sink = GST_HLS_SINK2_CAST (element);
+  GstPad *pad, *peer;
+  gboolean is_audio;
+
+  g_return_val_if_fail (strcmp (templ->name_template, "audio") == 0
+      || strcmp (templ->name_template, "video") == 0, NULL);
+  g_return_val_if_fail (strcmp (templ->name_template, "audio") != 0
+      || !sink->audio_sink, NULL);
+  g_return_val_if_fail (strcmp (templ->name_template, "video") != 0
+      || !sink->video_sink, NULL);
+
+  is_audio = strcmp (templ->name_template, "audio") == 0;
+
+  peer =
+      gst_element_get_request_pad (sink->splitmuxsink,
+      is_audio ? "audio_0" : "video");
+  if (!peer)
+    return NULL;
+
+  pad = gst_ghost_pad_new_from_template (templ->name_template, peer, templ);
+  gst_pad_set_active (pad, TRUE);
+  gst_element_add_pad (element, pad);
+  gst_object_unref (peer);
+
+  if (is_audio)
+    sink->audio_sink = pad;
+  else
+    sink->video_sink = pad;
+
+  return pad;
+}
+
+static void
+gst_hls_sink2_release_pad (GstElement * element, GstPad * pad)
+{
+  GstHlsSink2 *sink = GST_HLS_SINK2_CAST (element);
+  GstPad *peer;
+
+  g_return_if_fail (pad == sink->audio_sink || pad == sink->video_sink);
+
+  peer = gst_pad_get_peer (pad);
+  if (peer) {
+    gst_element_release_request_pad (sink->splitmuxsink, pad);
+    gst_object_unref (peer);
+  }
+
+  gst_object_ref (pad);
+  gst_element_remove_pad (element, pad);
+  gst_pad_set_active (pad, FALSE);
+  if (pad == sink->audio_sink)
+    sink->audio_sink = NULL;
+  else
+    sink->video_sink = NULL;
+
+  gst_object_unref (pad);
+}
+
+static GstStateChangeReturn
+gst_hls_sink2_change_state (GstElement * element, GstStateChange trans)
+{
+  GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+  GstHlsSink2 *sink = GST_HLS_SINK2_CAST (element);
+
+  switch (trans) {
+    case GST_STATE_CHANGE_NULL_TO_READY:
+      if (!sink->splitmuxsink) {
+        return GST_STATE_CHANGE_FAILURE;
+      }
+      break;
+    default:
+      break;
+  }
+
+  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, trans);
+
+  switch (trans) {
+    case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+      break;
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+    case GST_STATE_CHANGE_READY_TO_NULL:
+      gst_hls_sink2_reset (sink);
+      break;
+    default:
+      break;
+  }
+
+  return ret;
+}
+
+static void
+gst_hls_sink2_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstHlsSink2 *sink = GST_HLS_SINK2_CAST (object);
+
+  switch (prop_id) {
+    case PROP_LOCATION:
+      g_free (sink->location);
+      sink->location = g_value_dup_string (value);
+      if (sink->splitmuxsink)
+        g_object_set (sink->splitmuxsink, "location", sink->location, NULL);
+      break;
+    case PROP_PLAYLIST_LOCATION:
+      g_free (sink->playlist_location);
+      sink->playlist_location = g_value_dup_string (value);
+      break;
+    case PROP_PLAYLIST_ROOT:
+      g_free (sink->playlist_root);
+      sink->playlist_root = g_value_dup_string (value);
+      break;
+    case PROP_MAX_FILES:
+      sink->max_files = g_value_get_uint (value);
+      break;
+    case PROP_TARGET_DURATION:
+      sink->target_duration = g_value_get_uint (value);
+      if (sink->splitmuxsink) {
+        g_object_set (sink->splitmuxsink, "max-size-time",
+            ((GstClockTime) sink->target_duration * GST_SECOND), NULL);
+      }
+      break;
+    case PROP_PLAYLIST_LENGTH:
+      sink->playlist_length = g_value_get_uint (value);
+      sink->playlist->window_size = sink->playlist_length;
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gst_hls_sink2_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstHlsSink2 *sink = GST_HLS_SINK2_CAST (object);
+
+  switch (prop_id) {
+    case PROP_LOCATION:
+      g_value_set_string (value, sink->location);
+      break;
+    case PROP_PLAYLIST_LOCATION:
+      g_value_set_string (value, sink->playlist_location);
+      break;
+    case PROP_PLAYLIST_ROOT:
+      g_value_set_string (value, sink->playlist_root);
+      break;
+    case PROP_MAX_FILES:
+      g_value_set_uint (value, sink->max_files);
+      break;
+    case PROP_TARGET_DURATION:
+      g_value_set_uint (value, sink->target_duration);
+      break;
+    case PROP_PLAYLIST_LENGTH:
+      g_value_set_uint (value, sink->playlist_length);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+gboolean
+gst_hls_sink2_plugin_init (GstPlugin * plugin)
+{
+  GST_DEBUG_CATEGORY_INIT (gst_hls_sink2_debug, "hlssink2", 0, "HlsSink2");
+  return gst_element_register (plugin, "hlssink2", GST_RANK_NONE,
+      gst_hls_sink2_get_type ());
+}
diff --git a/ext/hls/gsthlssink2.h b/ext/hls/gsthlssink2.h
new file mode 100644 (file)
index 0000000..effefd6
--- /dev/null
@@ -0,0 +1,69 @@
+/* GStreamer
+ * Copyright (C) 2011 Alessandro Decina <alessandro.d@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+#ifndef _GST_HLS_SINK2_H_
+#define _GST_HLS_SINK2_H_
+
+#include "gstm3u8playlist.h"
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_HLS_SINK2   (gst_hls_sink2_get_type())
+#define GST_HLS_SINK2(obj)   (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_HLS_SINK2,GstHlsSink2))
+#define GST_HLS_SINK2_CAST(obj)   ((GstHlsSink2 *) obj)
+#define GST_HLS_SINK2_CLASS(klass)   (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_HLS_SINK2,GstHlsSink2Class))
+#define GST_IS_HLS_SINK2(obj)   (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_HLS_SINK2))
+#define GST_IS_HLS_SINK2_CLASS(obj)   (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_HLS_SINK2))
+
+typedef struct _GstHlsSink2 GstHlsSink2;
+typedef struct _GstHlsSink2Class GstHlsSink2Class;
+
+struct _GstHlsSink2
+{
+  GstBin bin;
+
+  GstElement *splitmuxsink;
+  GstPad *audio_sink, *video_sink;
+
+  gchar *location;
+  gchar *playlist_location;
+  gchar *playlist_root;
+  guint playlist_length;
+  gint max_files;
+  gint target_duration;
+
+  GstM3U8Playlist *playlist;
+  guint index;
+
+  gchar *current_location;
+  GstClockTime current_running_time_start;
+  GQueue old_locations;
+};
+
+struct _GstHlsSink2Class
+{
+  GstBinClass bin_class;
+};
+
+GType gst_hls_sink2_get_type (void);
+gboolean gst_hls_sink2_plugin_init (GstPlugin * plugin);
+
+G_END_DECLS
+
+#endif
index 0e74175..d74977b 100644 (file)
@@ -111,6 +111,7 @@ gst_m3u8_unref (GstM3U8 * self)
     g_list_free (self->files);
 
     g_free (self->last_data);
+    g_mutex_clear (&self->lock);
     g_free (self);
   }
 }
@@ -309,139 +310,138 @@ gst_hls_variant_stream_compare_by_bitrate (gconstpointer a, gconstpointer b)
   return vs_a->bandwidth - vs_b->bandwidth;
 }
 
+/* If we have MEDIA-SEQUENCE, ensure that it's consistent. If it is not,
+ * the client SHOULD halt playback (6.3.4), which is what we do then. */
 static gboolean
-gst_m3u8_update_check_consistent_media_seqnums (GstM3U8 * self,
-    gboolean have_mediasequence, GList * previous_files)
+check_media_seqnums (GstM3U8 * self, GList * previous_files)
 {
-  if (!previous_files)
+  GList *l, *m;
+  GstM3U8MediaFile *f1 = NULL, *f2 = NULL;
+
+  g_return_val_if_fail (previous_files, FALSE);
+
+  if (!self->files) {
+    /* Empty playlists are trivially consistent */
     return TRUE;
+  }
 
-  /* If we have MEDIA-SEQUENCE, ensure that it's consistent. If it is not,
-   * the client SHOULD halt playback (6.3.4), which is what we do then.
-   *
-   * If we don't have MEDIA-SEQUENCE, we check URIs in the previous and
-   * current playlist to calculate the/a correct MEDIA-SEQUENCE for the new
-   * playlist in relation to the old. That is, same URIs get the same number
-   * and later URIs get higher numbers */
-  if (have_mediasequence) {
-    GList *l, *m;
-    GstM3U8MediaFile *f1 = NULL, *f2 = NULL;
-
-    /* Find first case of higher/equal sequence number in new playlist or
-     * same URI. From there on we can linearly step ahead */
-    for (l = self->files; l; l = l->next) {
-      gboolean match = FALSE;
+  /* Find first case of higher/equal sequence number in new playlist.
+   * From there on we can linearly step ahead */
+  for (l = self->files; l; l = l->next) {
+    gboolean match = FALSE;
 
-      f1 = l->data;
-      for (m = previous_files; m; m = m->next) {
-        f2 = m->data;
+    f1 = l->data;
+    for (m = previous_files; m; m = m->next) {
+      f2 = m->data;
 
-        if (f1->sequence >= f2->sequence || g_str_equal (f1->uri, f2->uri)) {
-          match = TRUE;
-          break;
-        }
-      }
-      if (match)
+      if (f1->sequence >= f2->sequence) {
+        match = TRUE;
         break;
+      }
     }
+    if (match)
+      break;
+  }
 
-    if (!l) {
-      /* No match, no sequence in the new playlist was higher than
-       * any in the old, and no URI was found again. This is bad! */
-      GST_ERROR ("Media sequences inconsistent, ignoring");
-      return FALSE;
-    } else {
-      g_assert (f1 != NULL);
-      g_assert (f2 != NULL);
-
-      for (; l && m; l = l->next, m = m->next) {
-        f1 = l->data;
-        f2 = m->data;
-
-        if (f1->sequence == f2->sequence) {
-          if (!g_str_equal (f1->uri, f2->uri)) {
-            /* Same sequence, different URI. This is bad! */
-            GST_ERROR ("Media sequences inconsistent, ignoring");
-            return FALSE;
-          } else {
-            /* Good case, we advance and check the next one */
-          }
-        } else if (g_str_equal (f1->uri, f2->uri)) {
-          /* Same URIs but different sequences, this is bad! */
-          GST_ERROR ("Media sequences inconsistent, ignoring");
-          return FALSE;
-        } else {
-          /* Not same URI, not same sequence but by construction sequence
-           * must be higher in the new one. All good in that case, if it
-           * isn't then this means that sequence numbers are decreasing
-           * or files were inserted */
-          if (f1->sequence < f2->sequence) {
-            GST_ERROR ("Media sequences inconsistent, ignoring");
-            return FALSE;
-          }
-        }
-      }
+  /* We must have seen at least one entry on each list */
+  g_assert (f1 != NULL);
+  g_assert (f2 != NULL);
+
+  if (!l) {
+    /* No match, no sequence in the new playlist was higher than
+     * any in the old. This is bad! */
+    GST_ERROR ("Media sequence doesn't continue: last new %" G_GINT64_FORMAT
+        " < last old %" G_GINT64_FORMAT, f1->sequence, f2->sequence);
+    return FALSE;
+  }
+
+  for (; l && m; l = l->next, m = m->next) {
+    f1 = l->data;
+    f2 = m->data;
 
-      /* All good if we're getting here */
+    if (f1->sequence == f2->sequence && !g_str_equal (f1->uri, f2->uri)) {
+      /* Same sequence, different URI. This is bad! */
+      GST_ERROR ("Media URIs inconsistent (sequence %" G_GINT64_FORMAT
+          "): had '%s', got '%s'", f1->sequence, f2->uri, f1->uri);
+      return FALSE;
+    } else if (f1->sequence < f2->sequence) {
+      /* Not same sequence but by construction sequence must be higher in the
+       * new one. All good in that case, if it isn't then this means that
+       * sequence numbers are decreasing or files were inserted */
+      GST_ERROR ("Media sequences inconsistent: %" G_GINT64_FORMAT " < %"
+          G_GINT64_FORMAT ": URIs new '%s' old '%s'", f1->sequence,
+          f2->sequence, f1->uri, f2->uri);
+      return FALSE;
     }
-  } else {
-    GList *l, *m;
-    GstM3U8MediaFile *f1 = NULL, *f2 = NULL;
-    gint64 mediasequence;
+  }
 
-    for (l = self->files; l; l = l->next) {
-      gboolean match = FALSE;
+  /* All good if we're getting here */
+  return TRUE;
+}
 
-      f1 = l->data;
-      for (m = previous_files; m; m = m->next) {
-        f2 = m->data;
+/* If we don't have MEDIA-SEQUENCE, we check URIs in the previous and
+ * current playlist to calculate the/a correct MEDIA-SEQUENCE for the new
+ * playlist in relation to the old. That is, same URIs get the same number
+ * and later URIs get higher numbers */
+static void
+generate_media_seqnums (GstM3U8 * self, GList * previous_files)
+{
+  GList *l, *m;
+  GstM3U8MediaFile *f1 = NULL, *f2 = NULL;
+  gint64 mediasequence;
 
-        if (g_str_equal (f1->uri, f2->uri)) {
-          match = TRUE;
-          break;
-        }
-      }
+  g_return_if_fail (previous_files);
 
-      if (match)
-        break;
-    }
+  /* Find first case of same URI in new playlist.
+   * From there on we can linearly step ahead */
+  for (l = self->files; l; l = l->next) {
+    gboolean match = FALSE;
 
-    if (!l) {
-      /* No match, this means f2 is the last item in the previous playlist
-       * and we have to start our new playlist at that sequence */
-      mediasequence = f2->sequence + 1;
+    f1 = l->data;
+    for (m = previous_files; m; m = m->next) {
+      f2 = m->data;
 
-      for (l = self->files; l; l = l->next) {
-        f1 = l->data;
-        f1->sequence = mediasequence;
-        mediasequence++;
+      if (g_str_equal (f1->uri, f2->uri)) {
+        match = TRUE;
+        break;
       }
-    } else {
-      /* Match, check that all following ones are matching too and continue
-       * sequence numbers from there on */
+    }
+
+    if (match)
+      break;
+  }
 
-      mediasequence = f2->sequence;
+  if (l) {
+    /* Match, check that all following ones are matching too and continue
+     * sequence numbers from there on */
 
-      for (; l; l = l->next) {
-        f1 = l->data;
-        f2 = m ? m->data : NULL;
+    mediasequence = f2->sequence;
 
-        f1->sequence = mediasequence;
-        mediasequence++;
+    for (; l && m; l = l->next, m = m->next) {
+      f1 = l->data;
+      f2 = m->data;
 
-        if (f2) {
-          if (!g_str_equal (f1->uri, f2->uri)) {
-            GST_WARNING ("Inconsistent URIs after playlist update");
-          }
-        }
+      f1->sequence = mediasequence;
+      mediasequence++;
 
-        if (m)
-          m = m->next;
+      if (!g_str_equal (f1->uri, f2->uri)) {
+        GST_WARNING ("Inconsistent URIs after playlist update: '%s' != '%s'",
+            f1->uri, f2->uri);
       }
     }
+  } else {
+    /* No match, this means f2 is the last item in the previous playlist
+     * and we have to start our new playlist at that sequence */
+    mediasequence = f2->sequence + 1;
+    l = self->files;
   }
 
-  return TRUE;
+  for (; l; l = l->next) {
+    f1 = l->data;
+
+    f1->sequence = mediasequence;
+    mediasequence++;
+  }
 }
 
 /*
@@ -694,8 +694,13 @@ gst_m3u8_update (GstM3U8 * self, gchar * data)
   self->files = g_list_reverse (self->files);
 
   if (previous_files) {
-    gboolean consistent = gst_m3u8_update_check_consistent_media_seqnums (self,
-        have_mediasequence, previous_files);
+    gboolean consistent = TRUE;
+
+    if (have_mediasequence) {
+      consistent = check_media_seqnums (self, previous_files);
+    } else {
+      generate_media_seqnums (self, previous_files);
+    }
 
     g_list_foreach (previous_files, (GFunc) gst_m3u8_media_file_unref, NULL);
     g_list_free (previous_files);
@@ -1000,7 +1005,7 @@ gst_m3u8_advance_fragment (GstM3U8 * m3u8, gboolean forward)
     }
   }
   if (m3u8->current_file) {
-    /* Store duration of the fragment we're using to update the position
+    /* Store duration of the fragment we're using to update the position 
      * the next time we advance */
     m3u8->current_file_duration =
         GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->duration;
@@ -1530,8 +1535,15 @@ gst_hls_master_playlist_new_from_data (gchar * data, const gchar * base_uri)
       data += stream->iframe ? 26 : 18;
       while (data && parse_attributes (&data, &a, &v)) {
         if (g_str_equal (a, "BANDWIDTH")) {
+          if (!stream->bandwidth) {
+            if (!int_from_string (v, NULL, &stream->bandwidth))
+              GST_WARNING ("Error while reading BANDWIDTH");
+          }
+        } else if (g_str_equal (a, "AVERAGE-BANDWIDTH")) {
+          GST_DEBUG
+              ("AVERAGE-BANDWIDTH attribute available. Using it as stream bandwidth");
           if (!int_from_string (v, NULL, &stream->bandwidth))
-            GST_WARNING ("Error while reading BANDWIDTH");
+            GST_WARNING ("Error while reading AVERAGE-BANDWIDTH");
         } else if (g_str_equal (a, "PROGRAM-ID")) {
           if (!int_from_string (v, NULL, &stream->program_id))
             GST_WARNING ("Error while reading PROGRAM-ID");
@@ -1866,7 +1878,7 @@ gst_hls_master_playlist_get_variant_for_bitrate (GstHLSMasterPlaylist *
 
   /* variant lists are sorted low to high, so iterate from highest to lowest */
   if (current_variant == NULL || !current_variant->iframe)
-    l = g_list_last (playlist->variants); /* highest */
+    l = g_list_last (playlist->variants);
   else
     l = g_list_last (playlist->iframe_variants);
 
index ec9cd41..ab02f47 100644 (file)
@@ -18,7 +18,7 @@ if TV_PROFILE
 SUBDIRS = codecparsers mpegts $(WAYLAND_DIR)
 else
 SUBDIRS = uridownloader adaptivedemux interfaces basecamerabinsrc codecparsers \
-        insertbin mpegts base video audio player allocators $(GL_DIR) $(WAYLAND_DIR) \
+        insertbin mpegts base video audio player isoff allocators $(GL_DIR) $(WAYLAND_DIR) \
         $(OPENCV_DIR)
 endif
 
@@ -28,7 +28,7 @@ if TV_PROFILE
 DIST_SUBDIRS = codecparsers mpegts wayland
 else
 DIST_SUBDIRS = uridownloader adaptivedemux interfaces gl basecamerabinsrc \
-       codecparsers insertbin mpegts wayland opencv base video audio player allocators
+       codecparsers insertbin mpegts wayland opencv base video audio player isoff allocators
 endif
 
 #dependencies
@@ -43,7 +43,7 @@ INDEPENDENT_SUBDIRS = \
 else
 INDEPENDENT_SUBDIRS = \
        interfaces basecamerabinsrc codecparsers insertbin uridownloader \
-       mpegts base player allocators $(GL_DIR) $(WAYLAND_DIR) $(OPENCV_DIR)
+       mpegts base player isoff allocators $(GL_DIR) $(WAYLAND_DIR) $(OPENCV_DIR)
 endif
 
 .PHONY: independent-subdirs $(INDEPENDENT_SUBDIRS)
index 74d7c42..2af0517 100644 (file)
@@ -8,7 +8,7 @@ libgstadaptivedemux_@GST_API_VERSION@_la_SOURCES = \
 
 libgstadaptivedemux_@GST_API_VERSION@includedir = $(includedir)/gstreamer-@GST_API_VERSION@/gst/adaptivedemux
 
-noinst_HEADERS = gstadaptivedemux.h
+noinst_HEADERS = gstadaptivedemux.h adaptive-demux-prelude.h
 
 libgstadaptivedemux_@GST_API_VERSION@_la_CFLAGS = \
        $(GST_PLUGINS_BAD_CFLAGS) \
diff --git a/gst-libs/gst/adaptivedemux/adaptive-demux-prelude.h b/gst-libs/gst/adaptivedemux/adaptive-demux-prelude.h
new file mode 100644 (file)
index 0000000..402a78f
--- /dev/null
@@ -0,0 +1,31 @@
+/* GStreamer AdaptiveDemux Library
+ * Copyright (C) 2018 GStreamer developers
+ *
+ * adaptive-demux-prelude.h: prelude include header for gst-adaptivedemux library
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_ADAPTIVE_DEMUX_PRELUDE_H__
+#define __GST_ADAPTIVE_DEMUX_PRELUDE_H__
+
+#include <gst/gst.h>
+
+#ifndef GST_ADAPTIVE_DEMUX_API
+#define GST_ADAPTIVE_DEMUX_API GST_EXPORT
+#endif
+
+#endif /* __GST_ADAPTIVE_DEMUX_PRELUDE_H__ */
index 70a47bc..93ce139 100644 (file)
@@ -31,7 +31,7 @@
  *
  * Adaptive formats (HLS, DASH, MSS) are composed of a manifest file and
  * a set of fragments. The manifest describes the available media and
- * the sequence of fragments to use. Each fragments contains a small
+ * the sequence of fragments to use. Each fragment contains a small
  * part of the media (typically only a few seconds). It is possible for
  * the manifest to have the same media available in different configurations
  * (bitrates for example) so that the client can select the one that
@@ -688,11 +688,10 @@ gst_adaptive_demux_sink_event (GstPad * pad, GstObject * parent,
     GstEvent * event)
 {
   GstAdaptiveDemux *demux = GST_ADAPTIVE_DEMUX_CAST (parent);
-  GstAdaptiveDemuxClass *demux_class;
   gboolean ret;
 
   switch (event->type) {
-    case GST_EVENT_FLUSH_STOP:
+    case GST_EVENT_FLUSH_STOP:{
       GST_API_LOCK (demux);
       GST_MANIFEST_LOCK (demux);
 
@@ -704,7 +703,9 @@ gst_adaptive_demux_sink_event (GstPad * pad, GstObject * parent,
       GST_API_UNLOCK (demux);
 
       return ret;
+    }
     case GST_EVENT_EOS:{
+      GstAdaptiveDemuxClass *demux_class;
       GstQuery *query;
       gboolean query_res;
       gboolean ret = TRUE;
@@ -1245,10 +1246,14 @@ gst_adaptive_demux_prepare_streams (GstAdaptiveDemux * demux,
     if (!gst_adaptive_demux_prepare_stream (demux,
             GST_ADAPTIVE_DEMUX_STREAM_CAST (stream))) {
       /* TODO act on error */
+      GST_FIXME_OBJECT (stream->pad,
+          "Do something on failure to expose stream");
     }
 
     if (first_and_live) {
-      /* TODO we only need the first timestamp, maybe create a simple function */
+      /* TODO we only need the first timestamp, maybe create a simple function to
+       * get the current PTS of a fragment ? */
+      GST_DEBUG_OBJECT (demux, "Calling update_fragment_info");
       gst_adaptive_demux_stream_update_fragment_info (demux, stream);
 
       if (GST_CLOCK_TIME_IS_VALID (min_pts)) {
@@ -1377,6 +1382,7 @@ gst_adaptive_demux_prepare_streams (GstAdaptiveDemux * demux,
 
     stream->pending_segment = gst_event_new_segment (&stream->segment);
     gst_event_set_seqnum (stream->pending_segment, demux->priv->segment_seqnum);
+    stream->qos_earliest_time = GST_CLOCK_TIME_NONE;
 
     GST_DEBUG_OBJECT (demux,
         "Prepared segment %" GST_SEGMENT_FORMAT " for stream %p",
@@ -1451,6 +1457,7 @@ gst_adaptive_demux_expose_streams (GstAdaptiveDemux * demux)
       gst_task_stop (stream->download_task);
       g_mutex_lock (&stream->fragment_download_lock);
       stream->cancelled = TRUE;
+      stream->replaced = TRUE;
       g_cond_signal (&stream->fragment_download_cond);
       g_mutex_unlock (&stream->fragment_download_lock);
     }
@@ -1493,6 +1500,7 @@ gst_adaptive_demux_stream_new (GstAdaptiveDemux * demux, GstPad * pad)
   stream->fragment_bitrates =
       g_malloc0 (sizeof (guint64) * NUM_LOOKBACK_FRAGMENTS);
   gst_pad_set_element_private (pad, stream);
+  stream->qos_earliest_time = GST_CLOCK_TIME_NONE;
 
   g_mutex_lock (&demux->priv->preroll_lock);
   stream->do_block = TRUE;
@@ -1692,11 +1700,12 @@ gst_adaptive_demux_update_streams_segment (GstAdaptiveDemux * demux,
     seg_evt = gst_event_new_segment (&stream->segment);
     gst_event_set_seqnum (seg_evt, demux->priv->segment_seqnum);
     gst_event_replace (&stream->pending_segment, seg_evt);
-    GST_DEBUG_OBJECT (stream->pad, "Pending segment now %" GST_SEGMENT_FORMAT,
-        &stream->pending_segment);
+    GST_DEBUG_OBJECT (stream->pad, "Pending segment now %" GST_PTR_FORMAT,
+        stream->pending_segment);
     gst_event_unref (seg_evt);
     /* Make sure the first buffer after a seek has the discont flag */
     stream->discont = TRUE;
+    stream->qos_earliest_time = GST_CLOCK_TIME_NONE;
   }
 }
 
@@ -1743,46 +1752,111 @@ gst_adaptive_demux_handle_seek_event (GstAdaptiveDemux * demux, GstPad * pad,
   if (format != GST_FORMAT_TIME) {
     GST_MANIFEST_UNLOCK (demux);
     GST_API_UNLOCK (demux);
+    GST_WARNING_OBJECT (demux,
+        "Adaptive demuxers only support TIME-based seeking");
+    gst_event_unref (event);
+    return FALSE;
+  }
+
+  if (flags & GST_SEEK_FLAG_SEGMENT) {
+    GST_FIXME_OBJECT (demux, "Handle segment seeks");
+    GST_MANIFEST_UNLOCK (demux);
+    GST_API_UNLOCK (demux);
     gst_event_unref (event);
     return FALSE;
   }
 
+  seqnum = gst_event_get_seqnum (event);
+
   if (gst_adaptive_demux_is_live (demux)) {
     gint64 range_start, range_stop;
+    gboolean changed = FALSE;
+    gboolean start_valid = TRUE, stop_valid = TRUE;
+
     if (!gst_adaptive_demux_get_live_seek_range (demux, &range_start,
             &range_stop)) {
       GST_MANIFEST_UNLOCK (demux);
       GST_API_UNLOCK (demux);
       gst_event_unref (event);
+      GST_WARNING_OBJECT (demux, "Failure getting the live seek ranges");
       return FALSE;
     }
 
-    if (!(flags & GST_SEEK_FLAG_ACCURATE)) {
-      /* If the accurate flag is not set, we allow seeking before the start
-       * to map to the start for live cases, since those can return a "moving
-       * target" based on wall time.
-       */
-      if (start < range_start) {
-        guint64 dt = range_start - start;
-        GST_DEBUG_OBJECT (demux,
-            "Non accurate seek before live stream start, offsetting by %"
-            GST_TIME_FORMAT, GST_TIME_ARGS (dt));
-        start = range_start;
-        if (stop != GST_CLOCK_TIME_NONE)
-          stop += dt;
-      }
+    GST_DEBUG_OBJECT (demux,
+        "Live range is %" GST_STIME_FORMAT " %" GST_STIME_FORMAT,
+        GST_STIME_ARGS (range_start), GST_STIME_ARGS (range_stop));
+
+    /* Handle relative positioning for live streams (relative to the range_stop) */
+    if (start_type == GST_SEEK_TYPE_END) {
+      start = range_stop + start;
+      start_type = GST_SEEK_TYPE_SET;
+      changed = TRUE;
+    }
+    if (stop_type == GST_SEEK_TYPE_END) {
+      stop = range_stop + stop;
+      stop_type = GST_SEEK_TYPE_SET;
+      changed = TRUE;
+    }
+
+    /* Adjust the requested start/stop position if it falls beyond the live
+     * seek range.
+     * The only case where we don't adjust is for the starting point of
+     * an accurate seek (start if forward and stop if backwards)
+     */
+    if (start_type == GST_SEEK_TYPE_SET && start < range_start &&
+        (rate < 0 || !(flags & GST_SEEK_FLAG_ACCURATE))) {
+      GST_DEBUG_OBJECT (demux,
+          "seek before live stream start, setting to range start: %"
+          GST_TIME_FORMAT, GST_TIME_ARGS (range_start));
+      start = range_start;
+      changed = TRUE;
+    }
+    /* truncate stop position also if set */
+    if (stop_type == GST_SEEK_TYPE_SET && stop > range_stop &&
+        (rate > 0 || !(flags & GST_SEEK_FLAG_ACCURATE))) {
+      GST_DEBUG_OBJECT (demux,
+          "seek ending after live start, adjusting to: %"
+          GST_TIME_FORMAT, GST_TIME_ARGS (range_stop));
+      stop = range_stop;
+      changed = TRUE;
     }
 
-    if (start < range_start || start >= range_stop) {
+    if (start_type == GST_SEEK_TYPE_SET && GST_CLOCK_TIME_IS_VALID (start) &&
+        (start < range_start || start > range_stop)) {
+      GST_WARNING_OBJECT (demux,
+          "Seek to invalid position start:%" GST_STIME_FORMAT
+          " out of seekable range (%" GST_STIME_FORMAT " - %" GST_STIME_FORMAT
+          ")", GST_STIME_ARGS (start), GST_STIME_ARGS (range_start),
+          GST_STIME_ARGS (range_stop));
+      start_valid = FALSE;
+    }
+    if (stop_type == GST_SEEK_TYPE_SET && GST_CLOCK_TIME_IS_VALID (stop) &&
+        (stop < range_start || stop > range_stop)) {
+      GST_WARNING_OBJECT (demux,
+          "Seek to invalid position stop:%" GST_STIME_FORMAT
+          " out of seekable range (%" GST_STIME_FORMAT " - %" GST_STIME_FORMAT
+          ")", GST_STIME_ARGS (stop), GST_STIME_ARGS (range_start),
+          GST_STIME_ARGS (range_stop));
+      stop_valid = FALSE;
+    }
+
+    /* If the seek position is still outside of the seekable range, refuse the seek */
+    if (!start_valid || !stop_valid) {
       GST_MANIFEST_UNLOCK (demux);
       GST_API_UNLOCK (demux);
-      GST_WARNING_OBJECT (demux, "Seek to invalid position");
       gst_event_unref (event);
       return FALSE;
     }
-  }
 
-  seqnum = gst_event_get_seqnum (event);
+    /* Re-create seek event with changed/updated values */
+    if (changed) {
+      gst_event_unref (event);
+      event =
+          gst_event_new_seek (rate, format, flags,
+          start_type, start, stop_type, stop);
+      gst_event_set_seqnum (event, seqnum);
+    }
+  }
 
   GST_DEBUG_OBJECT (demux, "seek event, %" GST_PTR_FORMAT, event);
 
@@ -1974,6 +2048,28 @@ gst_adaptive_demux_src_event (GstPad * pad, GstObject * parent,
       gst_event_unref (event);
       return TRUE;
     }
+    case GST_EVENT_QOS:{
+      GstAdaptiveDemuxStream *stream;
+
+      GST_MANIFEST_LOCK (demux);
+      stream = gst_adaptive_demux_find_stream_for_pad (demux, pad);
+
+      if (stream) {
+        GstClockTimeDiff diff;
+        GstClockTime timestamp;
+
+        gst_event_parse_qos (event, NULL, NULL, &diff, &timestamp);
+        /* Only take into account lateness if late */
+        if (diff > 0)
+          stream->qos_earliest_time = timestamp + 2 * diff;
+        else
+          stream->qos_earliest_time = timestamp;
+        GST_DEBUG_OBJECT (stream->pad, "qos_earliest_time %" GST_TIME_FORMAT,
+            GST_TIME_ARGS (stream->qos_earliest_time));
+      }
+      GST_MANIFEST_UNLOCK (demux);
+      break;
+    }
     default:
       break;
   }
@@ -2141,6 +2237,7 @@ gst_adaptive_demux_start_tasks (GstAdaptiveDemux * demux,
     if (!start_preroll_streams) {
       g_mutex_lock (&stream->fragment_download_lock);
       stream->cancelled = FALSE;
+      stream->replaced = FALSE;
       g_mutex_unlock (&stream->fragment_download_lock);
     }
 
@@ -2158,6 +2255,7 @@ gst_adaptive_demux_stop_manifest_update_task (GstAdaptiveDemux * demux)
   gst_task_stop (demux->priv->updates_task);
 
   g_mutex_lock (&demux->priv->updates_timed_lock);
+  GST_DEBUG_OBJECT (demux, "requesting stop of the manifest update task");
   demux->priv->stop_updates_task = TRUE;
   g_cond_signal (&demux->priv->updates_timed_cond);
   g_mutex_unlock (&demux->priv->updates_timed_lock);
@@ -2176,6 +2274,7 @@ gst_adaptive_demux_start_manifest_update_task (GstAdaptiveDemux * demux)
     g_mutex_unlock (&demux->priv->updates_timed_lock);
     /* Task to periodically update the manifest */
     if (demux_class->requires_periodical_playlist_update (demux)) {
+      GST_DEBUG_OBJECT (demux, "requesting start of the manifest update task");
       gst_task_start (demux->priv->updates_task);
     }
   }
@@ -2190,20 +2289,27 @@ gst_adaptive_demux_start_manifest_update_task (GstAdaptiveDemux * demux)
 static void
 gst_adaptive_demux_stop_tasks (GstAdaptiveDemux * demux, gboolean stop_updates)
 {
+  int i;
   GList *iter;
+  GList *list_to_process;
 
   GST_LOG_OBJECT (demux, "Stopping tasks");
 
   if (stop_updates)
     gst_adaptive_demux_stop_manifest_update_task (demux);
-  for (iter = demux->streams; iter; iter = g_list_next (iter)) {
-    GstAdaptiveDemuxStream *stream = iter->data;
 
-    g_mutex_lock (&stream->fragment_download_lock);
-    stream->cancelled = TRUE;
-    gst_task_stop (stream->download_task);
-    g_cond_signal (&stream->fragment_download_cond);
-    g_mutex_unlock (&stream->fragment_download_lock);
+  list_to_process = demux->streams;
+  for (i = 0; i < 2; ++i) {
+    for (iter = list_to_process; iter; iter = g_list_next (iter)) {
+      GstAdaptiveDemuxStream *stream = iter->data;
+
+      g_mutex_lock (&stream->fragment_download_lock);
+      stream->cancelled = TRUE;
+      gst_task_stop (stream->download_task);
+      g_cond_signal (&stream->fragment_download_cond);
+      g_mutex_unlock (&stream->fragment_download_lock);
+    }
+    list_to_process = demux->prepared_streams;
   }
 
   GST_MANIFEST_UNLOCK (demux);
@@ -2221,23 +2327,27 @@ gst_adaptive_demux_stop_tasks (GstAdaptiveDemux * demux, gboolean stop_updates)
    * object. Even if we temporarily release manifest_lock, the demux->streams
    * cannot change and iter cannot be invalidated.
    */
-  for (iter = demux->streams; iter; iter = g_list_next (iter)) {
-    GstAdaptiveDemuxStream *stream = iter->data;
-    GstElement *src = stream->src;
+  list_to_process = demux->streams;
+  for (i = 0; i < 2; ++i) {
+    for (iter = list_to_process; iter; iter = g_list_next (iter)) {
+      GstAdaptiveDemuxStream *stream = iter->data;
+      GstElement *src = stream->src;
 
-    GST_MANIFEST_UNLOCK (demux);
+      GST_MANIFEST_UNLOCK (demux);
 
-    if (src) {
-      gst_element_set_locked_state (src, TRUE);
-      gst_element_set_state (src, GST_STATE_READY);
-    }
+      if (src) {
+        gst_element_set_locked_state (src, TRUE);
+        gst_element_set_state (src, GST_STATE_READY);
+      }
 
-    /* stream->download_task value never changes, so it is safe to read it
-     * outside critical section
-     */
-    gst_task_join (stream->download_task);
+      /* stream->download_task value never changes, so it is safe to read it
+       * outside critical section
+       */
+      gst_task_join (stream->download_task);
 
-    GST_MANIFEST_LOCK (demux);
+      GST_MANIFEST_LOCK (demux);
+    }
+    list_to_process = demux->prepared_streams;
   }
 
   GST_MANIFEST_UNLOCK (demux);
@@ -2246,11 +2356,16 @@ gst_adaptive_demux_stop_tasks (GstAdaptiveDemux * demux, gboolean stop_updates)
 
   GST_MANIFEST_LOCK (demux);
 
-  for (iter = demux->streams; iter; iter = g_list_next (iter)) {
-    GstAdaptiveDemuxStream *stream = iter->data;
+  list_to_process = demux->streams;
+  for (i = 0; i < 2; ++i) {
+    for (iter = list_to_process; iter; iter = g_list_next (iter)) {
+      GstAdaptiveDemuxStream *stream = iter->data;
 
-    stream->download_error_count = 0;
-    stream->need_header = TRUE;
+      stream->download_error_count = 0;
+      stream->need_header = TRUE;
+      stream->qos_earliest_time = GST_CLOCK_TIME_NONE;
+    }
+    list_to_process = demux->prepared_streams;
   }
 }
 
@@ -2493,6 +2608,9 @@ gst_adaptive_demux_stream_push_buffer (GstAdaptiveDemuxStream * stream,
   GstEvent *pending_caps = NULL, *pending_segment = NULL, *pending_tags = NULL;
   GList *pending_events = NULL;
 
+  /* FIXME : 
+   * This is duplicating *exactly* the same thing as what is done at the beginning
+   * of _src_chain if starting_fragment is TRUE */
   if (stream->first_fragment_buffer) {
     GstClockTime offset =
         gst_adaptive_demux_stream_get_presentation_offset (demux, stream);
@@ -2645,6 +2763,9 @@ gst_adaptive_demux_stream_push_buffer (GstAdaptiveDemuxStream * stream,
   }
 
   /* Wait for preroll if blocking */
+  GST_DEBUG_OBJECT (stream->pad,
+      "About to push buffer of size %" G_GSIZE_FORMAT,
+      gst_buffer_get_size (buffer));
 
   ret = gst_pad_push (stream->pad, buffer);
 
@@ -2720,6 +2841,11 @@ _src_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
   }
   g_mutex_unlock (&stream->fragment_download_lock);
 
+  /* starting_fragment is set to TRUE at the beginning of
+   * _stream_download_fragment()
+   * /!\ If there is a header/index being downloaded, then this will
+   * be TRUE for the first one ... but FALSE for the remaining ones,
+   * including the *actual* fragment ! */
   if (stream->starting_fragment) {
     GstClockTime offset =
         gst_adaptive_demux_stream_get_presentation_offset (demux, stream);
@@ -2757,6 +2883,15 @@ _src_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
   } else {
     GST_BUFFER_PTS (buffer) = GST_CLOCK_TIME_NONE;
   }
+
+  /* downloading_first_buffer is set to TRUE in download_uri() just before
+   * activating the source (i.e. requesting a given URI)
+   *
+   * The difference with starting_fragment is that this will be called
+   * for *all* first buffers (of index, and header, and fragment)
+   *
+   * ... to then only do something useful (in this block) for actual
+   * fragments... */
   if (stream->downloading_first_buffer) {
     gint64 chunk_size = 0;
 
@@ -2896,6 +3031,10 @@ _src_event (GstPad * pad, GstObject * parent, GstEvent * event)
 
       gst_adaptive_demux_eos_handling (stream);
 
+      /* FIXME ?
+       * _eos_handling() calls  fragment_download_finish() which does the
+       * same thing as below.
+       * Could this cause races ? */
       g_mutex_lock (&stream->fragment_download_lock);
       stream->download_finished = TRUE;
       g_cond_signal (&stream->fragment_download_cond);
@@ -3055,6 +3194,7 @@ gst_adaptive_demux_stream_update_source (GstAdaptiveDemuxStream * stream,
     return FALSE;
   }
 
+  /* Try to re-use existing source element */
   if (stream->src != NULL) {
     gchar *old_protocol, *new_protocol;
     gchar *old_uri;
@@ -3256,7 +3396,7 @@ gst_ad_stream_src_to_ready_cb (GstPad * pad, GstPadProbeInfo * info,
   g_cond_signal (&stream->fragment_download_cond);
   g_mutex_unlock (&stream->fragment_download_lock);
 
-  return GST_PAD_PROBE_OK;
+  return GST_PAD_PROBE_REMOVE;
 }
 
 #ifndef GST_DISABLE_GST_DEBUG
@@ -3273,6 +3413,8 @@ uritype (GstAdaptiveDemuxStream * s)
 
 /* must be called with manifest_lock taken.
  * Can temporarily release manifest_lock
+ *
+ * Will return when URI is fully downloaded (or aborted/errored)
  */
 static GstFlowReturn
 gst_adaptive_demux_stream_download_uri (GstAdaptiveDemux * demux,
@@ -3297,6 +3439,7 @@ gst_adaptive_demux_stream_download_uri (GstAdaptiveDemux * demux,
   GST_MANIFEST_UNLOCK (demux);
   if (gst_element_set_state (stream->src,
           GST_STATE_READY) != GST_STATE_CHANGE_FAILURE) {
+    /* If ranges are specified, seek to it */
     if (start != 0 || end != -1) {
       /* HTTP ranges are inclusive, GStreamer segments are exclusive for the
        * stop position */
@@ -3355,12 +3498,19 @@ gst_adaptive_demux_stream_download_uri (GstAdaptiveDemux * demux,
         ret = stream->last_ret = GST_FLOW_FLUSHING;
         return ret;
       }
+      /* download_finished is only set:
+       * * in ::fragment_download_finish()
+       * * if EOS is received on the _src pad
+       * */
       while (!stream->cancelled && !stream->download_finished) {
         g_cond_wait (&stream->fragment_download_cond,
             &stream->fragment_download_lock);
       }
       g_mutex_unlock (&stream->fragment_download_lock);
 
+      GST_DEBUG_OBJECT (stream->pad,
+          "Finished Waiting for %s download: %s", uritype (stream), uri);
+
       GST_MANIFEST_LOCK (demux);
       g_mutex_lock (&stream->fragment_download_lock);
       if (G_UNLIKELY (stream->cancelled)) {
@@ -3385,6 +3535,7 @@ gst_adaptive_demux_stream_download_uri (GstAdaptiveDemux * demux,
      */
     GST_MANIFEST_UNLOCK (demux);
   } else {
+    GST_MANIFEST_UNLOCK (demux);
     if (stream->last_ret == GST_FLOW_OK)
       stream->last_ret = GST_FLOW_CUSTOM_ERROR;
     ret = GST_FLOW_CUSTOM_ERROR;
@@ -3478,10 +3629,17 @@ gst_adaptive_demux_stream_download_fragment (GstAdaptiveDemuxStream * stream)
   guint http_status;
   guint last_status_code;
 
+  /* FIXME :  */
+  /* THERE ARE THREE DIFFERENT VARIABLES FOR THE "BEGINNING" OF A FRAGMENT ! */
   stream->starting_fragment = TRUE;
   stream->last_ret = GST_FLOW_OK;
   stream->first_fragment_buffer = TRUE;
 
+  GST_DEBUG_OBJECT (stream->pad, "Downloading %s%s%s",
+      stream->fragment.uri ? "FRAGMENT " : "",
+      stream->fragment.header_uri ? "HEADER " : "",
+      stream->fragment.index_uri ? "INDEX" : "");
+
   if (stream->fragment.uri == NULL && stream->fragment.header_uri == NULL &&
       stream->fragment.index_uri == NULL)
     goto no_url_error;
@@ -3504,8 +3662,10 @@ again:
   stream->last_ret = GST_FLOW_OK;
   http_status = 200;
 
+  /* Download the actual fragment, either in fragments or in one go */
   if (klass->need_another_chunk && klass->need_another_chunk (stream)
       && stream->fragment.chunk_size != 0) {
+    /* Handle chunk downloading */
     gint64 range_start, range_end, chunk_start, chunk_end;
     guint64 download_total_bytes;
     gint chunk_size = stream->fragment.chunk_size;
@@ -3598,7 +3758,9 @@ again:
       last_status_code, stream->download_error_count);
 
   live = gst_adaptive_demux_is_live (demux);
-  if (!retried_once && ((last_status_code / 100 == 4 && live) || last_status_code / 100 == 5)) {        /* 4xx/5xx */
+  if (!retried_once && ((last_status_code / 100 == 4 && live)
+          || last_status_code / 100 == 5)) {
+    /* 4xx/5xx */
     /* if current position is before available start, switch to next */
     if (!gst_adaptive_demux_stream_has_next_fragment (demux, stream))
       goto flushing;
@@ -3616,6 +3778,7 @@ again:
         ret = gst_adaptive_demux_eos_handling (stream);
         GST_DEBUG_OBJECT (stream->pad, "finish_fragment: %s",
             gst_flow_get_name (ret));
+        GST_DEBUG_OBJECT (demux, "Calling update_fragment_info");
         ret = gst_adaptive_demux_stream_update_fragment_info (demux, stream);
         GST_DEBUG_OBJECT (stream->pad, "finish_fragment: %s",
             gst_flow_get_name (ret));
@@ -3666,9 +3829,7 @@ again:
       GST_DEBUG_OBJECT (stream->pad, "Converting error of live stream to EOS");
       return GST_FLOW_EOS;
     }
-  }
-
-  else if (!gst_adaptive_demux_stream_has_next_fragment (demux, stream)) {
+  } else if (!gst_adaptive_demux_stream_has_next_fragment (demux, stream)) {
     /* If this is the last fragment, consider failures EOS and not actual
      * errors. Due to rounding errors in the durations, the last fragment
      * might not actually exist */
@@ -3766,6 +3927,8 @@ gst_adaptive_demux_stream_download_loop (GstAdaptiveDemuxStream * stream)
     g_mutex_unlock (&stream->fragment_download_lock);
   }
 
+  /* Restarting download, figure out new position
+   * FIXME : Move this to a separate function ? */
   if (G_UNLIKELY (stream->restart_download)) {
     GstEvent *seg_event;
     GstClockTime cur, ts = 0;
@@ -3840,6 +4003,8 @@ gst_adaptive_demux_stream_download_loop (GstAdaptiveDemuxStream * stream)
 
   live = gst_adaptive_demux_is_live (demux);
 
+  /* Get information about the fragment to download */
+  GST_DEBUG_OBJECT (demux, "Calling update_fragment_info");
   ret = gst_adaptive_demux_stream_update_fragment_info (demux, stream);
   GST_DEBUG_OBJECT (stream->pad, "Fragment info update result: %d %s",
       ret, gst_flow_get_name (ret));
@@ -3921,20 +4086,27 @@ gst_adaptive_demux_stream_download_loop (GstAdaptiveDemuxStream * stream)
         if (!demux_class->requires_periodical_playlist_update (demux)) {
           ret = gst_adaptive_demux_update_manifest (demux);
           break;
-        } else if (gst_adaptive_demux_stream_wait_manifest_update (demux,
-                stream)) {
+          /* Wait only if we can ensure current manifest has been expired.
+           * The meaning "we have next period" *WITH* EOS is that, current
+           * period has been ended but we can continue to the next period */
+        } else if (!gst_adaptive_demux_has_next_period (demux) &&
+            gst_adaptive_demux_stream_wait_manifest_update (demux, stream)) {
           goto end;
         }
         gst_task_stop (stream->download_task);
+        if (stream->replaced) {
+          goto end;
+        }
       } else {
         gst_task_stop (stream->download_task);
-        if (gst_adaptive_demux_combine_flows (demux) == GST_FLOW_EOS) {
-          if (gst_adaptive_demux_has_next_period (demux)) {
-            GST_DEBUG_OBJECT (stream->pad,
-                "Next period available, not sending EOS");
-            gst_adaptive_demux_advance_period (demux);
-            ret = GST_FLOW_OK;
-          }
+      }
+
+      if (gst_adaptive_demux_combine_flows (demux) == GST_FLOW_EOS) {
+        if (gst_adaptive_demux_has_next_period (demux)) {
+          GST_DEBUG_OBJECT (stream->pad,
+              "Next period available, not sending EOS");
+          gst_adaptive_demux_advance_period (demux);
+          ret = GST_FLOW_OK;
         }
       }
       break;
@@ -4259,6 +4431,9 @@ gst_adaptive_demux_stream_has_next_fragment (GstAdaptiveDemux * demux,
 }
 
 /* must be called with manifest_lock taken */
+/* Called from:
+ *  the ::finish_fragment() handlers when an *actual* fragment is done
+ *   */
 GstFlowReturn
 gst_adaptive_demux_stream_advance_fragment (GstAdaptiveDemux * demux,
     GstAdaptiveDemuxStream * stream, GstClockTime duration)
@@ -4285,10 +4460,17 @@ gst_adaptive_demux_stream_advance_fragment_unlocked (GstAdaptiveDemux * demux,
 
   g_return_val_if_fail (klass->stream_advance_fragment != NULL, GST_FLOW_ERROR);
 
+  GST_LOG_OBJECT (stream->pad,
+      "timestamp %" GST_TIME_FORMAT " duration:%" GST_TIME_FORMAT,
+      GST_TIME_ARGS (stream->fragment.timestamp), GST_TIME_ARGS (duration));
+
   stream->download_error_count = 0;
   g_clear_error (&stream->last_error);
 
   /* FIXME - url has no indication of byte ranges for subsegments */
+  /* FIXME : All those time statistics are biased, since they are calculated
+   * *AFTER* the queue2, which might be blocking. They should ideally be
+   * calculated *before* queue2 in the uri_handler_probe */
   gst_element_post_message (GST_ELEMENT_CAST (demux),
       gst_message_new_element (GST_OBJECT_CAST (demux),
           gst_structure_new (GST_ADAPTIVE_DEMUX_STATISTICS_MESSAGE_NAME,
@@ -4397,6 +4579,7 @@ gst_adaptive_demux_stream_update_fragment_info (GstAdaptiveDemux * demux,
     GstAdaptiveDemuxStream * stream)
 {
   GstAdaptiveDemuxClass *klass = GST_ADAPTIVE_DEMUX_GET_CLASS (demux);
+  GstFlowReturn ret;
 
   g_return_val_if_fail (klass->stream_update_fragment_info != NULL,
       GST_FLOW_ERROR);
@@ -4406,7 +4589,24 @@ gst_adaptive_demux_stream_update_fragment_info (GstAdaptiveDemux * demux,
   stream->fragment.bitrate = 0;
   stream->fragment.finished = FALSE;
 
-  return klass->stream_update_fragment_info (stream);
+  GST_LOG_OBJECT (stream->pad, "position %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (stream->segment.position));
+
+  ret = klass->stream_update_fragment_info (stream);
+
+  GST_LOG_OBJECT (stream->pad, "ret:%s uri:%s", gst_flow_get_name (ret),
+      stream->fragment.uri);
+  if (ret == GST_FLOW_OK) {
+    GST_LOG_OBJECT (stream->pad,
+        "timestamp %" GST_TIME_FORMAT " duration:%" GST_TIME_FORMAT,
+        GST_TIME_ARGS (stream->fragment.timestamp),
+        GST_TIME_ARGS (stream->fragment.duration));
+    GST_LOG_OBJECT (stream->pad,
+        "range start:%" G_GINT64_FORMAT " end:%" G_GINT64_FORMAT,
+        stream->fragment.range_start, stream->fragment.range_end);
+  }
+
+  return ret;
 }
 
 /* must be called with manifest_lock taken */
@@ -4490,6 +4690,18 @@ gst_adaptive_demux_update_manifest (GstAdaptiveDemux * demux)
       GST_DEBUG_OBJECT (demux,
           "Duration unknown, can not send the duration message");
     }
+
+    /* If a manifest changes it's liveness or periodic updateness, we need
+     * to start/stop the manifest update task appropriately */
+    /* Keep this condition in sync with the one in
+     * gst_adaptive_demux_start_manifest_update_task()
+     */
+    if (gst_adaptive_demux_is_live (demux) &&
+        klass->requires_periodical_playlist_update (demux)) {
+      gst_adaptive_demux_start_manifest_update_task (demux);
+    } else {
+      gst_adaptive_demux_stop_manifest_update_task (demux);
+    }
   }
 
   return ret;
index b422812..1c09c26 100644 (file)
@@ -25,6 +25,7 @@
 #include <gst/gst.h>
 #include <gst/base/gstadapter.h>
 #include <gst/uridownloader/gsturidownloader.h>
+#include <gst/adaptivedemux/adaptive-demux-prelude.h>
 
 G_BEGIN_DECLS
 
@@ -38,7 +39,7 @@ G_BEGIN_DECLS
   (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_ADAPTIVE_DEMUX,GstAdaptiveDemuxClass))
 #define GST_IS_ADAPTIVE_DEMUX(obj) \
   (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_ADAPTIVE_DEMUX))
-#define GST_IS_ADAPTIVE_DEMUX_CLASS(klass) \
+#define GST_IS_ADAPTIVE_DEMUX_CLASS(obj) \
   (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_ADAPTIVE_DEMUX))
 #define GST_ADAPTIVE_DEMUX_CAST(obj) ((GstAdaptiveDemux *)obj)
 
@@ -59,6 +60,8 @@ G_BEGIN_DECLS
  */
 #define GST_ADAPTIVE_DEMUX_SINK_PAD(obj)        (((GstAdaptiveDemux *) (obj))->sinkpad)
 
+#define GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS(obj) ((((GstAdaptiveDemux*)(obj))->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) == GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS)
+
 #define GST_ADAPTIVE_DEMUX_STREAM_PAD(obj)      (((GstAdaptiveDemuxStream *) (obj))->pad)
 
 #define GST_ADAPTIVE_DEMUX_STREAM_NEED_HEADER(obj) (((GstAdaptiveDemuxStream *) (obj))->need_header)
@@ -173,7 +176,8 @@ struct _GstAdaptiveDemuxStream
   GMutex fragment_download_lock;
   GCond fragment_download_cond;
   gboolean download_finished;   /* protected by fragment_download_lock */
-  gboolean cancelled;           /* protected by fragment_download_lock */
+  gboolean cancelled; /* protected by fragment_download_lock */
+  gboolean replaced; /* replaced in a bitrate switch (used with cancelled) */
   gboolean src_at_ready;     /* protected by fragment_download_lock */
   gboolean starting_fragment;
   gboolean first_fragment_buffer;
@@ -195,6 +199,9 @@ struct _GstAdaptiveDemuxStream
   guint moving_index;
   guint64 *fragment_bitrates;
 
+  /* QoS data */
+  GstClockTime qos_earliest_time;
+
   GstAdaptiveDemuxStreamFragment fragment;
 
   guint download_error_count;
@@ -276,7 +283,7 @@ struct _GstAdaptiveDemuxClass
    * Parse the manifest and add the created streams using
    * gst_adaptive_demux_stream_new()
    *
-   * Returns: #TRUE if successful
+   * Returns: %TRUE if successful
    */
   gboolean      (*process_manifest) (GstAdaptiveDemux * demux, GstBuffer * manifest);
 
@@ -337,7 +344,7 @@ struct _GstAdaptiveDemuxClass
    * The demuxer should seek on all its streams to the specified position
    * in the seek event
    *
-   * Returns: #TRUE if successful
+   * Returns: %TRUE if successful
    */
   gboolean      (*seek)             (GstAdaptiveDemux * demux, GstEvent * seek);
 
@@ -350,7 +357,7 @@ struct _GstAdaptiveDemuxClass
    * this function is called to verify if there is a new period to be played
    * in sequence.
    *
-   * Returns: #TRUE if there is another period
+   * Returns: %TRUE if there is another period
    */
   gboolean      (*has_next_period)  (GstAdaptiveDemux * demux);
   /**
@@ -371,7 +378,7 @@ struct _GstAdaptiveDemuxClass
    * need_another_chunk:
    * @stream: #GstAdaptiveDemuxStream
    *
-   * If chunked downloading is used (chunk_size != 0) this is called once as
+   * If chunked downloading is used (chunk_size != 0) this is called once a
    * chunk is finished to decide whether more has to be downloaded or not.
    * May update chunk_size to a different value
    */
@@ -399,7 +406,7 @@ struct _GstAdaptiveDemuxClass
    * needs a caps change it should set the new caps using
    * gst_adaptive_demux_stream_set_caps().
    *
-   * Returns: #TRUE if the stream changed bitrate, #FALSE otherwise
+   * Returns: %TRUE if the stream changed bitrate, %FALSE otherwise
    */
   gboolean      (*stream_select_bitrate) (GstAdaptiveDemuxStream * stream, guint64 bitrate);
   /**
@@ -410,7 +417,7 @@ struct _GstAdaptiveDemuxClass
    * to download the fragment. This is useful to avoid downloading a fragment that
    * isn't available yet.
    *
-   * Returns: The waiting time in microsseconds
+   * Returns: The waiting time in microseconds
    */
   gint64        (*stream_get_fragment_waiting_time) (GstAdaptiveDemuxStream * stream);
 
@@ -423,7 +430,7 @@ struct _GstAdaptiveDemuxClass
    * of a new fragment. Can be used to reset/init internal state that is
    * needed before each fragment, like decryption engines.
    *
-   * Returns: #TRUE if successful.
+   * Returns: %TRUE if successful.
    */
   gboolean      (*start_fragment) (GstAdaptiveDemux * demux, GstAdaptiveDemuxStream * stream);
   /**
@@ -501,30 +508,49 @@ struct _GstAdaptiveDemuxClass
   gboolean (*requires_periodical_playlist_update) (GstAdaptiveDemux * demux);
 };
 
+GST_ADAPTIVE_DEMUX_API
 GType    gst_adaptive_demux_get_type (void);
 
+GST_ADAPTIVE_DEMUX_API
 void     gst_adaptive_demux_set_stream_struct_size (GstAdaptiveDemux * demux,
                                                     gsize struct_size);
 
 
+GST_ADAPTIVE_DEMUX_API
 GstAdaptiveDemuxStream *gst_adaptive_demux_stream_new (GstAdaptiveDemux * demux,
                                                        GstPad * pad);
+
+GST_ADAPTIVE_DEMUX_API
 GstAdaptiveDemuxStream *gst_adaptive_demux_find_stream_for_pad (GstAdaptiveDemux * demux,
                                                                 GstPad * pad);
+
+GST_ADAPTIVE_DEMUX_API
 void gst_adaptive_demux_stream_set_caps (GstAdaptiveDemuxStream * stream,
                                          GstCaps * caps);
+
+GST_ADAPTIVE_DEMUX_API
 void gst_adaptive_demux_stream_set_tags (GstAdaptiveDemuxStream * stream,
                                          GstTagList * tags);
+
+GST_ADAPTIVE_DEMUX_API
 void gst_adaptive_demux_stream_fragment_clear (GstAdaptiveDemuxStreamFragment * f);
 
+GST_ADAPTIVE_DEMUX_API
 GstFlowReturn gst_adaptive_demux_stream_push_buffer (GstAdaptiveDemuxStream * stream, GstBuffer * buffer);
+
+GST_ADAPTIVE_DEMUX_API
 GstFlowReturn
 gst_adaptive_demux_stream_advance_fragment (GstAdaptiveDemux * demux,
     GstAdaptiveDemuxStream * stream, GstClockTime duration);
+
+GST_ADAPTIVE_DEMUX_API
 void gst_adaptive_demux_stream_queue_event (GstAdaptiveDemuxStream * stream,
     GstEvent * event);
 
+GST_ADAPTIVE_DEMUX_API
 GstClockTime gst_adaptive_demux_get_monotonic_time (GstAdaptiveDemux * demux);
+
+GST_ADAPTIVE_DEMUX_API
 GDateTime *gst_adaptive_demux_get_client_now_utc (GstAdaptiveDemux * demux);
 
 #ifdef TIZEN_FEATURE_AVOID_PAD_SWITCHING
diff --git a/gst-libs/gst/isoff/Makefile.am b/gst-libs/gst/isoff/Makefile.am
new file mode 100644 (file)
index 0000000..b99acae
--- /dev/null
@@ -0,0 +1,24 @@
+lib_LTLIBRARIES = libgstisoff-@GST_API_VERSION@.la
+
+libgstisoff_@GST_API_VERSION@_la_SOURCES = \
+       gstisoff.c
+
+libgstisoff_@GST_API_VERSION@includedir = \
+       $(includedir)/gstreamer-@GST_API_VERSION@/gst/isoff
+
+libgstisoff_@GST_API_VERSION@include_HEADERS = \
+       gstisoff.h
+
+libgstisoff_@GST_API_VERSION@_la_CFLAGS = \
+       $(GST_PLUGINS_BAD_CFLAGS) \
+       -DGST_USE_UNSTABLE_API \
+       $(GST_CFLAGS)
+
+libgstisoff_@GST_API_VERSION@_la_LIBADD = \
+       $(GST_BASE_LIBS) \
+       $(GST_LIBS)
+
+libgstisoff_@GST_API_VERSION@_la_LDFLAGS = \
+       $(GST_LIB_LDFLAGS) \
+       $(GST_ALL_LDFLAGS) \
+       $(GST_LT_LDFLAGS)
similarity index 57%
rename from ext/dash/gstisoff.c
rename to gst-libs/gst/isoff/gstisoff.c
index f305b7c..e6f0cfb 100644 (file)
 #include <gst/base/gstbytereader.h>
 
 #include <string.h>
-#include "gstdash_debug.h"
 
-#define GST_CAT_DEFAULT gst_dash_demux_debug
+GST_DEBUG_CATEGORY_STATIC (gst_isoff_debug);
+#define GST_CAT_DEFAULT gst_isoff_debug
 
-/* gst_isoff_parse_box:
+static gboolean initialized = FALSE;
+
+#define INITIALIZE_DEBUG_CATEGORY \
+  if (!initialized) { \
+  GST_DEBUG_CATEGORY_INIT (gst_isoff_debug, "isoff", 0, \
+      "ISO File Format parsing library"); \
+    initialized = TRUE; \
+  }
+
+static const guint8 tfrf_uuid[] = {
+  0xd4, 0x80, 0x7e, 0xf2, 0xca, 0x39, 0x46, 0x95,
+  0x8e, 0x54, 0x26, 0xcb, 0x9e, 0x46, 0xa7, 0x9f
+};
+
+static const guint8 tfxd_uuid[] = {
+  0x6d, 0x1d, 0x9b, 0x05, 0x42, 0xd5, 0x44, 0xe6,
+  0x80, 0xe2, 0x14, 0x1d, 0xaf, 0xf7, 0x57, 0xb2
+};
+
+/* gst_isoff_parse_box_header:
  * @reader:
  * @type: type that was found at the current position
  * @extended_type: (allow-none): extended type if type=='uuid'
@@ -49,6 +68,7 @@ gst_isoff_parse_box_header (GstByteReader * reader, guint32 * type,
   guint header_start_offset;
   guint32 size_field;
 
+  INITIALIZE_DEBUG_CATEGORY;
   header_start_offset = gst_byte_reader_get_pos (reader);
 
   if (gst_byte_reader_get_remaining (reader) < 8)
@@ -92,10 +112,27 @@ gst_isoff_trun_box_clear (GstTrunBox * trun)
 }
 
 static void
+gst_isoff_tfrf_box_free (GstTfrfBox * tfrf)
+{
+  if (tfrf->entries)
+    g_array_free (tfrf->entries, TRUE);
+
+  g_free (tfrf);
+}
+
+static void
 gst_isoff_traf_box_clear (GstTrafBox * traf)
 {
   if (traf->trun)
     g_array_free (traf->trun, TRUE);
+
+  if (traf->tfrf)
+    gst_isoff_tfrf_box_free (traf->tfrf);
+
+  g_free (traf->tfxd);
+  traf->trun = NULL;
+  traf->tfrf = NULL;
+  traf->tfxd = NULL;
 }
 
 static gboolean
@@ -222,6 +259,146 @@ error:
 }
 
 static gboolean
+gst_isoff_tfdt_box_parse (GstTfdtBox * tfdt, GstByteReader * reader)
+{
+  gint8 version;
+
+  memset (tfdt, 0, sizeof (*tfdt));
+
+  if (gst_byte_reader_get_remaining (reader) < 4)
+    return FALSE;
+
+  version = gst_byte_reader_get_uint8_unchecked (reader);
+
+  if (!gst_byte_reader_skip (reader, 3))
+    return FALSE;
+
+  if (version == 1) {
+    if (!gst_byte_reader_get_uint64_be (reader, &tfdt->decode_time))
+      return FALSE;
+  } else {
+    guint32 dec_time = 0;
+    if (!gst_byte_reader_get_uint32_be (reader, &dec_time))
+      return FALSE;
+    tfdt->decode_time = dec_time;
+  }
+
+  return TRUE;
+}
+
+static gboolean
+gst_isoff_tfxd_box_parse (GstTfxdBox * tfxd, GstByteReader * reader)
+{
+  guint8 version;
+  guint32 flags = 0;
+  guint64 absolute_time = 0;
+  guint64 absolute_duration = 0;
+
+  memset (tfxd, 0, sizeof (*tfxd));
+
+  if (gst_byte_reader_get_remaining (reader) < 4)
+    return FALSE;
+
+  if (!gst_byte_reader_get_uint8 (reader, &version)) {
+    GST_ERROR ("Error getting box's version field");
+    return FALSE;
+  }
+
+  if (!gst_byte_reader_get_uint24_be (reader, &flags)) {
+    GST_ERROR ("Error getting box's flags field");
+    return FALSE;
+  }
+
+  tfxd->version = version;
+  tfxd->flags = flags;
+
+  if (gst_byte_reader_get_remaining (reader) < ((version & 0x01) ? 16 : 8))
+    return FALSE;
+
+  if (version & 0x01) {
+    gst_byte_reader_get_uint64_be (reader, &absolute_time);
+    gst_byte_reader_get_uint64_be (reader, &absolute_duration);
+  } else {
+    guint32 time = 0;
+    guint32 duration = 0;
+    gst_byte_reader_get_uint32_be (reader, &time);
+    gst_byte_reader_get_uint32_be (reader, &duration);
+    absolute_time = time;
+    absolute_duration = duration;
+  }
+
+  tfxd->time = absolute_time;
+  tfxd->duration = absolute_duration;
+
+  return TRUE;
+}
+
+static gboolean
+gst_isoff_tfrf_box_parse (GstTfrfBox * tfrf, GstByteReader * reader)
+{
+  guint8 version;
+  guint32 flags = 0;
+  guint8 fragment_count = 0;
+  guint8 index = 0;
+
+  memset (tfrf, 0, sizeof (*tfrf));
+
+  if (gst_byte_reader_get_remaining (reader) < 4)
+    return FALSE;
+
+  if (!gst_byte_reader_get_uint8 (reader, &version)) {
+    GST_ERROR ("Error getting box's version field");
+    return FALSE;
+  }
+
+  if (!gst_byte_reader_get_uint24_be (reader, &flags)) {
+    GST_ERROR ("Error getting box's flags field");
+    return FALSE;
+  }
+
+  tfrf->version = version;
+  tfrf->flags = flags;
+
+  if (!gst_byte_reader_get_uint8 (reader, &fragment_count))
+    return FALSE;
+
+  tfrf->entries_count = fragment_count;
+  tfrf->entries =
+      g_array_sized_new (FALSE, FALSE, sizeof (GstTfrfBoxEntry),
+      tfrf->entries_count);
+
+  for (index = 0; index < fragment_count; index++) {
+    GstTfrfBoxEntry entry = { 0, };
+    guint64 absolute_time = 0;
+    guint64 absolute_duration = 0;
+    if (gst_byte_reader_get_remaining (reader) < ((version & 0x01) ? 16 : 8))
+      return FALSE;
+
+    if (version & 0x01) {
+      if (!gst_byte_reader_get_uint64_be (reader, &absolute_time) ||
+          !gst_byte_reader_get_uint64_be (reader, &absolute_duration)) {
+        return FALSE;
+      }
+    } else {
+      guint32 time = 0;
+      guint32 duration = 0;
+      if (!gst_byte_reader_get_uint32_be (reader, &time) ||
+          !gst_byte_reader_get_uint32_be (reader, &duration)) {
+        return FALSE;
+      }
+      absolute_time = time;
+      absolute_duration = duration;
+    }
+    entry.time = absolute_time;
+    entry.duration = absolute_duration;
+
+    g_array_append_val (tfrf->entries, entry);
+  }
+
+  return TRUE;
+}
+
+static gboolean
 gst_isoff_traf_box_parse (GstTrafBox * traf, GstByteReader * reader)
 {
   gboolean had_tfhd = FALSE;
@@ -231,21 +408,23 @@ gst_isoff_traf_box_parse (GstTrafBox * traf, GstByteReader * reader)
   g_array_set_clear_func (traf->trun,
       (GDestroyNotify) gst_isoff_trun_box_clear);
 
+  traf->tfdt.decode_time = GST_CLOCK_TIME_NONE;
+
   while (gst_byte_reader_get_remaining (reader) > 0) {
     guint32 fourcc;
     guint header_size;
     guint64 size;
+    GstByteReader sub_reader;
+    guint8 extended_type[16] = { 0, };
 
-    if (!gst_isoff_parse_box_header (reader, &fourcc, NULL, &header_size,
-            &size))
+    if (!gst_isoff_parse_box_header (reader, &fourcc, extended_type,
+            &header_size, &size))
       goto error;
     if (gst_byte_reader_get_remaining (reader) < size - header_size)
       goto error;
 
     switch (fourcc) {
       case GST_ISOFF_FOURCC_TFHD:{
-        GstByteReader sub_reader;
-
         gst_byte_reader_get_sub_reader (reader, &sub_reader,
             size - header_size);
         if (!gst_isoff_tfhd_box_parse (&traf->tfhd, &sub_reader))
@@ -253,8 +432,14 @@ gst_isoff_traf_box_parse (GstTrafBox * traf, GstByteReader * reader)
         had_tfhd = TRUE;
         break;
       }
+      case GST_ISOFF_FOURCC_TFDT:{
+        gst_byte_reader_get_sub_reader (reader, &sub_reader,
+            size - header_size);
+        if (!gst_isoff_tfdt_box_parse (&traf->tfdt, &sub_reader))
+          goto error;
+        break;
+      }
       case GST_ISOFF_FOURCC_TRUN:{
-        GstByteReader sub_reader;
         GstTrunBox trun;
 
         gst_byte_reader_get_sub_reader (reader, &sub_reader,
@@ -265,6 +450,27 @@ gst_isoff_traf_box_parse (GstTrafBox * traf, GstByteReader * reader)
         g_array_append_val (traf->trun, trun);
         break;
       }
+      case GST_ISOFF_FOURCC_UUID:{
+        /* smooth-streaming specific */
+        if (memcmp (extended_type, tfrf_uuid, 16) == 0) {
+          traf->tfrf = g_new0 (GstTfrfBox, 1);
+          gst_byte_reader_get_sub_reader (reader, &sub_reader,
+              size - header_size);
+
+          if (!gst_isoff_tfrf_box_parse (traf->tfrf, &sub_reader))
+            goto error;
+        } else if (memcmp (extended_type, tfxd_uuid, 16) == 0) {
+          traf->tfxd = g_new0 (GstTfxdBox, 1);
+          gst_byte_reader_get_sub_reader (reader, &sub_reader,
+              size - header_size);
+
+          if (!gst_isoff_tfxd_box_parse (traf->tfxd, &sub_reader))
+            goto error;
+        } else {
+          gst_byte_reader_skip (reader, size - header_size);
+        }
+        break;
+      }
       default:
         gst_byte_reader_skip (reader, size - header_size);
         break;
@@ -287,7 +493,10 @@ gst_isoff_moof_box_parse (GstByteReader * reader)
 {
   GstMoofBox *moof;
   gboolean had_mfhd = FALSE;
+  GstByteReader sub_reader;
 
+
+  INITIALIZE_DEBUG_CATEGORY;
   moof = g_new0 (GstMoofBox, 1);
   moof->traf = g_array_new (FALSE, FALSE, sizeof (GstTrafBox));
   g_array_set_clear_func (moof->traf,
@@ -306,8 +515,6 @@ gst_isoff_moof_box_parse (GstByteReader * reader)
 
     switch (fourcc) {
       case GST_ISOFF_FOURCC_MFHD:{
-        GstByteReader sub_reader;
-
         gst_byte_reader_get_sub_reader (reader, &sub_reader,
             size - header_size);
         if (!gst_isoff_mfhd_box_parse (&moof->mfhd, &sub_reader))
@@ -316,7 +523,6 @@ gst_isoff_moof_box_parse (GstByteReader * reader)
         break;
       }
       case GST_ISOFF_FOURCC_TRAF:{
-        GstByteReader sub_reader;
         GstTrafBox traf;
 
         gst_byte_reader_get_sub_reader (reader, &sub_reader,
@@ -350,6 +556,239 @@ gst_isoff_moof_box_free (GstMoofBox * moof)
   g_free (moof);
 }
 
+static gboolean
+gst_isoff_mdhd_box_parse (GstMdhdBox * mdhd, GstByteReader * reader)
+{
+  guint8 version;
+
+  memset (mdhd, 0, sizeof (*mdhd));
+
+  if (gst_byte_reader_get_remaining (reader) < 4)
+    return FALSE;
+
+  version = gst_byte_reader_get_uint8_unchecked (reader);
+
+  if (!gst_byte_reader_skip (reader, 3))
+    return FALSE;
+
+  /* skip {creation, modification}_time, we don't have interest */
+  if (version == 1) {
+    if (!gst_byte_reader_skip (reader, 16))
+      return FALSE;
+  } else {
+    if (!gst_byte_reader_skip (reader, 8))
+      return FALSE;
+  }
+
+  if (!gst_byte_reader_get_uint32_be (reader, &mdhd->timescale))
+    return FALSE;
+
+  return TRUE;
+}
+
+static gboolean
+gst_isoff_hdlr_box_parse (GstHdlrBox * hdlr, GstByteReader * reader)
+{
+  memset (hdlr, 0, sizeof (*hdlr));
+
+  if (gst_byte_reader_get_remaining (reader) < 4)
+    return FALSE;
+
+  /* version & flag */
+  if (!gst_byte_reader_skip (reader, 4))
+    return FALSE;
+
+  /* pre_defined = 0 */
+  if (!gst_byte_reader_skip (reader, 4))
+    return FALSE;
+
+  if (!gst_byte_reader_get_uint32_le (reader, &hdlr->handler_type))
+    return FALSE;
+
+  return TRUE;
+}
+
+static gboolean
+gst_isoff_mdia_box_parse (GstMdiaBox * mdia, GstByteReader * reader)
+{
+  gboolean had_mdhd = FALSE, had_hdlr = FALSE;
+  while (gst_byte_reader_get_remaining (reader) > 0) {
+    guint32 fourcc;
+    guint header_size;
+    guint64 size;
+    GstByteReader sub_reader;
+
+    if (!gst_isoff_parse_box_header (reader, &fourcc, NULL, &header_size,
+            &size))
+      return FALSE;
+    if (gst_byte_reader_get_remaining (reader) < size - header_size)
+      return FALSE;
+
+    switch (fourcc) {
+      case GST_ISOFF_FOURCC_MDHD:{
+        gst_byte_reader_get_sub_reader (reader, &sub_reader,
+            size - header_size);
+        if (!gst_isoff_mdhd_box_parse (&mdia->mdhd, &sub_reader))
+          return FALSE;
+
+        had_mdhd = TRUE;
+        break;
+      }
+      case GST_ISOFF_FOURCC_HDLR:{
+        gst_byte_reader_get_sub_reader (reader, &sub_reader,
+            size - header_size);
+        if (!gst_isoff_hdlr_box_parse (&mdia->hdlr, &sub_reader))
+          return FALSE;
+
+        had_hdlr = TRUE;
+        break;
+      }
+      default:
+        gst_byte_reader_skip (reader, size - header_size);
+        break;
+    }
+  }
+
+  if (!had_mdhd || !had_hdlr)
+    return FALSE;
+
+  return TRUE;
+}
+
+static gboolean
+gst_isoff_tkhd_box_parse (GstTkhdBox * tkhd, GstByteReader * reader)
+{
+  guint8 version;
+
+  memset (tkhd, 0, sizeof (*tkhd));
+
+  if (gst_byte_reader_get_remaining (reader) < 4)
+    return FALSE;
+
+  if (!gst_byte_reader_get_uint8 (reader, &version))
+    return FALSE;
+
+  if (!gst_byte_reader_skip (reader, 3))
+    return FALSE;
+
+  /* skip {creation, modification}_time, we don't have interest */
+  if (version == 1) {
+    if (!gst_byte_reader_skip (reader, 16))
+      return FALSE;
+  } else {
+    if (!gst_byte_reader_skip (reader, 8))
+      return FALSE;
+  }
+
+  if (!gst_byte_reader_get_uint32_be (reader, &tkhd->track_id))
+    return FALSE;
+
+  return TRUE;
+}
+
+static gboolean
+gst_isoff_trak_box_parse (GstTrakBox * trak, GstByteReader * reader)
+{
+  gboolean had_mdia = FALSE, had_tkhd = FALSE;
+  while (gst_byte_reader_get_remaining (reader) > 0) {
+    guint32 fourcc;
+    guint header_size;
+    guint64 size;
+    GstByteReader sub_reader;
+
+    if (!gst_isoff_parse_box_header (reader, &fourcc, NULL, &header_size,
+            &size))
+      return FALSE;
+    if (gst_byte_reader_get_remaining (reader) < size - header_size)
+      return FALSE;
+
+    switch (fourcc) {
+      case GST_ISOFF_FOURCC_MDIA:{
+        gst_byte_reader_get_sub_reader (reader, &sub_reader,
+            size - header_size);
+        if (!gst_isoff_mdia_box_parse (&trak->mdia, &sub_reader))
+          return FALSE;
+
+        had_mdia = TRUE;
+        break;
+      }
+      case GST_ISOFF_FOURCC_TKHD:{
+        gst_byte_reader_get_sub_reader (reader, &sub_reader,
+            size - header_size);
+        if (!gst_isoff_tkhd_box_parse (&trak->tkhd, &sub_reader))
+          return FALSE;
+
+        had_tkhd = TRUE;
+        break;
+      }
+      default:
+        gst_byte_reader_skip (reader, size - header_size);
+        break;
+    }
+  }
+
+  if (!had_tkhd || !had_mdia)
+    return FALSE;
+
+  return TRUE;
+}
+
+GstMoovBox *
+gst_isoff_moov_box_parse (GstByteReader * reader)
+{
+  GstMoovBox *moov;
+  gboolean had_trak = FALSE;
+  moov = g_new0 (GstMoovBox, 1);
+  moov->trak = g_array_new (FALSE, FALSE, sizeof (GstTrakBox));
+
+  while (gst_byte_reader_get_remaining (reader) > 0) {
+    guint32 fourcc;
+    guint header_size;
+    guint64 size;
+
+    if (!gst_isoff_parse_box_header (reader, &fourcc, NULL, &header_size,
+            &size))
+      goto error;
+    if (gst_byte_reader_get_remaining (reader) < size - header_size)
+      goto error;
+
+    switch (fourcc) {
+      case GST_ISOFF_FOURCC_TRAK:{
+        GstByteReader sub_reader;
+        GstTrakBox trak;
+
+        gst_byte_reader_get_sub_reader (reader, &sub_reader,
+            size - header_size);
+        if (!gst_isoff_trak_box_parse (&trak, &sub_reader))
+          goto error;
+
+        had_trak = TRUE;
+        g_array_append_val (moov->trak, trak);
+        break;
+      }
+      default:
+        gst_byte_reader_skip (reader, size - header_size);
+        break;
+    }
+  }
+
+  if (!had_trak)
+    goto error;
+
+  return moov;
+
+error:
+  gst_isoff_moov_box_free (moov);
+  return NULL;
+}
+
+void
+gst_isoff_moov_box_free (GstMoovBox * moov)
+{
+  g_array_free (moov->trak, TRUE);
+  g_free (moov);
+}
+
 void
 gst_isoff_sidx_parser_init (GstSidxParser * parser)
 {
@@ -390,6 +829,7 @@ gst_isoff_sidx_parser_parse (GstSidxParser * parser,
   GstIsoffParserResult res = GST_ISOFF_PARSER_OK;
   gsize remaining;
 
+  INITIALIZE_DEBUG_CATEGORY;
   switch (parser->status) {
     case GST_ISOFF_SIDX_PARSER_INIT:
       /* Try again once we have enough data for the FullBox header */
@@ -492,6 +932,7 @@ gst_isoff_sidx_parser_add_buffer (GstSidxParser * parser, GstBuffer * buffer,
   GstMapInfo info;
   guint32 fourcc;
 
+  INITIALIZE_DEBUG_CATEGORY;
   if (!gst_buffer_map (buffer, &info, GST_MAP_READ)) {
     *consumed = 0;
     return GST_ISOFF_PARSER_ERROR;
similarity index 75%
rename from ext/dash/gstisoff.h
rename to gst-libs/gst/isoff/gstisoff.h
index cf9ee1c..437859b 100644 (file)
 
 G_BEGIN_DECLS
 
+#ifndef GST_ISOFF_API
+#define GST_ISOFF_API GST_EXPORT
+#endif
+
 typedef enum {
   GST_ISOFF_PARSER_OK,
   GST_ISOFF_PARSER_DONE,
@@ -37,6 +41,7 @@ typedef enum {
   GST_ISOFF_PARSER_ERROR
 } GstIsoffParserResult;
 
+GST_ISOFF_API
 gboolean gst_isoff_parse_box_header (GstByteReader * reader, guint32 * type, guint8 extended_type[16], guint * header_size, guint64 * size);
 
 #define GST_ISOFF_FOURCC_UUID GST_MAKE_FOURCC('u','u','i','d')
@@ -45,9 +50,20 @@ gboolean gst_isoff_parse_box_header (GstByteReader * reader, guint32 * type, gui
 #define GST_ISOFF_FOURCC_TFHD GST_MAKE_FOURCC('t','f','h','d')
 #define GST_ISOFF_FOURCC_TRUN GST_MAKE_FOURCC('t','r','u','n')
 #define GST_ISOFF_FOURCC_TRAF GST_MAKE_FOURCC('t','r','a','f')
+#define GST_ISOFF_FOURCC_TFDT GST_MAKE_FOURCC('t','f','d','t')
 #define GST_ISOFF_FOURCC_MDAT GST_MAKE_FOURCC('m','d','a','t')
+#define GST_ISOFF_FOURCC_MOOV GST_MAKE_FOURCC('m','o','o','v')
+#define GST_ISOFF_FOURCC_TRAK GST_MAKE_FOURCC('t','r','a','k')
+#define GST_ISOFF_FOURCC_TKHD GST_MAKE_FOURCC('t','k','h','d')
+#define GST_ISOFF_FOURCC_MDIA GST_MAKE_FOURCC('m','d','i','a')
+#define GST_ISOFF_FOURCC_MDHD GST_MAKE_FOURCC('m','d','h','d')
+#define GST_ISOFF_FOURCC_HDLR GST_MAKE_FOURCC('h','d','l','r')
 #define GST_ISOFF_FOURCC_SIDX GST_MAKE_FOURCC('s','i','d','x')
 
+/* handler type */
+#define GST_ISOFF_FOURCC_SOUN GST_MAKE_FOURCC('s','o','u','n')
+#define GST_ISOFF_FOURCC_VIDE GST_MAKE_FOURCC('v','i','d','e')
+
 #define GST_ISOFF_SAMPLE_FLAGS_IS_LEADING(flags)                   (((flags) >> 26) & 0x03)
 #define GST_ISOFF_SAMPLE_FLAGS_SAMPLE_DEPENDS_ON(flags)            (((flags) >> 24) & 0x03)
 #define GST_ISOFF_SAMPLE_FLAGS_SAMPLE_IS_DEPENDED_ON(flags)        (((flags) >> 22) & 0x03)
@@ -56,6 +72,32 @@ gboolean gst_isoff_parse_box_header (GstByteReader * reader, guint32 * type, gui
 #define GST_ISOFF_SAMPLE_FLAGS_SAMPLE_IS_NON_SYNC_SAMPLE(flags)    (((flags) >> 16) & 0x01)
 #define GST_ISOFF_SAMPLE_FLAGS_SAMPLE_DEGRADATION_PRIORITY(flags)  (((flags) >>  0) & 0x0f)
 
+/* Smooth-Streaming specific boxes */
+typedef struct _GstTfxdBox
+{
+  guint8 version;
+  guint32 flags;
+
+  guint64 time;
+  guint64 duration;
+} GstTfxdBox;
+
+typedef struct _GstTfrfBoxEntry
+{
+  guint64 time;
+  guint64 duration;
+} GstTfrfBoxEntry;
+
+typedef struct _GstTfrfBox
+{
+  guint8 version;
+  guint32 flags;
+
+  gint entries_count;
+  GArray *entries;
+} GstTfrfBox;
+
+/* Common boxes */
 typedef struct _GstMfhdBox
 {
   guint32 sequence_number;
@@ -122,10 +164,20 @@ typedef struct _GstTrunSample
   } sample_composition_time_offset;
 } GstTrunSample;
 
+typedef struct _GstTdftBox
+{
+  guint64 decode_time;
+} GstTfdtBox;
+
 typedef struct _GstTrafBox
 {
   GstTfhdBox tfhd;
+  GstTfdtBox tfdt;
   GArray *trun;
+
+  /* smooth-streaming specific */
+  GstTfrfBox *tfrf;
+  GstTfxdBox *tfxd;
 } GstTrafBox;
 
 typedef struct _GstMoofBox
@@ -134,9 +186,50 @@ typedef struct _GstMoofBox
   GArray *traf;
 } GstMoofBox;
 
+GST_ISOFF_API
 GstMoofBox * gst_isoff_moof_box_parse (GstByteReader *reader);
+
+GST_ISOFF_API
 void gst_isoff_moof_box_free (GstMoofBox *moof);
 
+typedef struct _GstTkhdBox
+{
+  guint32 track_id;
+} GstTkhdBox;
+
+typedef struct _GstMdhdBox
+{
+  guint32 timescale;
+} GstMdhdBox;
+
+typedef struct _GstHdlrBox
+{
+  guint32 handler_type;
+} GstHdlrBox;
+
+typedef struct _GstMdiaBox
+{
+  GstMdhdBox mdhd;
+  GstHdlrBox hdlr;
+} GstMdiaBox;
+
+typedef struct _GstTrakBox
+{
+  GstTkhdBox tkhd;
+  GstMdiaBox mdia;
+} GstTrakBox;
+
+typedef struct _GstMoovBox
+{
+  GArray *trak;
+} GstMoovBox;
+
+GST_ISOFF_API
+GstMoovBox * gst_isoff_moov_box_parse (GstByteReader *reader);
+
+GST_ISOFF_API
+void gst_isoff_moov_box_free (GstMoovBox *moov);
+
 typedef struct _GstSidxBoxEntry
 {
   gboolean ref_type;
@@ -185,12 +278,18 @@ typedef struct _GstSidxParser
   GstSidxBox sidx;
 } GstSidxParser;
 
+GST_ISOFF_API
 void gst_isoff_sidx_parser_init (GstSidxParser * parser);
+
+GST_ISOFF_API
 void gst_isoff_sidx_parser_clear (GstSidxParser * parser);
+
+GST_ISOFF_API
 GstIsoffParserResult gst_isoff_sidx_parser_parse (GstSidxParser * parser, GstByteReader * reader, guint * consumed);
+
+GST_ISOFF_API
 GstIsoffParserResult gst_isoff_sidx_parser_add_buffer (GstSidxParser * parser, GstBuffer * buf, guint * consumed);
 
 G_END_DECLS
 
 #endif /* __GST_ISOFF_H__ */
-
diff --git a/gst-libs/gst/isoff/meson.build b/gst-libs/gst/isoff/meson.build
new file mode 100644 (file)
index 0000000..8666ea1
--- /dev/null
@@ -0,0 +1,21 @@
+isoff_sources = [
+  'gstisoff.c',
+]
+isoff_headers = [
+  'gstisoff.h',
+]
+install_headers(isoff_headers, subdir : 'gstreamer-1.0/gst/isoff')
+
+gstisoff = library('gstisoff-' + api_version,
+  isoff_sources,
+  c_args : gst_plugins_bad_args + [ '-DGST_USE_UNSTABLE_API' ],
+  include_directories : [configinc, libsinc],
+  version : libversion,
+  soversion : soversion,
+  install : true,
+  dependencies : [gstbase_dep],
+)
+
+gstisoff_dep = declare_dependency(link_with : gstisoff,
+  include_directories : [libsinc],
+  dependencies : [gstbase_dep])
index 91b5187..d12257a 100644 (file)
@@ -7,7 +7,7 @@ libgsturidownloader_@GST_API_VERSION@includedir = \
        $(includedir)/gstreamer-@GST_API_VERSION@/gst/uridownloader
 
 libgsturidownloader_@GST_API_VERSION@include_HEADERS = \
-       gstfragment.h gsturidownloader.h gsturidownloader_debug.h
+       gstfragment.h gsturidownloader.h gsturidownloader_debug.h uridownloader-prelude.h
 
 libgsturidownloader_@GST_API_VERSION@_la_CFLAGS = \
        $(GST_PLUGINS_BAD_CFLAGS) \
index a8e0a54..517d155 100644 (file)
@@ -24,6 +24,7 @@
 
 #include <glib-object.h>
 #include <gst/gst.h>
+#include <gst/uridownloader/uridownloader-prelude.h>
 
 G_BEGIN_DECLS
 
@@ -65,12 +66,22 @@ struct _GstFragmentClass
   GObjectClass parent_class;
 };
 
+GST_URI_DOWNLOADER_API
 GType gst_fragment_get_type (void);
 
+GST_URI_DOWNLOADER_API
 GstBuffer * gst_fragment_get_buffer (GstFragment *fragment);
+
+GST_URI_DOWNLOADER_API
 void gst_fragment_set_caps (GstFragment * fragment, GstCaps * caps);
+
+GST_URI_DOWNLOADER_API
 GstCaps * gst_fragment_get_caps (GstFragment * fragment);
+
+GST_URI_DOWNLOADER_API
 gboolean gst_fragment_add_buffer (GstFragment *fragment, GstBuffer *buffer);
+
+GST_URI_DOWNLOADER_API
 GstFragment * gst_fragment_new (void);
 
 G_END_DECLS
index cb1e1b8..be53f9a 100644 (file)
@@ -154,7 +154,12 @@ gst_uri_downloader_finalize (GObject * object)
 GstUriDownloader *
 gst_uri_downloader_new (void)
 {
-  return g_object_new (GST_TYPE_URI_DOWNLOADER, NULL);
+  GstUriDownloader *downloader;
+
+  downloader = g_object_new (GST_TYPE_URI_DOWNLOADER, NULL);
+  gst_object_ref_sink (downloader);
+
+  return downloader;
 }
 
 /**
index c5ef0b2..97582e4 100644 (file)
@@ -58,20 +58,34 @@ struct _GstUriDownloaderClass
   gpointer _gst_reserved[GST_PADDING];
 };
 
+GST_URI_DOWNLOADER_API
 GType gst_uri_downloader_get_type (void);
 
+GST_URI_DOWNLOADER_API
 GstUriDownloader * gst_uri_downloader_new (void);
+
+GST_URI_DOWNLOADER_API
 void gst_uri_downloader_set_parent (GstUriDownloader * downloader, GstElement * parent);
+
 #ifdef TIZEN_FEATURE_ADAPTIVE_MODIFICATION
+GST_URI_DOWNLOADER_API
 GstFragment * gst_uri_downloader_fetch_uri (GstUriDownloader * downloader, const gchar * uri, const gchar * referer, gchar* user_agent, gchar** cookies, gint max_retry , gint timeout , gboolean compress, gboolean refresh, gboolean allow_cache, GError ** err);
+
+GST_URI_DOWNLOADER_API
 GstFragment * gst_uri_downloader_fetch_uri_with_range (GstUriDownloader * downloader, const gchar * uri, const gchar * referer, gchar* user_agent, gchar** cookies, gint max_retry , gint timeout , gboolean compress, gboolean refresh, gboolean allow_cache, gint64 range_start, gint64 range_end, GError ** err);
 #else
+GST_URI_DOWNLOADER_API
 GstFragment * gst_uri_downloader_fetch_uri (GstUriDownloader * downloader, const gchar * uri, const gchar * referer, gboolean compress, gboolean refresh, gboolean allow_cache, GError ** err);
+
+GST_URI_DOWNLOADER_API
 GstFragment * gst_uri_downloader_fetch_uri_with_range (GstUriDownloader * downloader, const gchar * uri, const gchar * referer, gboolean compress, gboolean refresh, gboolean allow_cache, gint64 range_start, gint64 range_end, GError ** err);
 #endif
+
+GST_URI_DOWNLOADER_API
 void gst_uri_downloader_reset (GstUriDownloader *downloader);
+
+GST_URI_DOWNLOADER_API
 void gst_uri_downloader_cancel (GstUriDownloader *downloader);
-void gst_uri_downloader_free (GstUriDownloader *downloader);
 
 G_END_DECLS
 #endif /* __GSTURIDOWNLOADER_H__ */
diff --git a/gst-libs/gst/uridownloader/uridownloader-prelude.h b/gst-libs/gst/uridownloader/uridownloader-prelude.h
new file mode 100644 (file)
index 0000000..f5ad584
--- /dev/null
@@ -0,0 +1,31 @@
+/* GStreamer UriDownloader Library
+ * Copyright (C) 2018 GStreamer developers
+ *
+ * uridownloader-prelude.h: prelude include header for gst-uridownloader library
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_URI_DOWNLOADER_PRELUDE_H__
+#define __GST_URI_DOWNLOADER_PRELUDE_H__
+
+#include <gst/gst.h>
+
+#ifndef GST_URI_DOWNLOADER_API
+#define GST_URI_DOWNLOADER_API GST_EXPORT
+#endif
+
+#endif /* __GST_URI_DOWNLOADER_PRELUDE_H__ */
index 4b52d07..3229bc2 100644 (file)
@@ -208,6 +208,7 @@ rm -rf $RPM_BUILD_ROOT
 %{_libdir}/libgsturidownloader-%{gst_branch}.so.0*
 %{_libdir}/libgstadaptivedemux-%{gst_branch}.so.0*
 %{_libdir}/libgstgl-%{gst_branch}.so.0*
+%{_libdir}/libgstisoff-%{gst_branch}.so.0*
 %endif
 %{_libdir}/gstreamer-%{gst_branch}/libgstdebugutilsbad.so
 %{_libdir}/gstreamer-%{gst_branch}/libgstmpegtsdemux.so