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]

Reply via email to