rx-player
Version:
Canal+ HTML5 Video Player
844 lines (791 loc) • 29.3 kB
text/typescript
/**
* Copyright 2015 CANAL+ Group
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import config from "../../../config";
import { formatError } from "../../../errors";
import log from "../../../log";
import Manifest from "../../../manifest/classes";
import type {
IInitialManifest,
ILoadedManifestFormat,
IPlayerError,
} from "../../../public_types";
import type {
IManifestLoaderOptions,
IRequestedData,
ITransportManifestPipeline,
ITransportName,
ITransportPipelines,
} from "../../../transports";
import EventEmitter from "../../../utils/event_emitter";
import getMonotonicTimeStamp from "../../../utils/monotonic_timestamp";
import noop from "../../../utils/noop";
import TaskCanceller from "../../../utils/task_canceller";
import type CmcdDataBuilder from "../../cmcd";
import errorSelector from "../utils/error_selector";
import type { IBackoffSettings } from "../utils/schedule_request";
import { scheduleRequestPromise } from "../utils/schedule_request";
/**
* Class allowing to facilitate the task of loading and parsing a Manifest, as
* well as automatically refreshing it.
* @class ManifestFetcher
*/
export default class ManifestFetcher extends EventEmitter<IManifestFetcherEvent> {
/**
* Allows to manually trigger a Manifest refresh.
* Will only have an effect if the Manifest has been fetched at least once.
* @param {Object} settings - refresh configuration.
*/
public scheduleManualRefresh: (settings: IManifestRefreshSettings) => void;
/** `ManifestFetcher` configuration. */
private _settings: IManifestFetcherSettings;
/** URLs through which the Manifest may be reached, by order of priority. */
private _manifestUrls: string[] | undefined;
/** Name of the current transport pipeline used. */
private _transportName: ITransportName;
/**
* Manifest loading and parsing pipelines linked to the current transport
* protocol used.
*/
private _pipelines: ITransportManifestPipeline;
/**
* `TaskCanceller` called when this `ManifestFetcher` is disposed, to clean
* resources.
*/
private _canceller: TaskCanceller;
/**
* Set to `true` once the Manifest has been fetched at least once through this
* `ManifestFetcher`.
*/
private _isStarted: boolean;
/**
* Set to `true` when a Manifest refresh is currently pending.
* Allows to avoid doing multiple concurrent Manifest refresh, as this is
* most of the time unnecessary.
*/
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.
* @param {Array.<string> | undefined} urls - Manifest URLs, will be used when
* no URL is provided to the `fetch` function.
* `undefined` if unknown or if a Manifest should be retrieved through other
* means than an HTTP request.
* @param {Object} pipelines - Transport pipelines used to perform the
* Manifest loading and parsing operations.
* @param {Object} settings - Configure the `ManifestFetcher`.
*/
constructor(
urls: string[] | undefined,
pipelines: ITransportPipelines,
settings: IManifestFetcherSettings,
) {
super();
this.scheduleManualRefresh = noop;
this._manifestUrls = urls;
this._pipelines = pipelines.manifest;
this._transportName = pipelines.transportName;
this._settings = settings;
this._canceller = new TaskCanceller();
this._isStarted = false;
this._isRefreshPending = false;
this._consecutiveUnsafeMode = 0;
this._prioritizedContentUrl = null;
}
/**
* Free resources and stop refresh mechanism from happening.
*
* Once `dispose` has been called. This `ManifestFetcher` cannot be relied on
* anymore.
*/
public dispose() {
this._canceller.cancel();
this.removeEventListener();
}
/**
* Start requesting the Manifest as well as the Manifest refreshing logic, if
* needed.
*
* Once `start` has been called, this mechanism can only be stopped by calling
* `dispose`.
*/
public start(): void {
if (this._isStarted) {
return;
}
this._isStarted = true;
let manifestProm: Promise<IManifestFetcherParsedResult>;
const initialManifest = this._settings.initialManifest;
if (initialManifest instanceof Manifest) {
manifestProm = Promise.resolve({ manifest: initialManifest });
} else if (initialManifest !== undefined) {
manifestProm = this.parse(
initialManifest,
{ previousManifest: null, unsafeMode: false },
undefined,
);
} else {
manifestProm = this._fetchManifest(undefined).then((val) => {
return val.parse({ previousManifest: null, unsafeMode: false });
});
}
manifestProm
.then((val: IManifestFetcherParsedResult) => {
this.trigger("manifestReady", val.manifest);
if (!this._canceller.isUsed()) {
this._recursivelyRefreshManifest(val.manifest, val);
}
})
.catch((err: unknown) => this._onFatalError(err));
}
/**
* Update URL of the fetched Manifest.
* @param {Array.<string> | 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
* a callback available on the response.
*
* You can set an `url` on which that Manifest will be requested.
* If not set, the regular Manifest url - defined on the `ManifestFetcher`
* instanciation - will be used instead.
*
* @param {string | undefined} url
* @returns {Promise}
*/
private async _fetchManifest(
url: string | undefined,
): Promise<IManifestFetcherResponse> {
const cancelSignal = this._canceller.signal;
const settings = this._settings;
const transportName = this._transportName;
const pipelines = this._pipelines;
// TODO Better handle multiple Manifest URLs
const requestUrl = url ?? this._manifestUrls?.[0];
const backoffSettings = this._getBackoffSetting((err) => {
this.trigger("warning", errorSelector(err));
});
try {
const response = await callLoaderWithRetries(requestUrl);
return {
parse: (parserOptions: IManifestFetcherParserOptions) => {
return this._parseLoadedManifest(response, parserOptions, requestUrl);
},
};
} catch (err) {
throw errorSelector(err);
}
/**
* Call the loader part of the pipeline, retrying if it fails according
* to the current settings.
* Returns the Promise of the last attempt.
* @param {string | undefined} manifestUrl
* @returns {Promise}
*/
function callLoaderWithRetries(
manifestUrl: string | undefined,
): Promise<IRequestedData<ILoadedManifestFormat>> {
const { loadManifest } = pipelines;
let requestTimeout: number | undefined =
settings.requestTimeout === undefined
? config.getCurrent().DEFAULT_REQUEST_TIMEOUT
: settings.requestTimeout;
let connectionTimeout: number | undefined =
settings.connectionTimeout === undefined
? config.getCurrent().DEFAULT_CONNECTION_TIMEOUT
: settings.connectionTimeout;
if (requestTimeout < 0) {
requestTimeout = undefined;
}
if (connectionTimeout < 0) {
connectionTimeout = undefined;
}
const requestOptions: IManifestLoaderOptions = {
timeout: requestTimeout,
connectionTimeout,
cmcdPayload: settings.cmcdDataBuilder?.getCmcdDataForManifest(transportName),
};
const callLoader = () => loadManifest(manifestUrl, requestOptions, cancelSignal);
return scheduleRequestPromise(callLoader, backoffSettings, cancelSignal);
}
}
/**
* Parse an already loaded Manifest.
*
* This method should be reserved for Manifests for which no request has been
* done.
* In other cases, it's preferable to go through the `fetch` method, so
* information on the request can be used by the parsing process.
* @param {*} manifest
* @param {Object} parserOptions
* @param {string | undefined} originalUrl
* @returns {Promise}
*/
private parse(
manifest: unknown,
parserOptions: IManifestFetcherParserOptions,
originalUrl: string | undefined,
): Promise<IManifestFetcherParsedResult> {
return this._parseLoadedManifest(
{ responseData: manifest, size: undefined, requestDuration: undefined },
parserOptions,
originalUrl,
);
}
/**
* Parse a Manifest.
*
* @param {Object} loaded - Information about the loaded Manifest as well as
* about the corresponding request.
* @param {Object} parserOptions - Options used when parsing the Manifest.
* @param {string | undefined} requestUrl
* @returns {Promise}
*/
private async _parseLoadedManifest(
loaded: IRequestedData<unknown>,
parserOptions: IManifestFetcherParserOptions,
requestUrl: string | undefined,
): Promise<IManifestFetcherParsedResult> {
const parsingTimeStart = getMonotonicTimeStamp();
const cancelSignal = this._canceller.signal;
const trigger = this.trigger.bind(this);
const { sendingTime, receivedTime } = loaded;
const backoffSettings = this._getBackoffSetting((err) => {
this.trigger("warning", errorSelector(err));
});
const originalUrl = requestUrl ?? this._manifestUrls?.[0];
const opts = {
externalClockOffset: parserOptions.externalClockOffset,
unsafeMode: parserOptions.unsafeMode,
previousManifest: parserOptions.previousManifest,
originalUrl,
};
try {
const res = this._pipelines.parseManifest(
loaded,
opts,
onWarnings,
cancelSignal,
scheduleRequest,
);
if (!isPromise(res)) {
return finish(res.manifest);
} else {
const { manifest } = await res;
return finish(manifest);
}
} catch (err) {
const formattedError = formatError(err, {
defaultCode: "PIPELINE_PARSE_ERROR",
defaultReason: "Unknown error when parsing the Manifest",
});
throw formattedError;
}
/**
* Perform a request with the same retry mechanisms and error handling
* than for a Manifest loader.
* @param {Function} performRequest
* @returns {Function}
*/
async function scheduleRequest<T>(performRequest: () => Promise<T>): Promise<T> {
try {
const data = await scheduleRequestPromise(
performRequest,
backoffSettings,
cancelSignal,
);
return data;
} catch (err) {
throw errorSelector(err);
}
}
/**
* Handle minor errors encountered by a Manifest parser.
* @param {Array.<Error>} warnings
*/
function onWarnings(warnings: Error[]): void {
for (const warning of warnings) {
if (cancelSignal.isCancelled()) {
return;
}
const formattedError = formatError(warning, {
defaultCode: "PIPELINE_PARSE_ERROR",
defaultReason: "Unknown error when parsing the Manifest",
});
trigger("warning", formattedError);
}
}
/**
* Emit a formatted "parsed" event through `obs`.
* To call once the Manifest has been parsed.
* @param {Object} manifest
*/
function finish(manifest: Manifest): IManifestFetcherParsedResult {
const parsingTime = getMonotonicTimeStamp() - parsingTimeStart;
log.info("MF", `Manifest parsed in ${parsingTime}ms`);
return { manifest, sendingTime, receivedTime, parsingTime };
}
}
/**
* Construct "backoff settings" that can be used with a range of functions
* allowing to perform multiple request attempts
* @param {Function} onRetry
* @returns {Object}
*/
private _getBackoffSetting(onRetry: (err: unknown) => void): IBackoffSettings {
const {
DEFAULT_MAX_MANIFEST_REQUEST_RETRY,
INITIAL_BACKOFF_DELAY_BASE,
MAX_BACKOFF_DELAY_BASE,
} = config.getCurrent();
const { lowLatencyMode, maxRetry: ogRegular } = this._settings;
const baseDelay = lowLatencyMode
? INITIAL_BACKOFF_DELAY_BASE.LOW_LATENCY
: INITIAL_BACKOFF_DELAY_BASE.REGULAR;
const maxDelay = lowLatencyMode
? MAX_BACKOFF_DELAY_BASE.LOW_LATENCY
: MAX_BACKOFF_DELAY_BASE.REGULAR;
const maxRetry = ogRegular ?? DEFAULT_MAX_MANIFEST_REQUEST_RETRY;
return { onRetry, baseDelay, maxDelay, maxRetry };
}
/**
* Performs Manifest refresh (recursively) when it judges it is time to do so.
* @param {Object} manifest
* @param {Object} manifestRequestInfos - Various information linked to the
* last Manifest loading and parsing operations.
*/
private _recursivelyRefreshManifest(
manifest: Manifest,
{
sendingTime,
parsingTime,
updatingTime,
}: {
sendingTime?: number | undefined;
parsingTime?: number | undefined;
updatingTime?: number | undefined;
},
): void {
const {
MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE,
MIN_MANIFEST_PARSING_TIME_TO_ENTER_UNSAFE_MODE,
} = config.getCurrent();
/**
* Total time taken to fully update the last Manifest, in milliseconds.
* Note: this time also includes possible requests done by the parsers.
*/
const totalUpdateTime =
parsingTime !== undefined ? parsingTime + (updatingTime ?? 0) : undefined;
/**
* "unsafeMode" is a mode where we unlock advanced Manifest parsing
* optimizations with the added risk to lose some information.
* `unsafeModeEnabled` is set to `true` when the `unsafeMode` is enabled.
*
* Only perform parsing in `unsafeMode` when the last full parsing took a
* lot of time and do not go higher than the maximum consecutive time.
*/
let unsafeModeEnabled = false;
if (this._consecutiveUnsafeMode > 0) {
unsafeModeEnabled =
this._consecutiveUnsafeMode < MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE;
} else if (totalUpdateTime !== undefined) {
unsafeModeEnabled =
totalUpdateTime >= MIN_MANIFEST_PARSING_TIME_TO_ENTER_UNSAFE_MODE;
}
/** Time elapsed since the beginning of the Manifest request, in milliseconds. */
const timeSinceRequest =
sendingTime === undefined ? 0 : getMonotonicTimeStamp() - sendingTime;
/** Minimum update delay we should not go below, in milliseconds. */
const minInterval = Math.max(
this._settings.minimumManifestUpdateInterval - timeSinceRequest,
0,
);
/**
* Multiple refresh trigger are scheduled here, but only the first one should
* be effectively considered.
* `nextRefreshCanceller` will allow to cancel every other when one is triggered.
*/
const nextRefreshCanceller = new TaskCanceller();
nextRefreshCanceller.linkToSignal(this._canceller.signal);
/* Function to manually schedule a Manifest refresh */
this.scheduleManualRefresh = (settings: IManifestRefreshSettings) => {
const { enablePartialRefresh, delay, canUseUnsafeMode } = settings;
const unsafeMode = canUseUnsafeMode && unsafeModeEnabled;
// The value allows to set a delay relatively to the last Manifest refresh
// (to avoid asking for it too often).
const timeSinceLastRefresh =
sendingTime === undefined ? 0 : getMonotonicTimeStamp() - sendingTime;
const _minInterval = Math.max(
this._settings.minimumManifestUpdateInterval - timeSinceLastRefresh,
0,
);
const timeoutId = setTimeout(
() => {
nextRefreshCanceller.cancel();
this._triggerNextManifestRefresh(manifest, {
enablePartialRefresh,
unsafeMode,
});
},
Math.max((delay ?? 0) - timeSinceLastRefresh, _minInterval),
);
nextRefreshCanceller.signal.register(() => {
clearTimeout(timeoutId);
});
};
/* Handle Manifest expiration. */
if (manifest.expired !== null) {
const timeoutId = setTimeout(() => {
manifest.expired?.then(() => {
nextRefreshCanceller.cancel();
this._triggerNextManifestRefresh(manifest, {
enablePartialRefresh: false,
unsafeMode: unsafeModeEnabled,
});
}, noop /* `expired` should not reject */);
}, minInterval);
nextRefreshCanceller.signal.register(() => {
clearTimeout(timeoutId);
});
}
/*
* Trigger Manifest refresh when the Manifest needs to be refreshed
* according to the Manifest's internal properties (parsing time is also
* taken into account in this operation to avoid refreshing too often).
*/
if (manifest.lifetime !== undefined && manifest.lifetime >= 0) {
/** Regular refresh delay as asked by the Manifest. */
const regularRefreshDelay = manifest.lifetime * 1000 - timeSinceRequest;
/** Actually choosen delay to refresh the Manifest. */
let actualRefreshInterval: number;
if (totalUpdateTime === undefined) {
actualRefreshInterval = regularRefreshDelay;
} else if (manifest.lifetime < 3 && totalUpdateTime >= 100) {
// If Manifest update is very frequent and we take time to update it,
// postpone it.
actualRefreshInterval = Math.min(
Math.max(
// Take 3 seconds as a default safe value for a base interval.
3000 - timeSinceRequest,
// Add update time to the original interval.
Math.max(regularRefreshDelay, 0) + totalUpdateTime,
),
// Limit the postponment's higher bound to a very high value relative
// to `regularRefreshDelay`.
// This avoid perpetually postponing a Manifest update when
// performance seems to have been abysmal one time.
regularRefreshDelay * 6,
);
log.info(
"MF",
"Manifest update rythm is too frequent. Postponing next request.",
{ regularRefreshDelay, newRefreshDelay: actualRefreshInterval },
);
} else if (totalUpdateTime >= (manifest.lifetime * 1000) / 10) {
// If Manifest updating time is very long relative to its lifetime,
// postpone it:
actualRefreshInterval = Math.min(
// Just add the update time to the original waiting time
Math.max(regularRefreshDelay, 0) + totalUpdateTime,
// Limit the postponment's higher bound to a very high value relative
// to `regularRefreshDelay`.
// This avoid perpetually postponing a Manifest update when
// performance seems to have been abysmal one time.
regularRefreshDelay * 6,
);
log.info("MF", "Manifest took too long to parse. Postponing next request", {
regularRefreshDelay,
newRefreshDelay: actualRefreshInterval,
});
} else {
actualRefreshInterval = regularRefreshDelay;
}
const timeoutId = setTimeout(
() => {
nextRefreshCanceller.cancel();
this._triggerNextManifestRefresh(manifest, {
enablePartialRefresh: false,
unsafeMode: unsafeModeEnabled,
});
},
Math.max(actualRefreshInterval, minInterval),
);
nextRefreshCanceller.signal.register(() => {
clearTimeout(timeoutId);
});
}
}
/**
* Refresh the Manifest, performing a full update if a partial update failed.
* Also re-call `recursivelyRefreshManifest` to schedule the next refresh
* trigger.
* @param {Object} manifest
* @param {Object} refreshInformation
*/
private _triggerNextManifestRefresh(
manifest: Manifest,
{
enablePartialRefresh,
unsafeMode,
}: { enablePartialRefresh: boolean; unsafeMode: boolean },
) {
const manifestUpdateUrl = manifest.updateUrl;
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.getUrls()[0] : manifestUpdateUrl;
}
const externalClockOffset = manifest.clockOffset;
if (unsafeMode) {
this._consecutiveUnsafeMode += 1;
log.info(
"MF",
'Refreshing the Manifest in "unsafeMode" for the ' +
String(this._consecutiveUnsafeMode) +
" consecutive time.",
);
} else if (this._consecutiveUnsafeMode > 0) {
log.info(
"MF",
'Not parsing the Manifest in "unsafeMode" anymore after ' +
String(this._consecutiveUnsafeMode) +
" consecutive times.",
);
this._consecutiveUnsafeMode = 0;
}
if (this._isRefreshPending) {
return;
}
this._isRefreshPending = true;
this._fetchManifest(refreshURL)
.then((res) =>
res.parse({
externalClockOffset,
previousManifest: manifest,
unsafeMode,
}),
)
.then((res) => {
this._isRefreshPending = false;
const { manifest: newManifest, sendingTime: newSendingTime, parsingTime } = res;
const updateTimeStart = getMonotonicTimeStamp();
if (fullRefresh) {
manifest.replace(newManifest);
} else {
try {
manifest.update(newManifest);
} catch (e) {
const message = e instanceof Error ? e.message : "unknown error";
log.warn(
"MF",
`Attempt to update Manifest failed: ${message}`,
"Re-downloading the Manifest fully",
);
const { FAILED_PARTIAL_UPDATE_MANIFEST_REFRESH_DELAY } = config.getCurrent();
// The value allows to set a delay relatively to the last Manifest refresh
// (to avoid asking for it too often).
const timeSinceLastRefresh =
newSendingTime === undefined ? 0 : getMonotonicTimeStamp() - newSendingTime;
const _minInterval = Math.max(
this._settings.minimumManifestUpdateInterval - timeSinceLastRefresh,
0,
);
let unregisterCanceller = noop;
const timeoutId = setTimeout(
() => {
unregisterCanceller();
this._triggerNextManifestRefresh(manifest, {
enablePartialRefresh: false,
unsafeMode: false,
});
},
Math.max(
FAILED_PARTIAL_UPDATE_MANIFEST_REFRESH_DELAY - timeSinceLastRefresh,
_minInterval,
),
);
unregisterCanceller = this._canceller.signal.register(() => {
clearTimeout(timeoutId);
});
return;
}
}
const updatingTime = getMonotonicTimeStamp() - updateTimeStart;
this._recursivelyRefreshManifest(manifest, {
sendingTime: newSendingTime,
parsingTime,
updatingTime,
});
})
.catch((err) => {
this._isRefreshPending = false;
this._onFatalError(err);
});
}
private _onFatalError(err: unknown): void {
if (this._canceller.isUsed()) {
return;
}
this.trigger("error", err);
this.dispose();
}
}
/**
* Returns `true` when the returned value seems to be a Promise instance, as
* created by the RxPlayer.
* @param {*} val
* @returns {boolean}
*/
function isPromise<T>(val: T | Promise<T>): val is Promise<T> {
return val instanceof Promise;
}
/** What will be sent once parsed. */
interface IManifestFetcherParsedResult {
/** The resulting Manifest */
manifest: Manifest;
/**
* Monotonically-raising timestamp (common one used by the RxPlayer's logic)
* at which the request was started (at which the JavaScript call was done).
*/
sendingTime?: number | undefined;
/** Monotonically-raising timestamp at which the request was fully received. */
receivedTime?: number | undefined;
/* The time taken to parse the Manifest through the corresponding parse function. */
parsingTime?: number | undefined;
}
/** Response emitted by a Manifest fetcher. */
interface IManifestFetcherResponse {
/** Allows to parse a fetched Manifest into a `Manifest` structure. */
parse(
parserOptions: IManifestFetcherParserOptions,
): Promise<IManifestFetcherParsedResult>;
}
interface IManifestFetcherParserOptions {
/**
* If set, offset to add to the monotonically-raising timestamp used by the
* RxPlayer to obtain the current server's time.
*/
externalClockOffset?: number | undefined;
/** The previous value of the Manifest (when updating). */
previousManifest: Manifest | null;
/**
* If set to `true`, the Manifest parser can perform advanced optimizations
* to speed-up the parsing process. Those optimizations might lead to a
* de-synchronization with what is actually on the server, hence the "unsafe"
* part.
* To use with moderation and only when needed.
*/
unsafeMode: boolean;
}
/** Options used by `createManifestFetcher`. */
export interface IManifestFetcherSettings {
/**
* Whether the content is played in a low-latency mode.
* This has an impact on default backoff delays.
*/
lowLatencyMode: boolean;
/** Maximum number of time a request on error will be retried. */
maxRetry: number | undefined;
/**
* Timeout after which request are aborted and, depending on other options,
* retried.
* To set to `-1` for no timeout.
* `undefined` will lead to a default, large, timeout being used.
*/
requestTimeout: number | undefined;
/**
* Connection timeout, in milliseconds, after which the request is canceled
* if the responses headers has not being received.
* Do not set or set to "undefined" to disable it.
*/
connectionTimeout: number | undefined;
/** Limit the frequency of Manifest updates. */
minimumManifestUpdateInterval: number;
/**
* Potential first Manifest to rely on, allowing to skip the initial Manifest
* request.
*/
initialManifest: IInitialManifest | undefined;
/**
* Optional module allowing to collect "Common Media Client Data" (a.k.a. CMCD)
* for the CDN.
*/
cmcdDataBuilder: CmcdDataBuilder | null;
}
/** Event sent by the `ManifestFetcher`. */
export interface IManifestFetcherEvent {
/** Event sent by the `ManifestFetcher` when a minor error has been encountered. */
warning: IPlayerError;
/**
* Event sent by the `ManifestFetcher` when a major error has been encountered,
* leading to the `ManifestFetcher` being disposed.
*/
error: unknown;
/** Event sent after the Manifest has first been fetched. */
manifestReady: Manifest;
}
/** Argument defined when forcing a Manifest refresh. */
export interface IManifestRefreshSettings {
/**
* if `false`, the Manifest should be fully updated.
* if `true`, a shorter version with just the added information can be loaded
* instead.
*
* Basically can be set to `true` in most updates to improve performances, but
* should be set to `false` if you suspect some iregularities in the Manifest,
* so a complete and thorough refresh is performed.
*
* Note that this optimization is only possible when a shorter version of the
* Manifest is available.
* In other cases, setting this value to `true` won't have any effect.
*/
enablePartialRefresh: boolean;
/**
* Optional wanted refresh delay, which is the minimum time you want to wait
* before updating the Manifest
*/
delay?: number | undefined;
/**
* Whether the parsing can be done in the more efficient "unsafeMode".
* This mode is extremely fast but can lead to de-synchronisation with the
* server.
*/
canUseUnsafeMode: boolean;
}