[vlc-commits] [Git][videolan/vlc][master] 8 commits: sout: hls: extract main manifest update in a function

François Cartegnie (@fcartegnie) gitlab at videolan.org
Tue Sep 1 12:13:37 UTC 2026



François Cartegnie pushed to branch master at VideoLAN / VLC


Commits:
1c1de99b by Alaric Senat at 2026-09-01T13:24:43+02:00
sout: hls: extract main manifest update in a function

No functional changes.

- - - - -
dba03293 by Alaric Senat at 2026-09-01T13:24:43+02:00
sout: hls: drain muxer before segment extraction

Generally, feleting the muxer causes it to drain. Doing it in the
playlist deletion method is too late: the last segment extraction
is already passed and data was lost.

- - - - -
f17360a0 by Alaric Senat at 2026-09-01T13:24:43+02:00
sout: hls: skip ended playlists in loops

Ended playlists should not be taken into account in the global manifest
or in segmentation. For now this was mostly fine because ended playlists
would get immediately discarded, but a subsequent rework will delay
their deletion to avoid kicking clients out of the stream immediately at
EOS.

- - - - -
07f60de3 by Alaric Senat at 2026-09-01T13:24:43+02:00
sout: hls: keep playlists alive after Del()

Deleting playlists directly at ES removal breaks live EOF for most
clients. Playlists should remain alive until a late client has gone
through all the remaining segments.

The pruning is done in SetPCR(), which makes polling the playlist
deletion simple, it should be enough for the common usecase.

- - - - -
231141ce by Alaric Senat at 2026-09-01T13:24:43+02:00
sout: hls: drain all the queued segments

Doing a single segment extraction pass is not enough, muxers can unload
more than a single segment when they drain.

- - - - -
56e1a517 by Alaric Senat at 2026-09-01T13:24:43+02:00
sout: hls: skip last segment extraction algorithm

The segment extraction is here to make sure the next segments are
IFrame padded and aligned to demuxing headers. This is not needed while
draining, the current segment being the last one.

- - - - -
b8b04269 by Alaric Senat at 2026-09-01T13:24:43+02:00
mux: mp4: split fragmentation function

Fragmentation of a single dequeue block will be used for draining
purposes.

No functional changes.

- - - - -
5fa872b7 by Alaric Senat at 2026-09-01T13:24:43+02:00
mux: fmp4: drain the input fifo

In the case of fragmented MP4, the input fifo was not drained, leading
to data loss up to the fragment length. On small samples, this is
clearly visible and cause issues with chromecast playback via the HLS
stream output.

The previous implementation was probably leaving out draining for FMP4
to keep the fragment to cut too early in the case of a Del called in the
middle of a streaming sessions with multiple tracks.
`MuxFragBlock` calling `WriteFragments` won't cut a new fragment until
all tracks go past the fragment duration, so this is not a concern.

- - - - -


2 changed files:

- modules/mux/mp4/mp4.c
- modules/stream_out/hls/hls.c


Changes:

=====================================
modules/mux/mp4/mp4.c
=====================================
@@ -154,6 +154,8 @@ typedef struct
     uint32_t         i_indexentries;
 } mp4_stream_t;
 
+static int MuxFragBlock(sout_mux_t *, mp4_stream_t *, block_t *);
+
 typedef struct
 {
     mp4mux_handle_t *muxh;
@@ -568,19 +570,24 @@ static void DelStream(sout_mux_t *p_mux, sout_input_t *p_input)
     sout_mux_sys_t *p_sys = p_mux->p_sys;
     mp4_stream_t *p_stream = (mp4_stream_t*)p_input->p_sys;
 
-    if(!mp4mux_Is(p_sys->muxh, FRAGMENTED))
+    vlc_fifo_Lock( p_input->p_fifo );
+    block_t *p_data = vlc_fifo_DequeueAllUnlocked( p_input->p_fifo );
+    vlc_fifo_Unlock( p_input->p_fifo );
+
+    const bool fragmented = mp4mux_Is(p_sys->muxh, FRAGMENTED);
+    while (p_data != NULL)
     {
-        vlc_fifo_Lock( p_input->p_fifo );
-        block_t *p_data = vlc_fifo_DequeueAllUnlocked( p_input->p_fifo );
-        vlc_fifo_Unlock( p_input->p_fifo );
-        while( p_data != NULL )
-        {
-              block_t *p_next = p_data->p_next;
-              p_data->p_next = NULL;
-              MuxStream(p_mux, p_input, p_stream, p_data);
-              p_data = p_next;
-        }
+        block_t *p_next = p_data->p_next;
+        p_data->p_next = NULL;
+        if (fragmented)
+            MuxFragBlock(p_mux, p_stream, p_data);
+        else
+            MuxStream(p_mux, p_input, p_stream, p_data);
+        p_data = p_next;
+    }
 
+    if (!fragmented)
+    {
         if(CreateCurrentEdit(p_stream, p_sys->i_start_dts, false))
             mp4mux_track_DebugEdits(VLC_OBJECT(p_mux), p_stream->tinfo);
     }
@@ -1505,8 +1512,6 @@ static void CloseFrag(vlc_object_t *p_this)
 
 static int MuxFrag(sout_mux_t *p_mux)
 {
-    sout_mux_sys_t *p_sys = (sout_mux_sys_t*) p_mux->p_sys;
-
     int i_stream = sout_MuxGetStream(p_mux, 1, NULL);
     if (i_stream < 0)
         return VLC_SUCCESS;
@@ -1518,6 +1523,15 @@ static int MuxFrag(sout_mux_t *p_mux)
     if(unlikely(!p_currentblock))
         return VLC_EGENERIC;
 
+    return MuxFragBlock(p_mux, p_stream, p_currentblock);
+}
+
+/* Mux one already-dequeued frame of p_stream in fragmented mode. */
+static int MuxFragBlock(sout_mux_t *p_mux, mp4_stream_t *p_stream,
+                        block_t *p_currentblock)
+{
+    sout_mux_sys_t *p_sys = (sout_mux_sys_t*) p_mux->p_sys;
+
     int ret = BlockConvert(p_stream, &p_currentblock);
     if (ret != VLC_SUCCESS)
         return ret;


=====================================
modules/stream_out/hls/hls.c
=====================================
@@ -108,6 +108,8 @@ typedef struct hls_playlist
     block_t *init_buff;
 
     bool ended;
+    /** Wall-clock time the playlist was finalized. */
+    vlc_tick_t ended_at;
 
     /**
      * Total duration of the muxed data.
@@ -180,6 +182,15 @@ typedef struct
             (i_##it == 0 ? &sys->variant_playlists : &sys->media_playlists),   \
             node)
 
+#define hls_active_playlists_foreach(it)                                       \
+    hls_playlists_foreach (it)                                                 \
+        if ((it)->ended) continue; else
+
+#define hls_active_playlist_foreach(it, list)                                  \
+    vlc_list_foreach_const (it, list, node)                                    \
+        if ((it)->ended) continue; else
+
+
 static int HTTPCallback(httpd_callback_sys_t *sys,
                         httpd_client_t *client,
                         httpd_message_t *answer,
@@ -261,7 +272,7 @@ VLC_MALLOC static char *GeneratePlaylistCodecInfo(const struct vlc_list *media_l
 
     /* Describe codecs from all the EXT-X-MEDIA tracks. */
     const hls_playlist_t *media;
-    vlc_list_foreach_const (media, media_list, node)
+    hls_active_playlist_foreach (media, media_list)
     {
         track = MediaGetTrack(media);
 
@@ -295,7 +306,7 @@ static bool HasMediaRendition(const sout_stream_sys_t *sys,
                               enum es_format_category_e cat)
 {
     const hls_playlist_t *playlist;
-    vlc_list_foreach_const (playlist, &sys->media_playlists, node)
+    hls_active_playlist_foreach (playlist, &sys->media_playlists)
     {
         if (MediaGetTrack(playlist)->input->fmt.i_cat == cat)
             return true;
@@ -350,7 +361,7 @@ static int GenerateMainManifest(const sout_stream_sys_t *sys,
     bool spu_default_set = false;
 
     const hls_playlist_t *playlist;
-    vlc_list_foreach_const (playlist, &sys->media_playlists, node)
+    hls_active_playlist_foreach (playlist, &sys->media_playlists)
     {
         const hls_track_t *track = MediaGetTrack(playlist);
         const es_format_t *fmt = &track->input->fmt;
@@ -404,7 +415,7 @@ static int GenerateMainManifest(const sout_stream_sys_t *sys,
     }
 
     /* Format EXT-X-STREAM-INF */
-    vlc_list_foreach_const (playlist, &sys->variant_playlists, node)
+    hls_active_playlist_foreach (playlist, &sys->variant_playlists)
     {
         MANIFEST_START_TAG("#EXT-X-STREAM-INF")
             unsigned int bandwidth = 0;
@@ -748,6 +759,17 @@ static hls_block_chain_t ExtractSegment(hls_playlist_t *playlist)
             return ExtractSubtitleSegment(&playlist->muxed_output, min_length);
         case HLS_PLAYLIST_TYPE_MP4:
         case HLS_PLAYLIST_TYPE_TS:
+            /* Draining: the last segment does not need to take care of the
+             * next one being IFrame padded. */
+            if (playlist->ended && playlist->muxed_output.length <= max_length)
+            {
+                hls_block_chain_t seg = {
+                    .begin = playlist->muxed_output.begin,
+                    .length = playlist->muxed_output.length,
+                };
+                hls_block_chain_Reset(&playlist->muxed_output);
+                return seg;
+            }
             return ExtractAVSegment(
                 playlist, &playlist->muxed_output, min_length, max_length);
     }
@@ -996,6 +1018,9 @@ static ssize_t AccessOutWrite(sout_access_out_t *access, block_t *block)
         if (it->access == access)
             PlaylistWriteMuxedOutput(it, block, length);
 
+        if (it->ended)
+            continue;
+
         if (!IsSegmentReady(it,
                             sys->config.segment_length,
                             sys->config.max_segment_length))
@@ -1005,7 +1030,7 @@ static ssize_t AccessOutWrite(sout_access_out_t *access, block_t *block)
 
     if (segments_ready)
     {
-        hls_playlists_foreach (it)
+        hls_active_playlists_foreach (it)
         {
             while (IsSegmentReady(it,
                                   sys->config.segment_length,
@@ -1103,6 +1128,7 @@ static hls_playlist_t *CreatePlaylist(sout_stream_t *stream,
     playlist->type = type;
     playlist->config = &sys->config;
     playlist->ended = false;
+    playlist->ended_at = VLC_TICK_INVALID;
     playlist->muxed_duration = 0;
     playlist->video_track_count = 0;
 
@@ -1184,7 +1210,8 @@ access_err:
 
 static void DeletePlaylist(hls_playlist_t *playlist)
 {
-    sout_MuxDelete(playlist->mux);
+    if (playlist->mux != NULL)
+        sout_MuxDelete(playlist->mux);
 
     sout_AccessOutDelete(playlist->access);
 
@@ -1212,6 +1239,25 @@ static void DeletePlaylist(hls_playlist_t *playlist)
     free(playlist);
 }
 
+static int UpdateMainManifest(sout_stream_t *stream)
+{
+    sout_stream_sys_t *sys = stream->p_sys;
+
+    struct hls_storage *new_manifest;
+    const int ret = GenerateMainManifest(sys, &new_manifest);
+    if (unlikely(ret != VLC_SUCCESS))
+        return ret;
+
+    if (sys->http_host != NULL)
+        httpd_UrlCatch(sys->http_manifest, HTTPD_MSG_GET, HTTPCallback,
+                       (httpd_callback_sys_t *)new_manifest);
+
+    if (sys->manifest != NULL)
+        hls_storage_Destroy(sys->manifest);
+    sys->manifest = new_manifest;
+    return VLC_SUCCESS;
+}
+
 static hls_playlist_t *AddPlaylist(sout_stream_t *stream,
                                    enum hls_playlist_type type,
                                    struct vlc_list *head)
@@ -1272,8 +1318,7 @@ Add(sout_stream_t *stream, const es_format_t *fmt, const char *es_id)
 
     vlc_list_append(&track->node, &playlist->tracks);
 
-    struct hls_storage *new_manifest;
-    const int manifest_ret = GenerateMainManifest(sys, &new_manifest);
+    const int manifest_ret = UpdateMainManifest(stream);
     if (unlikely(manifest_ret != VLC_SUCCESS))
     {
         msg_Err(stream, "Failed to generate main manifest: %s",
@@ -1283,18 +1328,6 @@ Add(sout_stream_t *stream, const es_format_t *fmt, const char *es_id)
         goto error;
     }
 
-    if (sys->http_host != NULL)
-    {
-        httpd_UrlCatch(sys->http_manifest,
-                       HTTPD_MSG_GET,
-                       HTTPCallback,
-                       (httpd_callback_sys_t *)new_manifest);
-    }
-
-    if (sys->manifest != NULL)
-        hls_storage_Destroy(sys->manifest);
-    sys->manifest = new_manifest;
-
     if (map != NULL && map->playlist_ref == NULL)
         map->playlist_ref = playlist;
 
@@ -1326,10 +1359,15 @@ static void Del(sout_stream_t *stream, void *id)
             map->playlist_ref = NULL;
 
         track->playlist_ref->ended = true;
-        ExtractAndAddSegment(track->playlist_ref, sys);
-        UpdatePlaylistManifest(track->playlist_ref);
+        track->playlist_ref->ended_at = vlc_tick_now();
+
+        sout_MuxDelete(track->playlist_ref->mux);
+        track->playlist_ref->mux = NULL;
 
-        DeletePlaylist(track->playlist_ref);
+        while (track->playlist_ref->muxed_output.begin != NULL &&
+               ExtractAndAddSegment(track->playlist_ref, sys) == VLC_SUCCESS)
+          ;
+        UpdatePlaylistManifest(track->playlist_ref);
     }
 
     free(track);
@@ -1357,12 +1395,34 @@ static void SetPCR(sout_stream_t *stream, vlc_tick_t pcr)
     const hls_playlist_t *playlist;
     vlc_list_foreach_const (playlist, &sys->media_playlists, node)
     {
-        if (playlist->type != HLS_PLAYLIST_TYPE_WEBVTT)
+        if (playlist->type != HLS_PLAYLIST_TYPE_WEBVTT || playlist->mux == NULL)
             continue;
 
         hls_sub_segmenter_SignalStreamUpdate(playlist->mux,
                                              sys->elapsed_stream_time);
     }
+
+    /* Prune finalized playlists once the wall-clock time exceeds the window a
+     * client could still be playing them for. */
+    if (sys->config.max_segments != 0)
+    {
+        const vlc_tick_t now = vlc_tick_now();
+        const vlc_tick_t keep_window =
+            sys->config.max_segments * sys->config.max_segment_length
+            + sys->config.segment_length;
+        hls_playlist_t *pl;
+        bool pruned = false;
+        hls_playlists_foreach (pl)
+        {
+            if (pl->ended && now - pl->ended_at > keep_window)
+            {
+                DeletePlaylist(pl);
+                pruned = true;
+            }
+        }
+        if (pruned)
+            UpdateMainManifest(stream);
+    }
 }
 
 static int Control(sout_stream_t *stream, int query, va_list args)
@@ -1406,6 +1466,10 @@ static void Close(sout_stream_t *stream)
 {
     sout_stream_sys_t *sys = stream->p_sys;
 
+    hls_playlist_t *pl;
+    hls_playlists_foreach(pl)
+        DeletePlaylist(pl);
+
     if (sys->http_host != NULL)
     {
         httpd_UrlDelete(sys->http_manifest);



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/637a652f4ed0489564861d0638b99b8b3326b3fb...5fa872b70f829af68d2b1194ae4a2b260f7e4f90

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/637a652f4ed0489564861d0638b99b8b3326b3fb...5fa872b70f829af68d2b1194ae4a2b260f7e4f90
You're receiving this email because of your account on code.videolan.org. Manage all notifications: https://code.videolan.org/-/profile/notifications | Help: https://code.videolan.org/help




More information about the vlc-commits mailing list