This is an automated email from the git hooks/post-receive script.
Git pushed a commit to branch master
in repository ffmpeg.
The following commit(s) were added to refs/heads/master by this push:
new e8cfe912f4 avformat/hls: Also report stream-level metadata update.
e8cfe912f4 is described below
commit e8cfe912f48b30077d8df87abeb09d39bf93fe37
Author: Romain Beauxis <[email protected]>
AuthorDate: Sun Apr 26 19:00:30 2026 -0500
Commit: Romain Beauxis <[email protected]>
CommitDate: Wed Jun 17 15:39:33 2026 -0500
avformat/hls: Also report stream-level metadata update.
---
libavformat/hls.c | 61 +++++++++++++++++++++++++++++++++++
tests/ref/fate/ts-timed-id3-hls-demux | 2 +-
2 files changed, 62 insertions(+), 1 deletion(-)
diff --git a/libavformat/hls.c b/libavformat/hls.c
index b03201c690..4c56b954bb 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -174,6 +174,14 @@ struct playlist {
int n_init_sections;
struct segment **init_sections;
int is_subtitle; /* Indicates if it's a subtitle playlist */
+
+ /* Some HLS streams repeat the current timed-ID3 metadata at every segment
+ * boundary so late-joining clients get a fresh copy. Track the last seen
+ * value to avoid raising AVSTREAM_EVENT_FLAG_METADATA_UPDATED spuriously
+ * on those duplicates. timed_id3_stream_index is the subdemuxer stream
+ * index of the first timed-ID3 stream seen; -1 if none. */
+ AVDictionary *timed_id3_metadata;
+ int timed_id3_stream_index;
};
/*
@@ -275,6 +283,7 @@ static void free_playlist_list(HLSContext *c)
av_freep(&pls->renditions);
av_freep(&pls->id3_buf);
av_dict_free(&pls->id3_initial);
+ av_dict_free(&pls->timed_id3_metadata);
ff_id3v2_free_extra_meta(&pls->id3_deferred_extra);
av_freep(&pls->init_sec_buf);
av_packet_free(&pls->pkt);
@@ -335,6 +344,7 @@ static struct playlist *new_playlist(HLSContext *c, const
char *url,
pls->is_id3_timestamped = -1;
pls->id3_mpegts_timestamp = AV_NOPTS_VALUE;
+ pls->timed_id3_stream_index = -1;
dynarray_add(&c->playlists, &c->n_playlists, pls);
return pls;
@@ -2108,6 +2118,16 @@ static int
update_streams_from_subdemuxer(AVFormatContext *s, struct playlist *p
err = set_stream_info_from_input_stream(st, pls, ist);
if (err < 0)
return err;
+
+ if (ist->codecpar->codec_id == AV_CODEC_ID_TIMED_ID3) {
+ if (pls->timed_id3_stream_index < 0)
+ pls->timed_id3_stream_index = ist_idx;
+ else
+ av_log(s, AV_LOG_WARNING,
+ "Playlist has multiple timed ID3 streams; "
+ "only stream %d will be used for metadata
propagation\n",
+ pls->timed_id3_stream_index);
+ }
}
return 0;
@@ -2553,6 +2573,29 @@ static int compare_ts_with_wrapdetect(int64_t ts_a,
struct playlist *pls_a,
return av_compare_mod(scaled_ts_a, scaled_ts_b, 1LL << 33);
}
+/**
+ * Check whether any entry in @p update differs from the corresponding entry
+ * in @p current.
+ *
+ * HLS segments can repeat timed ID3 metadata in every segment so that
+ * listeners joining mid-stream immediately receive the current track
+ * information. Only signal a metadata change when values actually differ
+ * to avoid spurious updates on every segment boundary.
+ */
+static int metadata_has_changed(const AVDictionary *current,
+ const AVDictionary *update)
+{
+ if (av_dict_count(current) != av_dict_count(update))
+ return 1;
+ const AVDictionaryEntry *e = NULL;
+ while ((e = av_dict_iterate(update, e))) {
+ const AVDictionaryEntry *cur = av_dict_get(current, e->key, NULL, 0);
+ if (!cur || strcmp(cur->value, e->value))
+ return 1;
+ }
+ return 0;
+}
+
static int hls_read_packet(AVFormatContext *s, AVPacket *pkt)
{
HLSContext *c = s->priv_data;
@@ -2694,6 +2737,24 @@ static int hls_read_packet(AVFormatContext *s, AVPacket
*pkt)
ist = pls->ctx->streams[pls->pkt->stream_index];
st = pls->main_streams[pls->pkt->stream_index];
+ // Propagate timed-ID3 metadata changes to the main stream, suppressing
+ // duplicate packets that repeat unchanged metadata at segment
boundaries.
+ if (ist->codecpar->codec_id == AV_CODEC_ID_TIMED_ID3 &&
+ pls->pkt->stream_index == pls->timed_id3_stream_index &&
+ ist->event_flags & AVSTREAM_EVENT_FLAG_METADATA_UPDATED) {
+ ist->event_flags &= ~AVSTREAM_EVENT_FLAG_METADATA_UPDATED;
+ if (metadata_has_changed(pls->timed_id3_metadata, ist->metadata)) {
+ int ret = av_dict_copy(&st->metadata, ist->metadata, 0);
+ if (ret < 0)
+ return ret;
+ av_dict_free(&pls->timed_id3_metadata);
+ ret = av_dict_copy(&pls->timed_id3_metadata, ist->metadata, 0);
+ if (ret < 0)
+ return ret;
+ st->event_flags |= AVSTREAM_EVENT_FLAG_METADATA_UPDATED;
+ }
+ }
+
av_packet_move_ref(pkt, pls->pkt);
pkt->stream_index = st->index;
diff --git a/tests/ref/fate/ts-timed-id3-hls-demux
b/tests/ref/fate/ts-timed-id3-hls-demux
index c0da932512..ea238b0738 100644
--- a/tests/ref/fate/ts-timed-id3-hls-demux
+++ b/tests/ref/fate/ts-timed-id3-hls-demux
@@ -1,4 +1,4 @@
packet|codec_type=data|stream_index=0|pts=126000|pts_time=1.400000|dts=126000|dts_time=1.400000|duration=N/A|duration_time=N/A|size=26|pos=564|flags=K__|data_hash=CRC32:469f474b|side_datum/mpegts_stream_id:side_data_type=MPEGTS
Stream ID|side_datum/mpegts_stream_id:id=189
packet|codec_type=data|stream_index=0|pts=577350|pts_time=6.415000|dts=577350|dts_time=6.415000|duration=N/A|duration_time=N/A|size=26|pos=1316|flags=K__|data_hash=CRC32:469f474b|side_datum/mpegts_stream_id:side_data_type=MPEGTS
Stream ID|side_datum/mpegts_stream_id:id=189
-stream|index=0|codec_name=timed_id3|profile=unknown|codec_type=data|codec_tag_string=ID3
|codec_tag=0x20334449|id=0x0|r_frame_rate=0/0|avg_frame_rate=0/0|time_base=1/90000|start_pts=126000|start_time=1.400000|duration_ts=N/A|duration=N/A|bit_rate=N/A|max_bit_rate=N/A|bits_per_raw_sample=N/A|nb_frames=N/A|nb_read_frames=N/A|nb_read_packets=2|disposition:default=0|disposition:dub=0|disposition:original=0|disposition:comment=0|disposition:lyrics=0|disposition:karaoke=0|disposition:forced=0|
[...]
+stream|index=0|codec_name=timed_id3|profile=unknown|codec_type=data|codec_tag_string=ID3
|codec_tag=0x20334449|id=0x0|r_frame_rate=0/0|avg_frame_rate=0/0|time_base=1/90000|start_pts=126000|start_time=1.400000|duration_ts=N/A|duration=N/A|bit_rate=N/A|max_bit_rate=N/A|bits_per_raw_sample=N/A|nb_frames=N/A|nb_read_frames=N/A|nb_read_packets=2|disposition:default=0|disposition:dub=0|disposition:original=0|disposition:comment=0|disposition:lyrics=0|disposition:karaoke=0|disposition:forced=0|
[...]
format|filename=id3.m3u8|nb_streams=1|nb_programs=1|nb_stream_groups=0|format_name=hls|start_time=1.400000|duration=2.000000|size=65|bit_rate=260|probe_score=100
_______________________________________________
ffmpeg-cvslog mailing list -- [email protected]
To unsubscribe send an email to [email protected]