Commit 8191fa0b8c for ffmpeg

commit 8191fa0b8c05da7919f5ffecf631a7aa669b9203
Author: Romain Beauxis <romain.beauxis@gmail.com>
Date:   Sat Oct 17 06:32:53 2026 +0200

    avformat/hls: accept EXT-X-START in Master Playlists

    EXT-X-START may appear in both Master and Media Playlists (RFC 8216
    4.3.5.2). Parsing it allocated a Media Playlist, so a Master Playlist
    carrying it before its EXT-X-STREAM-INF entries was rejected since
    0e6eef3551.

    Record the offset while parsing and apply it once the playlist type is
    known. A value from a Master Playlist applies to all its Media
    Playlists, which then ignore their own, as RFC 8216 4.3.5 recommends.

    Defer EXT-X-ENDLIST the same way, so that it is not lost when it
    precedes the first tag allocating the Media Playlist.

diff --git a/libavformat/hls.c b/libavformat/hls.c
index 519d3d852f..c68b5e88cf 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -264,6 +264,7 @@ typedef struct HLSContext {
     int m3u8_hold_counters;
     int live_start_index;
     int prefer_x_start;
+    int master_time_offset_flag; ///< EXT-X-START was set by the Master Playlist
     int first_packet;
     int64_t first_timestamp;
     struct playlist *first_timestamp_pls;
@@ -979,6 +980,9 @@ static int parse_playlist(HLSContext *c, const char *url,
     int prev_n_segments = 0;
     int64_t prev_start_seq_no = -1;
     int64_t load_start = av_gettime_relative();
+    int64_t start_time_offset = 0;
+    int time_offset_flag = 0;
+    int finished = 0;

     if (is_http && !in && c->http_persistent && c->playlist_pb) {
         in = c->playlist_pb;
@@ -1139,15 +1143,11 @@ static int parse_playlist(HLSContext *c, const char *url,

         } else if (av_strstart(line, "#EXT-X-START:", &ptr)) {
             const char *time_offset_value = NULL;
-            ret = ensure_playlist(c, &pls, url);
-            if (ret < 0) {
-                goto fail;
-            }
             if (av_strstart(ptr, "TIME-OFFSET=", &time_offset_value)) {
                 double offset = strtod(time_offset_value, NULL) * AV_TIME_BASE;
                 if (offset >= -0x1p63 && offset < 0x1p63) {
-                    pls->start_time_offset = offset;
-                    pls->time_offset_flag = 1;
+                    start_time_offset = offset;
+                    time_offset_flag  = 1;
                 } else {
                     av_log(c->ctx, AV_LOG_WARNING, "TIME-OFFSET value is"
                                                     "invalid, it will be ignored");
@@ -1158,8 +1158,7 @@ static int parse_playlist(HLSContext *c, const char *url,
                 continue;
             }
         } else if (av_strstart(line, "#EXT-X-ENDLIST", &ptr)) {
-            if (pls)
-                pls->finished = 1;
+            finished = 1;
         } else if (av_strstart(line, "#EXTINF:", &ptr)) {
             double d = atof(ptr) * AV_TIME_BASE;
             if (d < 0 || d > INT64_MAX || isnan(d)) {
@@ -1290,6 +1289,27 @@ static int parse_playlist(HLSContext *c, const char *url,
         }
     }

+    /* Media Playlist tags may precede the first tag that allocates the
+     * playlist. */
+    if (pls && finished)
+        pls->finished = 1;
+
+    /* EXT-X-START is valid in both playlist types. A value from the Master
+     * Playlist applies to all its Media Playlists, which then ignore their
+     * own (RFC 8216 4.3.5). */
+    if (time_offset_flag && !c->master_time_offset_flag) {
+        if (pls) {
+            pls->start_time_offset = start_time_offset;
+            pls->time_offset_flag  = 1;
+        } else {
+            c->master_time_offset_flag = 1;
+            for (int i = 0; i < c->n_playlists; i++) {
+                c->playlists[i]->start_time_offset = start_time_offset;
+                c->playlists[i]->time_offset_flag  = 1;
+            }
+        }
+    }
+
 fail:
     free_segment_dynarray(prev_segments, prev_n_segments);
     av_freep(&prev_segments);
diff --git a/tests/ref/fate/hls-parse-master-start b/tests/ref/fate/hls-parse-master-start
index 401c7af49d..57e88ecc7d 100644
--- a/tests/ref/fate/hls-parse-master-start
+++ b/tests/ref/fate/hls-parse-master-start
@@ -1 +1,3 @@
-error: Invalid data found when processing input
+2 variant(s), 2 playlist(s)
+a.m3u8: live, start offset 1500000
+b.m3u8: live, start offset 1500000
diff --git a/tests/ref/fate/hls-parse-master-start-media b/tests/ref/fate/hls-parse-master-start-media
index 401c7af49d..57e88ecc7d 100644
--- a/tests/ref/fate/hls-parse-master-start-media
+++ b/tests/ref/fate/hls-parse-master-start-media
@@ -1 +1,3 @@
-error: Invalid data found when processing input
+2 variant(s), 2 playlist(s)
+a.m3u8: live, start offset 1500000
+b.m3u8: live, start offset 1500000
diff --git a/tests/ref/fate/hls-parse-master-start-override b/tests/ref/fate/hls-parse-master-start-override
index 401c7af49d..57e88ecc7d 100644
--- a/tests/ref/fate/hls-parse-master-start-override
+++ b/tests/ref/fate/hls-parse-master-start-override
@@ -1 +1,3 @@
-error: Invalid data found when processing input
+2 variant(s), 2 playlist(s)
+a.m3u8: live, start offset 1500000
+b.m3u8: live, start offset 1500000