/**
 * 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.
 */
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        if (typeof b !== "function" && b !== null)
            throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (g && (g = 0, op[0] && (_ = 0)), _) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
import config from "../../../config";
import { formatError } from "../../../errors";
import log from "../../../log";
import Manifest from "../../../manifest";
import assert from "../../../utils/assert";
import EventEmitter from "../../../utils/event_emitter";
import isNullOrUndefined from "../../../utils/is_null_or_undefined";
import noop from "../../../utils/noop";
import TaskCanceller from "../../../utils/task_canceller";
import errorSelector from "../utils/error_selector";
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
 */
var ManifestFetcher = /** @class */ (function (_super) {
    __extends(ManifestFetcher, _super);
    /**
     * 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`.
     */
    function ManifestFetcher(urls, pipelines, settings) {
        var _this = _super.call(this) || this;
        _this.scheduleManualRefresh = noop;
        _this._manifestUrls = urls;
        _this._pipelines = pipelines.manifest;
        _this._settings = settings;
        _this._canceller = new TaskCanceller();
        _this._isStarted = false;
        _this._isRefreshPending = false;
        _this._consecutiveUnsafeMode = 0;
        _this._prioritizedContentUrl = null;
        return _this;
    }
    /**
     * Free resources and stop refresh mechanism from happening.
     *
     * Once `dispose` has been called. This `ManifestFetcher` cannot be relied on
     * anymore.
     */
    ManifestFetcher.prototype.dispose = function () {
        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`.
     */
    ManifestFetcher.prototype.start = function () {
        var _this = this;
        if (this._isStarted) {
            return;
        }
        this._isStarted = true;
        var manifestProm;
        var 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(function (val) {
                return val.parse({ previousManifest: null, unsafeMode: false });
            });
        }
        manifestProm
            .then(function (val) {
            _this.trigger("manifestReady", val.manifest);
            if (!_this._canceller.isUsed()) {
                _this._recursivelyRefreshManifest(val.manifest, val);
            }
        })
            .catch(function (err) { return _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.
     */
    ManifestFetcher.prototype.updateContentUrls = function (urls, refreshNow) {
        var _a;
        this._prioritizedContentUrl = (_a = urls === null || urls === void 0 ? void 0 : urls[0]) !== null && _a !== void 0 ? _a : 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}
     */
    ManifestFetcher.prototype._fetchManifest = function (url) {
        var _a;
        return __awaiter(this, void 0, void 0, function () {
            /**
             * Call the resolver part of the pipeline, retrying if it fails according
             * to the current settings.
             * Returns the Promise of the last attempt.
             * /!\ This pipeline should have a `resolveManifestUrl` function defined.
             * @param {string | undefined}  resolverUrl
             * @returns {Promise}
             */
            function callResolverWithRetries(resolverUrl) {
                var resolveManifestUrl = pipelines.resolveManifestUrl;
                assert(resolveManifestUrl !== undefined);
                var callResolver = function () { return resolveManifestUrl(resolverUrl, cancelSignal); };
                return scheduleRequestPromise(callResolver, backoffSettings, cancelSignal);
            }
            /**
             * 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) {
                var loadManifest = pipelines.loadManifest;
                var requestTimeout = isNullOrUndefined(settings.requestTimeout) ?
                    config.getCurrent().DEFAULT_REQUEST_TIMEOUT :
                    settings.requestTimeout;
                if (requestTimeout < 0) {
                    requestTimeout = undefined;
                }
                var callLoader = function () { return loadManifest(manifestUrl, { timeout: requestTimeout }, cancelSignal); };
                return scheduleRequestPromise(callLoader, backoffSettings, cancelSignal);
            }
            var cancelSignal, settings, pipelines, requestUrl, backoffSettings, loadingPromise, response_1, err_1;
            var _this = this;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        cancelSignal = this._canceller.signal;
                        settings = this._settings;
                        pipelines = this._pipelines;
                        requestUrl = url !== null && url !== void 0 ? url : (_a = this._manifestUrls) === null || _a === void 0 ? void 0 : _a[0];
                        backoffSettings = this._getBackoffSetting(function (err) {
                            _this.trigger("warning", errorSelector(err));
                        });
                        loadingPromise = pipelines.resolveManifestUrl === undefined ?
                            callLoaderWithRetries(requestUrl) :
                            callResolverWithRetries(requestUrl).then(callLoaderWithRetries);
                        _b.label = 1;
                    case 1:
                        _b.trys.push([1, 3, , 4]);
                        return [4 /*yield*/, loadingPromise];
                    case 2:
                        response_1 = _b.sent();
                        return [2 /*return*/, {
                                parse: function (parserOptions) {
                                    return _this._parseLoadedManifest(response_1, parserOptions, requestUrl);
                                },
                            }];
                    case 3:
                        err_1 = _b.sent();
                        throw errorSelector(err_1);
                    case 4: return [2 /*return*/];
                }
            });
        });
    };
    /**
     * 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}
     */
    ManifestFetcher.prototype.parse = function (manifest, parserOptions, originalUrl) {
        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}
     */
    ManifestFetcher.prototype._parseLoadedManifest = function (loaded, parserOptions, requestUrl) {
        var _a;
        return __awaiter(this, void 0, void 0, function () {
            /**
             * Perform a request with the same retry mechanisms and error handling
             * than for a Manifest loader.
             * @param {Function} performRequest
             * @returns {Function}
             */
            function scheduleRequest(performRequest) {
                return __awaiter(this, void 0, void 0, function () {
                    var data, err_3;
                    return __generator(this, function (_a) {
                        switch (_a.label) {
                            case 0:
                                _a.trys.push([0, 2, , 3]);
                                return [4 /*yield*/, scheduleRequestPromise(performRequest, backoffSettings, cancelSignal)];
                            case 1:
                                data = _a.sent();
                                return [2 /*return*/, data];
                            case 2:
                                err_3 = _a.sent();
                                throw errorSelector(err_3);
                            case 3: return [2 /*return*/];
                        }
                    });
                });
            }
            /**
             * Handle minor errors encountered by a Manifest parser.
             * @param {Array.<Error>} warnings
             */
            function onWarnings(warnings) {
                for (var _i = 0, warnings_1 = warnings; _i < warnings_1.length; _i++) {
                    var warning = warnings_1[_i];
                    if (cancelSignal.isCancelled()) {
                        return;
                    }
                    var 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) {
                onWarnings(manifest.contentWarnings);
                var parsingTime = performance.now() - parsingTimeStart;
                log.info("MF: Manifest parsed in ".concat(parsingTime, "ms"));
                return { manifest: manifest, sendingTime: sendingTime, receivedTime: receivedTime, parsingTime: parsingTime };
            }
            var parsingTimeStart, cancelSignal, trigger, sendingTime, receivedTime, backoffSettings, originalUrl, opts, res, manifest, err_2, formattedError;
            var _this = this;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        parsingTimeStart = performance.now();
                        cancelSignal = this._canceller.signal;
                        trigger = this.trigger.bind(this);
                        sendingTime = loaded.sendingTime, receivedTime = loaded.receivedTime;
                        backoffSettings = this._getBackoffSetting(function (err) {
                            _this.trigger("warning", errorSelector(err));
                        });
                        originalUrl = requestUrl !== null && requestUrl !== void 0 ? requestUrl : (_a = this._manifestUrls) === null || _a === void 0 ? void 0 : _a[0];
                        opts = { externalClockOffset: parserOptions.externalClockOffset,
                            unsafeMode: parserOptions.unsafeMode,
                            previousManifest: parserOptions.previousManifest, originalUrl: originalUrl };
                        _b.label = 1;
                    case 1:
                        _b.trys.push([1, 5, , 6]);
                        res = this._pipelines.parseManifest(loaded, opts, onWarnings, cancelSignal, scheduleRequest);
                        if (!!isPromise(res)) return [3 /*break*/, 2];
                        return [2 /*return*/, finish(res.manifest)];
                    case 2: return [4 /*yield*/, res];
                    case 3:
                        manifest = (_b.sent()).manifest;
                        return [2 /*return*/, finish(manifest)];
                    case 4: return [3 /*break*/, 6];
                    case 5:
                        err_2 = _b.sent();
                        formattedError = formatError(err_2, {
                            defaultCode: "PIPELINE_PARSE_ERROR",
                            defaultReason: "Unknown error when parsing the Manifest",
                        });
                        throw formattedError;
                    case 6: return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Construct "backoff settings" that can be used with a range of functions
     * allowing to perform multiple request attempts
     * @param {Function} onRetry
     * @returns {Object}
     */
    ManifestFetcher.prototype._getBackoffSetting = function (onRetry) {
        var _a = config.getCurrent(), DEFAULT_MAX_MANIFEST_REQUEST_RETRY = _a.DEFAULT_MAX_MANIFEST_REQUEST_RETRY, DEFAULT_MAX_REQUESTS_RETRY_ON_OFFLINE = _a.DEFAULT_MAX_REQUESTS_RETRY_ON_OFFLINE, INITIAL_BACKOFF_DELAY_BASE = _a.INITIAL_BACKOFF_DELAY_BASE, MAX_BACKOFF_DELAY_BASE = _a.MAX_BACKOFF_DELAY_BASE;
        var _b = this._settings, lowLatencyMode = _b.lowLatencyMode, ogRegular = _b.maxRetryRegular, ogOffline = _b.maxRetryOffline;
        var baseDelay = lowLatencyMode ? INITIAL_BACKOFF_DELAY_BASE.LOW_LATENCY :
            INITIAL_BACKOFF_DELAY_BASE.REGULAR;
        var maxDelay = lowLatencyMode ? MAX_BACKOFF_DELAY_BASE.LOW_LATENCY :
            MAX_BACKOFF_DELAY_BASE.REGULAR;
        var maxRetryRegular = ogRegular !== null && ogRegular !== void 0 ? ogRegular : DEFAULT_MAX_MANIFEST_REQUEST_RETRY;
        var maxRetryOffline = ogOffline !== null && ogOffline !== void 0 ? ogOffline : DEFAULT_MAX_REQUESTS_RETRY_ON_OFFLINE;
        return { onRetry: onRetry, baseDelay: baseDelay, maxDelay: maxDelay, maxRetryRegular: maxRetryRegular, maxRetryOffline: maxRetryOffline };
    };
    /**
     * 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.
     */
    ManifestFetcher.prototype._recursivelyRefreshManifest = function (manifest, _a) {
        var _this = this;
        var sendingTime = _a.sendingTime, parsingTime = _a.parsingTime, updatingTime = _a.updatingTime;
        var _b = config.getCurrent(), MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE = _b.MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE, MIN_MANIFEST_PARSING_TIME_TO_ENTER_UNSAFE_MODE = _b.MIN_MANIFEST_PARSING_TIME_TO_ENTER_UNSAFE_MODE;
        /**
         * Total time taken to fully update the last Manifest, in milliseconds.
         * Note: this time also includes possible requests done by the parsers.
         */
        var totalUpdateTime = parsingTime !== undefined ?
            parsingTime + (updatingTime !== null && updatingTime !== void 0 ? 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.
         */
        var unsafeModeEnabled = this._consecutiveUnsafeMode > 0 ?
            this._consecutiveUnsafeMode < MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE :
            totalUpdateTime !== undefined ?
                (totalUpdateTime >= MIN_MANIFEST_PARSING_TIME_TO_ENTER_UNSAFE_MODE) :
                false;
        /** Time elapsed since the beginning of the Manifest request, in milliseconds. */
        var timeSinceRequest = sendingTime === undefined ?
            0 :
            performance.now() - sendingTime;
        /** Minimum update delay we should not go below, in milliseconds. */
        var 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.
         */
        var nextRefreshCanceller = new TaskCanceller();
        nextRefreshCanceller.linkToSignal(this._canceller.signal);
        /* Function to manually schedule a Manifest refresh */
        this.scheduleManualRefresh = function (settings) {
            var enablePartialRefresh = settings.enablePartialRefresh, delay = settings.delay, canUseUnsafeMode = settings.canUseUnsafeMode;
            var unsafeMode = canUseUnsafeMode && unsafeModeEnabled;
            // The value allows to set a delay relatively to the last Manifest refresh
            // (to avoid asking for it too often).
            var timeSinceLastRefresh = sendingTime === undefined ?
                0 :
                performance.now() - sendingTime;
            var _minInterval = Math.max(_this._settings.minimumManifestUpdateInterval -
                timeSinceLastRefresh, 0);
            var timeoutId = setTimeout(function () {
                nextRefreshCanceller.cancel();
                _this._triggerNextManifestRefresh(manifest, { enablePartialRefresh: enablePartialRefresh, unsafeMode: unsafeMode });
            }, Math.max((delay !== null && delay !== void 0 ? delay : 0) - timeSinceLastRefresh, _minInterval));
            nextRefreshCanceller.signal.register(function () {
                clearTimeout(timeoutId);
            });
        };
        /* Handle Manifest expiration. */
        if (manifest.expired !== null) {
            var timeoutId_1 = setTimeout(function () {
                var _a;
                (_a = manifest.expired) === null || _a === void 0 ? void 0 : _a.then(function () {
                    nextRefreshCanceller.cancel();
                    _this._triggerNextManifestRefresh(manifest, { enablePartialRefresh: false,
                        unsafeMode: unsafeModeEnabled });
                }, noop /* `expired` should not reject */);
            }, minInterval);
            nextRefreshCanceller.signal.register(function () {
                clearTimeout(timeoutId_1);
            });
        }
        /*
         * 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. */
            var regularRefreshDelay = manifest.lifetime * 1000 - timeSinceRequest;
            /** Actually choosen delay to refresh the Manifest. */
            var actualRefreshInterval = void 0;
            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("MUS: Manifest update rythm is too frequent. Postponing next request.", regularRefreshDelay, 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("MUS: Manifest took too long to parse. Postponing next request", actualRefreshInterval, actualRefreshInterval);
            }
            else {
                actualRefreshInterval = regularRefreshDelay;
            }
            var timeoutId_2 = setTimeout(function () {
                nextRefreshCanceller.cancel();
                _this._triggerNextManifestRefresh(manifest, { enablePartialRefresh: false,
                    unsafeMode: unsafeModeEnabled });
            }, Math.max(actualRefreshInterval, minInterval));
            nextRefreshCanceller.signal.register(function () {
                clearTimeout(timeoutId_2);
            });
        }
    };
    /**
     * 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
     */
    ManifestFetcher.prototype._triggerNextManifestRefresh = function (manifest, _a) {
        var _this = this;
        var enablePartialRefresh = _a.enablePartialRefresh, unsafeMode = _a.unsafeMode;
        var manifestUpdateUrl = manifest.updateUrl;
        var fullRefresh;
        var refreshURL;
        if (this._prioritizedContentUrl !== null) {
            fullRefresh = true;
            refreshURL = this._prioritizedContentUrl;
            this._prioritizedContentUrl = null;
        }
        else {
            fullRefresh = !enablePartialRefresh || manifestUpdateUrl === undefined;
            refreshURL = fullRefresh ? manifest.getUrl() :
                manifestUpdateUrl;
        }
        var externalClockOffset = manifest.clockOffset;
        if (unsafeMode) {
            this._consecutiveUnsafeMode += 1;
            log.info("Init: Refreshing the Manifest in \"unsafeMode\" for the " +
                String(this._consecutiveUnsafeMode) + " consecutive time.");
        }
        else if (this._consecutiveUnsafeMode > 0) {
            log.info("Init: 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(function (res) { return res.parse({ externalClockOffset: externalClockOffset, previousManifest: manifest, unsafeMode: unsafeMode }); })
            .then(function (res) {
            _this._isRefreshPending = false;
            var newManifest = res.manifest, newSendingTime = res.sendingTime, parsingTime = res.parsingTime;
            var updateTimeStart = performance.now();
            if (fullRefresh) {
                manifest.replace(newManifest);
            }
            else {
                try {
                    manifest.update(newManifest);
                }
                catch (e) {
                    var message = e instanceof Error ? e.message :
                        "unknown error";
                    log.warn("MUS: Attempt to update Manifest failed: ".concat(message), "Re-downloading the Manifest fully");
                    var FAILED_PARTIAL_UPDATE_MANIFEST_REFRESH_DELAY = config.getCurrent().FAILED_PARTIAL_UPDATE_MANIFEST_REFRESH_DELAY;
                    // The value allows to set a delay relatively to the last Manifest refresh
                    // (to avoid asking for it too often).
                    var timeSinceLastRefresh = newSendingTime === undefined ?
                        0 :
                        performance.now() - newSendingTime;
                    var _minInterval = Math.max(_this._settings.minimumManifestUpdateInterval -
                        timeSinceLastRefresh, 0);
                    var unregisterCanceller_1 = noop;
                    var timeoutId_3 = setTimeout(function () {
                        unregisterCanceller_1();
                        _this._triggerNextManifestRefresh(manifest, { enablePartialRefresh: false,
                            unsafeMode: false });
                    }, Math.max(FAILED_PARTIAL_UPDATE_MANIFEST_REFRESH_DELAY -
                        timeSinceLastRefresh, _minInterval));
                    unregisterCanceller_1 = _this._canceller.signal.register(function () {
                        clearTimeout(timeoutId_3);
                    });
                    return;
                }
            }
            var updatingTime = performance.now() - updateTimeStart;
            _this._recursivelyRefreshManifest(manifest, { sendingTime: newSendingTime, parsingTime: parsingTime, updatingTime: updatingTime });
        })
            .catch(function (err) {
            _this._isRefreshPending = false;
            _this._onFatalError(err);
        });
    };
    ManifestFetcher.prototype._onFatalError = function (err) {
        if (this._canceller.isUsed()) {
            return;
        }
        this.trigger("error", err);
        this.dispose();
    };
    return ManifestFetcher;
}(EventEmitter));
export default ManifestFetcher;
/**
 * Returns `true` when the returned value seems to be a Promise instance, as
 * created by the RxPlayer.
 * @param {*} val
 * @returns {boolean}
 */
function isPromise(val) {
    return val instanceof Promise;
}
