From ec01e19a26e083820f4d0cccf1a217bffc438b4c Mon Sep 17 00:00:00 2001 From: Paul Berberian Date: Fri, 18 Nov 2022 17:10:20 +0100 Subject: [PATCH 1/2] Add `updateContentUrls` API This commit adds the `updateContentUrls` API allowing to update at any time and during playback, the URL through which a content's Manifest is reached. This may be used anytime an application prefer a new Manifest URLs to be used, whether it is because the previous URL will soon expire, because the new URL is seen as more qualitative or to rebalance load. The first argument given to `updateContentUrls` is actually an array of strings, listing new URLs from the most prioritized to the least prioritized. This is not used for now, but should soon be used to define fallback URLs when former one fail to fetch the resource. `updateContentUrls` can be given a second argument named `refreshNow` which can be used to ask the RxPlayer to refresh immediately the Manifest behind the given URL. For now, this API will throw for directfile contents, I'm not sure whether it is pertinent to enable it also in this case. --- doc/api/Content_Information/.docConfig.json | 4 ++ .../Content_Information/updateContentUrls.md | 56 +++++++++++++++++++ doc/reference/API_Reference.md | 3 + src/core/api/public_api.ts | 17 ++++++ .../fetchers/manifest/manifest_fetcher.ts | 38 ++++++++++++- .../init/directfile_content_initializer.ts | 4 ++ .../init/media_source_content_initializer.ts | 11 ++++ src/core/init/types.ts | 12 ++++ 8 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 doc/api/Content_Information/updateContentUrls.md diff --git a/doc/api/Content_Information/.docConfig.json b/doc/api/Content_Information/.docConfig.json index b61314dd798..d018afb03cd 100644 --- a/doc/api/Content_Information/.docConfig.json +++ b/doc/api/Content_Information/.docConfig.json @@ -4,6 +4,10 @@ "path": "./getUrl.md", "displayName": "getUrl" }, + { + "path": "./updateContentUrls.md", + "displayName": "updateContentUrls" + }, { "path": "./isLive.md", "displayName": "isLive" diff --git a/doc/api/Content_Information/updateContentUrls.md b/doc/api/Content_Information/updateContentUrls.md new file mode 100644 index 00000000000..7b3bb3f80a2 --- /dev/null +++ b/doc/api/Content_Information/updateContentUrls.md @@ -0,0 +1,56 @@ +# updateContentUrls + +## Description + +Update URL of the content currently being played (e.g. of DASH's MPD), +optionally also allowing to request an immediate refresh of it. + +This method can for example be called when you would prefer that the content and +its associated resources to be reached through another URL than what has been +used until now. + +Note that if a request through one of the given URL lead to a HTTP redirect, the +RxPlayer will generally prefer the redirected URL over the URL explicitely +communicated (to prevent more HTTP redirect). + +
+In DirectFile mode (see loadVideo options), +this method has no effect. +
+ +## Syntax + +```js +player.updateContentUrls(urls); +// or +player.updateContentUrls(urls, refreshNow); +``` + + - **arguments**: + + 1. _urls_ `Array.|under`: URLs to reach that content / Manifest + from the most prioritized URL to the least prioritized URL. + + 2. _refreshNow_ `boolean`: If `true` the resource in question (e.g. + DASH's MPD) will be refreshed immediately. + +## Examples + +```js +// Update with only one URL +player.updateContentUrls(["http://my.new.url"]); + +// Update with multiple URLs +player.updateContentUrls([ + "http://more.prioritized.url", + "http://less.prioritized.url", +]); + +// Set no URL (only is useful in some very specific situations, like for content +// with no Manifest refresh or when a `manifestLoader` is set). +player.updateContentUrls(undefined); + +// Update and ask to refresh immediately +player.updateContentUrls(["http://my.new.url"], true); +``` diff --git a/doc/reference/API_Reference.md b/doc/reference/API_Reference.md index 86ec8cd93e5..b7d7b378e5b 100644 --- a/doc/reference/API_Reference.md +++ b/doc/reference/API_Reference.md @@ -453,6 +453,9 @@ properties, methods, events and so on. - [`getUrl`](../api/Content_Information/getUrl.md): Get URL of the currently-played content. + - [`updateContentUrls`](../api/Content_Information/updateContentUrls.md): + Update URL(s) of the content currently being played. + - [`isLive`](../api/Content_Information/isLive.md): Returns `true` if the content is a "live" content. diff --git a/src/core/api/public_api.ts b/src/core/api/public_api.ts index 096c937d986..73fbe07b03a 100644 --- a/src/core/api/public_api.ts +++ b/src/core/api/public_api.ts @@ -777,6 +777,7 @@ class Player extends EventEmitter { contentId: generateContentId(), originalUrl: url, currentContentCanceller, + initializer, isDirectFile, segmentBuffersStore: null, thumbnails: null, @@ -1129,6 +1130,20 @@ class Player extends EventEmitter { return undefined; } + /** + * Update URL of the content currently being played (e.g. DASH's MPD). + * @param {Array.|undefined} urls - URLs to reach that content / + * Manifest from the most prioritized URL to the least prioritized URL. + * @param {boolean} refreshNow - If `true` the resource in question (e.g. + * DASH's MPD) will be refreshed immediately. + */ + public updateContentUrls(urls : string[] | undefined, refreshNow : boolean) : void { + if (this._priv_contentInfos === null) { + throw new Error("No content loaded"); + } + this._priv_contentInfos.initializer.updateContentUrls(urls, refreshNow); + } + /** * Returns the video duration, in seconds. * NaN if no video is playing. @@ -2851,6 +2866,8 @@ interface IPublicApiContentInfos { contentId : string; /** Original URL set to load the content. */ originalUrl : string | undefined; + /** `ContentInitializer` used to load the content. */ + initializer : ContentInitializer; /** TaskCanceller triggered when it's time to stop the current content. */ currentContentCanceller : TaskCanceller; /** diff --git a/src/core/fetchers/manifest/manifest_fetcher.ts b/src/core/fetchers/manifest/manifest_fetcher.ts index 58fc6f8785c..2de0c8433fd 100644 --- a/src/core/fetchers/manifest/manifest_fetcher.ts +++ b/src/core/fetchers/manifest/manifest_fetcher.ts @@ -79,6 +79,11 @@ export default class ManifestFetcher extends EventEmitter private _isRefreshPending; /** Number of consecutive times the Manifest parsing has been done in `unsafeMode`. */ private _consecutiveUnsafeMode; + /** + * If set to a string or `undefined`, the given URL should be prioritized on + * the next Manifest fetching operation, it can then be reset to `null`. + */ + private _prioritizedContentUrl : string | undefined | null; /** * Construct a new ManifestFetcher. @@ -104,6 +109,7 @@ export default class ManifestFetcher extends EventEmitter this._isStarted = false; this._isRefreshPending = false; this._consecutiveUnsafeMode = 0; + this._prioritizedContentUrl = null; } /** @@ -156,6 +162,24 @@ export default class ManifestFetcher extends EventEmitter .catch((err : unknown) => this._onFatalError(err)); } + /** + * Update URL of the fetched Manifest. + * @param {Array. | undefined} urls - New Manifest URLs by order of + * priority or `undefined` if there's now no URL. + * @param {boolean} refreshNow - If set to `true`, the next Manifest refresh + * will be triggered immediately. + */ + public updateContentUrls(urls : string[] | undefined, refreshNow : boolean) : void { + this._prioritizedContentUrl = urls?.[0] ?? undefined; + if (refreshNow) { + this.scheduleManualRefresh({ + enablePartialRefresh: false, + delay: 0, + canUseUnsafeMode: false, + }); + } + } + /** * (re-)Load the Manifest. * This method does not yet parse it, parsing will then be available through @@ -560,9 +584,17 @@ export default class ManifestFetcher extends EventEmitter unsafeMode : boolean; } ) { const manifestUpdateUrl = manifest.updateUrl; - const fullRefresh = !enablePartialRefresh || manifestUpdateUrl === undefined; - const refreshURL = fullRefresh ? manifest.getUrl() : - manifestUpdateUrl; + let fullRefresh : boolean; + let refreshURL : string | undefined; + if (this._prioritizedContentUrl !== null) { + fullRefresh = true; + refreshURL = this._prioritizedContentUrl; + this._prioritizedContentUrl = null; + } else { + fullRefresh = !enablePartialRefresh || manifestUpdateUrl === undefined; + refreshURL = fullRefresh ? manifest.getUrl() : + manifestUpdateUrl; + } const externalClockOffset = manifest.clockOffset; if (unsafeMode) { diff --git a/src/core/init/directfile_content_initializer.ts b/src/core/init/directfile_content_initializer.ts index d9938a8f1ba..ceb1e4ad8e4 100644 --- a/src/core/init/directfile_content_initializer.ts +++ b/src/core/init/directfile_content_initializer.ts @@ -123,6 +123,10 @@ export default class DirectFileContentInitializer extends ContentInitializer { }, { emitCurrentValue: true, clearSignal: cancelSignal }); } + public updateContentUrls(_urls : string[] | undefined, _refreshNow : boolean) : void { + throw new Error("Cannot update content URL of directfile contents"); + } + public dispose(): void { this._initCanceller.cancel(); } diff --git a/src/core/init/media_source_content_initializer.ts b/src/core/init/media_source_content_initializer.ts index fd21928fa9e..d5edb9d37e7 100644 --- a/src/core/init/media_source_content_initializer.ts +++ b/src/core/init/media_source_content_initializer.ts @@ -173,6 +173,17 @@ export default class MediaSourceContentInitializer extends ContentInitializer { }); } + /** + * Update URL of the Manifest. + * @param {Array.|undefined} urls - URLs to reach that Manifest from + * the most prioritized URL to the least prioritized URL. + * @param {boolean} refreshNow - If `true` the resource in question (e.g. + * DASH's MPD) will be refreshed immediately. + */ + public updateContentUrls(urls : string[] | undefined, refreshNow : boolean) : void { + this._manifestFetcher.updateContentUrls(urls, refreshNow); + } + public dispose(): void { this._initCanceller.cancel(); } diff --git a/src/core/init/types.ts b/src/core/init/types.ts index bd29f41f0e1..a233019ad8e 100644 --- a/src/core/init/types.ts +++ b/src/core/init/types.ts @@ -83,6 +83,18 @@ export abstract class ContentInitializer extends EventEmitter|undefined} urls - URLs to reach that content / + * Manifest from the most prioritized URL to the least prioritized URL. + * @param {boolean} refreshNow - If `true` the resource in question (e.g. + * DASH's MPD) will be refreshed immediately. + */ + public abstract updateContentUrls( + urls : string[] | undefined, + refreshNow : boolean + ) : void; + /** * Stop playing the content linked to this `ContentInitializer` on the * `HTMLMediaElement` linked to it and dispose of every resources taken while From ca29e21a673ddd18dda45224a030a1559336e0ea Mon Sep 17 00:00:00 2001 From: Paul Berberian Date: Wed, 21 Dec 2022 14:58:49 +0100 Subject: [PATCH 2/2] Set second argument of updateContentUrls as an object --- doc/api/Content_Information/updateContentUrls.md | 13 +++++++++---- src/core/api/public_api.ts | 11 ++++++++--- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/doc/api/Content_Information/updateContentUrls.md b/doc/api/Content_Information/updateContentUrls.md index 7b3bb3f80a2..a8c9ae5c7e3 100644 --- a/doc/api/Content_Information/updateContentUrls.md +++ b/doc/api/Content_Information/updateContentUrls.md @@ -24,7 +24,7 @@ this method has no effect. ```js player.updateContentUrls(urls); // or -player.updateContentUrls(urls, refreshNow); +player.updateContentUrls(urls, params); ``` - **arguments**: @@ -32,8 +32,13 @@ player.updateContentUrls(urls, refreshNow); 1. _urls_ `Array.|under`: URLs to reach that content / Manifest from the most prioritized URL to the least prioritized URL. - 2. _refreshNow_ `boolean`: If `true` the resource in question (e.g. - DASH's MPD) will be refreshed immediately. + 2. _params_ `Object|undefined`: Optional parameters linked to this URL + change. + + Can contain the following properties: + + - _refresh_ `boolean`: If `true` the resource in question (e.g. + DASH's MPD) will be refreshed immediately. ## Examples @@ -52,5 +57,5 @@ player.updateContentUrls([ player.updateContentUrls(undefined); // Update and ask to refresh immediately -player.updateContentUrls(["http://my.new.url"], true); +player.updateContentUrls(["http://my.new.url"], { refresh: true }); ``` diff --git a/src/core/api/public_api.ts b/src/core/api/public_api.ts index 73fbe07b03a..a3459417278 100644 --- a/src/core/api/public_api.ts +++ b/src/core/api/public_api.ts @@ -1134,13 +1134,18 @@ class Player extends EventEmitter { * Update URL of the content currently being played (e.g. DASH's MPD). * @param {Array.|undefined} urls - URLs to reach that content / * Manifest from the most prioritized URL to the least prioritized URL. - * @param {boolean} refreshNow - If `true` the resource in question (e.g. - * DASH's MPD) will be refreshed immediately. + * @param {Object|undefined} [params] + * @param {boolean} params.refresh - If `true` the resource in question + * (e.g. DASH's MPD) will be refreshed immediately. */ - public updateContentUrls(urls : string[] | undefined, refreshNow : boolean) : void { + public updateContentUrls( + urls : string[] | undefined, + params? : { refresh?: boolean } | undefined + ) : void { if (this._priv_contentInfos === null) { throw new Error("No content loaded"); } + const refreshNow = params?.refresh === true; this._priv_contentInfos.initializer.updateContentUrls(urls, refreshNow); }