PR #23334 opened by Niklas Haas (haasn)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23334
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23334.patch

This is needed/useful to support incrementally reading from streams that are 
still being written; analogous to `-follow` in the file protocol, with the 
difference that we don't need a separate option (per se) because the server is 
responsible for telling us that more bytes are still expected (via the 
`Content-Range: <range>/*` syntax).

The one major downside this PR still has, and that I'm not happy with, is that 
by default (without `-request_size` to constrain it), we will spam requests as 
fast as possible, even if it ends up degenerating into a loop where we spam the 
server with requests that each return only a small number of bytes at a time, 
without ever fully exhausting the buffer (so no 416 - that would trigger 
retry/sleep - is ever hit).

I am considering working around this by also forcing a small minimum request 
size if the filesize is detected as unknown, but I'm not sure if it's worth 
exposing this as a user-configurable option or just deferring to e.g. 
hard-coding the avio built-in default buffer size of 32768.

Also includes a cleanup commit that's the start of a planned refactor of http.c 
into separate modules.


>From 13bb413723f3787f2446ef63d6950fcf81279e62 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Thu, 4 Jun 2026 13:18:33 +0200
Subject: [PATCH 1/7] avformat/http: organize HTTP context fields into sections
 (cosmetic)

This is a best-effort attempt at re-organizing these fields a bit to
make it more clear what's used where and why.

Sponsored-by: nxtedition AB
Signed-off-by: Niklas Haas <[email protected]>
---
 libavformat/http.c | 113 +++++++++++++++++++++++++++------------------
 1 file changed, 67 insertions(+), 46 deletions(-)

diff --git a/libavformat/http.c b/libavformat/http.c
index 32ec5192c4..0aad5bb5ce 100644
--- a/libavformat/http.c
+++ b/libavformat/http.c
@@ -71,18 +71,13 @@ typedef enum {
 
 typedef struct HTTPContext {
     const AVClass *class;
-    URLContext *hd;
     unsigned char buffer[BUFFER_SIZE], *buf_ptr, *buf_end;
-    int line_count;
-    int http_code;
-    /* Used if "Transfer-Encoding: chunked" otherwise -1. */
-    uint64_t chunksize;
-    int chunkend;
-    uint64_t off, end_off, filesize, range_end;
-    char *uri;
+
+    /*************************
+     * Configuration options *
+     *************************/
+    uint64_t off, end_off; /* `off` is also mutated by seeking / reading */
     char *location;
-    HTTPAuthState auth_state;
-    HTTPAuthState proxy_auth_state;
     char *http_proxy;
     char *headers;
     char *mime_type;
@@ -90,38 +85,16 @@ typedef struct HTTPContext {
     char *user_agent;
     char *referer;
     char *content_type;
-    /* Set if the server correctly handles Connection: close and will close
-     * the connection after feeding us the content. */
-    int willclose;
     int seekable;           /**< Control seekability, 0 = disable, 1 = enable, 
-1 = probe. */
     int chunked_post;
-    /* A flag which indicates if the end of chunked encoding has been sent. */
-    int end_chunked_post;
-    /* A flag which indicates we have finished to read POST reply. */
-    int end_header;
-    /* A flag which indicates if we use persistent connections. */
-    int multiple_requests;
+    int multiple_requests; /**< A flag which indicates if we use persistent 
connections. */
     uint8_t *post_data;
     int post_datalen;
-    int is_akamai;
-    int is_mediagateway;
     char *cookies;          ///< holds newline (\n) delimited Set-Cookie 
header field values (without the "Set-Cookie: " field name)
-    /* A dictionary containing cookies keyed by cookie name */
-    AVDictionary *cookie_dict;
     int icy;
-    /* how much data was read since the last ICY metadata packet */
-    uint64_t icy_data_read;
-    /* after how many bytes of read data a new metadata packet will be found */
-    uint64_t icy_metaint;
     char *icy_metadata_headers;
     char *icy_metadata_packet;
     AVDictionary *metadata;
-#if CONFIG_ZLIB
-    int compressed;
-    z_stream inflate_stream;
-    uint8_t *inflate_buffer;
-#endif /* CONFIG_ZLIB */
-    AVDictionary *chained_options;
     /* -1 = try to send if applicable, 0 = always disabled, 1 = always enabled 
*/
     int send_expect_100;
     char *method;
@@ -129,26 +102,32 @@ typedef struct HTTPContext {
     int reconnect_at_eof;
     int reconnect_on_network_error;
     int reconnect_streamed;
+    int reconnect_max_retries;
     int reconnect_delay_max;
+    int reconnect_delay_total_max;
     char *reconnect_on_http_error;
     int listen;
     char *resource;
     int reply_code;
-    int is_multi_client;
-    HandshakeState handshake_step;
-    int is_connected_server;
     int short_seek_size;
-    int64_t expires;
-    char *new_location;
-    AVDictionary *redirect_cache;
-    uint64_t filesize_from_content_range;
+    int max_redirects;
     int respect_retry_after;
-    unsigned int retry_after;
-    int reconnect_max_retries;
-    int reconnect_delay_total_max;
-    uint64_t initial_request_size;
     uint64_t request_size;
-    int initial_requests; /* whether or not to limit requests to 
initial_request_size */
+    uint64_t initial_request_size;
+
+    /**********************
+     * Context-wide state *
+     **********************/
+    HTTPAuthState auth_state; /* auth_state.auth_type is also a config option 
*/
+    HTTPAuthState proxy_auth_state;
+    uint64_t filesize;
+    int is_akamai;
+    int is_mediagateway;
+    /* A dictionary containing cookies keyed by cookie name */
+    AVDictionary *cookie_dict;
+    AVDictionary *chained_options;
+    AVDictionary *redirect_cache;
+
     /* Connection statistics */
     int nb_connections;
     int nb_requests;
@@ -157,7 +136,49 @@ typedef struct HTTPContext {
     int nb_redirects;
     int64_t sum_latency; /* divide by nb_requests */
     int64_t max_latency;
-    int max_redirects;
+
+    /************************
+     * Per-connection state *
+     ************************/
+    URLContext *hd;
+    char *uri;
+    char *new_location;
+    int http_code;
+    int64_t expires;
+    /* Used if "Transfer-Encoding: chunked" otherwise -1. */
+    uint64_t chunksize;
+    int chunkend;
+    uint64_t range_end;
+    /* Set if the server correctly handles Connection: close and will close
+     * the connection after feeding us the content. */
+    int willclose;
+    /* A flag which indicates if the end of chunked encoding has been sent. */
+    int end_chunked_post;
+    /* A flag which indicates we have finished to read POST reply. */
+    int end_header;
+    /* how much data was read since the last ICY metadata packet */
+    uint64_t icy_data_read;
+    /* after how many bytes of read data a new metadata packet will be found */
+    uint64_t icy_metaint;
+#if CONFIG_ZLIB
+    int compressed;
+    z_stream inflate_stream;
+    uint8_t *inflate_buffer;
+#endif /* CONFIG_ZLIB */
+    unsigned int retry_after;
+    int initial_requests; /* whether or not to limit requests to 
initial_request_size */
+
+    /* Temporary during header parsing */
+    uint64_t filesize_from_content_range;
+    int line_count;
+
+    /******************
+     * Listener state *
+     ******************/
+    /* URLContext *hd; */
+    HandshakeState handshake_step;
+    int is_multi_client;
+    int is_connected_server;
 } HTTPContext;
 
 #define OFFSET(x) offsetof(HTTPContext, x)
-- 
2.52.0


>From da96c488bf7b79efafd80d4f27470fcefa8eb57b Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Thu, 4 Jun 2026 14:41:13 +0200
Subject: [PATCH 2/7] avformat/http: make reconnection message less misleading

If using a keepalive connection, this does not actually reconnect.

Sponsored-by: nxtedition AB
Signed-off-by: Niklas Haas <[email protected]>
---
 libavformat/http.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/libavformat/http.c b/libavformat/http.c
index 0aad5bb5ce..3a545acae6 100644
--- a/libavformat/http.c
+++ b/libavformat/http.c
@@ -470,7 +470,8 @@ redo:
             s->nb_retries++;
         }
 
-        av_log(h, AV_LOG_WARNING, "Will reconnect at %"PRIu64" in %d 
second(s).\n", off, reconnect_delay);
+        av_log(h, AV_LOG_WARNING, "Will %s at %"PRIu64" in %d second(s).\n",
+               s->willclose ? "reconnect" : "retry", off, reconnect_delay);
         ret = ff_network_sleep_interruptible(1000U * 1000 * reconnect_delay, 
&h->interrupt_callback);
         if (ret != AVERROR(ETIMEDOUT))
             goto fail;
@@ -1897,7 +1898,8 @@ retry:
             reconnect_delay_total > s->reconnect_delay_total_max)
             return AVERROR(EIO);
 
-        av_log(h, AV_LOG_WARNING, "Will reconnect at %"PRIu64" in %d 
second(s), error=%s.\n", s->off, reconnect_delay, av_err2str(read_ret));
+        av_log(h, AV_LOG_WARNING, "Will %s at %"PRIu64" in %d second(s), 
error=%s.\n", s->willclose ? "reconnect" : "retry",
+               s->off, reconnect_delay, av_err2str(read_ret));
         err = ff_network_sleep_interruptible(1000U*1000*reconnect_delay, 
&h->interrupt_callback);
         if (err != AVERROR(ETIMEDOUT))
             return err;
-- 
2.52.0


>From 3bebd229fa2226c549a75e9ecf13a098286e2ba7 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Thu, 4 Jun 2026 16:12:34 +0200
Subject: [PATCH 3/7] avformat/http: imply keep-alive by default when
 beneficial

By turning it into a tristate that defaults to -1 (auto). Users can still
force a particular value for debugging.

Impactful for the following commits.

Sponsored-by: nxtedition AB
Signed-off-by: Niklas Haas <[email protected]>
---
 doc/protocols.texi |  7 ++++---
 libavformat/http.c | 10 +++++++---
 2 files changed, 11 insertions(+), 6 deletions(-)

diff --git a/doc/protocols.texi b/doc/protocols.texi
index a62156f4d8..b3953f4c39 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -451,15 +451,16 @@ string describing the libavformat build. 
("Lavf/<version>")
 Set the Referer header. Include 'Referer: URL' header in HTTP request.
 
 @item multiple_requests
-Use persistent connections if set to 1, default is 0.
+Force persistent connections if set to 1, or disable if 0. Default is -1,
+which means auto (implies keep-alive when using -multiple_requests or
+encountering partial files).
 
 @item request_size
 Limit the size of requests made. This is useful for some pathological servers
 that throttle unbounded range requests, as well as when expecting to seek
 frequently. Disabled (set to 0) by default.
 
-Note that if enabling this option, it's strongly recommended to also enable
-the @option{multiple_requests} option, as well as setting
+Note that if enabling this option, it's strongly recommended to also set
 @option{short_seek_size} to the same value or higher. Doing so allows FFmpeg
 to reuse a single HTTP connection wherever possible.
 
diff --git a/libavformat/http.c b/libavformat/http.c
index 3a545acae6..9273da4fe9 100644
--- a/libavformat/http.c
+++ b/libavformat/http.c
@@ -194,7 +194,7 @@ static const AVOption http_options[] = {
     { "content_type", "set a specific content type for the POST messages", 
OFFSET(content_type), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E },
     { "user_agent", "override User-Agent header", OFFSET(user_agent), 
AV_OPT_TYPE_STRING, { .str = DEFAULT_USER_AGENT }, 0, 0, D },
     { "referer", "override referer header", OFFSET(referer), 
AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D },
-    { "multiple_requests", "use persistent connections", 
OFFSET(multiple_requests), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D | E },
+    { "multiple_requests", "use persistent connections", 
OFFSET(multiple_requests), AV_OPT_TYPE_BOOL, { .i64 = -1 }, -1, 1, D | E },
     { "request_size", "size (in bytes) of requests to make", 
OFFSET(request_size), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, D },
     { "initial_request_size", "size (in bytes) of initial requests made during 
probing / header parsing", OFFSET(initial_request_size), AV_OPT_TYPE_INT64, { 
.i64 = 0 }, 0, INT64_MAX, D },
     { "post_data", "set custom HTTP post data", OFFSET(post_data), 
AV_OPT_TYPE_BINARY, .flags = D | E },
@@ -1629,8 +1629,12 @@ static int http_connect(URLContext *h, const char *path, 
const char *local_path,
     if (send_expect_100 && !has_header(s->headers, "\r\nExpect: "))
         av_bprintf(&request, "Expect: 100-continue\r\n");
 
-    if (!has_header(s->headers, "\r\nConnection: "))
-        av_bprintf(&request, "Connection: %s\r\n", s->multiple_requests ? 
"keep-alive" : "close");
+    if (!has_header(s->headers, "\r\nConnection: ")) {
+        int keep_alive = s->multiple_requests > 0;
+        if (s->multiple_requests < 0 /* auto */ && s->request_size)
+            keep_alive = 1;
+        av_bprintf(&request, "Connection: %s\r\n", keep_alive ? "keep-alive" : 
"close");
+    }
 
     if (!has_header(s->headers, "\r\nHost: "))
         av_bprintf(&request, "Host: %s\r\n", hoststr);
-- 
2.52.0


>From a09ee01e97931a2788a5fc27b5d50e4ef1027016 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Thu, 4 Jun 2026 14:47:04 +0200
Subject: [PATCH 4/7] avutil/error: add HTTP 416 Range Not Satisfiable

This is returned by HTTPds for files with unknown/growing filesize,
when trying to read past what's already available. Needed to implement
support for such streaming-file / incremental scenarios, both internally,
and as a user-facing failure condition when e.g. the maximum retry delay
is reached.

Sponsored-by: nxtedition AB
Signed-off-by: Niklas Haas <[email protected]>
---
 libavutil/error.c   | 1 +
 libavutil/error.h   | 1 +
 libavutil/version.h | 2 +-
 3 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/libavutil/error.c b/libavutil/error.c
index 2c9f0028bd..0e6395d5c4 100644
--- a/libavutil/error.c
+++ b/libavutil/error.c
@@ -55,6 +55,7 @@
     E(HTTP_FORBIDDEN,           "Server returned 403 Forbidden (access 
denied)")                \
     E(HTTP_NOT_FOUND,           "Server returned 404 Not Found")               
                 \
     E(HTTP_TOO_MANY_REQUESTS,   "Server returned 429 Too Many Requests")       
                 \
+    E(HTTP_RANGE_NOT_SATISFIABLE,"Server returned 416 Range Not Satisfiable")  
                 \
     E(HTTP_OTHER_4XX,           "Server returned 4XX Client Error, but not one 
of 40{0,1,3,4}") \
     E(HTTP_SERVER_ERROR,        "Server returned 5XX Server Error reply")      
                 \
 
diff --git a/libavutil/error.h b/libavutil/error.h
index 1efa86c4c1..148717a907 100644
--- a/libavutil/error.h
+++ b/libavutil/error.h
@@ -79,6 +79,7 @@
 #define AVERROR_HTTP_UNAUTHORIZED  FFERRTAG(0xF8,'4','0','1')
 #define AVERROR_HTTP_FORBIDDEN     FFERRTAG(0xF8,'4','0','3')
 #define AVERROR_HTTP_NOT_FOUND     FFERRTAG(0xF8,'4','0','4')
+#define AVERROR_HTTP_RANGE_NOT_SATISFIABLE FFERRTAG(0xF8,'4','1','6')
 #define AVERROR_HTTP_TOO_MANY_REQUESTS FFERRTAG(0xF8,'4','2','9')
 #define AVERROR_HTTP_OTHER_4XX     FFERRTAG(0xF8,'4','X','X')
 #define AVERROR_HTTP_SERVER_ERROR  FFERRTAG(0xF8,'5','X','X')
diff --git a/libavutil/version.h b/libavutil/version.h
index e7aeab9995..4392f77220 100644
--- a/libavutil/version.h
+++ b/libavutil/version.h
@@ -80,7 +80,7 @@
 
 #define LIBAVUTIL_VERSION_MAJOR  60
 #define LIBAVUTIL_VERSION_MINOR  32
-#define LIBAVUTIL_VERSION_MICRO 100
+#define LIBAVUTIL_VERSION_MICRO 101
 
 #define LIBAVUTIL_VERSION_INT   AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \
                                                LIBAVUTIL_VERSION_MINOR, \
-- 
2.52.0


>From 2c55707377064eeb41e5884109779fb7ab7bdaa8 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Thu, 4 Jun 2026 14:51:45 +0200
Subject: [PATCH 5/7] avformat/http: add AVERROR_HTTP_RANGE_NOT_SATISFIABLE

This just adds the http-internal definitions / glue code for plumbing
this error code. It does not yet add any special handling.

Sponsored-by: nxtedition AB
Signed-off-by: Niklas Haas <[email protected]>
---
 libavformat/http.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/libavformat/http.c b/libavformat/http.c
index 9273da4fe9..61cfa1f616 100644
--- a/libavformat/http.c
+++ b/libavformat/http.c
@@ -355,6 +355,7 @@ static int http_should_reconnect(HTTPContext *s, int err)
     case AVERROR_HTTP_UNAUTHORIZED:
     case AVERROR_HTTP_FORBIDDEN:
     case AVERROR_HTTP_NOT_FOUND:
+    case AVERROR_HTTP_RANGE_NOT_SATISFIABLE:
     case AVERROR_HTTP_TOO_MANY_REQUESTS:
     case AVERROR_HTTP_OTHER_4XX:
         status_group = "4xx";
@@ -618,6 +619,7 @@ int ff_http_averror(int status_code, int default_averror)
         case 401: return AVERROR_HTTP_UNAUTHORIZED;
         case 403: return AVERROR_HTTP_FORBIDDEN;
         case 404: return AVERROR_HTTP_NOT_FOUND;
+        case 416: return AVERROR_HTTP_RANGE_NOT_SATISFIABLE;
         case 429: return AVERROR_HTTP_TOO_MANY_REQUESTS;
         default: break;
     }
@@ -661,6 +663,11 @@ static int http_write_reply(URLContext* h, int status_code)
         reply_code = 404;
         reply_text = "Not Found";
         break;
+    case AVERROR_HTTP_RANGE_NOT_SATISFIABLE:
+    case 416:
+        reply_code = 416;
+        reply_text = "Range Not Satisfiable";
+        break;
     case AVERROR_HTTP_TOO_MANY_REQUESTS:
     case 429:
         reply_code = 429;
-- 
2.52.0


>From 9a75a09ea0c1da21cc3fae92256c4facd224159d Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Thu, 4 Jun 2026 14:58:51 +0200
Subject: [PATCH 6/7] avformat/http: parse Content-Range: <range>/*

The * here indicates that the true filesize is unknown, possibly because
the file is still being written to. Currently, this fails to parse, so we
end up exposing the Content-Length as the `filesize` implicitly, leading to
errors like:

[in#0 @ 0x3d1a0300] Error opening input: Invalid data found when processing 
input
Error opening input file http://localhost:8000.
Error opening input files: Invalid data found when processing input

Because the file appears to be truncated / EOF is falsely propagated. This
simple change allows the client to continue requesting the rest of the file in
such a case, until the server stops giving us data (416 Range Not Satisfiable).

It's worth pointing out that this relies on the existing logic for retrying
after a partial request (analog to -request_size).

It's also worth noting that this commit on its own is incomplete, as it will
still usually eventually error out due to not retrying after a 416. That will
come in the next commit.

Sponsored-by: nxtedition AB
Signed-off-by: Niklas Haas <[email protected]>
---
 doc/protocols.texi |  3 ++-
 libavformat/http.c | 18 ++++++++++++++----
 2 files changed, 16 insertions(+), 5 deletions(-)

diff --git a/doc/protocols.texi b/doc/protocols.texi
index b3953f4c39..ae07e9618b 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -458,7 +458,8 @@ encountering partial files).
 @item request_size
 Limit the size of requests made. This is useful for some pathological servers
 that throttle unbounded range requests, as well as when expecting to seek
-frequently. Disabled (set to 0) by default.
+frequently, or when requesting growing resources with unknown size. Disabled
+(set to 0) by default.
 
 Note that if enabling this option, it's strongly recommended to also set
 @option{short_seek_size} to the same value or higher. Doing so allows FFmpeg
diff --git a/libavformat/http.c b/libavformat/http.c
index 61cfa1f616..83d4379e13 100644
--- a/libavformat/http.c
+++ b/libavformat/http.c
@@ -170,6 +170,7 @@ typedef struct HTTPContext {
 
     /* Temporary during header parsing */
     uint64_t filesize_from_content_range;
+    int filesize_unknown;
     int line_count;
 
     /******************
@@ -952,8 +953,13 @@ static void parse_content_range(URLContext *h, const char 
*p)
         s->off = strtoull(p, NULL, 10);
         if ((end = strchr(p, '-')) && strlen(end) > 0)
             s->range_end = strtoull(end + 1, NULL, 10) + 1;
-        if ((slash = strchr(p, '/')) && strlen(slash) > 0)
-            s->filesize_from_content_range = strtoull(slash + 1, NULL, 10);
+        if ((slash = strchr(p, '/')) && strlen(slash) > 0) {
+            const char *size = slash + 1;
+            if (!strcmp(size, "*"))
+                s->filesize_unknown = 1;
+            else
+                s->filesize_from_content_range = strtoull(size, NULL, 10);
+        }
     }
     if (s->seekable == -1 && (!s->is_akamai || s->filesize != 2147483647))
         h->is_streamed = 0; /* we _can_ in fact seek */
@@ -1502,8 +1508,11 @@ static int http_read_header(URLContext *h)
         return http_err;
 
     // filesize from Content-Range can always be used, even if using chunked 
Transfer-Encoding
-    if (s->filesize_from_content_range != UINT64_MAX)
+    if (s->filesize_from_content_range != UINT64_MAX) {
         s->filesize = s->filesize_from_content_range;
+        s->filesize_unknown = 0; /* the case of a 416 error is already handled 
above */
+    } else if (s->filesize_unknown)
+        s->filesize = UINT64_MAX;
 
     if (s->seekable == -1 && s->is_mediagateway && s->filesize == 2000000000)
         h->is_streamed = 1; /* we can in fact _not_ seek */
@@ -1638,7 +1647,8 @@ static int http_connect(URLContext *h, const char *path, 
const char *local_path,
 
     if (!has_header(s->headers, "\r\nConnection: ")) {
         int keep_alive = s->multiple_requests > 0;
-        if (s->multiple_requests < 0 /* auto */ && s->request_size)
+        if (s->multiple_requests < 0 /* auto */ &&
+            (s->request_size || s->filesize_unknown))
             keep_alive = 1;
         av_bprintf(&request, "Connection: %s\r\n", keep_alive ? "keep-alive" : 
"close");
     }
-- 
2.52.0


>From f9dc55e9955b9b5bc513d053cf8e192fe40563c6 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Thu, 4 Jun 2026 16:35:03 +0200
Subject: [PATCH 7/7] avformat/http: add -reconnect_partial option for growing
 inputs

This is useful for servers which are streaming files that are still being
generated, such as livestream recordings. In this case, we treat 416 not
as a hard error condition, but as a soft-error which will trigger the normal
connection retry logic.

Sponsored-by: nxtedition AB
Signed-off-by: Niklas Haas <[email protected]>
---
 doc/protocols.texi |  9 +++++++--
 libavformat/http.c | 17 ++++++++++++++---
 2 files changed, 21 insertions(+), 5 deletions(-)

diff --git a/doc/protocols.texi b/doc/protocols.texi
index ae07e9618b..497647a623 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -575,6 +575,11 @@ include specific status codes (e.g. '503') or the strings 
'4xx' / '5xx'.
 @item reconnect_streamed
 If set then even streamed/non seekable streams will be reconnected on errors.
 
+@item reconnect_partial
+If set then partial / growing files are reconnected when the requested range is
+not (yet) satisfiable, i.e. on HTTP 416 (Range Not Satisfiable) responses. This
+is useful for following files that are still being written.
+
 @item reconnect_delay_max
 Set the maximum delay in seconds after which to give up reconnecting.
 
@@ -586,8 +591,8 @@ Set the maximum total delay in seconds after which to give 
up reconnecting.
 
 @item respect_retry_after
 If enabled, and a Retry-After header is encountered, its requested reconnection
-delay will be honored, rather than using exponential backoff. Useful for 429 
and
-503 errors. Default enabled.
+delay will be honored, rather than using exponential backoff. Useful for 429,
+416 and 503 errors. Default enabled.
 
 @item listen
 If set to 1 enables experimental HTTP server. This can be used to send data 
when
diff --git a/libavformat/http.c b/libavformat/http.c
index 83d4379e13..9c8b5359a8 100644
--- a/libavformat/http.c
+++ b/libavformat/http.c
@@ -102,6 +102,7 @@ typedef struct HTTPContext {
     int reconnect_at_eof;
     int reconnect_on_network_error;
     int reconnect_streamed;
+    int reconnect_partial;
     int reconnect_max_retries;
     int reconnect_delay_max;
     int reconnect_delay_total_max;
@@ -219,6 +220,7 @@ static const AVOption http_options[] = {
     { "reconnect_on_network_error", "auto reconnect in case of tcp/tls error 
during connect", OFFSET(reconnect_on_network_error), AV_OPT_TYPE_BOOL, { .i64 = 
0 }, 0, 1, D },
     { "reconnect_on_http_error", "list of http status codes to reconnect on", 
OFFSET(reconnect_on_http_error), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D },
     { "reconnect_streamed", "auto reconnect streamed / non seekable streams", 
OFFSET(reconnect_streamed), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D },
+    { "reconnect_partial", "auto reconnect partial / growing files", 
OFFSET(reconnect_partial), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D },
     { "reconnect_delay_max", "max reconnect delay in seconds after which to 
give up", OFFSET(reconnect_delay_max), AV_OPT_TYPE_INT, { .i64 = 120 }, 0, 
UINT_MAX/1000/1000, D },
     { "reconnect_max_retries", "the max number of times to retry a 
connection", OFFSET(reconnect_max_retries), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 
INT_MAX, D },
     { "reconnect_delay_total_max", "max total reconnect delay in seconds after 
which to give up", OFFSET(reconnect_delay_total_max), AV_OPT_TYPE_INT, { .i64 = 
256 }, 0, UINT_MAX/1000/1000, D },
@@ -352,11 +354,14 @@ static int http_should_reconnect(HTTPContext *s, int err)
     char http_code[4];
 
     switch (err) {
+    case AVERROR_HTTP_RANGE_NOT_SATISFIABLE:
+        if (s->reconnect_partial)
+            return 1;
+        av_fallthrough;
     case AVERROR_HTTP_BAD_REQUEST:
     case AVERROR_HTTP_UNAUTHORIZED:
     case AVERROR_HTTP_FORBIDDEN:
     case AVERROR_HTTP_NOT_FOUND:
-    case AVERROR_HTTP_RANGE_NOT_SATISFIABLE:
     case AVERROR_HTTP_TOO_MANY_REQUESTS:
     case AVERROR_HTTP_OTHER_4XX:
         status_group = "4xx";
@@ -918,11 +923,12 @@ static int http_get_line(HTTPContext *s, char *line, int 
line_size)
 static int check_http_code(URLContext *h, int http_code, const char *end)
 {
     HTTPContext *s = h->priv_data;
-    /* error codes are 4xx and 5xx, but regard 401 as a success, so we
+    /* error codes are 4xx and 5xx, but regard 401 and 416 as a success, so we
      * don't abort until all headers have been parsed. */
     if (http_code >= 400 && http_code < 600 &&
         (http_code != 401 || s->auth_state.auth_type != HTTP_AUTH_NONE) &&
-        (http_code != 407 || s->proxy_auth_state.auth_type != HTTP_AUTH_NONE)) 
{
+        (http_code != 407 || s->proxy_auth_state.auth_type != HTTP_AUTH_NONE) 
&&
+        (http_code != 416 || !s->reconnect_partial)) {
         end += strspn(end, SPACE_CHARS);
         av_log(h, AV_LOG_WARNING, "HTTP error %d %s\n", http_code, end);
         return ff_http_averror(http_code, AVERROR(EIO));
@@ -1506,6 +1512,11 @@ static int http_read_header(URLContext *h)
     }
     if (http_err)
         return http_err;
+    else if (s->http_code == 416) {
+        av_assert0(s->reconnect_partial); // should have errored otherwise
+        s->filesize_unknown = 1;
+        return AVERROR_HTTP_RANGE_NOT_SATISFIABLE;
+    }
 
     // filesize from Content-Range can always be used, even if using chunked 
Transfer-Encoding
     if (s->filesize_from_content_range != UINT64_MAX) {
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to