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 cff0b11f67140e90566f7ff135b45bf8d8a08bab..fc1e6c44cc3c472f4e89e5ad86eb3fe588222cd2 100644 --- a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-stream.c +++ b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-stream.c @@ -1952,8 +1952,11 @@ gst_hls_demux_stream_select_bitrate (GstAdaptiveDemux2Stream * stream, /* Handle variant streams */ GST_DEBUG_OBJECT (hlsdemux, "Checking playlist change for main variant stream"); - gst_hls_demux_change_variant_playlist (hlsdemux, bitrate / MAX (1.0, - ABS (play_rate)), &changed); + if (!gst_hls_demux_change_variant_playlist (hlsdemux, + hlsdemux->current_variant->iframe, + bitrate / MAX (1.0, ABS (play_rate)), &changed)) { + GST_ERROR_OBJECT (hlsdemux, "Failed to choose a new variant to play"); + } GST_DEBUG_OBJECT (hlsdemux, "Returning changed: %d", changed); return changed; diff --git a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux.c b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux.c index 2756d4d652e4b51675915790de652b52a9f4f930..c9989c1b7f1d6409bc2dcfc033b664aaee0afbac 100644 --- a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux.c +++ b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux.c @@ -358,17 +358,16 @@ gst_hls_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek) && rate < -1.0 && old_rate >= -1.0 && old_rate <= 1.0) { /* Switch to I-frame variant */ - gst_hls_demux_set_current_variant (hlsdemux, - hlsdemux->master->iframe_variants->data); + if (!gst_hls_demux_change_variant_playlist (hlsdemux, TRUE, + bitrate / ABS (rate), NULL)) + return FALSE; } else if (rate > -1.0 && rate <= 1.0 && (old_rate < -1.0 || old_rate > 1.0)) { /* Switch to normal variant */ - gst_hls_demux_set_current_variant (hlsdemux, - hlsdemux->master->variants->data); + if (!gst_hls_demux_change_variant_playlist (hlsdemux, FALSE, bitrate, NULL)) + return FALSE; } - gst_hls_demux_change_variant_playlist (hlsdemux, bitrate / ABS (rate), NULL); - /* Of course the playlist isn't loaded as soon as we ask - we need to wait */ GstFlowReturn flow_ret = gst_hls_demux_wait_for_variant_playlist (hlsdemux); if (flow_ret == GST_FLOW_FLUSHING) @@ -716,19 +715,25 @@ gst_hls_demux_process_initial_manifest (GstAdaptiveDemux * demux, } else if (hlsdemux->start_bitrate > 0) { variant = gst_hls_master_playlist_get_variant_for_bitrate (hlsdemux->master, - NULL, hlsdemux->start_bitrate, demux->min_bitrate); + FALSE, hlsdemux->start_bitrate, demux->min_bitrate, + hlsdemux->failed_variants); } else { variant = gst_hls_master_playlist_get_variant_for_bitrate (hlsdemux->master, - NULL, demux->connection_speed, demux->min_bitrate); + FALSE, demux->connection_speed, demux->min_bitrate, + hlsdemux->failed_variants); } - if (variant) { - GST_INFO_OBJECT (hlsdemux, - "Manifest processed, initial variant selected : `%s`", variant->name); - gst_hls_demux_set_current_variant (hlsdemux, variant); + if (variant == NULL) { + GST_ELEMENT_ERROR (demux, STREAM, FAILED, + (_("Internal data stream error.")), + ("Could not find an initial variant to play")); } + GST_INFO_OBJECT (hlsdemux, + "Manifest processed, initial variant selected : `%s`", variant->name); + gst_hls_demux_set_current_variant (hlsdemux, variant); + GST_DEBUG_OBJECT (hlsdemux, "Manifest handled, now setting up streams"); ret = gst_hls_demux_setup_streams (demux); @@ -1057,12 +1062,34 @@ gst_hls_demux_handle_variant_playlist_update (GstHLSDemux * demux, } if (demux->pending_variant) { + /* The pending variant must always match the one that just got updated: + * The loader should only do a callback for the most recently set URI */ g_assert (g_str_equal (demux->pending_variant->uri, playlist_uri)); + gboolean changed = (demux->pending_variant != demux->current_variant); + gst_hls_variant_stream_unref (demux->current_variant); /* Stealing ref */ demux->current_variant = demux->pending_variant; demux->pending_variant = NULL; + + if (changed) { + GstAdaptiveDemux *basedemux = GST_ADAPTIVE_DEMUX (demux); + const gchar *main_uri = + gst_adaptive_demux_get_manifest_ref_uri (basedemux); + gchar *uri = demux->current_variant->uri; + gint new_bandwidth = demux->current_variant->bandwidth; + + gst_element_post_message (GST_ELEMENT_CAST (demux), + gst_message_new_element (GST_OBJECT_CAST (demux), + gst_structure_new (GST_ADAPTIVE_DEMUX_STATISTICS_MESSAGE_NAME, + "manifest-uri", G_TYPE_STRING, + main_uri, "uri", G_TYPE_STRING, + uri, "bitrate", G_TYPE_INT, new_bandwidth, NULL))); + + /* Mark discont on the next packet after switching variant */ + GST_ADAPTIVE_DEMUX2_STREAM (demux->main_stream)->discont = TRUE; + } } /* Update time mappings. We only use the variant stream for collecting @@ -1076,7 +1103,22 @@ 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"); + if (demux->pending_variant) { + GST_DEBUG_OBJECT (demux, "Variant playlist update failed. " + "Marking variant URL %s as failed and switching over to another variant", + playlist_uri); + + /* The pending variant must always match the one that just got updated: + * The loader should only do a callback for the most recently set URI */ + g_assert (g_str_equal (demux->pending_variant->uri, playlist_uri)); + g_assert (g_list_find (demux->failed_variants, + demux->pending_variant) == NULL); + + /* Steal pending_variant ref into the failed variants */ + demux->failed_variants = + g_list_prepend (demux->failed_variants, demux->pending_variant); + demux->pending_variant = NULL; + } } /* Reset hlsdemux in case of live synchronization loss (i.e. when a media @@ -1163,6 +1205,11 @@ gst_hls_demux_reset (GstAdaptiveDemux * ademux) gst_hls_variant_stream_unref (demux->pending_variant); demux->pending_variant = NULL; } + if (demux->failed_variants != NULL) { + g_list_free_full (demux->failed_variants, + (GDestroyNotify) gst_hls_variant_stream_unref); + demux->failed_variants = NULL; + } g_list_free_full (demux->mappings, (GDestroyNotify) gst_hls_time_map_free); demux->mappings = NULL; @@ -1182,101 +1229,47 @@ gst_hls_demux_check_variant_playlist_loaded (GstHLSDemux * demux) } gboolean -gst_hls_demux_change_variant_playlist (GstHLSDemux * demux, guint max_bitrate, - gboolean * changed) +gst_hls_demux_change_variant_playlist (GstHLSDemux * demux, + gboolean iframe_variant, guint max_bitrate, gboolean * changed) { - GstHLSVariantStream *lowest_variant, *lowest_ivariant; - GstHLSVariantStream *previous_variant, *new_variant; - gint old_bandwidth, new_bandwidth; GstAdaptiveDemux *adaptive_demux = GST_ADAPTIVE_DEMUX_CAST (demux); - GstAdaptiveDemux2Stream *stream; g_return_val_if_fail (demux->main_stream != NULL, FALSE); - stream = (GstAdaptiveDemux2Stream *) demux->main_stream; - /* Make sure we keep a reference in case we need to switch back */ - previous_variant = gst_hls_variant_stream_ref (demux->current_variant); - new_variant = + if (changed) + *changed = FALSE; + + /* Make sure we keep a reference for the debug output below */ + GstHLSVariantStream *new_variant = gst_hls_master_playlist_get_variant_for_bitrate (demux->master, - demux->current_variant, max_bitrate, adaptive_demux->min_bitrate); + iframe_variant, max_bitrate, adaptive_demux->min_bitrate, + demux->failed_variants); + + /* We're out of available variants to use */ + if (new_variant == NULL) { + return FALSE; + } -retry_failover_protection: - old_bandwidth = previous_variant->bandwidth; - new_bandwidth = new_variant->bandwidth; + GstHLSVariantStream *previous_variant = + gst_hls_variant_stream_ref (demux->current_variant); /* Don't do anything else if the playlist is the same */ - if (new_bandwidth == old_bandwidth) { + if (new_variant == previous_variant) { gst_hls_variant_stream_unref (previous_variant); return TRUE; } gst_hls_demux_set_current_variant (demux, new_variant); - GST_INFO_OBJECT (demux, "Client was on %dbps, max allowed is %dbps, switching" - " to bitrate %dbps", old_bandwidth, max_bitrate, new_bandwidth); - - 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; - - main_uri = gst_adaptive_demux_get_manifest_ref_uri (adaptive_demux); - gst_element_post_message (GST_ELEMENT_CAST (demux), - gst_message_new_element (GST_OBJECT_CAST (demux), - gst_structure_new (GST_ADAPTIVE_DEMUX_STATISTICS_MESSAGE_NAME, - "manifest-uri", G_TYPE_STRING, - main_uri, "uri", G_TYPE_STRING, - uri, "bitrate", G_TYPE_INT, new_bandwidth, NULL))); - if (changed) - *changed = TRUE; - stream->discont = TRUE; - } else if (gst_adaptive_demux2_is_running (GST_ADAPTIVE_DEMUX_CAST (demux))) { - GstHLSVariantStream *failover_variant = NULL; - GList *failover; - - GST_INFO_OBJECT (demux, "Unable to update playlist. Switching back"); - - /* we find variants by bitrate by going from highest to lowest, so it's - * possible that there's another variant with the same bitrate before the - * one selected which we can use as failover */ - failover = g_list_find (demux->master->variants, new_variant); - if (failover != NULL) - failover = failover->prev; - if (failover != NULL) - failover_variant = failover->data; - if (failover_variant && new_bandwidth == failover_variant->bandwidth) { - new_variant = failover_variant; - goto retry_failover_protection; - } - - gst_hls_demux_set_current_variant (demux, previous_variant); + gint new_bandwidth = new_variant->bandwidth; - /* Try a lower bitrate (or stop if we just tried the lowest) */ - if (previous_variant->iframe) { - lowest_ivariant = demux->master->iframe_variants->data; - if (new_bandwidth == lowest_ivariant->bandwidth) { - gst_hls_variant_stream_unref (previous_variant); - return FALSE; - } - } else { - lowest_variant = demux->master->variants->data; - if (new_bandwidth == lowest_variant->bandwidth) { - gst_hls_variant_stream_unref (previous_variant); - return FALSE; - } - } - gst_hls_variant_stream_unref (previous_variant); - return gst_hls_demux_change_variant_playlist (demux, new_bandwidth - 1, - changed); - } + GST_INFO_OBJECT (demux, "Client was on %dbps, max allowed is %dbps, switching" + " to bitrate %dbps", previous_variant->bandwidth, max_bitrate, + new_bandwidth); gst_hls_variant_stream_unref (previous_variant); + if (changed) + *changed = TRUE; return TRUE; } diff --git a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux.h b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux.h index fefceff92cd23a0cb494dbc5f523cf61904a5afd..4005c6a49252b5e0ec2cf807e388e3cafe38eac5 100644 --- a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux.h +++ b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux.h @@ -88,14 +88,17 @@ struct _GstHLSDemux2 GHashTable *keys; GMutex keys_lock; - /* FIXME: check locking, protected automatically by manifest_lock already? */ - /* The master playlist with the available variant streams */ + /* The master playlist with the available variant streams, + * created at demuxer start based on the input multivariant playlist */ GstHLSMasterPlaylist *master; GstHLSVariantStream *current_variant; - /* The variant to switch to */ + /* The variant we're switching to (currently being loaded by the playlist loader) */ GstHLSVariantStream *pending_variant; + /* List of failed variants that should be ignored */ + GList *failed_variants; + GstHLSDemuxStream *main_stream; /* Time Mappings (GstHLSTimeMap) */ @@ -122,7 +125,7 @@ void gst_hls_demux_handle_variant_playlist_update (GstHLSDemux * demux, 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); + gboolean iframe_variant, guint max_bitrate, gboolean * changed); GstFlowReturn gst_hls_demux_update_variant_playlist (GstHLSDemux * demux, GError ** err); diff --git a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/m3u8.c b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/m3u8.c index 04ce298fb85989a046442e8dee38bc2da919c7a3..fc12024d99711b28e57855355d2b1bc9a722dd98 100644 --- a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/m3u8.c +++ b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/m3u8.c @@ -3276,20 +3276,26 @@ hls_master_playlist_new_from_data (gchar * data, const gchar * base_uri) GstHLSVariantStream * hls_master_playlist_get_variant_for_bitrate (GstHLSMasterPlaylist * - playlist, GstHLSVariantStream * current_variant, guint bitrate, - guint min_bitrate) + playlist, gboolean iframe_variant, guint bitrate, + guint min_bitrate, GList * failed_variants) { - GstHLSVariantStream *variant = current_variant; - GstHLSVariantStream *variant_by_min = current_variant; + GstHLSVariantStream *variant = NULL; + GstHLSVariantStream *variant_by_min = NULL; GList *l; /* variant lists are sorted low to high, so iterate from highest to lowest */ - if (current_variant == NULL || !current_variant->iframe) - l = g_list_last (playlist->variants); - else + if (iframe_variant && playlist->iframe_variants != NULL) l = g_list_last (playlist->iframe_variants); + else + l = g_list_last (playlist->variants); while (l != NULL) { + if (g_list_find (failed_variants, l->data) != NULL) { + /* Ignore all variants from the failed list */ + l = l->prev; + continue; + } + variant = l->data; if (variant->bandwidth >= min_bitrate) variant_by_min = variant; diff --git a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/m3u8.h b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/m3u8.h index 8eb6d527e7c07010de5bf850ab44df3ea4a908d7..260e93ae608ca2302fdc689c1036aa2357bd1020 100644 --- a/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/m3u8.h +++ b/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/m3u8.h @@ -495,9 +495,10 @@ GstHLSMasterPlaylist * hls_master_playlist_new_from_data (gchar * data, #define gst_hls_master_playlist_get_variant_for_bitrate hls_master_playlist_get_variant_for_bitrate GstHLSVariantStream * hls_master_playlist_get_variant_for_bitrate (GstHLSMasterPlaylist * playlist, - GstHLSVariantStream * current_variant, - guint bitrate, - guint min_bitrate); + gboolean iframe_variant, + guint bitrate, + guint min_bitrate, + GList * failed_variants); #define gst_hls_master_playlist_get_common_caps hls_master_playlist_get_common_caps GstCaps * hls_master_playlist_get_common_caps (GstHLSMasterPlaylist *playlist); diff --git a/subprojects/gst-plugins-good/tests/check/elements/hlsdemux_m3u8.c b/subprojects/gst-plugins-good/tests/check/elements/hlsdemux_m3u8.c index c4e4ae2a185095224473cf61f930777e05bb3874..26c08442dce6db300f3945562f9452c0a14a645d 100644 --- a/subprojects/gst-plugins-good/tests/check/elements/hlsdemux_m3u8.c +++ b/subprojects/gst-plugins-good/tests/check/elements/hlsdemux_m3u8.c @@ -781,22 +781,27 @@ GST_START_TEST (test_get_stream_for_bitrate) GstHLSVariantStream *stream; master = load_master_playlist (VARIANT_PLAYLIST); - stream = gst_hls_master_playlist_get_variant_for_bitrate (master, NULL, 0, 0); + stream = + gst_hls_master_playlist_get_variant_for_bitrate (master, FALSE, 0, 0, + NULL); assert_equals_int (stream->bandwidth, 65000); stream = - gst_hls_master_playlist_get_variant_for_bitrate (master, NULL, - G_MAXINT32, 0); + gst_hls_master_playlist_get_variant_for_bitrate (master, FALSE, + G_MAXINT32, 0, NULL); assert_equals_int (stream->bandwidth, 768000); stream = - gst_hls_master_playlist_get_variant_for_bitrate (master, NULL, 300000, 0); + gst_hls_master_playlist_get_variant_for_bitrate (master, FALSE, 300000, 0, + NULL); assert_equals_int (stream->bandwidth, 256000); stream = - gst_hls_master_playlist_get_variant_for_bitrate (master, NULL, 500000, 0); + gst_hls_master_playlist_get_variant_for_bitrate (master, FALSE, 500000, 0, + NULL); assert_equals_int (stream->bandwidth, 256000); stream = - gst_hls_master_playlist_get_variant_for_bitrate (master, NULL, 255000, 0); + gst_hls_master_playlist_get_variant_for_bitrate (master, FALSE, 255000, 0, + NULL); assert_equals_int (stream->bandwidth, 128000); gst_hls_master_playlist_unref (master);