From 799d6d5ae3bba694cfc8a3109d15c046ec4c5fd8 Mon Sep 17 00:00:00 2001 From: FredTsang <17154608+FredZeng@users.noreply.github.com> Date: Wed, 20 Sep 2023 17:35:41 +0800 Subject: [PATCH 1/5] feat: support 'xhrTimeout' option --- docs/api.md | 1 + src/config.js | 3 ++- src/io/fetch-stream-loader.js | 31 +++++++++++++++++++++++++++++-- src/io/io-controller.js | 1 - src/io/xhr-moz-chunked-loader.js | 14 ++++++++++++++ src/io/xhr-range-loader.js | 14 ++++++++++++++ 6 files changed, 60 insertions(+), 4 deletions(-) diff --git a/docs/api.md b/docs/api.md index 5b5d4d25..bd166da7 100644 --- a/docs/api.md +++ b/docs/api.md @@ -91,6 +91,7 @@ In multipart mode, `duration` `filesize` `url` field in `MediaDataSource` struct | `reuseRedirectedURL?` | `boolean` | `false` | Reuse 301/302 redirected url for subsequence request like seek, reconnect, etc. | | `referrerPolicy?` | `string` | `no-referrer-when-downgrade` | Indicates the [Referrer Policy][] when using FetchStreamLoader | | `headers?` | `object` | `undefined` | Indicates additional headers that will be added to request | +| `xhrTimeout?` | `number` | `Infinity` | Timeout setting for IO Connection, in **milliseconds**. `xhrTimeout` must greater than zero. | [Referrer Policy]: https://w3c.github.io/webappsec-referrer-policy/#referrer-policy diff --git a/src/config.js b/src/config.js index 7dd6a79d..fe835965 100644 --- a/src/config.js +++ b/src/config.js @@ -50,7 +50,8 @@ export const defaultConfig = { // referrerPolicy: leave as unspecified headers: undefined, - customLoader: undefined + customLoader: undefined, + xhrTimeout: Infinity, }; export function createDefaultConfig() { diff --git a/src/io/fetch-stream-loader.js b/src/io/fetch-stream-loader.js index c04da5b0..860d32ba 100644 --- a/src/io/fetch-stream-loader.js +++ b/src/io/fetch-stream-loader.js @@ -121,6 +121,12 @@ class FetchStreamLoader extends BaseLoader { if (self.AbortController) { this._abortController = new self.AbortController(); params.signal = this._abortController.signal; + + if (this._config.xhrTimeout !== Infinity && this._config.xhrTimeout > 0) { + this._timeoutId = self.setTimeout(() => { + this.abort('timeout'); + }, this._config.xhrTimeout); + } } this._status = LoaderStatus.kConnecting; @@ -130,6 +136,9 @@ class FetchStreamLoader extends BaseLoader { res.body.cancel(); return; } + + this.clearXhrTimeout(); + if (res.ok && (res.status >= 200 && res.status <= 299)) { if (res.url !== seekConfig.url) { if (this._onURLRedirect) { @@ -159,9 +168,19 @@ class FetchStreamLoader extends BaseLoader { } }).catch((e) => { if (this._abortController && this._abortController.signal.aborted) { + if (this._abortController.signal.reason === 'timeout') { + this._status = LoaderStatus.kError; + if (this._onError) { + this._onError(LoaderErrors.CONNECTING_TIMEOUT, {code: -1, msg: 'fetch stream loader connecting timeout'}); + } else { + throw new RuntimeException('FetchStreamLoader: connecting timeout'); + } + } return; } + this.clearXhrTimeout(); + this._status = LoaderStatus.kError; if (this._onError) { this._onError(LoaderErrors.EXCEPTION, {code: -1, msg: e.message}); @@ -171,14 +190,16 @@ class FetchStreamLoader extends BaseLoader { }); } - abort() { + abort(reason) { this._requestAbort = true; + this.clearXhrTimeout(); + if (this._status !== LoaderStatus.kBuffering || !Browser.chrome) { // Chrome may throw Exception-like things here, avoid using if is buffering if (this._abortController) { try { - this._abortController.abort(); + this._abortController.abort(reason); } catch (e) {} } } @@ -261,6 +282,12 @@ class FetchStreamLoader extends BaseLoader { }); } + clearXhrTimeout() { + if (this._timeoutId) { + self.clearTimeout(this._timeoutId); + this._timeoutId = null; + } + } } export default FetchStreamLoader; diff --git a/src/io/io-controller.js b/src/io/io-controller.js index 5ccf1a96..fbe66d38 100644 --- a/src/io/io-controller.js +++ b/src/io/io-controller.js @@ -21,7 +21,6 @@ import SpeedSampler from './speed-sampler.js'; import {LoaderStatus, LoaderErrors} from './loader.js'; import FetchStreamLoader from './fetch-stream-loader.js'; import MozChunkedLoader from './xhr-moz-chunked-loader.js'; -import MSStreamLoader from './xhr-msstream-loader.js'; import RangeLoader from './xhr-range-loader.js'; import WebSocketLoader from './websocket-loader.js'; import RangeSeekHandler from './range-seek-handler.js'; diff --git a/src/io/xhr-moz-chunked-loader.js b/src/io/xhr-moz-chunked-loader.js index e6605046..6c8d071b 100644 --- a/src/io/xhr-moz-chunked-loader.js +++ b/src/io/xhr-moz-chunked-loader.js @@ -59,6 +59,7 @@ class MozChunkedLoader extends BaseLoader { this._xhr.onprogress = null; this._xhr.onloadend = null; this._xhr.onerror = null; + this._xhr.ontimeout = null; this._xhr = null; } super.destroy(); @@ -83,6 +84,7 @@ class MozChunkedLoader extends BaseLoader { xhr.onprogress = this._onProgress.bind(this); xhr.onloadend = this._onLoadEnd.bind(this); xhr.onerror = this._onXhrError.bind(this); + xhr.ontimeout = this._onXhrTimeout.bind(this); // cors is auto detected and enabled by xhr @@ -112,6 +114,10 @@ class MozChunkedLoader extends BaseLoader { } } + if (this._config.xhrTimeout !== Infinity && this._config.xhrTimeout > 0) { + xhr.timeout = this._config.xhrTimeout; + } + this._status = LoaderStatus.kConnecting; xhr.send(); } @@ -206,6 +212,14 @@ class MozChunkedLoader extends BaseLoader { } } + _onXhrTimeout(e) { + if (this._onError) { + this._onError(LoaderErrors.CONNECTING_TIMEOUT, {code: -1, msg: 'RangeLoader connecting timeout'}); + } else { + throw new RuntimeException('RangeLoader: connecting timeout'); + } + } + } export default MozChunkedLoader; \ No newline at end of file diff --git a/src/io/xhr-range-loader.js b/src/io/xhr-range-loader.js index 342a3808..99b931b6 100644 --- a/src/io/xhr-range-loader.js +++ b/src/io/xhr-range-loader.js @@ -76,6 +76,7 @@ class RangeLoader extends BaseLoader { this._xhr.onprogress = null; this._xhr.onload = null; this._xhr.onerror = null; + this._xhr.ontimeout = null; this._xhr = null; } super.destroy(); @@ -144,6 +145,7 @@ class RangeLoader extends BaseLoader { xhr.onprogress = this._onProgress.bind(this); xhr.onload = this._onLoad.bind(this); xhr.onerror = this._onXhrError.bind(this); + xhr.ontimeout = this._onXhrTimeout.bind(this); if (dataSource.withCredentials) { xhr.withCredentials = true; @@ -170,6 +172,10 @@ class RangeLoader extends BaseLoader { } } + if (this._config.xhrTimeout !== Infinity && this._config.xhrTimeout > 0) { + xhr.timeout = this._config.xhrTimeout; + } + xhr.send(); } @@ -185,6 +191,7 @@ class RangeLoader extends BaseLoader { this._xhr.onprogress = null; this._xhr.onload = null; this._xhr.onerror = null; + this._xhr.ontimeout = null; this._xhr.abort(); this._xhr = null; } @@ -361,6 +368,13 @@ class RangeLoader extends BaseLoader { } } + _onXhrTimeout(e) { + if (this._onError) { + this._onError(LoaderErrors.CONNECTING_TIMEOUT, {code: -1, msg: 'RangeLoader connecting timeout'}); + } else { + throw new RuntimeException('RangeLoader: connecting timeout'); + } + } } export default RangeLoader; \ No newline at end of file From 15d383cf4ba2aee69c9a6115f1a436d649fc693d Mon Sep 17 00:00:00 2001 From: FredTsang <17154608+FredZeng@users.noreply.github.com> Date: Sun, 24 Sep 2023 23:48:52 +0800 Subject: [PATCH 2/5] fix: rename 'xhrTimeout' to 'requestTimeout' --- docs/api.md | 2 +- src/config.js | 2 +- src/io/fetch-stream-loader.js | 12 ++++++------ src/io/xhr-moz-chunked-loader.js | 4 ++-- src/io/xhr-range-loader.js | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/api.md b/docs/api.md index bd166da7..18a0c15d 100644 --- a/docs/api.md +++ b/docs/api.md @@ -91,7 +91,7 @@ In multipart mode, `duration` `filesize` `url` field in `MediaDataSource` struct | `reuseRedirectedURL?` | `boolean` | `false` | Reuse 301/302 redirected url for subsequence request like seek, reconnect, etc. | | `referrerPolicy?` | `string` | `no-referrer-when-downgrade` | Indicates the [Referrer Policy][] when using FetchStreamLoader | | `headers?` | `object` | `undefined` | Indicates additional headers that will be added to request | -| `xhrTimeout?` | `number` | `Infinity` | Timeout setting for IO Connection, in **milliseconds**. `xhrTimeout` must greater than zero. | +| `requestTimeout?` | `number` | `Infinity` | Timeout setting for IO Connection, in **milliseconds**. `requestTimeout` must greater than zero. | [Referrer Policy]: https://w3c.github.io/webappsec-referrer-policy/#referrer-policy diff --git a/src/config.js b/src/config.js index fe835965..e6ea56c3 100644 --- a/src/config.js +++ b/src/config.js @@ -51,7 +51,7 @@ export const defaultConfig = { headers: undefined, customLoader: undefined, - xhrTimeout: Infinity, + requestTimeout: Infinity, }; export function createDefaultConfig() { diff --git a/src/io/fetch-stream-loader.js b/src/io/fetch-stream-loader.js index 860d32ba..a087fb32 100644 --- a/src/io/fetch-stream-loader.js +++ b/src/io/fetch-stream-loader.js @@ -122,10 +122,10 @@ class FetchStreamLoader extends BaseLoader { this._abortController = new self.AbortController(); params.signal = this._abortController.signal; - if (this._config.xhrTimeout !== Infinity && this._config.xhrTimeout > 0) { + if (this._config.requestTimeout !== Infinity && this._config.requestTimeout > 0) { this._timeoutId = self.setTimeout(() => { this.abort('timeout'); - }, this._config.xhrTimeout); + }, this._config.requestTimeout); } } @@ -137,7 +137,7 @@ class FetchStreamLoader extends BaseLoader { return; } - this.clearXhrTimeout(); + this.clearFetchTimeout(); if (res.ok && (res.status >= 200 && res.status <= 299)) { if (res.url !== seekConfig.url) { @@ -179,7 +179,7 @@ class FetchStreamLoader extends BaseLoader { return; } - this.clearXhrTimeout(); + this.clearFetchTimeout(); this._status = LoaderStatus.kError; if (this._onError) { @@ -193,7 +193,7 @@ class FetchStreamLoader extends BaseLoader { abort(reason) { this._requestAbort = true; - this.clearXhrTimeout(); + this.clearFetchTimeout(); if (this._status !== LoaderStatus.kBuffering || !Browser.chrome) { // Chrome may throw Exception-like things here, avoid using if is buffering @@ -282,7 +282,7 @@ class FetchStreamLoader extends BaseLoader { }); } - clearXhrTimeout() { + clearFetchTimeout() { if (this._timeoutId) { self.clearTimeout(this._timeoutId); this._timeoutId = null; diff --git a/src/io/xhr-moz-chunked-loader.js b/src/io/xhr-moz-chunked-loader.js index 6c8d071b..8a310fe4 100644 --- a/src/io/xhr-moz-chunked-loader.js +++ b/src/io/xhr-moz-chunked-loader.js @@ -114,8 +114,8 @@ class MozChunkedLoader extends BaseLoader { } } - if (this._config.xhrTimeout !== Infinity && this._config.xhrTimeout > 0) { - xhr.timeout = this._config.xhrTimeout; + if (this._config.requestTimeout !== Infinity && this._config.requestTimeout > 0) { + xhr.timeout = this._config.requestTimeout; } this._status = LoaderStatus.kConnecting; diff --git a/src/io/xhr-range-loader.js b/src/io/xhr-range-loader.js index 99b931b6..eca12768 100644 --- a/src/io/xhr-range-loader.js +++ b/src/io/xhr-range-loader.js @@ -172,8 +172,8 @@ class RangeLoader extends BaseLoader { } } - if (this._config.xhrTimeout !== Infinity && this._config.xhrTimeout > 0) { - xhr.timeout = this._config.xhrTimeout; + if (this._config.requestTimeout !== Infinity && this._config.requestTimeout > 0) { + xhr.timeout = this._config.requestTimeout; } xhr.send(); From aded38f4938cf7326dcff8fb85ece0773fe1aa25 Mon Sep 17 00:00:00 2001 From: FredTsang <17154608+FredZeng@users.noreply.github.com> Date: Mon, 25 Sep 2023 12:08:52 +0800 Subject: [PATCH 3/5] fix: missing LoaderStatus.kError status change --- src/io/xhr-moz-chunked-loader.js | 5 +++-- src/io/xhr-range-loader.js | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/io/xhr-moz-chunked-loader.js b/src/io/xhr-moz-chunked-loader.js index 8a310fe4..785fc1a5 100644 --- a/src/io/xhr-moz-chunked-loader.js +++ b/src/io/xhr-moz-chunked-loader.js @@ -213,10 +213,11 @@ class MozChunkedLoader extends BaseLoader { } _onXhrTimeout(e) { + this._status = LoaderStatus.kError; if (this._onError) { - this._onError(LoaderErrors.CONNECTING_TIMEOUT, {code: -1, msg: 'RangeLoader connecting timeout'}); + this._onError(LoaderErrors.CONNECTING_TIMEOUT, {code: -1, msg: 'MozChunkedLoader connecting timeout'}); } else { - throw new RuntimeException('RangeLoader: connecting timeout'); + throw new RuntimeException('MozChunkedLoader: connecting timeout'); } } diff --git a/src/io/xhr-range-loader.js b/src/io/xhr-range-loader.js index eca12768..723b7e0f 100644 --- a/src/io/xhr-range-loader.js +++ b/src/io/xhr-range-loader.js @@ -369,6 +369,7 @@ class RangeLoader extends BaseLoader { } _onXhrTimeout(e) { + this._status = LoaderStatus.kError; if (this._onError) { this._onError(LoaderErrors.CONNECTING_TIMEOUT, {code: -1, msg: 'RangeLoader connecting timeout'}); } else { From b24a113fdd9a4e18f52636eff906365a029b752d Mon Sep 17 00:00:00 2001 From: FredTsang <17154608+FredZeng@users.noreply.github.com> Date: Mon, 25 Sep 2023 12:24:06 +0800 Subject: [PATCH 4/5] fix: replace xhr.timeout with xhr.requestTimeoutId --- src/io/xhr-moz-chunked-loader.js | 24 ++++++++++++++---------- src/io/xhr-range-loader.js | 29 +++++++++++++++++++---------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/io/xhr-moz-chunked-loader.js b/src/io/xhr-moz-chunked-loader.js index 785fc1a5..77c2d3b5 100644 --- a/src/io/xhr-moz-chunked-loader.js +++ b/src/io/xhr-moz-chunked-loader.js @@ -59,7 +59,6 @@ class MozChunkedLoader extends BaseLoader { this._xhr.onprogress = null; this._xhr.onloadend = null; this._xhr.onerror = null; - this._xhr.ontimeout = null; this._xhr = null; } super.destroy(); @@ -84,7 +83,6 @@ class MozChunkedLoader extends BaseLoader { xhr.onprogress = this._onProgress.bind(this); xhr.onloadend = this._onLoadEnd.bind(this); xhr.onerror = this._onXhrError.bind(this); - xhr.ontimeout = this._onXhrTimeout.bind(this); // cors is auto detected and enabled by xhr @@ -115,7 +113,16 @@ class MozChunkedLoader extends BaseLoader { } if (this._config.requestTimeout !== Infinity && this._config.requestTimeout > 0) { - xhr.timeout = this._config.requestTimeout; + xhr.requestTimeoutId = window.setTimeout(() => { + xhr.abort(); + + this._status = LoaderStatus.kError; + if (this._onError) { + this._onError(LoaderErrors.CONNECTING_TIMEOUT, {code: -1, msg: 'MozChunkedLoader connecting timeout'}); + } else { + throw new RuntimeException('MozChunkedLoader: connecting timeout'); + } + }, this._config.requestTimeout); } this._status = LoaderStatus.kConnecting; @@ -212,15 +219,12 @@ class MozChunkedLoader extends BaseLoader { } } - _onXhrTimeout(e) { - this._status = LoaderStatus.kError; - if (this._onError) { - this._onError(LoaderErrors.CONNECTING_TIMEOUT, {code: -1, msg: 'MozChunkedLoader connecting timeout'}); - } else { - throw new RuntimeException('MozChunkedLoader: connecting timeout'); + _clearRequestTimeout() { + if (this._xhr && this._xhr.requestTimeoutId) { + clearTimeout(this._xhr.requestTimeoutId); + this._xhr.requestTimeoutId = undefined; } } - } export default MozChunkedLoader; \ No newline at end of file diff --git a/src/io/xhr-range-loader.js b/src/io/xhr-range-loader.js index 723b7e0f..5a403cf7 100644 --- a/src/io/xhr-range-loader.js +++ b/src/io/xhr-range-loader.js @@ -76,7 +76,6 @@ class RangeLoader extends BaseLoader { this._xhr.onprogress = null; this._xhr.onload = null; this._xhr.onerror = null; - this._xhr.ontimeout = null; this._xhr = null; } super.destroy(); @@ -145,7 +144,6 @@ class RangeLoader extends BaseLoader { xhr.onprogress = this._onProgress.bind(this); xhr.onload = this._onLoad.bind(this); xhr.onerror = this._onXhrError.bind(this); - xhr.ontimeout = this._onXhrTimeout.bind(this); if (dataSource.withCredentials) { xhr.withCredentials = true; @@ -173,7 +171,16 @@ class RangeLoader extends BaseLoader { } if (this._config.requestTimeout !== Infinity && this._config.requestTimeout > 0) { - xhr.timeout = this._config.requestTimeout; + xhr.requestTimeoutId = window.setTimeout(() => { + xhr.abort(); + + this._status = LoaderStatus.kError; + if (this._onError) { + this._onError(LoaderErrors.CONNECTING_TIMEOUT, {code: -1, msg: 'RangeLoader connecting timeout'}); + } else { + throw new RuntimeException('RangeLoader: connecting timeout'); + } + }, this._config.requestTimeout); } xhr.send(); @@ -187,11 +194,11 @@ class RangeLoader extends BaseLoader { _internalAbort() { if (this._xhr) { + this._clearRequestTimeout(); this._xhr.onreadystatechange = null; this._xhr.onprogress = null; this._xhr.onload = null; this._xhr.onerror = null; - this._xhr.ontimeout = null; this._xhr.abort(); this._xhr = null; } @@ -201,6 +208,8 @@ class RangeLoader extends BaseLoader { let xhr = e.target; if (xhr.readyState === 2) { // HEADERS_RECEIVED + this._clearRequestTimeout(); + if (xhr.responseURL != undefined) { // if the browser support this property let redirectedURL = this._seekHandler.removeURLParameters(xhr.responseURL); if (xhr.responseURL !== this._currentRequestURL && redirectedURL !== this._currentRedirectedURL) { @@ -348,6 +357,8 @@ class RangeLoader extends BaseLoader { } _onXhrError(e) { + this._clearRequestTimeout(); + this._status = LoaderStatus.kError; let type = 0; let info = null; @@ -368,12 +379,10 @@ class RangeLoader extends BaseLoader { } } - _onXhrTimeout(e) { - this._status = LoaderStatus.kError; - if (this._onError) { - this._onError(LoaderErrors.CONNECTING_TIMEOUT, {code: -1, msg: 'RangeLoader connecting timeout'}); - } else { - throw new RuntimeException('RangeLoader: connecting timeout'); + _clearRequestTimeout() { + if (this._xhr && this._xhr.requestTimeoutId) { + clearTimeout(this._xhr.requestTimeoutId); + this._xhr.requestTimeoutId = undefined; } } } From 8f778154ee0a2e14c23f1ebf6606e733fad2095a Mon Sep 17 00:00:00 2001 From: FredTsang <17154608+FredZeng@users.noreply.github.com> Date: Mon, 25 Sep 2023 12:29:28 +0800 Subject: [PATCH 5/5] fix(xhr-moz-chunked-loader): add missing _clearRequestTimeout() called --- src/io/xhr-moz-chunked-loader.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/io/xhr-moz-chunked-loader.js b/src/io/xhr-moz-chunked-loader.js index 77c2d3b5..45ddda9c 100644 --- a/src/io/xhr-moz-chunked-loader.js +++ b/src/io/xhr-moz-chunked-loader.js @@ -132,6 +132,7 @@ class MozChunkedLoader extends BaseLoader { abort() { this._requestAbort = true; if (this._xhr) { + this._clearRequestTimeout(); this._xhr.abort(); } this._status = LoaderStatus.kComplete; @@ -141,6 +142,8 @@ class MozChunkedLoader extends BaseLoader { let xhr = e.target; if (xhr.readyState === 2) { // HEADERS_RECEIVED + this._clearRequestTimeout(); + if (xhr.responseURL != undefined && xhr.responseURL !== this._requestURL) { if (this._onURLRedirect) { let redirectedURL = this._seekHandler.removeURLParameters(xhr.responseURL); @@ -200,6 +203,8 @@ class MozChunkedLoader extends BaseLoader { } _onXhrError(e) { + this._clearRequestTimeout(); + this._status = LoaderStatus.kError; let type = 0; let info = null;