From 427e4bf25dd199c80018dbf48dce4f266dcdfd7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= Date: Tue, 26 May 2026 18:06:32 +0200 Subject: [PATCH] stream_curl: support offset/end_offset passed in AVDictionary lavf's http demuxer exposes initial/final byte offsets as AVOptions. Some demuxers, like lavf/hls.c assume it is always available, even for custom IO. We need to use those values for correct demuxing. Fixes: https://github.com/mpv-player/mpv/pull/17879#issuecomment-4468476976 --- stream/stream_curl.c | 47 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/stream/stream_curl.c b/stream/stream_curl.c index f0e8dcedb6..5b199973b0 100644 --- a/stream/stream_curl.c +++ b/stream/stream_curl.c @@ -65,6 +65,14 @@ static const struct curl_scheme curl_schemes[] = { {bstr0_lit("ftps"), MP_CURL_PROTO_FTP}, }; +// Special args for use by lavf. Matches lavf/http.c "offset"/"end_offset" opts. +// `offset` is the inclusive starting byte. +// `end_offset` is the exclusive upper bound (0 = unbounded). +struct curl_open_args { + int64_t offset; + int64_t end_offset; +}; + struct curl_opts { bool enabled; int http_version; @@ -159,6 +167,7 @@ struct priv { // Producer state. Only touched by the curl thread. uint64_t request_start; // absolute byte position of next request uint64_t request_received; // bytes received in the current request + uint64_t request_end; // exclusive byte cap (0 = unbounded) int retry_count; // consecutive failed attempts at request_start bool active; // handle is currently active in the multi bool finished; // current request has reached EOF @@ -570,8 +579,11 @@ static void start_request(struct priv *p) bool ranged = !p->probed || p->seekable; bool chunked = ranged && p->opts->max_request_size > 0; + bool capped = ranged && p->request_end > 0; - if (p->seekable && p->content_size > 0 && start >= p->content_size) { + bool past_size = p->seekable && p->content_size > 0 && start >= p->content_size; + bool past_end = capped && start >= p->request_end; + if (past_size || past_end) { p->finished = true; mp_mutex_lock(&p->mtx); p->stream_eof = true; @@ -581,10 +593,14 @@ static void start_request(struct priv *p) } char range[64]; - if (chunked) { - uint64_t end = start + p->opts->max_request_size - 1; + if (chunked || capped) { + uint64_t end = UINT64_MAX; + if (chunked) + end = start + p->opts->max_request_size - 1; if (p->content_size > 0) end = MPMIN(end, p->content_size - 1); + if (capped) + end = MPMIN(end, p->request_end - 1); snprintf(range, sizeof(range), "%" PRIu64 "-%" PRIu64, start, end); curl_easy_setopt(p->curl, CURLOPT_RANGE, range); } else if (ranged) { @@ -623,7 +639,9 @@ static void on_done(struct priv *p, CURLcode code) p->retry_count = 0; bool chunked = p->seekable && p->opts->max_request_size > 0; - if (chunked && (p->content_size <= 0 || p->request_start < p->content_size)) { + bool past_size = p->content_size > 0 && p->request_start >= p->content_size; + bool past_end = p->request_end > 0 && p->request_start >= p->request_end; + if (chunked && !past_size && !past_end) { start_request(p); return; } @@ -872,6 +890,14 @@ static int curl_open(stream_t *s, const struct stream_open_args *args) p->buffer_size = p->opts->buffer_size; p->buffer = talloc_size(p, p->buffer_size); + if (args->special_arg) { + const struct curl_open_args *oa = args->special_arg; + if (oa->offset > 0) + p->request_start = oa->offset; + if (oa->end_offset > 0) + p->request_end = oa->end_offset; + } + mp_mutex_init(&p->mtx); mp_cond_init(&p->cond); p->aborted = false; @@ -1021,14 +1047,22 @@ int mp_curl_avio_open(struct demuxer *demuxer, AVIOContext **pb_out, // The context is required to be initialized in global. mp_require(demuxer->global && demuxer->global->curl); - // Nested IO plumbs whitelist/blacklist through the AVDictionary, use that - // if set, same as FFmpeg's implementation. + struct curl_open_args oa = {0}; if (options && *options) { AVDictionaryEntry *e; + // Nested IO plumbs whitelist/blacklist through the AVDictionary, + // use that if set, same as FFmpeg's implementation. if ((e = av_dict_get(*options, "protocol_whitelist", NULL, 0))) whitelist = e->value; if ((e = av_dict_get(*options, "protocol_blacklist", NULL, 0))) blacklist = e->value; + // lavf's http demuxer exposes initial/final byte offsets as AVOptions + // Some demuxers, like lavf/hls.c assume it is always available, even for + // custom IO... Add support for this. + if ((e = av_dict_get(*options, "offset", NULL, 0))) + oa.offset = strtoll(e->value, NULL, 10); + if ((e = av_dict_get(*options, "end_offset", NULL, 0))) + oa.end_offset = strtoll(e->value, NULL, 10); } if (!is_protocol_allowed(demuxer->log, cs->scheme, whitelist, blacklist)) @@ -1046,6 +1080,7 @@ int mp_curl_avio_open(struct demuxer *demuxer, AVIOContext **pb_out, .url = url, .flags = STREAM_READ | (demuxer->stream_origin & STREAM_ORIGIN_MASK), .sinfo = &stream_info_curl, + .special_arg = &oa, }; struct stream *s = NULL;