[FFmpeg-devel] avformat/hlsenc: Port support for LHLS from lavf/dashenc

Submitted by Zenon Mousmoulas on March 27, 2019, 9:08 a.m.

Details

Message ID 20190328070800.8C3ADD8001@mail.noc.grnet.gr
State New
Headers show

Commit Message

Zenon Mousmoulas March 27, 2019, 9:08 a.m.
Add experimental support for LHLS (low-latency HLS), following what
was introduced to lavf/dashenc in f22fcd4483f.
---
 doc/muxers.texi           |  6 +++++
 libavformat/dashenc.c     |  2 +-
 libavformat/hlsenc.c      | 63 ++++++++++++++++++++++++++++++++++++++---------
 libavformat/hlsplaylist.c | 28 +++++++++++++--------
 libavformat/hlsplaylist.h |  3 ++-
 5 files changed, 79 insertions(+), 23 deletions(-)

Comments

Oliver Collyer via ffmpeg-devel March 28, 2019, 8:40 a.m.
On 3/27/19 2:38 PM, Zenon Mousmoulas wrote:
> Add experimental support for LHLS (low-latency HLS), following what

> was introduced to lavf/dashenc in f22fcd4483f.

> ---

>  doc/muxers.texi           |  6 +++++

>  libavformat/dashenc.c     |  2 +-

>  libavformat/hlsenc.c      | 63 ++++++++++++++++++++++++++++++++++++++---------

>  libavformat/hlsplaylist.c | 28 +++++++++++++--------

>  libavformat/hlsplaylist.h |  3 ++-

>  5 files changed, 79 insertions(+), 23 deletions(-)

>

> diff --git a/doc/muxers.texi b/doc/muxers.texi

> index aac7d94edf..4ebaf559c5 100644

> --- a/doc/muxers.texi

> +++ b/doc/muxers.texi

> @@ -1067,6 +1067,12 @@ Set timeout for socket I/O operations. Applicable only for HTTP output.

>  @item -ignore_io_errors

>  Ignore IO errors during open, write and delete. Useful for long-duration runs with network output.

>  

> +@item -lhls @var{lhls}

> +Enable low-latency HLS (LHLS). Adds #EXT-X-PREFETCH tag with current segment's URI.

> +Apple does not have an official spec for LHLS. Meanwhile hls.js player folks are

> +trying to standardize an open LHLS spec. The draft spec is available in https://github.com/video-dev/hlsjs-rfcs/blob/lhls-spec/proposals/0001-lhls.md

> +This is an experimental feature.

> +

>  @end table

>  

>  @anchor{ico}

> diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c

> index 1b74bce060..04950222d3 100644

> --- a/libavformat/dashenc.c

> +++ b/libavformat/dashenc.c

> @@ -483,7 +483,7 @@ static void write_hls_media_playlist(OutputStream *os, AVFormatContext *s,

>                                  (double) seg->duration / timescale, 0,

>                                  seg->range_length, seg->start_pos, NULL,

>                                  c->single_file ? os->initfile : seg->file,

> -                                &prog_date_time);

> +                                &prog_date_time, 0);

>          if (ret < 0) {

>              av_log(os->ctx, AV_LOG_WARNING, "ff_hls_write_file_entry get error

> ");

>          }

> diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c

> index 5f9a200c6e..4a427fc514 100644

> --- a/libavformat/hlsenc.c

> +++ b/libavformat/hlsenc.c

> @@ -158,6 +158,7 @@ typedef struct VariantStream {

>      char *agroup; /* audio group name */

>      char *ccgroup; /* closed caption group name */

>      char *baseurl;

> +    int m3u8_got_prefetch;

>  } VariantStream;

>  

>  typedef struct ClosedCaptionsStream {

> @@ -230,6 +231,7 @@ typedef struct HLSContext {

>      AVIOContext *sub_m3u8_out;

>      int64_t timeout;

>      int ignore_io_errors;

> +    int lhls;

>      int has_default_key; /* has DEFAULT field of var_stream_map */

>      int has_video_m3u8; /* has video stream m3u8 list */

>  } HLSContext;

> @@ -1360,7 +1362,7 @@ fail:

>      return ret;

>  }

>  

> -static int hls_window(AVFormatContext *s, int last, VariantStream *vs)

> +static int hls_window(AVFormatContext *s, int last, VariantStream *vs, char *prefetch_url)

>  {

>      HLSContext *hls = s->priv_data;

>      HLSSegment *en;

> @@ -1439,12 +1441,23 @@ static int hls_window(AVFormatContext *s, int last, VariantStream *vs)

>          ret = ff_hls_write_file_entry(hls->m3u8_out, en->discont, byterange_mode,

>                                        en->duration, hls->flags & HLS_ROUND_DURATIONS,

>                                        en->size, en->pos, vs->baseurl,

> -                                      en->filename, prog_date_time_p);

> +                                      en->filename, prog_date_time_p, 0);

>          if (ret < 0) {

>              av_log(s, AV_LOG_WARNING, "ff_hls_write_file_entry get error

> ");

>          }

>      }

>  

> +    if (prefetch_url)

> +        ret = en ?

> +              ff_hls_write_file_entry(hls->m3u8_out, en->discont, byterange_mode,

> +                                      en->duration, hls->flags & HLS_ROUND_DURATIONS,

> +                                      en->size, en->pos, vs->baseurl,

> +                                      prefetch_url, prog_date_time_p, 1) :

> +              ff_hls_write_file_entry(hls->m3u8_out, 0, byterange_mode,

> +                                      0, hls->flags & HLS_ROUND_DURATIONS,

> +                                      0, 0, vs->baseurl,

> +                                      prefetch_url, prog_date_time_p, 1);

> +

>      if (last && (hls->flags & HLS_OMIT_ENDLIST)==0)

>          ff_hls_write_end_list(hls->m3u8_out);

>  

> @@ -1459,7 +1472,7 @@ static int hls_window(AVFormatContext *s, int last, VariantStream *vs)

>          for (en = vs->segments; en; en = en->next) {

>              ret = ff_hls_write_file_entry(hls->sub_m3u8_out, 0, byterange_mode,

>                                            en->duration, 0, en->size, en->pos,

> -                                          vs->baseurl, en->sub_filename, NULL);

> +                                          vs->baseurl, en->sub_filename, NULL, 0);

>              if (ret < 0) {

>                  av_log(s, AV_LOG_WARNING, "ff_hls_write_file_entry get error

> ");

>              }

> @@ -2158,6 +2171,7 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)

>      int64_t end_pts = 0;

>      int is_ref_pkt = 1;

>      int ret = 0, can_split = 1, i, j;

> +    int byterange_mode;

>      int stream_index = 0;

>      int range_length = 0;

>      const char *proto = NULL;

> @@ -2232,10 +2246,16 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)

>  

>      }

>  

> +    byterange_mode = (hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size > 0);

> +

> +    if (oc->url[0]) {

> +        proto = avio_find_protocol_name(oc->url);

> +        use_temp_file = proto && !strcmp(proto, "file") && (hls->flags & HLS_TEMP_FILE);

> +    }

> +

>      if (vs->packets_written && can_split && av_compare_ts(pkt->pts - vs->start_pts, st->time_base,

>                                                            end_pts, AV_TIME_BASE_Q) >= 0) {

>          int64_t new_start_pos;

> -        int byterange_mode = (hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size > 0);

>  

>          av_write_frame(vs->avf, NULL); /* Flush any buffered data */

>  

> @@ -2272,11 +2292,6 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)

>              }

>          }

>  

> -        if (oc->url[0]) {

> -            proto = avio_find_protocol_name(oc->url);

> -            use_temp_file = proto && !strcmp(proto, "file") && (hls->flags & HLS_TEMP_FILE);

> -        }

> -

>          // look to rename the asset name

>          if (use_temp_file) {

>              if (!(hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size <= 0))

> @@ -2358,9 +2373,18 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)

>  

>          // if we're building a VOD playlist, skip writing the manifest multiple times, and just wait until the end

>          if (hls->pl_type != PLAYLIST_TYPE_VOD) {

> -            if ((ret = hls_window(s, 0, vs)) < 0) {

> +            if ((ret = hls_window(s, 0, vs, NULL)) < 0) {

> +                return ret;

> +            }

> +            vs->m3u8_got_prefetch = 0;

> +        }

> +    } else if (hls->lhls && !use_temp_file && !byterange_mode &&

> +               !vs->m3u8_got_prefetch) {

> +        if (hls->pl_type != PLAYLIST_TYPE_VOD) {

> +            if ((ret = hls_window(s, 0, vs, (char *)av_basename(oc->url))) < 0) {

>                  return ret;

>              }

> +            vs->m3u8_got_prefetch = 1;

>          }

>      }

>  

> @@ -2504,7 +2528,7 @@ failed:

>          avformat_free_context(oc);

>  

>          vs->avf = NULL;

> -        hls_window(s, 1, vs);

> +        hls_window(s, 1, vs, NULL);

>          av_free(old_filename);

>      }

>  

> @@ -2588,6 +2612,22 @@ static int hls_init(AVFormatContext *s)

>          }

>      }

>  

> +    if (hls->lhls && s->strict_std_compliance > FF_COMPLIANCE_EXPERIMENTAL) {

> +        av_log(s, AV_LOG_ERROR,

> +               "LHLS is experimental, please set -strict experimental in order to enable it.

> ");

> +        return AVERROR_EXPERIMENTAL;

> +    }

> +    if (hls->lhls && hls->flags & HLS_TEMP_FILE) {

> +        av_log(s, AV_LOG_WARNING,

> +               "'lhls' will be disabled because 'temp_file' flag is enabled'

> ");

> +        hls->lhls = 0;

> +    }

> +    if (hls->lhls && hls->flags & HLS_SINGLE_FILE) {

> +        av_log(s, AV_LOG_WARNING,

> +               "'lhls' will be disabled because 'single_file' flag is enabled'

> ");

> +        hls->lhls = 0;

> +    }

> +

>      if (hls->segment_type == SEGMENT_TYPE_FMP4) {

>          pattern = "%d.m4s";

>      }

> @@ -2942,6 +2982,7 @@ static const AVOption options[] = {

>      {"http_persistent", "Use persistent HTTP connections", OFFSET(http_persistent), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, E },

>      {"timeout", "set timeout for socket I/O operations", OFFSET(timeout), AV_OPT_TYPE_DURATION, { .i64 = -1 }, -1, INT_MAX, .flags = E },

>      {"ignore_io_errors", "Ignore IO errors for stable long-duration runs with network output", OFFSET(ignore_io_errors), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E },

> +    { "lhls", "Enable low-latency HLS (experimental). Adds #EXT-X-PREFETCH tag with current segment's URI", OFFSET(lhls), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E },

>      { NULL },

>  };

>  

> diff --git a/libavformat/hlsplaylist.c b/libavformat/hlsplaylist.c

> index 0537049a97..8db0c93c12 100644

> --- a/libavformat/hlsplaylist.c

> +++ b/libavformat/hlsplaylist.c

> @@ -108,21 +108,27 @@ int ff_hls_write_file_entry(AVIOContext *out, int insert_discont,

>                               double duration, int round_duration,

>                               int64_t size, int64_t pos, //Used only if HLS_SINGLE_FILE flag is set

>                               char *baseurl, //Ignored if NULL

> -                             char *filename, double *prog_date_time) {

> +                             char *filename, double *prog_date_time,

> +                             int prefetch) {

Can we create a separate function like below for writing prefetch segments? 
int ff_hls_write_prefetch(AVIOContext *out, int insert_discont,
                             char *baseurl, //Ignored if NULL
                             char *filename)
Because the current function(ff_hls_write_file_entry) is already pretty complex with multiple arguments, most of which are not applicable for PREFETCH.
Anyways, the condition if(prefetch) is anyways there on the calling side(hlsenc.c and dashenc.c). 
A separate function like above called by both hlsenc.c and dashenc.c will be much cleaner.
>      if (!out || !filename)

>          return AVERROR(EINVAL);

>  

>      if (insert_discont) {

> -        avio_printf(out, "#EXT-X-DISCONTINUITY

> ");

> +        if (prefetch)

> +            avio_printf(out, "#EXT-X-PREFETCH-DISCONTINUITY

> ");

> +        else

> +            avio_printf(out, "#EXT-X-DISCONTINUITY

> ");

>      }

> -    if (round_duration)

> -        avio_printf(out, "#EXTINF:%ld,

> ",  lrint(duration));

> -    else

> -        avio_printf(out, "#EXTINF:%f,

> ", duration);

> -    if (byterange_mode)

> -        avio_printf(out, "#EXT-X-BYTERANGE:%"PRId64"@%"PRId64"

> ", size, pos);

> -

> -    if (prog_date_time) {

> +    if (!prefetch) {

> +        if (round_duration)

> +            avio_printf(out, "#EXTINF:%ld,

> ",  lrint(duration));

> +        else

> +            avio_printf(out, "#EXTINF:%f,

> ", duration);

> +        if (byterange_mode)

> +            avio_printf(out, "#EXT-X-BYTERANGE:%"PRId64"@%"PRId64"

> ", size, pos);

> +    }

> +

> +    if (!prefetch && prog_date_time) {

>          time_t tt, wrongsecs;

>          int milli;

>          struct tm *tm, tmpbuf;

> @@ -149,6 +155,8 @@ int ff_hls_write_file_entry(AVIOContext *out, int insert_discont,

>          avio_printf(out, "#EXT-X-PROGRAM-DATE-TIME:%s.%03d%s

> ", buf0, milli, buf1);

>          *prog_date_time += duration;

>      }

> +    if (prefetch)

> +        avio_printf(out, "#EXT-X-PREFETCH:");

>      if (baseurl)

>          avio_printf(out, "%s", baseurl);

>      avio_printf(out, "%s

> ", filename);

> diff --git a/libavformat/hlsplaylist.h b/libavformat/hlsplaylist.h

> index 54c93a3963..af35162e08 100644

> --- a/libavformat/hlsplaylist.h

> +++ b/libavformat/hlsplaylist.h

> @@ -52,7 +52,8 @@ int ff_hls_write_file_entry(AVIOContext *out, int insert_discont,

>                               double duration, int round_duration,

>                               int64_t size, int64_t pos, //Used only if HLS_SINGLE_FILE flag is set

>                               char *baseurl, //Ignored if NULL

> -                             char *filename, double *prog_date_time);

> +                             char *filename, double *prog_date_time,

> +                             int prefetch);

>  void ff_hls_write_end_list (AVIOContext *out);

>  

>  #endif /* AVFORMAT_HLSPLAYLIST_H_ */
Michael Niedermayer March 28, 2019, 5:16 p.m.
On Wed, Mar 27, 2019 at 11:08:02AM +0200, Zenon Mousmoulas wrote:
> Add experimental support for LHLS (low-latency HLS), following what
> was introduced to lavf/dashenc in f22fcd4483f.
> ---
>  doc/muxers.texi           |  6 +++++
>  libavformat/dashenc.c     |  2 +-
>  libavformat/hlsenc.c      | 63 ++++++++++++++++++++++++++++++++++++++---------
>  libavformat/hlsplaylist.c | 28 +++++++++++++--------
>  libavformat/hlsplaylist.h |  3 ++-
>  5 files changed, 79 insertions(+), 23 deletions(-)
> 
> diff --git a/doc/muxers.texi b/doc/muxers.texi
> index aac7d94edf..4ebaf559c5 100644
> --- a/doc/muxers.texi
> +++ b/doc/muxers.texi
> @@ -1067,6 +1067,12 @@ Set timeout for socket I/O operations. Applicable only for HTTP output.
>  @item -ignore_io_errors
>  Ignore IO errors during open, write and delete. Useful for long-duration runs with network output.
>  
> +@item -lhls @var{lhls}
> +Enable low-latency HLS (LHLS). Adds #EXT-X-PREFETCH tag with current segment's URI.
> +Apple does not have an official spec for LHLS. Meanwhile hls.js player folks are
> +trying to standardize an open LHLS spec. The draft spec is available in https://github.com/video-dev/hlsjs-rfcs/blob/lhls-spec/proposals/0001-lhls.md
> +This is an experimental feature.
> +
>  @end table
>  
>  @anchor{ico}
> diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c
> index 1b74bce060..04950222d3 100644
> --- a/libavformat/dashenc.c
> +++ b/libavformat/dashenc.c
> @@ -483,7 +483,7 @@ static void write_hls_media_playlist(OutputStream *os, AVFormatContext *s,
>                                  (double) seg->duration / timescale, 0,
>                                  seg->range_length, seg->start_pos, NULL,
>                                  c->single_file ? os->initfile : seg->file,
> -                                &prog_date_time);
> +                                &prog_date_time, 0);
>          if (ret < 0) {
>              av_log(os->ctx, AV_LOG_WARNING, "ff_hls_write_file_entry get error
> ");

patch corrupted by newlines

[...]

Patch hide | download patch | download mbox

diff --git a/doc/muxers.texi b/doc/muxers.texi
index aac7d94edf..4ebaf559c5 100644
--- a/doc/muxers.texi
+++ b/doc/muxers.texi
@@ -1067,6 +1067,12 @@  Set timeout for socket I/O operations. Applicable only for HTTP output.
 @item -ignore_io_errors
 Ignore IO errors during open, write and delete. Useful for long-duration runs with network output.
 
+@item -lhls @var{lhls}
+Enable low-latency HLS (LHLS). Adds #EXT-X-PREFETCH tag with current segment's URI.
+Apple does not have an official spec for LHLS. Meanwhile hls.js player folks are
+trying to standardize an open LHLS spec. The draft spec is available in https://github.com/video-dev/hlsjs-rfcs/blob/lhls-spec/proposals/0001-lhls.md
+This is an experimental feature.
+
 @end table
 
 @anchor{ico}
diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c
index 1b74bce060..04950222d3 100644
--- a/libavformat/dashenc.c
+++ b/libavformat/dashenc.c
@@ -483,7 +483,7 @@  static void write_hls_media_playlist(OutputStream *os, AVFormatContext *s,
                                 (double) seg->duration / timescale, 0,
                                 seg->range_length, seg->start_pos, NULL,
                                 c->single_file ? os->initfile : seg->file,
-                                &prog_date_time);
+                                &prog_date_time, 0);
         if (ret < 0) {
             av_log(os->ctx, AV_LOG_WARNING, "ff_hls_write_file_entry get error
");
         }
diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c
index 5f9a200c6e..4a427fc514 100644
--- a/libavformat/hlsenc.c
+++ b/libavformat/hlsenc.c
@@ -158,6 +158,7 @@  typedef struct VariantStream {
     char *agroup; /* audio group name */
     char *ccgroup; /* closed caption group name */
     char *baseurl;
+    int m3u8_got_prefetch;
 } VariantStream;
 
 typedef struct ClosedCaptionsStream {
@@ -230,6 +231,7 @@  typedef struct HLSContext {
     AVIOContext *sub_m3u8_out;
     int64_t timeout;
     int ignore_io_errors;
+    int lhls;
     int has_default_key; /* has DEFAULT field of var_stream_map */
     int has_video_m3u8; /* has video stream m3u8 list */
 } HLSContext;
@@ -1360,7 +1362,7 @@  fail:
     return ret;
 }
 
-static int hls_window(AVFormatContext *s, int last, VariantStream *vs)
+static int hls_window(AVFormatContext *s, int last, VariantStream *vs, char *prefetch_url)
 {
     HLSContext *hls = s->priv_data;
     HLSSegment *en;
@@ -1439,12 +1441,23 @@  static int hls_window(AVFormatContext *s, int last, VariantStream *vs)
         ret = ff_hls_write_file_entry(hls->m3u8_out, en->discont, byterange_mode,
                                       en->duration, hls->flags & HLS_ROUND_DURATIONS,
                                       en->size, en->pos, vs->baseurl,
-                                      en->filename, prog_date_time_p);
+                                      en->filename, prog_date_time_p, 0);
         if (ret < 0) {
             av_log(s, AV_LOG_WARNING, "ff_hls_write_file_entry get error
");
         }
     }
 
+    if (prefetch_url)
+        ret = en ?
+              ff_hls_write_file_entry(hls->m3u8_out, en->discont, byterange_mode,
+                                      en->duration, hls->flags & HLS_ROUND_DURATIONS,
+                                      en->size, en->pos, vs->baseurl,
+                                      prefetch_url, prog_date_time_p, 1) :
+              ff_hls_write_file_entry(hls->m3u8_out, 0, byterange_mode,
+                                      0, hls->flags & HLS_ROUND_DURATIONS,
+                                      0, 0, vs->baseurl,
+                                      prefetch_url, prog_date_time_p, 1);
+
     if (last && (hls->flags & HLS_OMIT_ENDLIST)==0)
         ff_hls_write_end_list(hls->m3u8_out);
 
@@ -1459,7 +1472,7 @@  static int hls_window(AVFormatContext *s, int last, VariantStream *vs)
         for (en = vs->segments; en; en = en->next) {
             ret = ff_hls_write_file_entry(hls->sub_m3u8_out, 0, byterange_mode,
                                           en->duration, 0, en->size, en->pos,
-                                          vs->baseurl, en->sub_filename, NULL);
+                                          vs->baseurl, en->sub_filename, NULL, 0);
             if (ret < 0) {
                 av_log(s, AV_LOG_WARNING, "ff_hls_write_file_entry get error
");
             }
@@ -2158,6 +2171,7 @@  static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
     int64_t end_pts = 0;
     int is_ref_pkt = 1;
     int ret = 0, can_split = 1, i, j;
+    int byterange_mode;
     int stream_index = 0;
     int range_length = 0;
     const char *proto = NULL;
@@ -2232,10 +2246,16 @@  static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
 
     }
 
+    byterange_mode = (hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size > 0);
+
+    if (oc->url[0]) {
+        proto = avio_find_protocol_name(oc->url);
+        use_temp_file = proto && !strcmp(proto, "file") && (hls->flags & HLS_TEMP_FILE);
+    }
+
     if (vs->packets_written && can_split && av_compare_ts(pkt->pts - vs->start_pts, st->time_base,
                                                           end_pts, AV_TIME_BASE_Q) >= 0) {
         int64_t new_start_pos;
-        int byterange_mode = (hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size > 0);
 
         av_write_frame(vs->avf, NULL); /* Flush any buffered data */
 
@@ -2272,11 +2292,6 @@  static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
             }
         }
 
-        if (oc->url[0]) {
-            proto = avio_find_protocol_name(oc->url);
-            use_temp_file = proto && !strcmp(proto, "file") && (hls->flags & HLS_TEMP_FILE);
-        }
-
         // look to rename the asset name
         if (use_temp_file) {
             if (!(hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size <= 0))
@@ -2358,9 +2373,18 @@  static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
 
         // if we're building a VOD playlist, skip writing the manifest multiple times, and just wait until the end
         if (hls->pl_type != PLAYLIST_TYPE_VOD) {
-            if ((ret = hls_window(s, 0, vs)) < 0) {
+            if ((ret = hls_window(s, 0, vs, NULL)) < 0) {
+                return ret;
+            }
+            vs->m3u8_got_prefetch = 0;
+        }
+    } else if (hls->lhls && !use_temp_file && !byterange_mode &&
+               !vs->m3u8_got_prefetch) {
+        if (hls->pl_type != PLAYLIST_TYPE_VOD) {
+            if ((ret = hls_window(s, 0, vs, (char *)av_basename(oc->url))) < 0) {
                 return ret;
             }
+            vs->m3u8_got_prefetch = 1;
         }
     }
 
@@ -2504,7 +2528,7 @@  failed:
         avformat_free_context(oc);
 
         vs->avf = NULL;
-        hls_window(s, 1, vs);
+        hls_window(s, 1, vs, NULL);
         av_free(old_filename);
     }
 
@@ -2588,6 +2612,22 @@  static int hls_init(AVFormatContext *s)
         }
     }
 
+    if (hls->lhls && s->strict_std_compliance > FF_COMPLIANCE_EXPERIMENTAL) {
+        av_log(s, AV_LOG_ERROR,
+               "LHLS is experimental, please set -strict experimental in order to enable it.
");
+        return AVERROR_EXPERIMENTAL;
+    }
+    if (hls->lhls && hls->flags & HLS_TEMP_FILE) {
+        av_log(s, AV_LOG_WARNING,
+               "'lhls' will be disabled because 'temp_file' flag is enabled'
");
+        hls->lhls = 0;
+    }
+    if (hls->lhls && hls->flags & HLS_SINGLE_FILE) {
+        av_log(s, AV_LOG_WARNING,
+               "'lhls' will be disabled because 'single_file' flag is enabled'
");
+        hls->lhls = 0;
+    }
+
     if (hls->segment_type == SEGMENT_TYPE_FMP4) {
         pattern = "%d.m4s";
     }
@@ -2942,6 +2982,7 @@  static const AVOption options[] = {
     {"http_persistent", "Use persistent HTTP connections", OFFSET(http_persistent), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, E },
     {"timeout", "set timeout for socket I/O operations", OFFSET(timeout), AV_OPT_TYPE_DURATION, { .i64 = -1 }, -1, INT_MAX, .flags = E },
     {"ignore_io_errors", "Ignore IO errors for stable long-duration runs with network output", OFFSET(ignore_io_errors), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E },
+    { "lhls", "Enable low-latency HLS (experimental). Adds #EXT-X-PREFETCH tag with current segment's URI", OFFSET(lhls), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E },
     { NULL },
 };
 
diff --git a/libavformat/hlsplaylist.c b/libavformat/hlsplaylist.c
index 0537049a97..8db0c93c12 100644
--- a/libavformat/hlsplaylist.c
+++ b/libavformat/hlsplaylist.c
@@ -108,21 +108,27 @@  int ff_hls_write_file_entry(AVIOContext *out, int insert_discont,
                              double duration, int round_duration,
                              int64_t size, int64_t pos, //Used only if HLS_SINGLE_FILE flag is set
                              char *baseurl, //Ignored if NULL
-                             char *filename, double *prog_date_time) {
+                             char *filename, double *prog_date_time,
+                             int prefetch) {
     if (!out || !filename)
         return AVERROR(EINVAL);
 
     if (insert_discont) {
-        avio_printf(out, "#EXT-X-DISCONTINUITY
");
+        if (prefetch)
+            avio_printf(out, "#EXT-X-PREFETCH-DISCONTINUITY
");
+        else
+            avio_printf(out, "#EXT-X-DISCONTINUITY
");
     }
-    if (round_duration)
-        avio_printf(out, "#EXTINF:%ld,
",  lrint(duration));
-    else
-        avio_printf(out, "#EXTINF:%f,
", duration);
-    if (byterange_mode)
-        avio_printf(out, "#EXT-X-BYTERANGE:%"PRId64"@%"PRId64"
", size, pos);
-
-    if (prog_date_time) {
+    if (!prefetch) {
+        if (round_duration)
+            avio_printf(out, "#EXTINF:%ld,
",  lrint(duration));
+        else
+            avio_printf(out, "#EXTINF:%f,
", duration);
+        if (byterange_mode)
+            avio_printf(out, "#EXT-X-BYTERANGE:%"PRId64"@%"PRId64"
", size, pos);
+    }
+
+    if (!prefetch && prog_date_time) {
         time_t tt, wrongsecs;
         int milli;
         struct tm *tm, tmpbuf;
@@ -149,6 +155,8 @@  int ff_hls_write_file_entry(AVIOContext *out, int insert_discont,
         avio_printf(out, "#EXT-X-PROGRAM-DATE-TIME:%s.%03d%s
", buf0, milli, buf1);
         *prog_date_time += duration;
     }
+    if (prefetch)
+        avio_printf(out, "#EXT-X-PREFETCH:");
     if (baseurl)
         avio_printf(out, "%s", baseurl);
     avio_printf(out, "%s
", filename);
diff --git a/libavformat/hlsplaylist.h b/libavformat/hlsplaylist.h
index 54c93a3963..af35162e08 100644
--- a/libavformat/hlsplaylist.h
+++ b/libavformat/hlsplaylist.h
@@ -52,7 +52,8 @@  int ff_hls_write_file_entry(AVIOContext *out, int insert_discont,
                              double duration, int round_duration,
                              int64_t size, int64_t pos, //Used only if HLS_SINGLE_FILE flag is set
                              char *baseurl, //Ignored if NULL
-                             char *filename, double *prog_date_time);
+                             char *filename, double *prog_date_time,
+                             int prefetch);
 void ff_hls_write_end_list (AVIOContext *out);
 
 #endif /* AVFORMAT_HLSPLAYLIST_H_ */