PR #23137 opened by Mikael Magnusson (Mikachu) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23137 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23137.patch
If a player pauses, the stream can pause wherever inside a segment. If we simply skip ahead to the next segment, the player will get corrupted data. Add a resume_offset parameter to open_input and use this from read_data_continuous. Other callers simply pass 0 here as before. >From 99dd0d32795865b878827cf223946730b05f2d35 Mon Sep 17 00:00:00 2001 From: Mikael Magnusson <[email protected]> Date: Mon, 18 May 2026 02:31:19 +0200 Subject: [PATCH] avformat/hls: retry segments from the offset they were paused at If a player pauses, the stream can pause wherever inside a segment. If we simply skip ahead to the next segment, the player will get corrupted data. Add a resume_offset parameter to open_input and use this from read_data_continuous. Other callers simply pass 0 here as before. --- libavformat/hls.c | 54 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/libavformat/hls.c b/libavformat/hls.c index 29dc08ef1f..d19aca6e79 100644 --- a/libavformat/hls.c +++ b/libavformat/hls.c @@ -1388,7 +1388,8 @@ static int read_key(HLSContext *c, struct playlist *pls, struct segment *seg) return 0; } -static int open_input(HLSContext *c, struct playlist *pls, struct segment *seg, AVIOContext **in) +static int open_input(HLSContext *c, struct playlist *pls, struct segment *seg, + AVIOContext **in, int64_t resume_offset) { AVDictionary *opts = NULL; int ret; @@ -1397,15 +1398,16 @@ static int open_input(HLSContext *c, struct playlist *pls, struct segment *seg, if (c->http_persistent) av_dict_set(&opts, "multiple_requests", "1", 0); - if (seg->size >= 0) { + if (seg->size >= 0 || resume_offset > 0) { /* try to restrict the HTTP request to the part we want * (if this is in fact a HTTP request) */ - av_dict_set_int(&opts, "offset", seg->url_offset, 0); - av_dict_set_int(&opts, "end_offset", seg->url_offset + seg->size, 0); + av_dict_set_int(&opts, "offset", seg->url_offset + resume_offset, 0); + if (seg->size >= 0) + av_dict_set_int(&opts, "end_offset", seg->url_offset + seg->size, 0); } av_log(pls->parent, AV_LOG_VERBOSE, "HLS request for url '%s', offset %"PRId64", playlist %d\n", - seg->url, seg->url_offset, pls->index); + seg->url, seg->url_offset + resume_offset, pls->index); if (seg->key_type == KEY_AES_128 || seg->key_type == KEY_SAMPLE_AES) { if (strcmp(seg->key, pls->key_url)) { @@ -1446,10 +1448,10 @@ static int open_input(HLSContext *c, struct playlist *pls, struct segment *seg, * as would be expected. Wrong offset received from the server will not be * noticed without the call, though. */ - if (ret == 0 && !is_http && seg->url_offset) { - int64_t seekret = avio_seek(*in, seg->url_offset, SEEK_SET); + if (ret == 0 && !is_http && (seg->url_offset || resume_offset)) { + int64_t seekret = avio_seek(*in, seg->url_offset + resume_offset, SEEK_SET); if (seekret < 0) { - av_log(pls->parent, AV_LOG_ERROR, "Unable to seek to offset %"PRId64" of HLS segment '%s'\n", seg->url_offset, seg->url); + av_log(pls->parent, AV_LOG_ERROR, "Unable to seek to offset %"PRId64" of HLS segment '%s'\n", seg->url_offset + resume_offset, seg->url); ret = seekret; ff_format_io_close(pls->parent, in); } @@ -1457,7 +1459,7 @@ static int open_input(HLSContext *c, struct playlist *pls, struct segment *seg, cleanup: av_dict_free(&opts); - pls->cur_seg_offset = 0; + pls->cur_seg_offset = (ret == 0) ? resume_offset : 0; return ret; } @@ -1477,7 +1479,7 @@ static int update_init_section(struct playlist *pls, struct segment *seg) if (!seg->init_section) return 0; - ret = open_input(c, pls, seg->init_section, &pls->input); + ret = open_input(c, pls, seg->init_section, &pls->input, 0); if (ret < 0) { av_log(pls->parent, AV_LOG_WARNING, "Failed to open an initialization section in playlist %d\n", @@ -1655,6 +1657,7 @@ static int read_data_continuous(void *opaque, uint8_t *buf, int buf_size) int ret; int just_opened = 0; int segment_retries = 0; + int64_t resume_offset = 0; struct segment *seg; if (c->http_persistent && v->input_read_done) { @@ -1678,13 +1681,13 @@ restart: if (ret) return ret; - if (c->http_multiple == 1 && v->input_next_requested) { + if (c->http_multiple == 1 && v->input_next_requested && resume_offset == 0) { FFSWAP(AVIOContext *, v->input, v->input_next); v->cur_seg_offset = 0; v->input_next_requested = 0; ret = 0; } else { - ret = open_input(c, v, seg, &v->input); + ret = open_input(c, v, seg, &v->input, resume_offset); } if (ret < 0) { if (ff_check_interrupt(c->interrupt_callback)) @@ -1698,13 +1701,16 @@ restart: v->index); v->cur_seq_no++; segment_retries = 0; + resume_offset = 0; } else { segment_retries++; } goto restart; } - segment_retries = 0; - just_opened = 1; + if (resume_offset) + segment_retries = 0; + just_opened = (resume_offset == 0); + resume_offset = 0; } if (c->http_multiple == -1) { @@ -1719,7 +1725,7 @@ restart: seg = next_segment(v); if (c->http_multiple == 1 && !v->input_next_requested && seg && seg->key_type == KEY_NONE && av_strstart(seg->url, "http", NULL)) { - ret = open_input(c, v, seg, &v->input_next); + ret = open_input(c, v, seg, &v->input_next, 0); if (ret < 0) { if (ff_check_interrupt(c->interrupt_callback)) return AVERROR_EXIT; @@ -1750,6 +1756,22 @@ restart: return ret; } + /* If the connection dropped mid-segment, try to resume from the current + * byte offset using, rather than skipping to the next segment and + * causing a discontinuity. */ + if (ret < 0 && ret != AVERROR_EOF && ret != AVERROR_EXIT && + v->cur_seg_offset > 0 && seg->key_type == KEY_NONE && + av_strstart(seg->url, "http", NULL) && + (seg->size < 0 || v->cur_seg_offset < seg->size)) + { + resume_offset = v->cur_seg_offset; + av_log(v->parent, AV_LOG_WARNING, + "hls: connection dropped mid-segment at offset %"PRId64", retrying\n", + resume_offset); + ff_format_io_close(v->parent, &v->input); + segment_retries++; + goto restart; + } if (c->http_persistent && seg->key_type == KEY_NONE && av_strstart(seg->url, "http", NULL)) { v->input_read_done = 1; @@ -1777,7 +1799,7 @@ static int read_data_subtitle_segment(void *opaque, uint8_t *buf, int buf_size) } if (!v->input) { - ret = open_input(c, v, seg, &v->input); + ret = open_input(c, v, seg, &v->input, 0); if (ret < 0) { if (ff_check_interrupt(c->interrupt_callback)) return AVERROR_EXIT; -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
