[FFmpeg-devel,RFC] hls: export range of currently available segments

Submitted by wm4 on Jan. 18, 2018, 4:13 p.m.

Details

Message ID 20180118161301.32154-1-nfxjfg@googlemail.com
State New
Headers show

Commit Message

wm4 Jan. 18, 2018, 4:13 p.m.
The intention is to:
1. let API users know whether a HLS stream is live
2. let API users know about the safe seek range

This is supposed to avoid Bad things happening when seeking outside of
range.

There are lots of problems with this patch:
- the API consists of adding yet another set of obscure format specific
  fields to an already huge struct
- determination of the first timestamp is fishy
- is it actually safe to seek to the very first segment?
- the range is only updated when the playlist is reloaded, but it could
  happen that the user accesses the fields while not having read any
  packets for a while, so the fields can be easily stale
- same when enabling/disabling streams (which happens by setting public
  fields, and the hls code only knows about this when reading a new
  packet or when seeking)

I'm open for ideas.

Also this change is untested (other than that it compiles).
---
 libavformat/avformat.h | 36 ++++++++++++++++++++++++++++++++++++
 libavformat/hls.c      | 37 +++++++++++++++++++++++++++++++++++++
 libavformat/options.c  |  3 +++
 3 files changed, 76 insertions(+)

Patch hide | download patch | download mbox

diff --git a/libavformat/avformat.h b/libavformat/avformat.h
index b0387214c5..eb1b6b4a68 100644
--- a/libavformat/avformat.h
+++ b/libavformat/avformat.h
@@ -1898,6 +1898,42 @@  typedef struct AVFormatContext {
      * - decoding: set by user
      */
     int max_streams;
+
+    /**
+     * If this is set to 1, this is a live stream. Typically it will be
+     * unseekable, or allow seeking within a time window only.
+     *
+     * If this is set to 0, it is unknown what kind of stream this is. For
+     * specific protocols (such as HLS), it will indicate that it is
+     * definitely a static stream.
+     *
+     * Other values are reserved for the future, but if used will also indicate
+     * some kind of seek-restricted stream.
+     *
+     * - encoding: unused
+     * - decoding: set by libavformat
+     */
+    int is_live_stream;
+
+    /**
+     * If this field and available_range_end are set to a value other than
+     * AV_NOPTS_VALUE, they indicate the currently available media range in
+     * AV_TIME_BASE fractional seconds. This is supported by specific network
+     * protocols only, such as HLS. Generally this means you can seek within
+     * this range.
+     *
+     * - encoding: unused
+     * - decoding: set by libavformat
+     */
+    int64_t available_range_start;
+
+    /**
+     * See available_range_start field.
+     *
+     * - encoding: unused
+     * - decoding: set by libavformat
+     */
+    int64_t available_range_end;
 } AVFormatContext;
 
 #if FF_API_FORMAT_GET_SET
diff --git a/libavformat/hls.c b/libavformat/hls.c
index 950cc4c3bd..557b185f4b 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -215,6 +215,8 @@  typedef struct HLSContext {
     AVIOContext *playlist_pb;
 } HLSContext;
 
+static int playlist_needed(struct playlist *pls);
+
 static int read_chomp_line(AVIOContext *s, char *buf, int maxlen)
 {
     int len = ff_get_line(s, buf, maxlen);
@@ -707,6 +709,40 @@  static int open_url(AVFormatContext *s, AVIOContext **pb, const char *url,
     return ret;
 }
 
+static void update_available_range(HLSContext *c)
+{
+    int n, i;
+
+    c->ctx->available_range_start = AV_NOPTS_VALUE;
+    c->ctx->available_range_end = AV_NOPTS_VALUE;
+    c->ctx->is_live_stream = 0;
+
+    for (n = 0; n < c->n_playlists; n++) {
+        struct playlist *pls = c->playlists[n];
+        int64_t ts;
+
+        if (!playlist_needed(pls) || !pls->n_segments)
+            continue;
+
+        // Not sure where to get the actual start timestamp - might require
+        // waiting until the first packet for every active stream on pls is read.
+        ts = c->first_timestamp == AV_NOPTS_VALUE ? 0 : c->first_timestamp;
+        if (c->ctx->available_range_start == AV_NOPTS_VALUE ||
+            c->ctx->available_range_start < ts)
+            c->ctx->available_range_start = ts;
+
+        for (i = 0; 0 < pls->n_segments; i++)
+            ts += pls->segments[i]->duration;
+
+        if (c->ctx->available_range_end == AV_NOPTS_VALUE ||
+            c->ctx->available_range_end > ts)
+            c->ctx->available_range_end = ts;
+
+        if (!pls->finished)
+            c->ctx->is_live_stream = 1;
+    }
+}
+
 static int parse_playlist(HLSContext *c, const char *url,
                           struct playlist *pls, AVIOContext *in)
 {
@@ -930,6 +966,7 @@  fail:
     av_free(new_url);
     if (close_in)
         ff_format_io_close(c->ctx, &in);
+    update_available_range(c);
     return ret;
 }
 
diff --git a/libavformat/options.c b/libavformat/options.c
index 9371c72667..875f81341e 100644
--- a/libavformat/options.c
+++ b/libavformat/options.c
@@ -138,6 +138,9 @@  static void avformat_get_context_defaults(AVFormatContext *s)
     s->io_open  = io_open_default;
     s->io_close = io_close_default;
 
+    s->available_range_start = AV_NOPTS_VALUE;
+    s->available_range_end = AV_NOPTS_VALUE;
+
     av_opt_set_defaults(s);
 }