From 083538df9e496f757e822b06e1d9713ab500b2bd Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Tue, 27 Dec 2022 03:04:36 +1100 Subject: [PATCH] hlsdemux2: Continue reworking code for async playlist updates Everything is working again now except for corner cases: - Failing over to another playlist after a load failure - Remembering playlist redirects and using that URI directly next time. Part-of: --- .../hls/gsthlsdemux-playlist-loader.c | 21 +- .../hls/gsthlsdemux-playlist-loader.h | 2 +- .../adaptivedemux2/hls/gsthlsdemux-stream.c | 492 ++++-------------- .../adaptivedemux2/hls/gsthlsdemux-stream.h | 9 +- .../ext/adaptivedemux2/hls/gsthlsdemux.c | 218 ++++---- .../ext/adaptivedemux2/hls/gsthlsdemux.h | 9 +- 6 files changed, 242 insertions(+), 509 deletions(-) diff --git a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-playlist-loader.c b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-playlist-loader.c index c9605a300d..3c50c45c66 100644 --- a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-playlist-loader.c +++ b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-playlist-loader.c @@ -258,18 +258,21 @@ gst_hls_demux_playlist_loader_set_playlist_uri (GstHLSDemuxPlaylistLoader * pl, } /* Check that the current playlist matches the target URI, and return - * a ref to it if so */ -GstHLSMediaPlaylist * -gst_hls_demux_stream_get_playlist_for_uri (GstHLSDemuxPlaylistLoader * pl, + * TRUE if so */ +gboolean +gst_hls_demux_playlist_loader_has_current_uri (GstHLSDemuxPlaylistLoader * pl, const gchar * target_playlist_uri) { GstHLSDemuxPlaylistLoaderPrivate *priv = pl->priv; + if (target_playlist_uri == NULL) + target_playlist_uri = priv->target_playlist_uri; + if (priv->current_playlist == NULL || !g_str_equal (target_playlist_uri, priv->current_playlist_uri)) - return NULL; + return FALSE; - return gst_hls_media_playlist_ref (priv->current_playlist); + return TRUE; } enum PlaylistDownloadParamFlags @@ -490,7 +493,6 @@ on_download_complete (DownloadRequest * download, DownloadRequestState state, if (priv->current_playlist) gst_hls_media_playlist_unref (priv->current_playlist); - /* FIXME: If there was a redirect, use that for the next update */ priv->current_playlist_uri = g_strdup (priv->loading_playlist_uri); priv->current_playlist = playlist; @@ -510,6 +512,10 @@ on_download_complete (DownloadRequest * download, DownloadRequestState state, GstClockTime delay = get_playlist_reload_interval (pl, priv, playlist); schedule_next_playlist_load (pl, priv, delay); } + } else { + GST_LOG_OBJECT (pl, "Playlist is not live. Not scheduling a reload"); + /* Go back to the starting state until/if the playlist uri is updated */ + priv->state = PLAYLIST_LOADER_STATE_STARTING; } out: @@ -557,7 +563,8 @@ start_playlist_download (GstHLSDemuxPlaylistLoader * pl, if (orig_uri == NULL) return; - struct PlaylistDownloadParams dl_params = { 0, }; + struct PlaylistDownloadParams dl_params; + memset (&dl_params, 0, sizeof (struct PlaylistDownloadParams)); GstHLSMediaPlaylist *current_playlist = priv->current_playlist; diff --git a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-playlist-loader.h b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-playlist-loader.h index 3180eae8bb..7491d6e286 100644 --- a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-playlist-loader.h +++ b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-playlist-loader.h @@ -73,7 +73,7 @@ void gst_hls_demux_playlist_loader_stop (GstHLSDemuxPlaylistLoader *pl); void gst_hls_demux_playlist_loader_set_playlist_uri (GstHLSDemuxPlaylistLoader *pl, const gchar *base_uri, const gchar *current_playlist_uri); -GstHLSMediaPlaylist *gst_hls_demux_stream_get_playlist_for_uri (GstHLSDemuxPlaylistLoader *pl, +gboolean gst_hls_demux_playlist_loader_has_current_uri (GstHLSDemuxPlaylistLoader *pl, const gchar *target_playlist_uri); G_END_DECLS diff --git a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-stream.c b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-stream.c index 34db1b75bf..f0b7e336c6 100644 --- a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-stream.c +++ b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-stream.c @@ -62,9 +62,6 @@ gst_hls_demux_stream_decrypt_start (GstHLSDemuxStream * stream, const guint8 * key_data, const guint8 * iv_data); static void gst_hls_demux_stream_decrypt_end (GstHLSDemuxStream * stream); -static GstFlowReturn -gst_hls_demux_stream_update_rendition_playlist (GstHLSDemuxStream * stream); - static gboolean gst_hls_demux_stream_start_fragment (GstAdaptiveDemux2Stream * stream); static GstFlowReturn @@ -212,14 +209,9 @@ gst_hls_demux_stream_seek (GstAdaptiveDemux2Stream * stream, gboolean forward, GST_TIME_FORMAT, hls_stream->is_variant, hls_stream->current_rendition, hlsdemux->current_variant, forward, GST_TIME_ARGS (ts)); - /* If the rendition playlist needs to be updated, do it now */ - if (!hls_stream->is_variant && !hls_stream->playlist_fetched) { - ret = gst_hls_demux_stream_update_rendition_playlist (hls_stream); - if (ret != GST_FLOW_OK) { - GST_WARNING_OBJECT (stream, - "Failed to update the rendition playlist before seeking"); - return ret; - } + /* If this stream doesn't have a playlist yet, we can't seek on it */ + if (!hls_stream->playlist_fetched) { + return GST_ADAPTIVE_DEMUX_FLOW_BUSY; } /* Allow jumping to partial segments in the last 2 segments in LL-HLS */ @@ -1263,233 +1255,6 @@ gst_hls_demux_stream_advance_fragment (GstAdaptiveDemux2Stream * stream) return GST_FLOW_EOS; } -enum PlaylistDownloadParamFlags -{ - PLAYLIST_DOWNLOAD_FLAG_SKIP_V1 = (1 << 0), - PLAYLIST_DOWNLOAD_FLAG_SKIP_V2 = (1 << 1), /* V2 also skips date-ranges */ - PLAYLIST_DOWNLOAD_FLAG_BLOCKING_REQUEST = (1 << 2), -}; - -struct PlaylistDownloadParams -{ - enum PlaylistDownloadParamFlags flags; - gint64 next_msn, next_part; -}; - -#define HLS_SKIP_QUERY_KEY "_HLS_skip" -#define HLS_MSN_QUERY_KEY "_HLS_msn" -#define HLS_PART_QUERY_KEY "_HLS_part" - -static gchar * -apply_directives_to_uri (GstHLSDemuxStream * stream, - const gchar * playlist_uri, struct PlaylistDownloadParams *dl_params) -{ - GstUri *uri = gst_uri_from_string (playlist_uri); - - if (dl_params->flags & PLAYLIST_DOWNLOAD_FLAG_SKIP_V1) { - GST_LOG_OBJECT (stream, "Doing HLS skip (v1) request"); - gst_uri_set_query_value (uri, HLS_SKIP_QUERY_KEY, "YES"); - } else if (dl_params->flags & PLAYLIST_DOWNLOAD_FLAG_SKIP_V2) { - GST_LOG_OBJECT (stream, "Doing HLS skip (v2) request"); - gst_uri_set_query_value (uri, HLS_SKIP_QUERY_KEY, "v2"); - } else { - gst_uri_remove_query_key (uri, HLS_SKIP_QUERY_KEY); - } - - if (dl_params->flags & PLAYLIST_DOWNLOAD_FLAG_BLOCKING_REQUEST - && dl_params->next_msn != -1) { - GST_LOG_OBJECT (stream, - "Doing HLS blocking request for URI %s with MSN %" G_GINT64_FORMAT - " part %" G_GINT64_FORMAT, playlist_uri, dl_params->next_msn, - dl_params->next_part); - - gchar *next_msn_str = - g_strdup_printf ("%" G_GINT64_FORMAT, dl_params->next_msn); - gst_uri_set_query_value (uri, HLS_MSN_QUERY_KEY, next_msn_str); - g_free (next_msn_str); - - if (dl_params->next_part != -1) { - gchar *next_part_str = - g_strdup_printf ("%" G_GINT64_FORMAT, dl_params->next_part); - gst_uri_set_query_value (uri, HLS_PART_QUERY_KEY, next_part_str); - g_free (next_part_str); - } else { - gst_uri_remove_query_key (uri, HLS_PART_QUERY_KEY); - } - } - - /* Produce the resulting URI with query arguments in UTF-8 order - * as required by the HLS spec: - * `Clients using Delivery Directives (Section 6.2.5) MUST ensure that - * all query parameters appear in UTF-8 order within the URI.` - */ - GList *keys = gst_uri_get_query_keys (uri); - if (keys) - keys = g_list_sort (keys, (GCompareFunc) g_strcmp0); - gchar *out_uri = gst_uri_to_string_with_keys (uri, keys); - gst_uri_unref (uri); - - return out_uri; -} - -static GstHLSMediaPlaylist * -download_media_playlist (GstHLSDemuxStream * stream, gchar * orig_uri, - GError ** err, GstHLSMediaPlaylist * current) -{ - gboolean allow_skip = TRUE; - - GstAdaptiveDemux2Stream *base_stream = GST_ADAPTIVE_DEMUX2_STREAM (stream); - GstAdaptiveDemux *demux = base_stream->demux; - const gchar *main_uri = gst_adaptive_demux_get_manifest_ref_uri (demux); - struct PlaylistDownloadParams dl_params; - -retry: - - memset (&dl_params, 0, sizeof (struct PlaylistDownloadParams)); - - /* If there's no previous playlist, or the URI changed this - * is not a refresh/update but a switch to a new playlist */ - gboolean playlist_uri_change = (current == NULL - || g_strcmp0 (orig_uri, current->uri) != 0); - - if (!playlist_uri_change) { - GST_LOG_OBJECT (stream, "Updating the playlist"); - - /* See if we can do a delta playlist update (if the playlist age is less than - * one half of the Skip Boundary */ - if (GST_CLOCK_TIME_IS_VALID (current->skip_boundary) && allow_skip) { - GstClockTime now = gst_adaptive_demux2_get_monotonic_time (demux); - GstClockTimeDiff playlist_age = - GST_CLOCK_DIFF (current->playlist_ts, now); - - if (GST_CLOCK_TIME_IS_VALID (current->playlist_ts) && - playlist_age <= current->skip_boundary / 2) { - if (current->can_skip_dateranges) { - dl_params.flags |= PLAYLIST_DOWNLOAD_FLAG_SKIP_V2; - } else { - dl_params.flags |= PLAYLIST_DOWNLOAD_FLAG_SKIP_V1; - } - } - } else if (GST_CLOCK_TIME_IS_VALID (current->skip_boundary)) { - GST_DEBUG_OBJECT (stream, - "Doing full playlist update after failed delta request"); - } - } - - /* Blocking playlist reload check */ - if (current != NULL && current->can_block_reload) { - if (playlist_uri_change) { - /* FIXME: We're changing playlist, but if there's a EXT-X-RENDITION-REPORT - * for the new playlist we might be able to use it to do a blocking request */ - } else { - /* Get the next MSN (and/or possibly part number) for the request params */ - gst_hls_media_playlist_get_next_msn_and_part (current, - stream->llhls_enabled, &dl_params.next_msn, &dl_params.next_part); - dl_params.flags |= PLAYLIST_DOWNLOAD_FLAG_BLOCKING_REQUEST; - } - } - - gchar *target_uri = apply_directives_to_uri (stream, orig_uri, &dl_params); - - DownloadRequest *download = downloadhelper_fetch_uri (demux->download_helper, - target_uri, main_uri, - DOWNLOAD_FLAG_COMPRESS | DOWNLOAD_FLAG_FORCE_REFRESH, err); - - g_free (target_uri); - - if (download == NULL) - return NULL; - - /* If we got a permanent redirect, use that as the new - * playlist URI, otherwise set the base URI of the playlist - * to the redirect target if any (NULL if there was no redirect) */ - GstHLSMediaPlaylist *playlist = NULL; - gchar *base_uri, *uri; - - if (download->redirect_permanent && download->redirect_uri) { - uri = g_strdup (download->redirect_uri); - base_uri = NULL; - } else { - uri = g_strdup (download->uri); - base_uri = g_strdup (download->redirect_uri); - } - - if (download->state == DOWNLOAD_REQUEST_STATE_ERROR) { - GST_WARNING_OBJECT (demux, - "Couldn't get the playlist, got HTTP status code %d", - download->status_code); - download_request_unref (download); - if (err) - g_set_error (err, GST_STREAM_ERROR, GST_STREAM_ERROR_WRONG_TYPE, - "Couldn't download the playlist"); - goto out; - } - - /* Calculate the newest time we know this playlist was valid to store on the HLS Media Playlist */ - GstClockTime playlist_ts = - MAX (0, GST_CLOCK_DIFF (download_request_get_age (download), - download->download_start_time)); - - GstBuffer *buf = download_request_take_buffer (download); - download_request_unref (download); - - /* there should be a buf if there wasn't an error (handled above) */ - g_assert (buf); - - gchar *playlist_data = gst_hls_buf_to_utf8_text (buf); - gst_buffer_unref (buf); - - if (playlist_data == NULL) { - GST_WARNING_OBJECT (demux, "Couldn't validate playlist encoding"); - if (err) - g_set_error (err, GST_STREAM_ERROR, GST_STREAM_ERROR_WRONG_TYPE, - "Couldn't validate playlist encoding"); - goto out; - } - - if (!playlist_uri_change && current - && gst_hls_media_playlist_has_same_data (current, playlist_data)) { - GST_DEBUG_OBJECT (demux, "Same playlist data"); - playlist = gst_hls_media_playlist_ref (current); - playlist->reloaded = TRUE; - g_free (playlist_data); - } else { - playlist = - gst_hls_media_playlist_parse (playlist_data, playlist_ts, uri, - base_uri); - if (!playlist) { - GST_WARNING_OBJECT (demux, "Couldn't parse playlist"); - if (err) - g_set_error (err, GST_STREAM_ERROR, GST_STREAM_ERROR_FAILED, - "Couldn't parse playlist"); - } - } - - /* Transfer over any skipped segments from the current playlist if - * we did a delta playlist update */ - if (!playlist_uri_change && current && playlist - && playlist->skipped_segments > 0) { - if (!gst_hls_media_playlist_sync_skipped_segments (playlist, current)) { - GST_DEBUG_OBJECT (stream, - "Could not merge delta update to playlist. Retrying with full request"); - - /* Delta playlist update failed. Load a full playlist */ - allow_skip = FALSE; - - gst_hls_media_playlist_unref (playlist); - g_free (uri); - g_free (base_uri); - goto retry; - } - } - -out: - g_free (uri); - g_free (base_uri); - - return playlist; -} - static void gst_hls_demux_stream_update_preloads (GstHLSDemuxStream * hlsdemux_stream) { @@ -1571,46 +1336,12 @@ gst_hls_demux_stream_submit_request (GstAdaptiveDemux2Stream * stream, (stream, download_req); } -void -gst_hls_demux_stream_set_playlist_uri (GstHLSDemuxStream * hls_stream, - gchar * uri) +static void +gst_hls_demux_stream_handle_playlist_update (GstHLSDemuxStream * stream, + const gchar * new_playlist_uri, GstHLSMediaPlaylist * new_playlist) { - GstAdaptiveDemux *demux = GST_ADAPTIVE_DEMUX2_STREAM_CAST (hls_stream)->demux; - - if (hls_stream->playlistloader == NULL) { - hls_stream->playlistloader = - gst_hls_demux_playlist_loader_new (demux, demux->download_helper, - hls_stream->llhls_enabled); - } - - const gchar *main_uri = gst_adaptive_demux_get_manifest_ref_uri (demux); - gst_hls_demux_playlist_loader_set_playlist_uri (hls_stream->playlistloader, - main_uri, uri); -} - -GstFlowReturn -gst_hls_demux_stream_update_media_playlist (GstHLSDemuxStream * stream, - gchar ** uri, GError ** err) -{ - GstHLSMediaPlaylist *new_playlist; GstHLSDemux *demux = GST_HLS_DEMUX_STREAM_GET_DEMUX (stream); - GST_DEBUG_OBJECT (stream, "Updating %s", *uri); - - new_playlist = download_media_playlist (stream, *uri, err, stream->playlist); - if (new_playlist == NULL) { - GST_WARNING_OBJECT (stream, "Could not get playlist '%s'", *uri); - return GST_FLOW_ERROR; - } - - /* Check if a redirect happened */ - if (g_strcmp0 (*uri, new_playlist->uri)) { - GST_DEBUG_OBJECT (stream, "Playlist URI update : '%s' => '%s'", *uri, - new_playlist->uri); - g_free (*uri); - *uri = g_strdup (new_playlist->uri); - } - /* 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 @@ -1707,30 +1438,32 @@ gst_hls_demux_stream_update_media_playlist (GstHLSDemuxStream * stream, GST_DEBUG_OBJECT (stream, "No current segment"); } - if (stream->playlist) { - gst_hls_media_playlist_unref (stream->playlist); - stream->playlist = new_playlist; - } else { - if (stream->is_variant) { - GST_DEBUG_OBJECT (stream, "Setting up initial playlist"); - gst_hls_demux_setup_initial_playlist (demux, new_playlist); - } - stream->playlist = new_playlist; + if (stream->is_variant) { + /* Updates on the variant playlist have some special requirements to + * set up the time mapping and initial stream config */ + gst_hls_demux_handle_variant_playlist_update (demux, new_playlist_uri, + new_playlist); + } else if (stream->pending_rendition) { + /* Switching rendition configures a new playlist on the loader, + * and we should never get a callback for a stale download URI */ + g_assert (g_str_equal (stream->pending_rendition->uri, new_playlist_uri)); + + gst_hls_rendition_stream_unref (stream->current_rendition); + /* Stealing ref */ + stream->current_rendition = stream->pending_rendition; + stream->pending_rendition = NULL; } + if (stream->playlist) + gst_hls_media_playlist_unref (stream->playlist); + stream->playlist = gst_hls_media_playlist_ref (new_playlist); + stream->playlist_fetched = TRUE; + if (!GST_HLS_MEDIA_PLAYLIST_IS_LIVE (stream->playlist)) { /* Make sure to cancel any preloads if a playlist isn't live after reload */ gst_hls_demux_stream_update_preloads (stream); } - if (stream->is_variant) { - /* Update time mappings. We only use the variant stream for collecting - * mappings since it is the reference on which rendition stream timing will - * be based. */ - gst_hls_update_time_mappings (demux, stream->playlist); - } - gst_hls_media_playlist_dump (stream->playlist); - if (stream->current_segment) { GST_DEBUG_OBJECT (stream, "After update, current segment now sn:%" G_GINT64_FORMAT @@ -1743,8 +1476,7 @@ gst_hls_demux_stream_update_media_playlist (GstHLSDemuxStream * stream, } GST_DEBUG_OBJECT (stream, "done"); - - return GST_FLOW_OK; + return; /* ERRORS */ lost_sync: @@ -1753,88 +1485,97 @@ lost_sync: if (stream->playlist) gst_hls_media_playlist_unref (stream->playlist); stream->playlist = new_playlist; + stream->playlist = gst_hls_media_playlist_ref (new_playlist); + stream->playlist_fetched = TRUE; gst_hls_demux_reset_for_lost_sync (demux); - - return GST_ADAPTIVE_DEMUX_FLOW_LOST_SYNC; } } -GstClockTime -gst_hls_demux_stream_get_playlist_reload_interval (GstHLSDemuxStream * stream) +static void +on_playlist_update_success (GstHLSDemuxPlaylistLoader * pl, + const gchar * new_playlist_uri, GstHLSMediaPlaylist * new_playlist, + gpointer userdata) { - GstHLSMediaPlaylist *playlist = stream->playlist; + GstHLSDemuxStream *hls_stream = GST_HLS_DEMUX_STREAM_CAST (userdata); - if (playlist == NULL) - return GST_CLOCK_TIME_NONE; /* No playlist yet */ - - if (!gst_hls_media_playlist_is_live (playlist)) - return GST_CLOCK_TIME_NONE; /* Not live playback */ - - /* Use the most recent segment (or part segment) duration, as per - * https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-11#section-6.3.4 - */ - GstClockTime target_duration = GST_CLOCK_TIME_NONE; - GstClockTime min_reload_interval = playlist->targetduration / 2; - - if (playlist->segments->len) { - GstM3U8MediaSegment *last_seg = - g_ptr_array_index (playlist->segments, playlist->segments->len - 1); - - target_duration = last_seg->duration; - - if (stream->llhls_enabled && last_seg->partial_segments) { - GstM3U8PartialSegment *last_part = - g_ptr_array_index (last_seg->partial_segments, - last_seg->partial_segments->len - 1); - - target_duration = last_part->duration; - if (GST_CLOCK_TIME_IS_VALID (playlist->partial_targetduration)) { - min_reload_interval = playlist->partial_targetduration / 2; - } else { - min_reload_interval = target_duration / 2; - } - } - } else if (stream->llhls_enabled - && GST_CLOCK_TIME_IS_VALID (playlist->partial_targetduration)) { - target_duration = playlist->partial_targetduration; - min_reload_interval = target_duration / 2; - } else if (playlist->version > 5) { - target_duration = playlist->targetduration; - } - - if (playlist->reloaded && target_duration > min_reload_interval) { - GST_DEBUG_OBJECT (stream, - "Playlist didn't change previously, returning lower update interval"); - target_duration = min_reload_interval; - } - - return target_duration; + gst_hls_demux_stream_handle_playlist_update (hls_stream, + new_playlist_uri, new_playlist); + gst_adaptive_demux2_stream_mark_prepared (GST_ADAPTIVE_DEMUX2_STREAM_CAST + (hls_stream)); } -static GstFlowReturn -gst_hls_demux_stream_update_rendition_playlist (GstHLSDemuxStream * stream) +static void +on_playlist_update_error (GstHLSDemuxPlaylistLoader * pl, + const gchar * playlist_uri, gpointer userdata) { - GstFlowReturn ret = GST_FLOW_OK; - GstHLSRenditionStream *target_rendition = - stream->pending_rendition ? stream-> - pending_rendition : stream->current_rendition; + GstHLSDemuxStream *hls_stream = GST_HLS_DEMUX_STREAM_CAST (userdata); - ret = gst_hls_demux_stream_update_media_playlist (stream, - &target_rendition->uri, NULL); - if (ret != GST_FLOW_OK) - return ret; + /* FIXME: How to handle rendition playlist update errors */ + if (hls_stream->is_variant) { + GstHLSDemux *demux = GST_HLS_DEMUX_STREAM_GET_DEMUX (hls_stream); + gst_hls_demux_handle_variant_playlist_update_error (demux, playlist_uri); + } +} - if (stream->pending_rendition) { - gst_hls_rendition_stream_unref (stream->current_rendition); - /* Stealing ref */ - stream->current_rendition = stream->pending_rendition; - stream->pending_rendition = NULL; +static GstHLSDemuxPlaylistLoader * +gst_hls_demux_stream_get_playlist_loader (GstHLSDemuxStream * hls_stream) +{ + GstAdaptiveDemux *demux = GST_ADAPTIVE_DEMUX2_STREAM_CAST (hls_stream)->demux; + if (hls_stream->playlistloader == NULL) { + hls_stream->playlistloader = + gst_hls_demux_playlist_loader_new (demux, demux->download_helper, + hls_stream->llhls_enabled); + gst_hls_demux_playlist_loader_set_callbacks (hls_stream->playlistloader, + on_playlist_update_success, on_playlist_update_error, hls_stream); } - stream->playlist_fetched = TRUE; + return hls_stream->playlistloader; +} - return ret; +void +gst_hls_demux_stream_set_playlist_uri (GstHLSDemuxStream * hls_stream, + gchar * uri) +{ + GstAdaptiveDemux *demux = GST_ADAPTIVE_DEMUX2_STREAM_CAST (hls_stream)->demux; + GstHLSDemuxPlaylistLoader *pl = + gst_hls_demux_stream_get_playlist_loader (hls_stream); + + const gchar *main_uri = gst_adaptive_demux_get_manifest_ref_uri (demux); + gst_hls_demux_playlist_loader_set_playlist_uri (pl, main_uri, uri); +} + +void +gst_hls_demux_stream_start_playlist_loading (GstHLSDemuxStream * hls_stream) +{ + GstHLSDemuxPlaylistLoader *pl = + gst_hls_demux_stream_get_playlist_loader (hls_stream); + gst_hls_demux_playlist_loader_start (pl); +} + +GstFlowReturn +gst_hls_demux_stream_check_current_playlist_uri (GstHLSDemuxStream * stream, + gchar * uri) +{ + GstHLSDemuxPlaylistLoader *pl = + gst_hls_demux_stream_get_playlist_loader (stream); + + if (!gst_hls_demux_playlist_loader_has_current_uri (pl, uri)) { + GST_LOG_OBJECT (stream, "Playlist '%s' not available yet", uri); + return GST_ADAPTIVE_DEMUX_FLOW_BUSY; + } + + return GST_FLOW_OK; + +#if 0 + /* Check if a redirect happened */ + if (g_strcmp0 (*uri, new_playlist->uri)) { + GST_DEBUG_OBJECT (stream, "Playlist URI update : '%s' => '%s'", *uri, + new_playlist->uri); + g_free (*uri); + *uri = g_strdup (new_playlist->uri); + } +#endif } static GstFlowReturn @@ -1848,11 +1589,10 @@ gst_hls_demux_stream_update_fragment_info (GstAdaptiveDemux2Stream * stream) GstM3U8PartialSegment *part = NULL; gboolean discont; - /* If the rendition playlist needs to be updated, do it now */ - if (!hlsdemux_stream->is_variant && !hlsdemux_stream->playlist_fetched) { - ret = gst_hls_demux_stream_update_rendition_playlist (hlsdemux_stream); - if (ret != GST_FLOW_OK) - return ret; + /* Return BUSY if the playlist isn't loaded yet */ + if (!hlsdemux_stream->playlist_fetched) { + gst_hls_demux_stream_start_playlist_loading (hlsdemux_stream); + return GST_ADAPTIVE_DEMUX_FLOW_BUSY; } #ifndef GST_DISABLE_GST_DEBUG GstClockTimeDiff live_edge_dist = @@ -2089,14 +1829,7 @@ gst_hls_demux_stream_start (GstAdaptiveDemux2Stream * stream) /* Start the playlist loader */ GstHLSDemuxStream *hls_stream = GST_HLS_DEMUX_STREAM_CAST (stream); - if (hls_stream->playlistloader == NULL) { - GstAdaptiveDemux *demux = stream->demux; - - hls_stream->playlistloader = - gst_hls_demux_playlist_loader_new (demux, demux->download_helper, - hls_stream->llhls_enabled); - } - gst_hls_demux_playlist_loader_start (hls_stream->playlistloader); + gst_hls_demux_stream_start_playlist_loading (hls_stream); /* Chain up, to start the downloading */ GST_ADAPTIVE_DEMUX2_STREAM_CLASS (stream_parent_class)->start (stream); @@ -2107,14 +1840,20 @@ gst_hls_demux_stream_stop (GstAdaptiveDemux2Stream * stream) { GstHLSDemuxStream *hls_stream = GST_HLS_DEMUX_STREAM_CAST (stream); - if (hls_stream->playlistloader) + if (hls_stream->playlistloader && !hls_stream->is_variant) { + /* Don't stop the loader for the variant stream, keep it running + * until the scheduler itself is stopped so we keep updating + * the live playlist timeline */ gst_hls_demux_playlist_loader_stop (hls_stream->playlistloader); + } /* Chain up, to stop the downloading */ GST_ADAPTIVE_DEMUX2_STREAM_CLASS (stream_parent_class)->stop (stream); } -/* Returns TRUE if the rendition stream switched group-id */ +/* Called when the variant is changed, to set a new rendition + * for this stream to download. Returns TRUE if the rendition + * stream switched group-id */ static gboolean gst_hls_demux_update_rendition_stream (GstHLSDemux * hlsdemux, GstHLSDemuxStream * hls_stream, GError ** err) @@ -2166,7 +1905,6 @@ gst_hls_demux_update_rendition_stream (GstHLSDemux * hlsdemux, GST_DEBUG_OBJECT (hlsdemux, "Use replacement playlist %s", replacement_media->name); - hls_stream->playlist_fetched = FALSE; if (hls_stream->pending_rendition) { GST_ERROR_OBJECT (hlsdemux, "Already had a pending rendition switch to '%s'", diff --git a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-stream.h b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-stream.h index b54e82dd9c..366ec4e726 100644 --- a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-stream.h +++ b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-stream.h @@ -49,7 +49,7 @@ G_BEGIN_DECLS (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_HLS_DEMUX_STREAM,GstHLSDemuxStream)) #define GST_HLS_DEMUX_STREAM_CAST(obj) ((GstHLSDemuxStream *)obj) -#define GST_HLS_DEMUX_STREAM_GET_DEMUX(obj) (GST_HLS_DEMUX_CAST(GST_ADAPTIVE_DEMUX2_STREAM(stream)->demux)) +#define GST_HLS_DEMUX_STREAM_GET_DEMUX(obj) (GST_HLS_DEMUX_CAST(GST_ADAPTIVE_DEMUX2_STREAM((obj))->demux)) typedef struct _GstHLSDemuxStream GstHLSDemuxStream; typedef GstAdaptiveDemux2StreamClass GstHLSDemuxStreamClass; @@ -169,11 +169,10 @@ gst_hls_demux_stream_seek (GstAdaptiveDemux2Stream * stream, gboolean forward, void gst_hls_demux_stream_set_playlist_uri (GstHLSDemuxStream * stream, gchar * uri); -GstFlowReturn -gst_hls_demux_stream_update_media_playlist (GstHLSDemuxStream * stream, gchar ** uri, GError ** err); +void +gst_hls_demux_stream_start_playlist_loading (GstHLSDemuxStream * stream); -GstClockTime -gst_hls_demux_stream_get_playlist_reload_interval (GstHLSDemuxStream * stream); +GstFlowReturn gst_hls_demux_stream_check_current_playlist_uri (GstHLSDemuxStream * stream, gchar *uri); void gst_hls_demux_stream_clear_pending_data (GstHLSDemuxStream * hls_stream, diff --git a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux.c b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux.c index d53c625ade..3daf93023a 100644 --- a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux.c +++ b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux.c @@ -50,6 +50,9 @@ #include #include +/* FIXME: Only needed for scheduler-unlock/lock hack */ +#include + #include "gsthlselements.h" #include "gstadaptivedemuxelements.h" #include "gsthlsdemux.h" @@ -82,17 +85,13 @@ static GstStateChangeReturn gst_hls_demux_change_state (GstElement * element, GstStateChange transition); /* GstHLSDemux */ -static GstFlowReturn gst_hls_demux_update_playlist (GstHLSDemux * demux, - gboolean update, GError ** err); +static GstFlowReturn +gst_hls_demux_check_variant_playlist_loaded (GstHLSDemux * demux); static gboolean gst_hls_demux_is_live (GstAdaptiveDemux * demux); static GstClockTime gst_hls_demux_get_duration (GstAdaptiveDemux * demux); -static gint64 gst_hls_demux_get_manifest_update_interval (GstAdaptiveDemux * - demux); -static gboolean gst_hls_demux_process_manifest (GstAdaptiveDemux * demux, - GstBuffer * buf); - -static GstFlowReturn gst_hls_demux_update_manifest (GstAdaptiveDemux * demux); +static gboolean gst_hls_demux_process_initial_manifest (GstAdaptiveDemux * + demux, GstBuffer * buf); static void gst_hls_prune_time_mappings (GstHLSDemux * demux); @@ -168,6 +167,16 @@ gst_hls_demux_get_property (GObject * object, guint prop_id, } } +static gboolean +hlsdemux_requires_periodical_playlist_update_default (GstAdaptiveDemux * + demux G_GNUC_UNUSED) +{ + /* We don't need the base class to update our manifest periodically, the + * playlist loader for the main stream will do that */ + return FALSE; +} + + static void gst_hls_demux2_class_init (GstHLSDemux2Class * klass) { @@ -208,10 +217,10 @@ gst_hls_demux2_class_init (GstHLSDemux2Class * klass) adaptivedemux_class->is_live = gst_hls_demux_is_live; adaptivedemux_class->get_live_seek_range = gst_hls_demux_get_live_seek_range; adaptivedemux_class->get_duration = gst_hls_demux_get_duration; - adaptivedemux_class->get_manifest_update_interval = - gst_hls_demux_get_manifest_update_interval; - adaptivedemux_class->process_manifest = gst_hls_demux_process_manifest; - adaptivedemux_class->update_manifest = gst_hls_demux_update_manifest; + adaptivedemux_class->requires_periodical_playlist_update = + hlsdemux_requires_periodical_playlist_update_default; + adaptivedemux_class->process_manifest = + gst_hls_demux_process_initial_manifest; adaptivedemux_class->reset = gst_hls_demux_reset; adaptivedemux_class->seek = gst_hls_demux_seek; } @@ -258,7 +267,10 @@ gst_hls_demux_get_bitrate (GstHLSDemux * hlsdemux) /* FIXME !!! * - * No, there isn't a single output :D */ + * No, there isn't a single output :D. + * Until the download helper can do estimates, + * use the main variant, or a video stream if the + * main variant stream is not loading */ /* Valid because hlsdemux only has a single output */ if (demux->input_period->streams) { @@ -328,7 +340,7 @@ gst_hls_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek) gst_hls_demux_set_current_variant (hlsdemux, hlsdemux->master->iframe_variants->data); - if (gst_hls_demux_update_playlist (hlsdemux, FALSE, &err) != GST_FLOW_OK) { + if (gst_hls_demux_check_variant_playlist_loaded (hlsdemux) != GST_FLOW_OK) { GST_ELEMENT_ERROR_FROM_ERROR (hlsdemux, "Could not switch playlist", err); return FALSE; } @@ -342,7 +354,7 @@ gst_hls_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek) gst_hls_demux_set_current_variant (hlsdemux, hlsdemux->master->variants->data); - if (gst_hls_demux_update_playlist (hlsdemux, FALSE, &err) != GST_FLOW_OK) { + if (gst_hls_demux_check_variant_playlist_loaded (hlsdemux) != GST_FLOW_OK) { GST_ELEMENT_ERROR_FROM_ERROR (hlsdemux, "Could not switch playlist", err); return FALSE; } @@ -391,14 +403,6 @@ gst_hls_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek) return TRUE; } -static GstFlowReturn -gst_hls_demux_update_manifest (GstAdaptiveDemux * demux) -{ - GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux); - - return gst_hls_demux_update_playlist (hlsdemux, TRUE, NULL); -} - static GstAdaptiveDemux2Stream * create_common_hls_stream (GstHLSDemux * demux, const gchar * name) { @@ -423,15 +427,16 @@ create_main_variant_stream (GstHLSDemux * demux) stream = create_common_hls_stream (demux, "hlsstream-variant"); demux->main_stream = hlsdemux_stream = (GstHLSDemuxStream *) stream; - gst_hls_demux_stream_set_playlist_uri (hlsdemux_stream, - demux->current_variant->uri); - hlsdemux_stream->is_variant = TRUE; - hlsdemux_stream->playlist_fetched = TRUE; + /* Due to HLS manifest information being so unreliable/inconsistent, we will * create the actual tracks once we have information about the streams present * in the variant data stream */ stream->pending_tracks = TRUE; + + gst_hls_demux_stream_set_playlist_uri (hlsdemux_stream, + demux->current_variant->uri); + gst_hls_demux_stream_start_playlist_loading (hlsdemux_stream); } GstAdaptiveDemuxTrack * @@ -590,6 +595,7 @@ gst_hls_demux_setup_streams (GstAdaptiveDemux * demux) if (media_stream->current_rendition) gst_hls_rendition_stream_unref (media_stream->current_rendition); media_stream->current_rendition = gst_hls_rendition_stream_ref (media); + gst_hls_demux_stream_set_playlist_uri (media_stream, media->uri); } if (!previous_media_stream) @@ -628,12 +634,16 @@ gst_hls_demux_set_current_variant (GstHLSDemux * hlsdemux, } if (hlsdemux->main_stream) { + /* The variant stream exists, update the playlist we're loading */ gst_hls_demux_stream_set_playlist_uri (hlsdemux->main_stream, variant->uri); } } +/* Called to process the initial multi-variant (or simple playlist) + * received on the element's sinkpad */ static gboolean -gst_hls_demux_process_manifest (GstAdaptiveDemux * demux, GstBuffer * buf) +gst_hls_demux_process_initial_manifest (GstAdaptiveDemux * demux, + GstBuffer * buf) { GstHLSVariantStream *variant; GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux); @@ -694,6 +704,8 @@ gst_hls_demux_process_manifest (GstAdaptiveDemux * demux, GstBuffer * buf) GST_DEBUG_OBJECT (hlsdemux, "Manifest handled, now setting up streams"); ret = gst_hls_demux_setup_streams (demux); + if (!ret) + return FALSE; if (simple_media_playlist) { GstM3U8SeekResult seek_result; @@ -711,23 +723,33 @@ gst_hls_demux_process_manifest (GstAdaptiveDemux * demux, GstBuffer * buf) seek_result.found_partial_segment; hlsdemux->main_stream->part_idx = seek_result.part_idx; - gst_hls_demux_setup_initial_playlist (hlsdemux, simple_media_playlist); - gst_hls_update_time_mappings (hlsdemux, simple_media_playlist); - gst_hls_media_playlist_dump (simple_media_playlist); + gst_hls_demux_handle_variant_playlist_update (hlsdemux, + simple_media_playlist->uri, simple_media_playlist); } - /* get the selected media playlist (unless the initial list was one already) */ + /* If this is a multi-variant playlist, wait for the initial variant playlist to load */ if (!hlsdemux->master->is_simple) { GError *err = NULL; + GstFlowReturn flow_ret; - if (gst_hls_demux_update_playlist (hlsdemux, FALSE, &err) != GST_FLOW_OK) { + while ((flow_ret = gst_hls_demux_check_variant_playlist_loaded (hlsdemux) + == GST_ADAPTIVE_DEMUX_FLOW_BUSY)) { + if (!gst_adaptive_demux2_stream_wait_prepared (GST_ADAPTIVE_DEMUX2_STREAM + (hlsdemux->main_stream))) { + GST_DEBUG_OBJECT (demux, + "Interrupted waiting for stream to be prepared"); + return FALSE; + } + } + + if (flow_ret != GST_FLOW_OK) { GST_ELEMENT_ERROR_FROM_ERROR (demux, "Could not fetch media playlist", err); return FALSE; } } - return ret; + return TRUE; } static GstClockTime @@ -997,21 +1019,45 @@ gst_hls_update_time_mappings (GstHLSDemux * demux, } void -gst_hls_demux_setup_initial_playlist (GstHLSDemux * demux, - GstHLSMediaPlaylist * playlist) +gst_hls_demux_handle_variant_playlist_update (GstHLSDemux * demux, + const gchar * playlist_uri, GstHLSMediaPlaylist * playlist) { - GstM3U8MediaSegment *segment; + if (demux->main_stream == NULL || !demux->main_stream->playlist_fetched) { + GstM3U8MediaSegment *segment; - GST_DEBUG_OBJECT (demux, - "Setting up initial variant segment and time mapping"); + GST_DEBUG_OBJECT (demux, + "Setting up initial variant segment and time mapping"); - /* This is the initial variant playlist. We will use it to base all our timing - * from. */ - segment = g_ptr_array_index (playlist->segments, 0); - if (segment) { - segment->stream_time = 0; - gst_hls_media_playlist_recalculate_stream_time (playlist, segment); + /* This is the initial variant playlist. We will use it to base all our timing + * from. */ + segment = g_ptr_array_index (playlist->segments, 0); + if (segment) { + segment->stream_time = 0; + gst_hls_media_playlist_recalculate_stream_time (playlist, segment); + } } + + if (demux->pending_variant) { + g_assert (g_str_equal (demux->pending_variant->uri, playlist_uri)); + + gst_hls_variant_stream_unref (demux->current_variant); + /* Stealing ref */ + demux->current_variant = demux->pending_variant; + demux->pending_variant = NULL; + } + + /* Update time mappings. We only use the variant stream for collecting + * mappings since it is the reference on which rendition stream timing will + * be based. */ + gst_hls_update_time_mappings (demux, playlist); + gst_hls_media_playlist_dump (playlist); +} + +void +gst_hls_demux_handle_variant_playlist_update_error (GstHLSDemux * demux, + const gchar * playlist_uri) +{ + GST_FIXME ("Variant playlist update failed. Switch over to another variant"); } /* Reset hlsdemux in case of live synchronization loss (i.e. when a media @@ -1106,63 +1152,14 @@ gst_hls_demux_reset (GstAdaptiveDemux * ademux) } static GstFlowReturn -gst_hls_demux_update_variant_playlist (GstHLSDemux * demux, GError ** err) +gst_hls_demux_check_variant_playlist_loaded (GstHLSDemux * demux) { - GstFlowReturn ret = GST_FLOW_OK; GstHLSVariantStream *target_variant = demux->pending_variant ? demux->pending_variant : demux->current_variant; GstHLSDemuxStream *stream = demux->main_stream; - ret = gst_hls_demux_stream_update_media_playlist (stream, - &target_variant->uri, err); - if (ret != GST_FLOW_OK) - return ret; - - if (demux->pending_variant) { - gst_hls_variant_stream_unref (demux->current_variant); - /* Stealing ref */ - demux->current_variant = demux->pending_variant; - demux->pending_variant = NULL; - } - - stream->playlist_fetched = TRUE; - - return ret; -} - - -/* - * update: TRUE only when requested from parent class (via - * ::demux_update_manifest() or ::change_variant_playlist() ). - */ -static GstFlowReturn -gst_hls_demux_update_playlist (GstHLSDemux * demux, gboolean update, - GError ** err) -{ - GstFlowReturn ret = GST_FLOW_OK; - GstAdaptiveDemux *adaptive_demux = GST_ADAPTIVE_DEMUX (demux); - - GST_DEBUG_OBJECT (demux, "update:%d", update); - - /* Download and update the appropriate variant playlist (pending if any, else - * current) */ - ret = gst_hls_demux_update_variant_playlist (demux, err); - if (ret != GST_FLOW_OK) - return ret; - - if (update && gst_hls_demux_is_live (adaptive_demux)) { - GList *tmp; - GST_DEBUG_OBJECT (demux, - "LIVE, Marking rendition streams to be updated next"); - /* We're live, instruct all rendition medias to be updated next */ - for (tmp = adaptive_demux->input_period->streams; tmp; tmp = tmp->next) { - GstHLSDemuxStream *hls_stream = tmp->data; - if (!hls_stream->is_variant) - hls_stream->playlist_fetched = FALSE; - } - } - - return GST_FLOW_OK; + return gst_hls_demux_stream_check_current_playlist_uri (stream, + target_variant->uri); } gboolean @@ -1199,7 +1196,14 @@ retry_failover_protection: GST_INFO_OBJECT (demux, "Client was on %dbps, max allowed is %dbps, switching" " to bitrate %dbps", old_bandwidth, max_bitrate, new_bandwidth); - if (gst_hls_demux_update_playlist (demux, TRUE, NULL) == GST_FLOW_OK) { + GstFlowReturn flow_ret = gst_hls_demux_check_variant_playlist_loaded (demux); + + /* If the stream is still fetching the playlist, stop */ + if (flow_ret == GST_ADAPTIVE_DEMUX_FLOW_BUSY) + return TRUE; + + /* FIXME: Dead code. We need a different fail and retry mechanism */ + if (flow_ret == GST_FLOW_OK) { const gchar *main_uri; gchar *uri = new_variant->uri; @@ -1257,24 +1261,6 @@ retry_failover_protection: return TRUE; } -static gint64 -gst_hls_demux_get_manifest_update_interval (GstAdaptiveDemux * demux) -{ - GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux); - GstClockTime target_duration = 5 * GST_SECOND; - GstHLSDemuxStream *main_stream = hlsdemux->main_stream; - - if (main_stream) { - target_duration = - gst_hls_demux_stream_get_playlist_reload_interval (main_stream); - } - - GST_DEBUG_OBJECT (demux, "Returning update interval of %" GST_TIME_FORMAT, - GST_TIME_ARGS (target_duration)); - - return gst_util_uint64_scale (target_duration, G_USEC_PER_SEC, GST_SECOND); -} - static gboolean gst_hls_demux_get_live_seek_range (GstAdaptiveDemux * demux, gint64 * start, gint64 * stop) diff --git a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux.h b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux.h index e7459b5b48..fefceff92c 100644 --- a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux.h +++ b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux.h @@ -117,11 +117,14 @@ void gst_hls_demux_reset_for_lost_sync (GstHLSDemux * hlsdemux); const GstHLSKey *gst_hls_demux_get_key (GstHLSDemux * demux, const gchar * key_url, const gchar * referer, gboolean allow_cache); -void -gst_hls_demux_setup_initial_playlist (GstHLSDemux * demux, - GstHLSMediaPlaylist * playlist); +void gst_hls_demux_handle_variant_playlist_update (GstHLSDemux * demux, + const gchar *playlist_uri, GstHLSMediaPlaylist * playlist); +void gst_hls_demux_handle_variant_playlist_update_error (GstHLSDemux * demux, + const gchar *playlist_uri); gboolean gst_hls_demux_change_variant_playlist (GstHLSDemux * demux, guint max_bitrate, gboolean * changed); +GstFlowReturn gst_hls_demux_update_variant_playlist (GstHLSDemux * demux, + GError ** err); void gst_hls_demux_add_time_mapping (GstHLSDemux * demux, gint64 dsn, GstClockTimeDiff stream_time, GDateTime * pdt);