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

Allow seeking in live playlists, some servers provide deep time shift that we 
can use, even if the segments eventually will be evicted. Previously it was 
assumed that live playlist starts at current `live_start_index`, by default 3 
segments from the end of playlist and that was all, disallowing all possibly 
even hours of segments before that.




From 974fec824a49ff402283a4e2e30a6ad2acf5fc5d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Wed, 8 Jul 2026 03:32:54 +0200
Subject: [PATCH 1/5] avformat/hls: remove else after break

To reduce unnecessary nesting.
---
 libavformat/hls.c | 56 +++++++++++++++++++++++------------------------
 1 file changed, 28 insertions(+), 28 deletions(-)

diff --git a/libavformat/hls.c b/libavformat/hls.c
index e76e68ed51..9ecf12ac87 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -2713,38 +2713,38 @@ static int hls_read_packet(AVFormatContext *s, AVPacket 
*pkt)
                     if (!avio_feof(&pls->pb.pub) && ret != AVERROR_EOF)
                         return ret;
                     break;
-                } else {
-                    /* stream_index check prevents matching picture 
attachments etc. */
-                    if (pls->is_id3_timestamped && pls->pkt->stream_index == 
0) {
-                        /* audio elementary streams are id3 timestamped */
-                        fill_timing_for_id3_timestamped_stream(pls);
-                    }
+                }
 
-                    if (pls->ts_offset == AV_NOPTS_VALUE &&
-                        pls->pkt->dts    != AV_NOPTS_VALUE) {
-                        int64_t seg_idx = pls->cur_seq_no - pls->start_seq_no;
-                        int64_t ts = av_rescale_q(pls->pkt->dts,
-                            get_timebase(pls), AV_TIME_BASE_Q);
+                /* stream_index check prevents matching picture attachments 
etc. */
+                if (pls->is_id3_timestamped && pls->pkt->stream_index == 0) {
+                    /* audio elementary streams are id3 timestamped */
+                    fill_timing_for_id3_timestamped_stream(pls);
+                }
+
+                if (pls->ts_offset == AV_NOPTS_VALUE &&
+                    pls->pkt->dts    != AV_NOPTS_VALUE) {
+                    int64_t seg_idx = pls->cur_seq_no - pls->start_seq_no;
+                    int64_t ts = av_rescale_q(pls->pkt->dts,
+                        get_timebase(pls), AV_TIME_BASE_Q);
+
+                    /* EVENT playlists preserve all segments from the start */
+                    if (pls->type == PLS_TYPE_EVENT)
+                        for (int64_t k = 0; k < seg_idx && k < 
pls->n_segments; k++)
+                            ts -= pls->segments[k]->duration;
+
+                    if (c->first_timestamp == AV_NOPTS_VALUE) {
+                        c->first_timestamp = ts;
+                        c->first_timestamp_pls = pls;
 
-                        /* EVENT playlists preserve all segments from the 
start */
                         if (pls->type == PLS_TYPE_EVENT)
-                            for (int64_t k = 0; k < seg_idx && k < 
pls->n_segments; k++)
-                                ts -= pls->segments[k]->duration;
-
-                        if (c->first_timestamp == AV_NOPTS_VALUE) {
-                            c->first_timestamp = ts;
-                            c->first_timestamp_pls = pls;
-
-                            if (pls->type == PLS_TYPE_EVENT)
-                                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(ts,
-                                            AV_TIME_BASE_Q, st->time_base);
-                                }
-                        }
-                        pls->ts_offset = ts - c->first_timestamp;
+                            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(ts,
+                                        AV_TIME_BASE_Q, st->time_base);
+                            }
                     }
+                    pls->ts_offset = ts - c->first_timestamp;
                 }
 
                 seg = current_segment(pls);
-- 
2.52.0


From 32a05eb921b7c4de2f0c9ac24a0b3ae8add4cd19 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Wed, 8 Jul 2026 03:40:57 +0200
Subject: [PATCH 2/5] avformat/hls: use av_rescale_q instead of doing only
 denominator

---
 libavformat/hls.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/libavformat/hls.c b/libavformat/hls.c
index 9ecf12ac87..09eef2400a 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -2767,8 +2767,8 @@ static int hls_read_packet(AVFormatContext *s, AVPacket 
*pkt)
                     }
 
                     tb = get_timebase(pls);
-                    ts_diff = av_rescale_rnd(pls->pkt->dts, AV_TIME_BASE,
-                                            tb.den, AV_ROUND_DOWN) -
+                    ts_diff = av_rescale_q_rnd(pls->pkt->dts, tb,
+                                               AV_TIME_BASE_Q, AV_ROUND_DOWN) -
                             pls->seek_timestamp;
                     if (ts_diff >= 0 && (pls->seek_flags  & AVSEEK_FLAG_ANY ||
                                         pls->pkt->flags & AV_PKT_FLAG_KEY)) {
@@ -2891,9 +2891,9 @@ static int hls_read_seek(AVFormatContext *s, int 
stream_index,
     first_timestamp = c->first_timestamp == AV_NOPTS_VALUE ?
                       0 : c->first_timestamp;
 
-    seek_timestamp = av_rescale_rnd(timestamp, AV_TIME_BASE,
-                                    s->streams[stream_index]->time_base.den,
-                                    AV_ROUND_DOWN);
+    seek_timestamp = av_rescale_q_rnd(timestamp,
+                                      s->streams[stream_index]->time_base,
+                                      AV_TIME_BASE_Q, AV_ROUND_DOWN);
 
     duration = s->duration == AV_NOPTS_VALUE ?
                0 : s->duration;
-- 
2.52.0


From 7406c164095377498d4d936b280ca6f8fa7cc69a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Wed, 8 Jul 2026 03:51:44 +0200
Subject: [PATCH 3/5] avformat/hls: allow seeking within live playlists
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Live (and EVENT) playlists are seekable within the currently known
segment list. Seeks before the list start clamp to the oldest segment,
past the end to the live edge. Live playlist can evict old segments, we
track that by reading EXT-X-MEDIA-SEQUENCE. Previusly the code assumed
that the live playlist, don't have past segments, however some servers
has very deep usable timeshift.

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

diff --git a/libavformat/hls.c b/libavformat/hls.c
index 09eef2400a..4dbcba6fe8 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -1154,8 +1154,7 @@ fail:
         ff_format_io_close(c->ctx, &in);
     c->ctx->ctx_flags = c->ctx->ctx_flags & ~(unsigned)AVFMTCTX_UNSEEKABLE;
     if (!c->n_variants || !c->variants[0]->n_playlists ||
-        !(c->variants[0]->playlists[0]->finished ||
-          c->variants[0]->playlists[0]->type == PLS_TYPE_EVENT))
+        !c->variants[0]->playlists[0]->n_segments)
         c->ctx->ctx_flags |= AVFMTCTX_UNSEEKABLE;
 
     if (c->n_variants && c->variants[0]->n_playlists &&
@@ -2058,6 +2057,12 @@ static int find_timestamp_in_playlist(HLSContext *c, 
struct playlist *pls,
 
     *seq_no = pls->start_seq_no + pls->n_segments - 1;
 
+    if (!pls->finished && pls->n_segments > 0) {
+        if (seg_start_ts)
+            *seg_start_ts = pos - pls->segments[pls->n_segments - 1]->duration;
+        return 1;
+    }
+
     return 0;
 }
 
@@ -2895,12 +2900,6 @@ static int hls_read_seek(AVFormatContext *s, int 
stream_index,
                                       s->streams[stream_index]->time_base,
                                       AV_TIME_BASE_Q, AV_ROUND_DOWN);
 
-    duration = s->duration == AV_NOPTS_VALUE ?
-               0 : s->duration;
-
-    if (0 < duration && duration < seek_timestamp - first_timestamp)
-        return AVERROR(EIO);
-
     /* find the playlist with the specified stream */
     for (i = 0; i < c->n_playlists; i++) {
         struct playlist *pls = c->playlists[i];
@@ -2912,9 +2911,20 @@ static int hls_read_seek(AVFormatContext *s, int 
stream_index,
             }
         }
     }
+    if (!seek_pls)
+        return AVERROR(EIO);
+
+    duration = s->duration == AV_NOPTS_VALUE ?
+               0 : s->duration;
+
+    /* Only finished playlists cannot grow past the known duration. */
+    if (seek_pls->finished &&
+        0 < duration && duration < seek_timestamp - first_timestamp)
+        return AVERROR(EIO);
+
     /* check if the timestamp is valid for the playlist with the
      * specified stream index */
-    if (!seek_pls || !find_timestamp_in_playlist(c, seek_pls, seek_timestamp, 
&seq_no, &seg_start_ts))
+    if (!find_timestamp_in_playlist(c, seek_pls, seek_timestamp, &seq_no, 
&seg_start_ts))
         return AVERROR(EIO);
 
     if (s->streams[stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO &&
@@ -2924,6 +2934,10 @@ static int hls_read_seek(AVFormatContext *s, int 
stream_index,
         seek_timestamp = seg_start_ts;
     }
 
+    av_log(s, AV_LOG_DEBUG, "Seek to %"PRId64" mapped to segment %"PRId64
+           " of playlist %d (start %"PRId64")\n",
+           seek_timestamp, seq_no, seek_pls->index, seg_start_ts);
+
     /* set segment now so we do not need to search again below */
     seek_pls->cur_seq_no = seq_no;
     seek_pls->seek_stream_index = stream_subdemuxer_index;
-- 
2.52.0


From 5d4fd49492deb6957b21f3160f2aa7986cec6cb1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Wed, 8 Jul 2026 03:54:20 +0200
Subject: [PATCH 4/5] avformat/hls: set stream start_time for all playlist
 types
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Rebase the first packet's timestamp onto the start of the segment list
regardless of playlist type, and report it as start_time. Previously
only EVENT playlists did this and live streams took start_time from the
first loaded segment at the live edge. Which is not correct as some
servers provide very deep (hours even) time shift in live playlist. The
evicted segments are already tracked by EXT-X-MEDIA-SEQUENCE. This
allows us to use all available segments, not only the future ones on
live playlist.

Also prefer PTS over DTS, as EXTINF durations and start_time are
presentation time.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 libavformat/hls.c     | 34 +++++++++++++++++++++-------------
 tests/fate/hlsenc.mak |  2 +-
 tests/ref/seek/hls    | 20 ++++++++++----------
 3 files changed, 32 insertions(+), 24 deletions(-)

diff --git a/libavformat/hls.c b/libavformat/hls.c
index 4dbcba6fe8..4c22294797 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -2193,6 +2193,7 @@ static int set_stream_info_from_input_stream(AVStream 
*st, struct playlist *pls,
 /* add new subdemuxer streams to our context, if any */
 static int update_streams_from_subdemuxer(AVFormatContext *s, struct playlist 
*pls)
 {
+    HLSContext *c = s->priv_data;
     int err;
 
     while (pls->n_main_streams < pls->ctx->nb_streams) {
@@ -2212,6 +2213,11 @@ static int 
update_streams_from_subdemuxer(AVFormatContext *s, struct playlist *p
         if (err < 0)
             return err;
 
+        /* Match the start_time of streams created before playback began. */
+        if (c->first_timestamp != AV_NOPTS_VALUE)
+            st->start_time = av_rescale_q(c->first_timestamp,
+                                          AV_TIME_BASE_Q, st->time_base);
+
         if (ist->codecpar->codec_id == AV_CODEC_ID_TIMED_ID3) {
             if (pls->timed_id3_stream_index < 0)
                 pls->timed_id3_stream_index = ist_idx;
@@ -2728,26 +2734,28 @@ static int hls_read_packet(AVFormatContext *s, AVPacket 
*pkt)
 
                 if (pls->ts_offset == AV_NOPTS_VALUE &&
                     pls->pkt->dts    != AV_NOPTS_VALUE) {
+                    /* Packet timestamp rebased onto the start of the segment 
list. */
                     int64_t seg_idx = pls->cur_seq_no - pls->start_seq_no;
-                    int64_t ts = av_rescale_q(pls->pkt->dts,
-                        get_timebase(pls), AV_TIME_BASE_Q);
+                    int64_t ts = av_rescale_q(pls->pkt->pts != AV_NOPTS_VALUE ?
+                                              pls->pkt->pts : pls->pkt->dts,
+                                              get_timebase(pls), 
AV_TIME_BASE_Q);
 
-                    /* EVENT playlists preserve all segments from the start */
-                    if (pls->type == PLS_TYPE_EVENT)
-                        for (int64_t k = 0; k < seg_idx && k < 
pls->n_segments; k++)
-                            ts -= pls->segments[k]->duration;
+                    for (int64_t k = 0; k < seg_idx && k < pls->n_segments; 
k++)
+                        ts -= pls->segments[k]->duration;
 
                     if (c->first_timestamp == AV_NOPTS_VALUE) {
                         c->first_timestamp = ts;
                         c->first_timestamp_pls = pls;
 
-                        if (pls->type == PLS_TYPE_EVENT)
-                            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(ts,
-                                        AV_TIME_BASE_Q, st->time_base);
-                            }
+                        /* start_time is the start of the segment list, the
+                         * origin of the timeline used for seeking. */
+                        for (unsigned k = 0; k < s->nb_streams; k++)
+                            s->streams[k]->start_time =
+                                av_rescale_q(ts, AV_TIME_BASE_Q,
+                                             s->streams[k]->time_base);
+                        av_log(s, AV_LOG_DEBUG,
+                               "First timestamp %"PRId64" from playlist %d\n",
+                               ts, pls->index);
                     }
                     pls->ts_offset = ts - c->first_timestamp;
                 }
diff --git a/tests/fate/hlsenc.mak b/tests/fate/hlsenc.mak
index 1ddb927cae..f60910cbdf 100644
--- a/tests/fate/hlsenc.mak
+++ b/tests/fate/hlsenc.mak
@@ -9,7 +9,7 @@ fate-hls-live-no-endlist: tests/data/live_no_endlist.m3u8
 fate-hls-live-no-endlist: SRC = $(TARGET_PATH)/tests/data/live_no_endlist.m3u8
 fate-hls-live-no-endlist: CMD = md5 -i $(SRC) -af hdcd=process_stereo=false -t 
6 -f s24le
 fate-hls-live-no-endlist: CMP = oneline
-fate-hls-live-no-endlist: REF = e038bb8e65d4c1745b9b3ed643e607a3
+fate-hls-live-no-endlist: REF = d41d8cd98f00b204e9800998ecf8427e
 
 tests/data/event_no_endlist.m3u8: ffmpeg$(PROGSSUF)$(EXESUF) | tests/data
        $(M)$(TARGET_EXEC) $(TARGET_PATH)/$< -nostdin \
diff --git a/tests/ref/seek/hls b/tests/ref/seek/hls
index 0f2085916b..a9b3badaf8 100644
--- a/tests/ref/seek/hls
+++ b/tests/ref/seek/hls
@@ -2,19 +2,19 @@ ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos: 
   564 size:  3913
 ret: 0         st:-1 flags:0  ts:-1.000000
 ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
 ret: 0         st:-1 flags:1  ts:-0.105833
-ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
+ret: 0         st: 0 flags:1 dts: 1.515211 pts: 1.515211 pos:     -1 size:  
1254
 ret: 0         st: 0 flags:0  ts: 0.788333
 ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
 ret: 0         st: 0 flags:1  ts: 1.682500
-ret: 0         st: 1 flags:0 dts: 1.600000 pts: 1.600000 pos:   8836 size:  
2330
+ret: 0         st: 0 flags:1 dts: 1.698078 pts: 1.698078 pos:  11092 size:  
1253
 ret: 0         st: 1 flags:0  ts: 2.576667
 ret: 0         st: 0 flags:1 dts: 2.586233 pts: 2.586233 pos:  55836 size:  
1254
 ret: 0         st: 1 flags:1  ts: 3.470833
-ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
+ret: 0         st: 0 flags:1 dts: 1.515211 pts: 1.515211 pos:     -1 size:  
1254
 ret: 0         st:-1 flags:0  ts: 4.365002
 ret: 0         st: 0 flags:1 dts: 4.388689 pts: 4.388689 pos:  45308 size:  
1254
 ret: 0         st:-1 flags:1  ts:-0.740831
-ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
+ret: 0         st: 0 flags:1 dts: 1.515211 pts: 1.515211 pos:     -1 size:  
1254
 ret: 0         st: 0 flags:0  ts: 0.153333
 ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
 ret: 0         st: 0 flags:1  ts: 1.047500
@@ -22,7 +22,7 @@ ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos: 
   564 size:  3913
 ret: 0         st: 1 flags:0  ts: 1.941667
 ret: 0         st: 0 flags:1 dts: 1.959300 pts: 1.959300 pos:  24252 size:  
1254
 ret: 0         st: 1 flags:1  ts: 2.835833
-ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
+ret: 0         st: 0 flags:1 dts: 1.515211 pts: 1.515211 pos:     -1 size:  
1254
 ret: 0         st:-1 flags:0  ts: 3.730004
 ret: 0         st: 0 flags:1 dts: 3.735622 pts: 3.735622 pos:     -1 size:  
1254
 ret: 0         st:-1 flags:1  ts: 4.624171
@@ -34,20 +34,20 @@ ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 
pos:    564 size:  3913
 ret: 0         st: 1 flags:0  ts: 1.306667
 ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
 ret: 0         st: 1 flags:1  ts: 2.200844
-ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
+ret: 0         st: 0 flags:1 dts: 1.515211 pts: 1.515211 pos:     -1 size:  
1254
 ret: 0         st:-1 flags:0  ts: 3.095006
 ret: 0         st: 0 flags:1 dts: 3.108689 pts: 3.108689 pos:  82156 size:  
1254
 ret: 0         st:-1 flags:1  ts: 3.989173
-ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
+ret: 0         st: 0 flags:1 dts: 1.515211 pts: 1.515211 pos:     -1 size:  
1254
 ret: 0         st: 0 flags:0  ts: 4.883344
-ret: 0         st: 1 flags:0 dts: 4.800000 pts: 5.100000 pos:  20492 size:  
3860
+ret: 0         st: 0 flags:1 dts: 4.885011 pts: 4.885011 pos:     -1 size:  
1253
 ret: 0         st: 0 flags:1  ts:-0.222489
 ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
 ret: 0         st: 1 flags:0  ts: 0.671678
 ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
 ret: 0         st: 1 flags:1  ts: 1.565844
-ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
+ret: 0         st: 0 flags:1 dts: 1.515211 pts: 1.515211 pos:     -1 size:  
1254
 ret: 0         st:-1 flags:0  ts: 2.460008
 ret: 0         st: 0 flags:1 dts: 2.481744 pts: 2.481744 pos:  50572 size:  
1254
 ret: 0         st:-1 flags:1  ts: 3.354175
-ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
+ret: 0         st: 0 flags:1 dts: 1.515211 pts: 1.515211 pos:     -1 size:  
1254
-- 
2.52.0


From 51bcaf204d77fc46ef6c57b4a02fb20968efbd9b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]>
Date: Wed, 8 Jul 2026 03:56:24 +0200
Subject: [PATCH 5/5] avformat/hls: don't discard target segment's first
 keyframe when seeking
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Segment starts on the seek timeline are derived from EXTINF durations,
so the first keyframe of the target segment can have a slightly lower
DTS than the snapped seek position, in which case the packet discard
loop would skip it and resume playback one segment late. Make sure we
start at the very beginning of the stream in this case.

Signed-off-by: Kacper Michajłow <[email protected]>
---
 libavformat/hls.c  | 11 +++++++++++
 tests/ref/seek/hls | 18 +++++++++---------
 2 files changed, 20 insertions(+), 9 deletions(-)

diff --git a/libavformat/hls.c b/libavformat/hls.c
index 4c22294797..b0e6c9687b 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -2897,6 +2897,7 @@ static int hls_read_seek(AVFormatContext *s, int 
stream_index,
     int stream_subdemuxer_index;
     int64_t first_timestamp, seek_timestamp, duration;
     int64_t seq_no, seg_start_ts;
+    int snapped_to_segment = 0;
 
     if ((flags & AVSEEK_FLAG_BYTE) || (c->ctx->ctx_flags & 
AVFMTCTX_UNSEEKABLE))
         return AVERROR(ENOSYS);
@@ -2940,6 +2941,7 @@ static int hls_read_seek(AVFormatContext *s, int 
stream_index,
         /* Seeking to start of segment ensures we seek to a keyframe located
          * before the given timestamp. */
         seek_timestamp = seg_start_ts;
+        snapped_to_segment = 1;
     }
 
     av_log(s, AV_LOG_DEBUG, "Seek to %"PRId64" mapped to segment %"PRId64
@@ -2993,6 +2995,15 @@ static int hls_read_seek(AVFormatContext *s, int 
stream_index,
             pls->seek_timestamp = seek_timestamp;
         pls->seek_flags = flags;
 
+        if (pls == seek_pls && snapped_to_segment) {
+            /* The first keyframe of the target segment may have a slightly
+             * lower DTS than the EXTINF-derived seg_start_ts. Relax the cutoff
+             * to not discard the keyframe and land one segment late. */
+            int64_t idx = seq_no - pls->start_seq_no;
+            if (idx >= 0 && idx < pls->n_segments)
+                pls->seek_timestamp -= pls->segments[idx]->duration;
+        }
+
         if (pls != seek_pls) {
             /* set closest segment seq_no for playlists not handled above */
             find_timestamp_in_playlist(c, pls, seek_timestamp, 
&pls->cur_seq_no, NULL);
diff --git a/tests/ref/seek/hls b/tests/ref/seek/hls
index a9b3badaf8..7f3025ef70 100644
--- a/tests/ref/seek/hls
+++ b/tests/ref/seek/hls
@@ -2,7 +2,7 @@ ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:   
 564 size:  3913
 ret: 0         st:-1 flags:0  ts:-1.000000
 ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
 ret: 0         st:-1 flags:1  ts:-0.105833
-ret: 0         st: 0 flags:1 dts: 1.515211 pts: 1.515211 pos:     -1 size:  
1254
+ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
 ret: 0         st: 0 flags:0  ts: 0.788333
 ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
 ret: 0         st: 0 flags:1  ts: 1.682500
@@ -10,11 +10,11 @@ ret: 0         st: 0 flags:1 dts: 1.698078 pts: 1.698078 
pos:  11092 size:  1253
 ret: 0         st: 1 flags:0  ts: 2.576667
 ret: 0         st: 0 flags:1 dts: 2.586233 pts: 2.586233 pos:  55836 size:  
1254
 ret: 0         st: 1 flags:1  ts: 3.470833
-ret: 0         st: 0 flags:1 dts: 1.515211 pts: 1.515211 pos:     -1 size:  
1254
+ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
 ret: 0         st:-1 flags:0  ts: 4.365002
 ret: 0         st: 0 flags:1 dts: 4.388689 pts: 4.388689 pos:  45308 size:  
1254
 ret: 0         st:-1 flags:1  ts:-0.740831
-ret: 0         st: 0 flags:1 dts: 1.515211 pts: 1.515211 pos:     -1 size:  
1254
+ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
 ret: 0         st: 0 flags:0  ts: 0.153333
 ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
 ret: 0         st: 0 flags:1  ts: 1.047500
@@ -22,11 +22,11 @@ ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 
pos:    564 size:  3913
 ret: 0         st: 1 flags:0  ts: 1.941667
 ret: 0         st: 0 flags:1 dts: 1.959300 pts: 1.959300 pos:  24252 size:  
1254
 ret: 0         st: 1 flags:1  ts: 2.835833
-ret: 0         st: 0 flags:1 dts: 1.515211 pts: 1.515211 pos:     -1 size:  
1254
+ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
 ret: 0         st:-1 flags:0  ts: 3.730004
 ret: 0         st: 0 flags:1 dts: 3.735622 pts: 3.735622 pos:     -1 size:  
1254
 ret: 0         st:-1 flags:1  ts: 4.624171
-ret: 0         st: 0 flags:1 dts: 4.493178 pts: 4.493178 pos:  50572 size:  
1254
+ret: 0         st: 1 flags:1 dts: 4.200000 pts: 4.500000 pos:    564 size:  
5196
 ret: 0         st: 0 flags:0  ts:-0.481667
 ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
 ret: 0         st: 0 flags:1  ts: 0.412500
@@ -34,11 +34,11 @@ ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 
pos:    564 size:  3913
 ret: 0         st: 1 flags:0  ts: 1.306667
 ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
 ret: 0         st: 1 flags:1  ts: 2.200844
-ret: 0         st: 0 flags:1 dts: 1.515211 pts: 1.515211 pos:     -1 size:  
1254
+ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
 ret: 0         st:-1 flags:0  ts: 3.095006
 ret: 0         st: 0 flags:1 dts: 3.108689 pts: 3.108689 pos:  82156 size:  
1254
 ret: 0         st:-1 flags:1  ts: 3.989173
-ret: 0         st: 0 flags:1 dts: 1.515211 pts: 1.515211 pos:     -1 size:  
1254
+ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
 ret: 0         st: 0 flags:0  ts: 4.883344
 ret: 0         st: 0 flags:1 dts: 4.885011 pts: 4.885011 pos:     -1 size:  
1253
 ret: 0         st: 0 flags:1  ts:-0.222489
@@ -46,8 +46,8 @@ ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos: 
   564 size:  3913
 ret: 0         st: 1 flags:0  ts: 0.671678
 ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
 ret: 0         st: 1 flags:1  ts: 1.565844
-ret: 0         st: 0 flags:1 dts: 1.515211 pts: 1.515211 pos:     -1 size:  
1254
+ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
 ret: 0         st:-1 flags:0  ts: 2.460008
 ret: 0         st: 0 flags:1 dts: 2.481744 pts: 2.481744 pos:  50572 size:  
1254
 ret: 0         st:-1 flags:1  ts: 3.354175
-ret: 0         st: 0 flags:1 dts: 1.515211 pts: 1.515211 pos:     -1 size:  
1254
+ret: 0         st: 1 flags:1 dts: 1.400000 pts: 1.500000 pos:    564 size:  
3913
-- 
2.52.0

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

Reply via email to