From 6d0c0d5d298e72eff880ede18f4194bfee4b9b47 Mon Sep 17 00:00:00 2001
From: Thibault Saunier <tsaunier@igalia.com>
Date: Wed, 11 Dec 2024 22:00:56 -0300
Subject: [PATCH] adaptivedemux2: Expose a `max-retries` property

So the user can configure what is the maximum number of time HTTP requests can
be performed

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/8128>
---
 .../docs/gst_plugins_cache.json               | 14 +++++++
 .../adaptivedemux2/gstadaptivedemux-private.h |  6 ++-
 .../adaptivedemux2/gstadaptivedemux-stream.c  |  8 ++--
 .../ext/adaptivedemux2/gstadaptivedemux.c     | 37 ++++++++++++++++++-
 .../ext/adaptivedemux2/gstadaptivedemux.h     |  1 +
 .../hls/gsthlsdemux-playlist-loader.c         |  3 +-
 6 files changed, 63 insertions(+), 6 deletions(-)

diff --git a/subprojects/gst-plugins-good/docs/gst_plugins_cache.json b/subprojects/gst-plugins-good/docs/gst_plugins_cache.json
index f856f950ab..16f90e9837 100644
--- a/subprojects/gst-plugins-good/docs/gst_plugins_cache.json
+++ b/subprojects/gst-plugins-good/docs/gst_plugins_cache.json
@@ -1404,6 +1404,20 @@
                         "type": "guint64",
                         "writable": true
                     },
+                    "max-retries": {
+                        "blurb": "Maximum number of retries for HTTP requests (-1=infinite)",
+                        "conditionally-available": false,
+                        "construct": false,
+                        "construct-only": false,
+                        "controllable": false,
+                        "default": "3",
+                        "max": "2147483647",
+                        "min": "-1",
+                        "mutable": "null",
+                        "readable": true,
+                        "type": "gint",
+                        "writable": true
+                    },
                     "min-bitrate": {
                         "blurb": "Minimum bitrate to use when switching to alternates (bits/s)",
                         "conditionally-available": false,
diff --git a/subprojects/gst-plugins-good/ext/adaptivedemux2/gstadaptivedemux-private.h b/subprojects/gst-plugins-good/ext/adaptivedemux2/gstadaptivedemux-private.h
index b80a92ad3c..b72219bf11 100644
--- a/subprojects/gst-plugins-good/ext/adaptivedemux2/gstadaptivedemux-private.h
+++ b/subprojects/gst-plugins-good/ext/adaptivedemux2/gstadaptivedemux-private.h
@@ -36,7 +36,6 @@
 G_BEGIN_DECLS
 
 #define NUM_LOOKBACK_FRAGMENTS 3
-#define MAX_DOWNLOAD_ERROR_COUNT 3
 
 /* Internal, so not using GST_FLOW_CUSTOM_SUCCESS_N */
 #define GST_ADAPTIVE_DEMUX_FLOW_SWITCH (GST_FLOW_CUSTOM_SUCCESS_2 + 2)
@@ -164,6 +163,11 @@ struct _GstAdaptiveDemuxPrivate
    * Head is the period being outputted, or to be outputted first
    * Tail is where new streams get added */
   GQueue *periods;
+
+  /* The maximum number of times HTTP request can be required before considering
+   * failed */
+  gint max_retries;
+
 };
 
 static inline gboolean gst_adaptive_demux_scheduler_lock(GstAdaptiveDemux *d)
diff --git a/subprojects/gst-plugins-good/ext/adaptivedemux2/gstadaptivedemux-stream.c b/subprojects/gst-plugins-good/ext/adaptivedemux2/gstadaptivedemux-stream.c
index 2aa23bb5c8..7c1bf82837 100644
--- a/subprojects/gst-plugins-good/ext/adaptivedemux2/gstadaptivedemux-stream.c
+++ b/subprojects/gst-plugins-good/ext/adaptivedemux2/gstadaptivedemux-stream.c
@@ -1212,6 +1212,7 @@ on_download_error (DownloadRequest * request, DownloadRequestState state,
       request->state, last_status_code,
       stream->download_error_count, live, stream->download_error_retry);
 
+  gint max_retries = gst_adaptive_demux_max_retries (demux);
   if (!stream->download_error_retry && ((last_status_code / 100 == 4 && live)
           || last_status_code / 100 == 5)) {
     /* 4xx/5xx */
@@ -1255,7 +1256,7 @@ on_download_error (DownloadRequest * request, DownloadRequestState state,
       }
     }
 
-    if (++stream->download_error_count >= MAX_DOWNLOAD_ERROR_COUNT) {
+    if (max_retries >= 0 && ++stream->download_error_count >= max_retries) {
       /* looks like there is no way of knowing when a live stream has ended
        * Have to assume we are falling behind and cause a manifest reload */
       GST_DEBUG_OBJECT (stream, "Converting error of live stream to EOS");
@@ -1271,7 +1272,7 @@ on_download_error (DownloadRequest * request, DownloadRequestState state,
     return;
   } else {
     /* retry same segment */
-    if (++stream->download_error_count > MAX_DOWNLOAD_ERROR_COUNT) {
+    if (max_retries >= 0 && ++stream->download_error_count > max_retries) {
       gst_adaptive_demux2_stream_error (stream);
       return;
     }
@@ -2007,7 +2008,8 @@ gst_adaptive_demux2_stream_load_a_fragment (GstAdaptiveDemux2Stream * stream)
     default:
       if (ret <= GST_FLOW_ERROR) {
         GST_WARNING_OBJECT (demux, "Error while downloading fragment");
-        if (++stream->download_error_count > MAX_DOWNLOAD_ERROR_COUNT) {
+        gint max_retries = gst_adaptive_demux_max_retries (demux);
+        if (max_retries >= 0 && ++stream->download_error_count > max_retries) {
           gst_adaptive_demux2_stream_error (stream);
           return FALSE;
         }
diff --git a/subprojects/gst-plugins-good/ext/adaptivedemux2/gstadaptivedemux.c b/subprojects/gst-plugins-good/ext/adaptivedemux2/gstadaptivedemux.c
index 40b4706cfb..92d52147d3 100644
--- a/subprojects/gst-plugins-good/ext/adaptivedemux2/gstadaptivedemux.c
+++ b/subprojects/gst-plugins-good/ext/adaptivedemux2/gstadaptivedemux.c
@@ -113,6 +113,7 @@ GST_DEBUG_CATEGORY_EXTERN (adaptivedemux2_debug);
 #define GST_CAT_DEFAULT adaptivedemux2_debug
 
 #define DEFAULT_FAILED_COUNT 3
+#define DEFAULT_MAX_RETRIES 3
 #define DEFAULT_CONNECTION_BITRATE 0
 #define DEFAULT_BANDWIDTH_TARGET_RATIO 0.8f
 
@@ -133,6 +134,7 @@ enum
 {
   PROP_0,
   PROP_CONNECTION_SPEED,
+  PROP_MAX_RETRIES,
   PROP_BANDWIDTH_TARGET_RATIO,
   PROP_CONNECTION_BITRATE,
   PROP_MIN_BITRATE,
@@ -288,6 +290,11 @@ gst_adaptive_demux_set_property (GObject * object, guint prop_id,
   GST_OBJECT_LOCK (demux);
 
   switch (prop_id) {
+    case PROP_MAX_RETRIES:
+      demux->priv->max_retries = g_value_get_int (value);
+      GST_DEBUG_OBJECT (demux, "Maximum retries set to %u",
+          demux->priv->max_retries);
+      break;
     case PROP_CONNECTION_SPEED:
       demux->connection_speed = g_value_get_uint (value) * 1000;
       GST_DEBUG_OBJECT (demux, "Connection speed set to %u",
@@ -339,6 +346,9 @@ gst_adaptive_demux_get_property (GObject * object, guint prop_id,
   GST_OBJECT_LOCK (demux);
 
   switch (prop_id) {
+    case PROP_MAX_RETRIES:
+      g_value_set_int (value, demux->priv->max_retries);
+      break;
     case PROP_CONNECTION_SPEED:
       g_value_set_uint (value, demux->connection_speed / 1000);
       break;
@@ -506,6 +516,20 @@ gst_adaptive_demux_class_init (GstAdaptiveDemuxClass * klass)
           G_PARAM_READABLE | GST_PARAM_MUTABLE_PLAYING |
           G_PARAM_STATIC_STRINGS));
 
+  /**
+   * GstAdaptiveDemux2:max-retries:
+   *
+   * Maximum number of times HTTP request can be retried before considering
+   * the request as failed (-1=infinite)
+   *
+   * Since: 1.26
+   */
+  g_object_class_install_property (gobject_class, PROP_MAX_RETRIES,
+      g_param_spec_int ("max-retries", "Maximum Retries",
+          "Maximum number of retries for HTTP requests (-1=infinite)",
+          -1, G_MAXUINT, DEFAULT_MAX_RETRIES,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
   gst_element_class_add_static_pad_template (gstelement_class,
       &gst_adaptive_demux_audiosrc_template);
   gst_element_class_add_static_pad_template (gstelement_class,
@@ -588,6 +612,7 @@ gst_adaptive_demux_init (GstAdaptiveDemux * demux,
 
   demux->current_level_time_video = DEFAULT_CURRENT_LEVEL_TIME_VIDEO;
   demux->current_level_time_audio = DEFAULT_CURRENT_LEVEL_TIME_AUDIO;
+  demux->priv->max_retries = DEFAULT_MAX_RETRIES;
 
   gst_element_add_pad (GST_ELEMENT (demux), demux->sinkpad);
 
@@ -3004,7 +3029,7 @@ gst_adaptive_demux_manifest_update_cb (GstAdaptiveDemux * demux)
   } else {
     demux->priv->update_failed_count++;
 
-    if (demux->priv->update_failed_count <= DEFAULT_FAILED_COUNT) {
+    if (demux->priv->update_failed_count <= demux->priv->max_retries) {
       GST_WARNING_OBJECT (demux, "Could not update the playlist, flow: %s",
           gst_flow_get_name (ret));
     } else {
@@ -3956,3 +3981,13 @@ gst_adaptive_demux_get_loop (GstAdaptiveDemux * demux)
 {
   return gst_adaptive_demux_loop_ref (demux->priv->scheduler_task);
 }
+
+gint
+gst_adaptive_demux_max_retries (GstAdaptiveDemux * self)
+{
+  GST_OBJECT_LOCK (self);
+  gint res = self->priv->max_retries;
+  GST_OBJECT_UNLOCK (self);
+
+  return res;
+}
diff --git a/subprojects/gst-plugins-good/ext/adaptivedemux2/gstadaptivedemux.h b/subprojects/gst-plugins-good/ext/adaptivedemux2/gstadaptivedemux.h
index b0ed01868e..50956206f1 100644
--- a/subprojects/gst-plugins-good/ext/adaptivedemux2/gstadaptivedemux.h
+++ b/subprojects/gst-plugins-good/ext/adaptivedemux2/gstadaptivedemux.h
@@ -481,6 +481,7 @@ gdouble gst_adaptive_demux_play_rate (GstAdaptiveDemux *demux);
 
 void gst_adaptive_demux2_manual_manifest_update (GstAdaptiveDemux * demux);
 GstAdaptiveDemuxLoop *gst_adaptive_demux_get_loop (GstAdaptiveDemux *demux);
+gint gst_adaptive_demux_max_retries (GstAdaptiveDemux *self);
 
 G_END_DECLS
 
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 e5da0ba371..763c1bb4a6 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
@@ -470,7 +470,8 @@ static void
 handle_download_error (GstHLSDemuxPlaylistLoader * pl,
     GstHLSDemuxPlaylistLoaderPrivate * priv)
 {
-  if (++priv->download_error_count > MAX_DOWNLOAD_ERROR_COUNT) {
+  gint max_retries = gst_adaptive_demux_max_retries (priv->demux);
+  if (max_retries >= 0 && ++priv->download_error_count > max_retries) {
     GST_DEBUG_OBJECT (pl,
         "Reached %d download failures on URI %s. Reporting the failure",
         priv->download_error_count, priv->loading_playlist_uri);