/** * 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 { CustomLoaderError } from "../../errors"; import type { IManifestLoader, ILoadedManifestFormat } from "../../public_types"; import getMonotonicTimeStamp from "../../utils/monotonic_timestamp"; import type { CancellationError, CancellationSignal } from "../../utils/task_canceller"; import type { IManifestLoaderOptions, IRequestedData } from "../types"; export default function callCustomManifestLoader( customManifestLoader: IManifestLoader, fallbackManifestLoader: ( url: string | undefined, loaderOptions: IManifestLoaderOptions, cancelSignal: CancellationSignal, ) => Promise>, ): ( url: string | undefined, loaderOptions: IManifestLoaderOptions, cancelSignal: CancellationSignal, ) => Promise> { return ( url: string | undefined, loaderOptions: IManifestLoaderOptions, cancelSignal: CancellationSignal, ): Promise> => { return new Promise((res, rej) => { const timeAPIsDelta = Date.now() - getMonotonicTimeStamp(); /** `true` when the custom segmentLoader should not be active anymore. */ let hasFinished = false; /** * Callback triggered when the custom manifest loader has a response. * @param {Object} _args */ const resolve = (_args: { data: ILoadedManifestFormat; size?: number | undefined; duration?: number | undefined; url?: string | undefined; receivingTime?: number | undefined; sendingTime?: number | undefined; }) => { if (hasFinished || cancelSignal.isCancelled()) { return; } hasFinished = true; cancelSignal.deregister(abortCustomLoader); const receivedTime = _args.receivingTime !== undefined ? _args.receivingTime - timeAPIsDelta : undefined; const sendingTime = _args.sendingTime !== undefined ? _args.sendingTime - timeAPIsDelta : undefined; res({ responseData: _args.data, size: _args.size, requestDuration: _args.duration, url: _args.url, receivedTime, sendingTime, }); }; /** * Callback triggered when the custom manifest loader fails * @param {*} err - The corresponding error encountered */ const reject = (err: unknown): void => { if (hasFinished || cancelSignal.isCancelled()) { return; } hasFinished = true; cancelSignal.deregister(abortCustomLoader); // Format error and send it const castedErr = err as | null | undefined | { message?: string; canRetry?: boolean; xhr?: XMLHttpRequest }; const message = castedErr?.message ?? "Unknown error when fetching the Manifest through a " + "custom manifestLoader."; const emittedErr = new CustomLoaderError( message, castedErr?.canRetry ?? false, castedErr?.xhr, ); rej(emittedErr); }; /** * Callback triggered when the custom manifest loader wants to fallback to * the "regular" implementation */ const fallback = () => { if (hasFinished || cancelSignal.isCancelled()) { return; } hasFinished = true; cancelSignal.deregister(abortCustomLoader); fallbackManifestLoader(url, loaderOptions, cancelSignal).then(res, rej); }; const callbacks = { reject, resolve, fallback }; const abort = customManifestLoader( { url, timeout: loaderOptions.timeout, cmcdPayload: loaderOptions.cmcdPayload }, callbacks, ); cancelSignal.register(abortCustomLoader); /** * The logic to run when the custom loader is cancelled while pending. * @param {Error} err */ function abortCustomLoader(err: CancellationError) { if (hasFinished) { return; } hasFinished = true; if (typeof abort === "function") { abort(); } rej(err); } }); }; }