From 629c811e841a7647027e58103f191990732d2b02 Mon Sep 17 00:00:00 2001
From: Astound <kirito@disroot.org>
Date: Fri, 26 Jan 2024 01:12:54 +0800
Subject: av-merge: Retry failed requests Should reduce playback stalling

---
 youtube/static/js/av-merge.js | 48 +++++++++++++++++++++++++++++++++----------
 1 file changed, 37 insertions(+), 11 deletions(-)

diff --git a/youtube/static/js/av-merge.js b/youtube/static/js/av-merge.js
index 865c96b..3226244 100644
--- a/youtube/static/js/av-merge.js
+++ b/youtube/static/js/av-merge.js
@@ -204,6 +204,7 @@ Stream.prototype.setup = async function(){
             this.url,
             this.initRange.start,
             this.indexRange.end,
+            'Initialization+index segments',
         ).then(
             (buffer) => {
                 let init_end = this.initRange.end - this.initRange.start + 1;
@@ -219,6 +220,7 @@ Stream.prototype.setup = async function(){
             this.url,
             this.initRange.start,
             this.initRange.end,
+            'Initialization segment',
         ).then(this.setupInitSegment.bind(this));
 
         // sidx (segment index) table
@@ -226,6 +228,7 @@ Stream.prototype.setup = async function(){
             this.url,
             this.indexRange.start,
             this.indexRange.end,
+            'Index segment',
         ).then(this.setupSegmentIndex.bind(this));
     }
 }
@@ -485,6 +488,7 @@ Stream.prototype.fetchSegment = function(segmentIdx) {
         this.url,
         entry.start,
         entry.end,
+        String(this.streamType) + ' segment ' + String(segmentIdx),
     ).then(this.appendSegment.bind(this, segmentIdx));
 }
 Stream.prototype.fetchSegmentIfNeeded = function(segmentIdx) {
@@ -523,27 +527,49 @@ Stream.prototype.reportError = function(...args) {
 
 // https://gomakethings.com/promise-based-xhr/
 // https://stackoverflow.com/a/30008115
-function fetchRange(url, start, end) {
+// http://lofi.limo/blog/retry-xmlhttprequest-carefully
+function fetchRange(url, start, end, debugInfo) {
     return new Promise((resolve, reject) => {
+        let retryCount = 0;
         let xhr = new XMLHttpRequest();
+        function onFailure(err, message, maxRetries=5){
+            message = debugInfo + ': ' + message + ' - Err: ' + String(err);
+            retryCount++;
+            if (retryCount > maxRetries || xhr.status == 403){
+                reportError('fetchRange error while fetching ' + message);
+                reject(message);
+                return;
+            } else {
+                reportWarning('Failed to fetch ' + message
+                    + '. Attempting retry '
+                    + String(retryCount) +'/' + String(maxRetries));
+            }
+
+            // Retry in 1 second, doubled for each next retry
+            setTimeout(function(){
+                xhr.open('get',url);
+                xhr.send();
+            }, 1000*Math.pow(2,(retryCount-1)));
+        }
         xhr.open('get', url);
+        xhr.timeout = 15000;
         xhr.responseType = 'arraybuffer';
         xhr.setRequestHeader('Range', 'bytes=' + start + '-' + end);
-        xhr.onload = function () {
+        xhr.onload = function (e) {
             if (xhr.status >= 200 && xhr.status < 300) {
                 resolve(xhr.response);
             } else {
-                reject({
-                    status: xhr.status,
-                    statusText: xhr.statusText
-                });
+                onFailure(e,
+                    'Status '
+                    + String(xhr.status) + ' ' + String(xhr.statusText)
+                );
             }
         };
-        xhr.onerror = function () {
-            reject({
-                status: xhr.status,
-                statusText: xhr.statusText
-            });
+        xhr.onerror = function (event) {
+            onFailure(e, 'Network error');
+        };
+        xhr.ontimeout = function (event){
+            onFailure(null, 'Timeout (15s)', maxRetries=1);
         };
         xhr.send();
     });
-- 
cgit v1.2.3