This is an automated email from the git hooks/post-receive script. Git pushed a commit to branch master in repository ffmpeg.
commit e27ad5760c0c8eca7c95bb907dc6e4e62dbf129b Author: Kacper Michajłow <[email protected]> AuthorDate: Wed Jun 17 07:00:42 2026 +0200 Commit: Kacper Michajłow <[email protected]> CommitDate: Tue Jun 23 20:40:28 2026 +0200 avformat/hls: rebase seek discard threshold to each playlist's DTS baseline After a seek, hls_read_packet drops packets until each playlist reaches seek_timestamp. That threshold lives on c->first_timestamp's baseline, i.e. the DTS of whichever stream produced the very first packet. Streams can have different DTS baselines though. So comparing one stream's threshold against another stream's DTS is wrong. A stream whose first keyframe precedes the global baseline gets its segment keyframe dropped and resumes a segment (or more) late. Track the first DTS per playlist and rebase the threshold onto each playlist's own baseline. Since HLS segments are presentation-aligned across renditions, every stream then resumes at the same presentation time. seek_timestamp keeps its meaning on the global timeline, only the per-playlist comparison value is translated. This fixes playlists that would be unable to play from the start, and instead skip one or more segments, before data is output. Signed-off-by: Kacper Michajłow <[email protected]> --- libavformat/hls.c | 46 ++++++++++++++++++++++++++++++++-------------- tests/ref/seek/hls | 20 ++++++++++---------- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/libavformat/hls.c b/libavformat/hls.c index ea58a6d2f6..80a0b3f01c 100644 --- a/libavformat/hls.c +++ b/libavformat/hls.c @@ -159,6 +159,9 @@ struct playlist { HLSAudioSetupInfo audio_setup_info; + /* Constant offset of this playlist's DTS baseline relative to + * c->first_timestamp. */ + int64_t ts_offset; int64_t seek_timestamp; int seek_flags; int seek_stream_index; /* into subdemuxer stream array */ @@ -349,6 +352,7 @@ static struct playlist *new_playlist(HLSContext *c, const char *url, return NULL; } av_strlcpy(pls->url, abs_url, sizeof(pls->url)); + pls->ts_offset = AV_NOPTS_VALUE; pls->seek_timestamp = AV_NOPTS_VALUE; pls->is_id3_timestamped = -1; @@ -2582,7 +2586,8 @@ static int recheck_discard_flags(AVFormatContext *s, int first) pls->pb.pub.eof_reached = 0; if (c->cur_timestamp != AV_NOPTS_VALUE) { /* catch up */ - pls->seek_timestamp = c->cur_timestamp; + pls->seek_timestamp = c->cur_timestamp + + (pls->ts_offset != AV_NOPTS_VALUE ? pls->ts_offset : 0); pls->seek_flags = AVSEEK_FLAG_ANY; pls->seek_stream_index = -1; } @@ -2700,24 +2705,29 @@ static int hls_read_packet(AVFormatContext *s, AVPacket *pkt) fill_timing_for_id3_timestamped_stream(pls); } - if (c->first_timestamp == AV_NOPTS_VALUE && - pls->pkt->dts != AV_NOPTS_VALUE) { + 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; - c->first_timestamp = av_rescale_q(pls->pkt->dts, + 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) { + if (pls->type == PLS_TYPE_EVENT) 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); - } + ts -= pls->segments[k]->duration; + + if (c->first_timestamp == AV_NOPTS_VALUE) { + c->first_timestamp = ts; + + 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; } } @@ -2934,7 +2944,15 @@ static int hls_read_seek(AVFormatContext *s, int stream_index, /* Reset the init segment so it's re-fetched and served appropriately */ pls->cur_init_section = NULL; - pls->seek_timestamp = seek_timestamp; + /* The discard in hls_read_packet compares this playlist's packet DTS + * against seek_timestamp, but seek_timestamp is on c->first_timestamp's + * baseline (whichever stream produced the first packet). Streams can + * have different DTS baselines, so translate the threshold onto this + * playlist's own baseline using its captured offset. */ + if (pls->ts_offset != AV_NOPTS_VALUE) + pls->seek_timestamp = seek_timestamp + pls->ts_offset; + else + pls->seek_timestamp = seek_timestamp; pls->seek_flags = flags; if (pls != seek_pls) { diff --git a/tests/ref/seek/hls b/tests/ref/seek/hls index a9b3badaf8..0f2085916b 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: 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 -ret: 0 st: 0 flags:1 dts: 1.698078 pts: 1.698078 pos: 11092 size: 1253 +ret: 0 st: 1 flags:0 dts: 1.600000 pts: 1.600000 pos: 8836 size: 2330 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,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: 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 @@ -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: 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: 1 flags:0 dts: 4.800000 pts: 5.100000 pos: 20492 size: 3860 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: 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 _______________________________________________ ffmpeg-cvslog mailing list -- [email protected] To unsubscribe send an email to [email protected]
