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: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3883>
This commit is contained in:
Jan Schmidt 2022-12-27 03:04:36 +11:00 committed by GStreamer Marge Bot
parent 93d92d5ddf
commit 083538df9e
6 changed files with 242 additions and 509 deletions

View file

@ -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;

View file

@ -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

View file

@ -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'",

View file

@ -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,

View file

@ -50,6 +50,9 @@
#include <gst/base/gsttypefindhelper.h>
#include <gst/tag/tag.h>
/* FIXME: Only needed for scheduler-unlock/lock hack */
#include <gstadaptivedemux-private.h>
#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)

View file

@ -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);