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]
