PR #22610 opened by Kacper Michajłow (kasper93)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22610
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22610.patch


From 17c48808ce60c698a7ceddb7bae0b24bf221bf12 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Tue, 24 Mar 2026 23:55:14 +0100
Subject: [PATCH 1/3] avformat/hls: fix seeking in EVENT playlists that start
 mid-stream
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

HLS EVENT playlists (e.g. Twitch VODs) are seekable but not finished,
so live_start_index causes playback to begin near the end. The first
packet's DTS then becomes first_timestamp, creating a wrong mapping
between timestamps and segments.

Fix this by subtracting the cumulative duration of skipped segments from
first_timestamp so it reflects the true start of the playlist.

Also set per-stream start_time from first_timestamp so correct time is
reported, reset pts_wrap_reference on seek to prevent bogus wrap
arounds.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 libavformat/hls.c | 37 ++++++++++++++++++++++++++++++++++---
 1 file changed, 34 insertions(+), 3 deletions(-)

diff --git a/libavformat/hls.c b/libavformat/hls.c
index 28c883097a..18e32c4d16 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -1111,6 +1111,17 @@ fail:
         !(c->variants[0]->playlists[0]->finished ||
           c->variants[0]->playlists[0]->type == PLS_TYPE_EVENT))
         c->ctx->ctx_flags |= AVFMTCTX_UNSEEKABLE;
+
+    if (c->n_variants && c->variants[0]->n_playlists &&
+        c->variants[0]->playlists[0]->type == PLS_TYPE_EVENT &&
+        !c->variants[0]->playlists[0]->finished) {
+        struct playlist *p = c->variants[0]->playlists[0];
+        int64_t duration = 0;
+        for (int i = 0; i < p->n_segments; i++)
+            duration += p->segments[i]->duration;
+        c->ctx->duration = duration;
+    }
+
     return ret;
 }
 
@@ -2184,7 +2195,8 @@ static int hls_read_header(AVFormatContext *s)
 
     /* If this isn't a live stream, calculate the total duration of the
      * stream. */
-    if (c->variants[0]->playlists[0]->finished) {
+    if (c->variants[0]->playlists[0]->finished ||
+        c->variants[0]->playlists[0]->type == PLS_TYPE_EVENT) {
         int64_t duration = 0;
         for (i = 0; i < c->variants[0]->playlists[0]->n_segments; i++)
             duration += c->variants[0]->playlists[0]->segments[i]->duration;
@@ -2549,9 +2561,20 @@ static int hls_read_packet(AVFormatContext *s, AVPacket 
*pkt)
                     }
 
                     if (c->first_timestamp == AV_NOPTS_VALUE &&
-                        pls->pkt->dts       != AV_NOPTS_VALUE)
+                        pls->pkt->dts       != AV_NOPTS_VALUE) {
+                        int64_t seg_idx = pls->cur_seq_no - pls->start_seq_no;
                         c->first_timestamp = av_rescale_q(pls->pkt->dts,
                             get_timebase(pls), AV_TIME_BASE_Q);
+                        for (int64_t k = 0; k < seg_idx && k < 
pls->n_segments; k++)
+                            c->first_timestamp -= pls->segments[k]->duration;
+
+                        for (unsigned k = 0; k < s->nb_streams; k++) {
+                            AVStream *st = s->streams[k];
+                            if (st->start_time == AV_NOPTS_VALUE)
+                                st->start_time = 
av_rescale_q(c->first_timestamp,
+                                    AV_TIME_BASE_Q, st->time_base);
+                        }
+                    }
                 }
 
                 seg = current_segment(pls);
@@ -2717,6 +2740,11 @@ static int hls_read_seek(AVFormatContext *s, int 
stream_index,
     seek_pls->cur_seq_no = seq_no;
     seek_pls->seek_stream_index = stream_subdemuxer_index;
 
+    /* Reset PTS wrap detection so backward seeks don't get misinterpreted
+     * as a forward PTS wrap. */
+    for (i = 0; i < (int)s->nb_streams; i++)
+        ffstream(s->streams[i])->pts_wrap_reference = AV_NOPTS_VALUE;
+
     for (i = 0; i < c->n_playlists; i++) {
         /* Reset reading */
         struct playlist *pls = c->playlists[i];
@@ -2732,8 +2760,11 @@ static int hls_read_seek(AVFormatContext *s, int 
stream_index,
         /* Reset the pos, to let the mpegts/mov demuxer know we've seeked. */
         pb->pos = 0;
         /* Flush the packet queue of the subdemuxer. */
-        if (pls->ctx)
+        if (pls->ctx) {
             ff_read_frame_flush(pls->ctx);
+            for (j = 0; j < (int)pls->ctx->nb_streams; j++)
+                ffstream(pls->ctx->streams[j])->pts_wrap_reference = 
AV_NOPTS_VALUE;
+        }
         if (pls->is_subtitle)
             avformat_close_input(&pls->ctx);
 
-- 
2.52.0


From b309150411c6eadf9fe45b6ea428523c1b282517 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Wed, 25 Mar 2026 00:07:48 +0100
Subject: [PATCH 2/3] MAINTAINERS: add myself as HLS demuxer maintainer
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Kacper Michajłow <[email protected]>
---
 MAINTAINERS | 1 +
 1 file changed, 1 insertion(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index cb63c0cf49..8b33fff215 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -411,6 +411,7 @@ Muxers/Demuxers:
   flvenc.c                              Michael Niedermayer, Steven Liu
   gxf.c                                 Reimar Doeffinger
   gxfenc.c                              Baptiste Coudurier
+  hls.c                                 Kacper Michajłow
   hlsenc.c                              Christian Suloway, Steven Liu
   hxvs.c                                Zhao Zhili
   iamf*                             [2] James Almer
-- 
2.52.0


From d42f5b7be63fdb1d09f4abba237f8a9c62d42615 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Wed, 25 Mar 2026 00:09:44 +0100
Subject: [PATCH 3/3] .forgejo/CODEOWNERS: add myself for hls.*
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Kacper Michajłow <[email protected]>
---
 .forgejo/CODEOWNERS | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.forgejo/CODEOWNERS b/.forgejo/CODEOWNERS
index b898596ccd..cde16021c7 100644
--- a/.forgejo/CODEOWNERS
+++ b/.forgejo/CODEOWNERS
@@ -140,6 +140,7 @@ libavformat/electronicarts.* @pross
 libavformat/.*exif.* @Traneptora
 libavformat/filmstrip.* @pross
 libavformat/frm.* @pross
+libavformat/hls.* @kasper93
 libavformat/hxvs.* @quink
 libavformat/iamf.* @jamrial
 libavformat/icecast.c @ePirat
-- 
2.52.0

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

Reply via email to