hlsdemux2: Handle loss of synchronization in live

Loss of synchronization happens when the updated media playlist has no
relationship to the previous ones. This could happen because of network issues,
server issues, etc...

When this happens, we take no chance and "reset" ourselves so that we can "seek
back to live" against the new updated playlists.

Since this happens at the "media playlist update" level, make sure the custom
flow return is propagated up.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/2839>
This commit is contained in:
Edward Hervey 2022-07-08 10:44:51 +02:00 committed by Tim-Philipp Müller
parent abe5a06421
commit 9ca1dcd563

View file

@ -82,7 +82,7 @@ static GstStateChangeReturn
gst_hls_demux_change_state (GstElement * element, GstStateChange transition); gst_hls_demux_change_state (GstElement * element, GstStateChange transition);
/* GstHLSDemux */ /* GstHLSDemux */
static gboolean gst_hls_demux_update_playlist (GstHLSDemux * demux, static GstFlowReturn gst_hls_demux_update_playlist (GstHLSDemux * demux,
gboolean update, GError ** err); gboolean update, GError ** err);
/* FIXME: the return value is never used? */ /* FIXME: the return value is never used? */
@ -101,8 +101,8 @@ static gint64 gst_hls_demux_get_manifest_update_interval (GstAdaptiveDemux *
demux); demux);
static gboolean gst_hls_demux_process_manifest (GstAdaptiveDemux * demux, static gboolean gst_hls_demux_process_manifest (GstAdaptiveDemux * demux,
GstBuffer * buf); GstBuffer * buf);
static gboolean gst_hls_demux_stream_update_rendition_playlist (GstHLSDemux * static GstFlowReturn gst_hls_demux_stream_update_rendition_playlist (GstHLSDemux
demux, GstHLSDemuxStream * stream); * demux, GstHLSDemuxStream * stream);
static GstFlowReturn gst_hls_demux_update_manifest (GstAdaptiveDemux * demux); static GstFlowReturn gst_hls_demux_update_manifest (GstAdaptiveDemux * demux);
static void setup_initial_playlist (GstHLSDemux * demux, static void setup_initial_playlist (GstHLSDemux * demux,
@ -417,7 +417,7 @@ gst_hls_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek)
gst_hls_demux_set_current_variant (hlsdemux, gst_hls_demux_set_current_variant (hlsdemux,
hlsdemux->master->iframe_variants->data); hlsdemux->master->iframe_variants->data);
if (!gst_hls_demux_update_playlist (hlsdemux, FALSE, &err)) { if (gst_hls_demux_update_playlist (hlsdemux, FALSE, &err) != GST_FLOW_OK) {
GST_ELEMENT_ERROR_FROM_ERROR (hlsdemux, "Could not switch playlist", err); GST_ELEMENT_ERROR_FROM_ERROR (hlsdemux, "Could not switch playlist", err);
return FALSE; return FALSE;
} }
@ -430,7 +430,7 @@ gst_hls_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek)
gst_hls_demux_set_current_variant (hlsdemux, gst_hls_demux_set_current_variant (hlsdemux,
hlsdemux->master->variants->data); hlsdemux->master->variants->data);
if (!gst_hls_demux_update_playlist (hlsdemux, FALSE, &err)) { if (gst_hls_demux_update_playlist (hlsdemux, FALSE, &err) != GST_FLOW_OK) {
GST_ELEMENT_ERROR_FROM_ERROR (hlsdemux, "Could not switch playlist", err); GST_ELEMENT_ERROR_FROM_ERROR (hlsdemux, "Could not switch playlist", err);
return FALSE; return FALSE;
} }
@ -483,6 +483,7 @@ static GstFlowReturn
gst_hls_demux_stream_seek (GstAdaptiveDemux2Stream * stream, gboolean forward, gst_hls_demux_stream_seek (GstAdaptiveDemux2Stream * stream, gboolean forward,
GstSeekFlags flags, GstClockTimeDiff ts, GstClockTimeDiff * final_ts) GstSeekFlags flags, GstClockTimeDiff ts, GstClockTimeDiff * final_ts)
{ {
GstFlowReturn ret = GST_FLOW_OK;
GstHLSDemuxStream *hls_stream = GST_HLS_DEMUX_STREAM_CAST (stream); GstHLSDemuxStream *hls_stream = GST_HLS_DEMUX_STREAM_CAST (stream);
GstHLSDemux *hlsdemux = (GstHLSDemux *) stream->demux; GstHLSDemux *hlsdemux = (GstHLSDemux *) stream->demux;
GstM3U8MediaSegment *new_position; GstM3U8MediaSegment *new_position;
@ -494,10 +495,11 @@ gst_hls_demux_stream_seek (GstAdaptiveDemux2Stream * stream, gboolean forward,
/* If the rendition playlist needs to be updated, do it now */ /* If the rendition playlist needs to be updated, do it now */
if (!hls_stream->is_variant && !hls_stream->playlist_fetched) { if (!hls_stream->is_variant && !hls_stream->playlist_fetched) {
if (!gst_hls_demux_stream_update_rendition_playlist (hlsdemux, hls_stream)) { ret = gst_hls_demux_stream_update_rendition_playlist (hlsdemux, hls_stream);
if (ret != GST_FLOW_OK) {
GST_WARNING_OBJECT (stream, GST_WARNING_OBJECT (stream,
"Failed to update the rendition playlist before seeking"); "Failed to update the rendition playlist before seeking");
return GST_FLOW_ERROR; return ret;
} }
} }
@ -512,20 +514,18 @@ gst_hls_demux_stream_seek (GstAdaptiveDemux2Stream * stream, gboolean forward,
*final_ts = new_position->stream_time; *final_ts = new_position->stream_time;
} else { } else {
GST_WARNING_OBJECT (stream, "Seeking failed"); GST_WARNING_OBJECT (stream, "Seeking failed");
return GST_FLOW_ERROR; ret = GST_FLOW_ERROR;
} }
return GST_FLOW_OK; return ret;
} }
static GstFlowReturn static GstFlowReturn
gst_hls_demux_update_manifest (GstAdaptiveDemux * demux) gst_hls_demux_update_manifest (GstAdaptiveDemux * demux)
{ {
GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux); GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux);
if (!gst_hls_demux_update_playlist (hlsdemux, TRUE, NULL))
return GST_FLOW_ERROR;
return GST_FLOW_OK; return gst_hls_demux_update_playlist (hlsdemux, TRUE, NULL);
} }
static GstAdaptiveDemux2Stream * static GstAdaptiveDemux2Stream *
@ -943,7 +943,7 @@ gst_hls_demux_process_manifest (GstAdaptiveDemux * demux, GstBuffer * buf)
if (!hlsdemux->master->is_simple) { if (!hlsdemux->master->is_simple) {
GError *err = NULL; GError *err = NULL;
if (!gst_hls_demux_update_playlist (hlsdemux, FALSE, &err)) { if (gst_hls_demux_update_playlist (hlsdemux, FALSE, &err) != GST_FLOW_OK) {
GST_ELEMENT_ERROR_FROM_ERROR (demux, "Could not fetch media playlist", GST_ELEMENT_ERROR_FROM_ERROR (demux, "Could not fetch media playlist",
err); err);
return FALSE; return FALSE;
@ -2007,7 +2007,55 @@ setup_initial_playlist (GstHLSDemux * demux, GstHLSMediaPlaylist * playlist)
} }
} }
static gboolean /* Reset hlsdemux in case of live synchronization loss (i.e. when a media
* playlist update doesn't match at all with the previous one) */
static void
gst_hls_demux_reset_for_lost_sync (GstHLSDemux * hlsdemux)
{
GstAdaptiveDemux *demux = (GstAdaptiveDemux *) hlsdemux;
GList *iter;
GST_DEBUG_OBJECT (hlsdemux, "Resetting for lost sync");
for (iter = demux->input_period->streams; iter; iter = iter->next) {
GstHLSDemuxStream *hls_stream = iter->data;
GstAdaptiveDemux2Stream *stream = (GstAdaptiveDemux2Stream *) hls_stream;
if (hls_stream->current_segment)
gst_m3u8_media_segment_unref (hls_stream->current_segment);
hls_stream->current_segment = NULL;
if (hls_stream->is_variant) {
GstHLSTimeMap *map;
/* Resynchronize the variant stream */
g_assert (stream->current_position != GST_CLOCK_STIME_NONE);
hls_stream->current_segment =
gst_hls_media_playlist_get_starting_segment (hls_stream->playlist);
hls_stream->current_segment->stream_time = stream->current_position;
gst_hls_media_playlist_recalculate_stream_time (hls_stream->playlist,
hls_stream->current_segment);
GST_DEBUG_OBJECT (stream,
"Resynced variant playlist to %" GST_STIME_FORMAT,
GST_STIME_ARGS (stream->current_position));
map =
gst_hls_find_time_map (hlsdemux,
hls_stream->current_segment->discont_sequence);
if (map)
map->internal_time = GST_CLOCK_TIME_NONE;
gst_hls_update_time_mappings (hlsdemux, hls_stream->playlist);
gst_hls_media_playlist_dump (hls_stream->playlist);
} else {
/* Force playlist update for the rendition streams, it will resync to the
* variant stream on the next round */
if (hls_stream->playlist)
gst_hls_media_playlist_unref (hls_stream->playlist);
hls_stream->playlist = NULL;
hls_stream->playlist_fetched = FALSE;
}
}
}
static GstFlowReturn
gst_hls_demux_stream_update_media_playlist (GstHLSDemux * demux, gst_hls_demux_stream_update_media_playlist (GstHLSDemux * demux,
GstHLSDemuxStream * stream, gchar ** uri, GError ** err) GstHLSDemuxStream * stream, gchar ** uri, GError ** err)
{ {
@ -2018,7 +2066,7 @@ gst_hls_demux_stream_update_media_playlist (GstHLSDemux * demux,
new_playlist = download_media_playlist (demux, *uri, err, stream->playlist); new_playlist = download_media_playlist (demux, *uri, err, stream->playlist);
if (new_playlist == NULL) { if (new_playlist == NULL) {
GST_WARNING_OBJECT (stream, "Could not get playlist '%s'", *uri); GST_WARNING_OBJECT (stream, "Could not get playlist '%s'", *uri);
return FALSE; return GST_FLOW_ERROR;
} }
/* Check if a redirect happened */ /* Check if a redirect happened */
@ -2029,16 +2077,17 @@ gst_hls_demux_stream_update_media_playlist (GstHLSDemux * demux,
*uri = g_strdup (new_playlist->uri); *uri = g_strdup (new_playlist->uri);
} }
/* Synchronize playlist with previous one */ /* Synchronize playlist with previous one. If we can't update the playlist
* timing and inform the base class that we lost sync */
if (stream->playlist if (stream->playlist
&& !gst_hls_media_playlist_sync_to_playlist (new_playlist, && !gst_hls_media_playlist_sync_to_playlist (new_playlist,
stream->playlist)) { stream->playlist)) {
/* Failure to synchronize with the previous media playlist is only fatal for /* Failure to synchronize with the previous media playlist is only fatal for
* variant streams. */ * variant streams. */
if (stream->is_variant) { if (stream->is_variant) {
GST_ERROR_OBJECT (stream, GST_DEBUG_OBJECT (stream,
"Could not synchronize new variant playlist with previous one !"); "Could not synchronize new variant playlist with previous one !");
return FALSE; goto lost_sync;
} }
/* For rendition streams, we can attempt synchronization against the /* For rendition streams, we can attempt synchronization against the
@ -2046,9 +2095,9 @@ gst_hls_demux_stream_update_media_playlist (GstHLSDemux * demux,
if (demux->main_stream->playlist if (demux->main_stream->playlist
&& !gst_hls_media_playlist_sync_to_playlist (new_playlist, && !gst_hls_media_playlist_sync_to_playlist (new_playlist,
demux->main_stream->playlist)) { demux->main_stream->playlist)) {
GST_ERROR_OBJECT (stream, GST_DEBUG_OBJECT (stream,
"Could not do fallback synchronization of rendition stream to variant stream"); "Could not do fallback synchronization of rendition stream to variant stream");
return FALSE; goto lost_sync;
} }
} else if (!stream->is_variant && demux->main_stream->playlist) { } else if (!stream->is_variant && demux->main_stream->playlist) {
/* For initial rendition media playlist, attempt to synchronize the playlist /* For initial rendition media playlist, attempt to synchronize the playlist
@ -2124,19 +2173,37 @@ gst_hls_demux_stream_update_media_playlist (GstHLSDemux * demux,
GST_DEBUG_OBJECT (stream, "No current segment selected"); GST_DEBUG_OBJECT (stream, "No current segment selected");
} }
return TRUE; GST_DEBUG_OBJECT (stream, "done");
return GST_FLOW_OK;
/* ERRORS */
lost_sync:
{
/* Set new playlist, lost sync handler will know what to do with it */
if (stream->playlist)
gst_hls_media_playlist_unref (stream->playlist);
stream->playlist = new_playlist;
gst_hls_demux_reset_for_lost_sync (demux);
return GST_ADAPTIVE_DEMUX_FLOW_LOST_SYNC;
}
} }
static gboolean static GstFlowReturn
gst_hls_demux_stream_update_rendition_playlist (GstHLSDemux * demux, gst_hls_demux_stream_update_rendition_playlist (GstHLSDemux * demux,
GstHLSDemuxStream * stream) GstHLSDemuxStream * stream)
{ {
GstFlowReturn ret = GST_FLOW_OK;
GstHLSRenditionStream *target_rendition = GstHLSRenditionStream *target_rendition =
stream->pending_rendition ? stream-> stream->pending_rendition ? stream->pending_rendition : stream->
pending_rendition : stream->current_rendition; current_rendition;
if (!gst_hls_demux_stream_update_media_playlist (demux, stream,
&target_rendition->uri, NULL)) ret = gst_hls_demux_stream_update_media_playlist (demux, stream,
return FALSE; &target_rendition->uri, NULL);
if (ret != GST_FLOW_OK)
return ret;
if (stream->pending_rendition) { if (stream->pending_rendition) {
gst_hls_rendition_stream_unref (stream->current_rendition); gst_hls_rendition_stream_unref (stream->current_rendition);
@ -2147,19 +2214,21 @@ gst_hls_demux_stream_update_rendition_playlist (GstHLSDemux * demux,
stream->playlist_fetched = TRUE; stream->playlist_fetched = TRUE;
return TRUE; return ret;
} }
static gboolean static GstFlowReturn
gst_hls_demux_stream_update_variant_playlist (GstHLSDemux * demux, gst_hls_demux_stream_update_variant_playlist (GstHLSDemux * demux,
GstHLSDemuxStream * stream, GError ** err) GstHLSDemuxStream * stream, GError ** err)
{ {
GstFlowReturn ret = GST_FLOW_OK;
GstHLSVariantStream *target_variant = GstHLSVariantStream *target_variant =
demux->pending_variant ? demux->pending_variant : demux->current_variant; demux->pending_variant ? demux->pending_variant : demux->current_variant;
if (!gst_hls_demux_stream_update_media_playlist (demux, stream, ret = gst_hls_demux_stream_update_media_playlist (demux, stream,
&target_variant->uri, err)) &target_variant->uri, err);
return FALSE; if (ret != GST_FLOW_OK)
return ret;
if (demux->pending_variant) { if (demux->pending_variant) {
gst_hls_variant_stream_unref (demux->current_variant); gst_hls_variant_stream_unref (demux->current_variant);
@ -2170,12 +2239,13 @@ gst_hls_demux_stream_update_variant_playlist (GstHLSDemux * demux,
stream->playlist_fetched = TRUE; stream->playlist_fetched = TRUE;
return TRUE; return ret;
} }
static GstFlowReturn static GstFlowReturn
gst_hls_demux_update_fragment_info (GstAdaptiveDemux2Stream * stream) gst_hls_demux_update_fragment_info (GstAdaptiveDemux2Stream * stream)
{ {
GstFlowReturn ret = GST_FLOW_OK;
GstHLSDemuxStream *hlsdemux_stream = GST_HLS_DEMUX_STREAM_CAST (stream); GstHLSDemuxStream *hlsdemux_stream = GST_HLS_DEMUX_STREAM_CAST (stream);
GstAdaptiveDemux *demux = stream->demux; GstAdaptiveDemux *demux = stream->demux;
GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux); GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux);
@ -2184,9 +2254,10 @@ gst_hls_demux_update_fragment_info (GstAdaptiveDemux2Stream * stream)
/* If the rendition playlist needs to be updated, do it now */ /* If the rendition playlist needs to be updated, do it now */
if (!hlsdemux_stream->is_variant && !hlsdemux_stream->playlist_fetched) { if (!hlsdemux_stream->is_variant && !hlsdemux_stream->playlist_fetched) {
if (!gst_hls_demux_stream_update_rendition_playlist (hlsdemux, ret = gst_hls_demux_stream_update_rendition_playlist (hlsdemux,
hlsdemux_stream)) hlsdemux_stream);
return GST_FLOW_ERROR; if (ret != GST_FLOW_OK)
return ret;
} }
GST_DEBUG_OBJECT (stream, GST_DEBUG_OBJECT (stream,
@ -2275,7 +2346,7 @@ gst_hls_demux_update_fragment_info (GstAdaptiveDemux2Stream * stream)
if (discont) if (discont)
stream->discont = TRUE; stream->discont = TRUE;
return GST_FLOW_OK; return ret;
} }
static gboolean static gboolean
@ -2437,19 +2508,21 @@ gst_hls_demux_reset (GstAdaptiveDemux * ademux)
* update: TRUE only when requested from parent class (via * update: TRUE only when requested from parent class (via
* ::demux_update_manifest() or ::change_playlist() ). * ::demux_update_manifest() or ::change_playlist() ).
*/ */
static gboolean static GstFlowReturn
gst_hls_demux_update_playlist (GstHLSDemux * demux, gboolean update, gst_hls_demux_update_playlist (GstHLSDemux * demux, gboolean update,
GError ** err) GError ** err)
{ {
GstFlowReturn ret = GST_FLOW_OK;
GstAdaptiveDemux *adaptive_demux = GST_ADAPTIVE_DEMUX (demux); GstAdaptiveDemux *adaptive_demux = GST_ADAPTIVE_DEMUX (demux);
GST_DEBUG_OBJECT (demux, "update:%d", update); GST_DEBUG_OBJECT (demux, "update:%d", update);
/* Download and update the appropriate variant playlist (pending if any, else /* Download and update the appropriate variant playlist (pending if any, else
* current) */ * current) */
if (!gst_hls_demux_stream_update_variant_playlist (demux, demux->main_stream, ret = gst_hls_demux_stream_update_variant_playlist (demux, demux->main_stream,
err)) err);
return FALSE; if (ret != GST_FLOW_OK)
return ret;
if (update && gst_hls_demux_is_live (adaptive_demux)) { if (update && gst_hls_demux_is_live (adaptive_demux)) {
GList *tmp; GList *tmp;
@ -2463,7 +2536,7 @@ gst_hls_demux_update_playlist (GstHLSDemux * demux, gboolean update,
} }
} }
return TRUE; return GST_FLOW_OK;
} }
static gboolean static gboolean
@ -2500,7 +2573,7 @@ retry_failover_protection:
GST_INFO_OBJECT (demux, "Client was on %dbps, max allowed is %dbps, switching" GST_INFO_OBJECT (demux, "Client was on %dbps, max allowed is %dbps, switching"
" to bitrate %dbps", old_bandwidth, max_bitrate, new_bandwidth); " to bitrate %dbps", old_bandwidth, max_bitrate, new_bandwidth);
if (gst_hls_demux_update_playlist (demux, TRUE, NULL)) { if (gst_hls_demux_update_playlist (demux, TRUE, NULL) == GST_FLOW_OK) {
const gchar *main_uri; const gchar *main_uri;
gchar *uri = new_variant->uri; gchar *uri = new_variant->uri;