/**
 * 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 { shouldReloadMediaSourceOnDecipherabilityUpdate } from "../../compat";
import config from "../../config";
import { MediaError } from "../../errors";
import log from "../../log";
import assert from "../../utils/assert";
import createCancellablePromise from "../../utils/create_cancellable_promise";
import objectAssign from "../../utils/object_assign";
import SharedReference from "../../utils/reference";
import TaskCanceller from "../../utils/task_canceller";
import AdaptiveRepresentationSelector from "../adaptive";
import { getKeySystemConfiguration, } from "../decrypt";
import { ManifestFetcher, SegmentFetcherCreator, } from "../fetchers";
import SegmentBuffersStore from "../segment_buffers";
import StreamOrchestrator from "../stream";
import { ContentInitializer } from "./types";
import ContentTimeBoundariesObserver from "./utils/content_time_boundaries_observer";
import openMediaSource from "./utils/create_media_source";
import createStreamPlaybackObserver from "./utils/create_stream_playback_observer";
import { maintainEndOfStream } from "./utils/end_of_stream";
import getInitialTime from "./utils/get_initial_time";
import getLoadedReference from "./utils/get_loaded_reference";
import performInitialSeekAndPlay from "./utils/initial_seek_and_play";
import initializeContentDecryption from "./utils/initialize_content_decryption";
import MediaSourceDurationUpdater from "./utils/media_source_duration_updater";
import RebufferingController from "./utils/rebuffering_controller";
import StreamEventsEmitter from "./utils/stream_events_emitter";
import listenToMediaError from "./utils/throw_on_media_error";
/**
 * Allows to load a new content thanks to the MediaSource Extensions (a.k.a. MSE)
 * Web APIs.
 *
 * Through this `ContentInitializer`, a Manifest will be fetched (and depending
 * on the situation, refreshed), a `MediaSource` instance will be linked to the
 * wanted `HTMLMediaElement` and chunks of media data, called segments, will be
 * pushed on buffers associated to this `MediaSource` instance.
 *
 * @class MediaSourceContentInitializer
 */
var MediaSourceContentInitializer = /** @class */ (function (_super) {
    __extends(MediaSourceContentInitializer, _super);
    /**
     * Create a new `MediaSourceContentInitializer`, associated to the given
     * settings.
     * @param {Object} settings
     */
    function MediaSourceContentInitializer(settings) {
        var _this = _super.call(this) || this;
        _this._settings = settings;
        _this._initCanceller = new TaskCanceller();
        _this._initialManifestProm = null;
        var urls = settings.url === undefined ? undefined :
            [settings.url];
        _this._manifestFetcher = new ManifestFetcher(urls, settings.transport, settings.manifestRequestSettings);
        return _this;
    }
    /**
     * Perform non-destructive preparation steps, to prepare a future content.
     * For now, this mainly mean loading the Manifest document.
     */
    MediaSourceContentInitializer.prototype.prepare = function () {
        var _this = this;
        if (this._initialManifestProm !== null) {
            return;
        }
        this._initialManifestProm = createCancellablePromise(this._initCanceller.signal, function (res, rej) {
            _this._manifestFetcher.addEventListener("warning", function (err) {
                return _this.trigger("warning", err);
            });
            _this._manifestFetcher.addEventListener("error", function (err) {
                _this.trigger("error", err);
                rej(err);
            });
            _this._manifestFetcher.addEventListener("manifestReady", function (manifest) {
                res(manifest);
            });
        });
        this._manifestFetcher.start();
        this._initCanceller.signal.register(function () {
            _this._manifestFetcher.dispose();
        });
    };
    /**
     * @param {HTMLMediaElement} mediaElement
     * @param {Object} playbackObserver
     */
    MediaSourceContentInitializer.prototype.start = function (mediaElement, playbackObserver) {
        var _this = this;
        this.prepare(); // Load Manifest if not already done
        /** Translate errors coming from the media element into RxPlayer errors. */
        listenToMediaError(mediaElement, function (error) { return _this._onFatalError(error); }, this._initCanceller.signal);
        /** Send content protection initialization data to the decryption logic. */
        var protectionRef = new SharedReference(null, this._initCanceller.signal);
        this._initializeMediaSourceAndDecryption(mediaElement, protectionRef)
            .then(function (initResult) { return _this._onInitialMediaSourceReady(mediaElement, initResult.mediaSource, playbackObserver, initResult.drmSystemId, protectionRef, initResult.unlinkMediaSource); })
            .catch(function (err) {
            _this._onFatalError(err);
        });
    };
    /**
     * Update URL of the Manifest.
     * @param {Array.<string>|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.
     */
    MediaSourceContentInitializer.prototype.updateContentUrls = function (urls, refreshNow) {
        this._manifestFetcher.updateContentUrls(urls, refreshNow);
    };
    MediaSourceContentInitializer.prototype.dispose = function () {
        this._initCanceller.cancel();
    };
    MediaSourceContentInitializer.prototype._onFatalError = function (err) {
        if (this._initCanceller.isUsed()) {
            return;
        }
        this._initCanceller.cancel();
        this.trigger("error", err);
    };
    MediaSourceContentInitializer.prototype._initializeMediaSourceAndDecryption = function (mediaElement, protectionRef) {
        var _this = this;
        var initCanceller = this._initCanceller;
        return createCancellablePromise(initCanceller.signal, function (resolve) {
            var keySystems = _this._settings.keySystems;
            /** Initialize decryption capabilities. */
            var drmInitRef = initializeContentDecryption(mediaElement, keySystems, protectionRef, { onWarning: function (err) { return _this.trigger("warning", err); },
                onError: function (err) { return _this._onFatalError(err); } }, initCanceller.signal);
            drmInitRef.onUpdate(function (drmStatus, stopListeningToDrmUpdates) {
                if (drmStatus.initializationState.type === "uninitialized") {
                    return;
                }
                stopListeningToDrmUpdates();
                var mediaSourceCanceller = new TaskCanceller();
                mediaSourceCanceller.linkToSignal(initCanceller.signal);
                openMediaSource(mediaElement, mediaSourceCanceller.signal)
                    .then(function (mediaSource) {
                    var lastDrmStatus = drmInitRef.getValue();
                    if (lastDrmStatus.initializationState.type === "awaiting-media-link") {
                        lastDrmStatus.initializationState.value.isMediaLinked.setValue(true);
                        drmInitRef.onUpdate(function (newDrmStatus, stopListeningToDrmUpdatesAgain) {
                            if (newDrmStatus.initializationState.type === "initialized") {
                                stopListeningToDrmUpdatesAgain();
                                resolve({ mediaSource: mediaSource, drmSystemId: newDrmStatus.drmSystemId,
                                    unlinkMediaSource: mediaSourceCanceller });
                                return;
                            }
                        }, { emitCurrentValue: true, clearSignal: initCanceller.signal });
                    }
                    else if (drmStatus.initializationState.type === "initialized") {
                        resolve({ mediaSource: mediaSource, drmSystemId: drmStatus.drmSystemId,
                            unlinkMediaSource: mediaSourceCanceller });
                        return;
                    }
                })
                    .catch(function (err) {
                    if (mediaSourceCanceller.isUsed()) {
                        return;
                    }
                    _this._onFatalError(err);
                });
            }, { emitCurrentValue: true, clearSignal: initCanceller.signal });
        });
    };
    MediaSourceContentInitializer.prototype._onInitialMediaSourceReady = function (mediaElement, initialMediaSource, playbackObserver, drmSystemId, protectionRef, initialMediaSourceCanceller) {
        return __awaiter(this, void 0, void 0, function () {
            /**
             * Load the content defined by the Manifest in the mediaSource given at the
             * given position and playing status.
             * This function recursively re-call itself when a MediaSource reload is
             * wanted.
             * @param {MediaSource} mediaSource
             * @param {number} startingPos
             * @param {Object} currentCanceller
             * @param {boolean} shouldPlay
             */
            function recursivelyLoadOnMediaSource(mediaSource, startingPos, shouldPlay, currentCanceller) {
                var opts = { mediaElement: mediaElement, playbackObserver: playbackObserver, mediaSource: mediaSource, initialTime: startingPos,
                    autoPlay: shouldPlay, manifest: manifest, representationEstimator: representationEstimator, segmentFetcherCreator: segmentFetcherCreator, speed: speed, protectionRef: protectionRef, bufferOptions: subBufferOptions };
                bufferOnMediaSource(opts, onReloadMediaSource, currentCanceller.signal);
                function onReloadMediaSource(reloadOrder) {
                    currentCanceller.cancel();
                    if (initCanceller.isUsed()) {
                        return;
                    }
                    triggerEvent("reloadingMediaSource", reloadOrder);
                    if (initCanceller.isUsed()) {
                        return;
                    }
                    var newCanceller = new TaskCanceller();
                    newCanceller.linkToSignal(initCanceller.signal);
                    openMediaSource(mediaElement, newCanceller.signal)
                        .then(function (newMediaSource) {
                        recursivelyLoadOnMediaSource(newMediaSource, reloadOrder.position, reloadOrder.autoPlay, newCanceller);
                    })
                        .catch(function (err) {
                        if (newCanceller.isUsed()) {
                            return;
                        }
                        onFatalError(err);
                    });
                }
            }
            var _a, adaptiveOptions, autoPlay, bufferOptions, lowLatencyMode, segmentRequestOptions, speed, startAt, textTrackOptions, transport, initCanceller, manifestProm, manifest, _e_1, initialTime, representationEstimator, subBufferOptions, segmentFetcherCreator, bufferOnMediaSource, triggerEvent, onFatalError;
            var _this = this;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        _a = this._settings, adaptiveOptions = _a.adaptiveOptions, autoPlay = _a.autoPlay, bufferOptions = _a.bufferOptions, lowLatencyMode = _a.lowLatencyMode, segmentRequestOptions = _a.segmentRequestOptions, speed = _a.speed, startAt = _a.startAt, textTrackOptions = _a.textTrackOptions, transport = _a.transport;
                        initCanceller = this._initCanceller;
                        assert(this._initialManifestProm !== null);
                        manifestProm = this._initialManifestProm;
                        _b.label = 1;
                    case 1:
                        _b.trys.push([1, 3, , 4]);
                        return [4 /*yield*/, manifestProm];
                    case 2:
                        manifest = _b.sent();
                        return [3 /*break*/, 4];
                    case 3:
                        _e_1 = _b.sent();
                        return [2 /*return*/]; // The error should already have been processed through an event listener
                    case 4:
                        manifest.addEventListener("manifestUpdate", function () {
                            _this.trigger("manifestUpdate", null);
                        }, initCanceller.signal);
                        manifest.addEventListener("decipherabilityUpdate", function (args) {
                            _this.trigger("decipherabilityUpdate", args);
                        }, initCanceller.signal);
                        log.debug("Init: Calculating initial time");
                        initialTime = getInitialTime(manifest, lowLatencyMode, startAt);
                        log.debug("Init: Initial time calculated:", initialTime);
                        representationEstimator = AdaptiveRepresentationSelector(adaptiveOptions);
                        subBufferOptions = objectAssign({ textTrackOptions: textTrackOptions, drmSystemId: drmSystemId }, bufferOptions);
                        segmentFetcherCreator = new SegmentFetcherCreator(transport, segmentRequestOptions, initCanceller.signal);
                        this.trigger("manifestReady", manifest);
                        if (initCanceller.isUsed()) {
                            return [2 /*return*/];
                        }
                        bufferOnMediaSource = this._startBufferingOnMediaSource.bind(this);
                        triggerEvent = this.trigger.bind(this);
                        onFatalError = this._onFatalError.bind(this);
                        // handle initial load and reloads
                        recursivelyLoadOnMediaSource(initialMediaSource, initialTime, autoPlay, initialMediaSourceCanceller);
                        return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Buffer the content on the given MediaSource.
     * @param {Object} args
     * @param {function} onReloadOrder
     * @param {Object} cancelSignal
     */
    MediaSourceContentInitializer.prototype._startBufferingOnMediaSource = function (args, onReloadOrder, cancelSignal) {
        var _this = this;
        var _a;
        var autoPlay = args.autoPlay, bufferOptions = args.bufferOptions, initialTime = args.initialTime, manifest = args.manifest, mediaElement = args.mediaElement, mediaSource = args.mediaSource, playbackObserver = args.playbackObserver, protectionRef = args.protectionRef, representationEstimator = args.representationEstimator, segmentFetcherCreator = args.segmentFetcherCreator, speed = args.speed;
        var initialPeriod = (_a = manifest.getPeriodForTime(initialTime)) !== null && _a !== void 0 ? _a : manifest.getNextPeriod(initialTime);
        if (initialPeriod === undefined) {
            var error = new MediaError("MEDIA_STARTING_TIME_NOT_FOUND", "Wanted starting time not found in the Manifest.");
            return this._onFatalError(error);
        }
        /** Interface to create media buffers. */
        var segmentBuffersStore = new SegmentBuffersStore(mediaElement, mediaSource);
        cancelSignal.register(function () {
            segmentBuffersStore.disposeAll();
        });
        var _b = performInitialSeekAndPlay(mediaElement, playbackObserver, initialTime, autoPlay, function (err) { return _this.trigger("warning", err); }, true, cancelSignal), autoPlayResult = _b.autoPlayResult, initialPlayPerformed = _b.initialPlayPerformed, initialSeekPerformed = _b.initialSeekPerformed;
        if (cancelSignal.isCancelled()) {
            return;
        }
        initialPlayPerformed.onUpdate(function (isPerformed, stopListening) {
            if (isPerformed) {
                stopListening();
                var streamEventsEmitter_1 = new StreamEventsEmitter(manifest, mediaElement, playbackObserver);
                streamEventsEmitter_1.addEventListener("event", function (payload) {
                    _this.trigger("streamEvent", payload);
                }, cancelSignal);
                streamEventsEmitter_1.addEventListener("eventSkip", function (payload) {
                    _this.trigger("streamEventSkip", payload);
                }, cancelSignal);
                streamEventsEmitter_1.start();
                cancelSignal.register(function () {
                    streamEventsEmitter_1.stop();
                });
            }
        }, { clearSignal: cancelSignal, emitCurrentValue: true });
        var streamObserver = createStreamPlaybackObserver(playbackObserver, { autoPlay: autoPlay, manifest: manifest, initialPlayPerformed: initialPlayPerformed, initialSeekPerformed: initialSeekPerformed, speed: speed, startTime: initialTime }, cancelSignal);
        var rebufferingController = this._createRebufferingController(playbackObserver, manifest, segmentBuffersStore, speed, cancelSignal);
        rebufferingController.addEventListener("needsReload", function () {
            // NOTE couldn't both be always calculated at event destination?
            // Maybe there are exceptions?
            var position = initialSeekPerformed.getValue() ?
                playbackObserver.getCurrentTime() :
                initialTime;
            var autoplay = initialPlayPerformed.getValue() ?
                !playbackObserver.getIsPaused() :
                autoPlay;
            onReloadOrder({ position: position, autoPlay: autoplay });
        }, cancelSignal);
        var contentTimeBoundariesObserver = this
            ._createContentTimeBoundariesObserver(manifest, mediaSource, streamObserver, segmentBuffersStore, cancelSignal);
        /**
         * Emit a "loaded" events once the initial play has been performed and the
         * media can begin playback.
         * Also emits warning events if issues arise when doing so.
         */
        autoPlayResult
            .then(function () {
            getLoadedReference(playbackObserver, mediaElement, false, cancelSignal)
                .onUpdate(function (isLoaded, stopListening) {
                if (isLoaded) {
                    stopListening();
                    _this.trigger("loaded", { segmentBuffersStore: segmentBuffersStore });
                }
            }, { emitCurrentValue: true, clearSignal: cancelSignal });
        })
            .catch(function (err) {
            if (cancelSignal.isCancelled()) {
                return; // Current loading cancelled, no need to trigger the error
            }
            _this._onFatalError(err);
        });
        /* eslint-disable-next-line @typescript-eslint/no-this-alias */
        var self = this;
        StreamOrchestrator({ manifest: manifest, initialPeriod: initialPeriod }, streamObserver, representationEstimator, segmentBuffersStore, segmentFetcherCreator, bufferOptions, handleStreamOrchestratorCallbacks(), cancelSignal);
        /**
         * Returns Object handling the callbacks from a `StreamOrchestrator`, which
         * are basically how it communicates about events.
         * @returns {Object}
         */
        function handleStreamOrchestratorCallbacks() {
            return {
                needsBufferFlush: function () {
                    var seekedTime = mediaElement.currentTime + 0.001;
                    playbackObserver.setCurrentTime(seekedTime);
                    // Seek again once data begins to be buffered.
                    // This is sadly necessary on some browsers to avoid decoding
                    // issues after a flush.
                    //
                    // NOTE: there's in theory a potential race condition in the following
                    // logic as the callback could be called when media data is still
                    // being removed by the browser - which is an asynchronous process.
                    // The following condition checking for buffered data could thus lead
                    // to a false positive where we're actually checking previous data.
                    // For now, such scenario is avoided by setting the
                    // `includeLastObservation` option to `false` and calling
                    // `needsBufferFlush` once MSE media removal operations have been
                    // explicitely validated by the browser, but that's a complex and easy
                    // to break system.
                    playbackObserver.listen(function (obs, stopListening) {
                        if (
                        // Data is buffered around the current position
                        obs.currentRange !== null ||
                            // Or, for whatever reason, playback is already advancing
                            obs.position > seekedTime + 0.1) {
                            stopListening();
                            playbackObserver.setCurrentTime(obs.position + 0.001);
                        }
                    }, { includeLastObservation: false, clearSignal: cancelSignal });
                },
                streamStatusUpdate: function (value) {
                    // Announce discontinuities if found
                    var period = value.period, bufferType = value.bufferType, imminentDiscontinuity = value.imminentDiscontinuity, position = value.position;
                    rebufferingController.updateDiscontinuityInfo({
                        period: period,
                        bufferType: bufferType,
                        discontinuity: imminentDiscontinuity,
                        position: position,
                    });
                    if (cancelSignal.isCancelled()) {
                        return; // Previous call has stopped streams due to a side-effect
                    }
                    // If the status for the last Period indicates that segments are all loaded
                    // or on the contrary that the loading resumed, announce it to the
                    // ContentTimeBoundariesObserver.
                    if (manifest.isLastPeriodKnown &&
                        value.period.id === manifest.periods[manifest.periods.length - 1].id) {
                        var hasFinishedLoadingLastPeriod = value.hasFinishedLoading ||
                            value.isEmptyStream;
                        if (hasFinishedLoadingLastPeriod) {
                            contentTimeBoundariesObserver
                                .onLastSegmentFinishedLoading(value.bufferType);
                        }
                        else {
                            contentTimeBoundariesObserver
                                .onLastSegmentLoadingResume(value.bufferType);
                        }
                    }
                },
                needsManifestRefresh: function () { return self._manifestFetcher.scheduleManualRefresh({
                    enablePartialRefresh: true,
                    canUseUnsafeMode: true,
                }); },
                manifestMightBeOufOfSync: function () {
                    var OUT_OF_SYNC_MANIFEST_REFRESH_DELAY = config.getCurrent().OUT_OF_SYNC_MANIFEST_REFRESH_DELAY;
                    self._manifestFetcher.scheduleManualRefresh({
                        enablePartialRefresh: false,
                        canUseUnsafeMode: false,
                        delay: OUT_OF_SYNC_MANIFEST_REFRESH_DELAY,
                    });
                },
                lockedStream: function (value) {
                    return rebufferingController.onLockedStream(value.bufferType, value.period);
                },
                adaptationChange: function (value) {
                    self.trigger("adaptationChange", value);
                    if (cancelSignal.isCancelled()) {
                        return; // Previous call has stopped streams due to a side-effect
                    }
                    contentTimeBoundariesObserver.onAdaptationChange(value.type, value.period, value.adaptation);
                },
                representationChange: function (value) {
                    self.trigger("representationChange", value);
                    if (cancelSignal.isCancelled()) {
                        return; // Previous call has stopped streams due to a side-effect
                    }
                    contentTimeBoundariesObserver.onRepresentationChange(value.type, value.period);
                },
                inbandEvent: function (value) { return self.trigger("inbandEvents", value); },
                warning: function (value) { return self.trigger("warning", value); },
                periodStreamReady: function (value) { return self.trigger("periodStreamReady", value); },
                periodStreamCleared: function (value) {
                    contentTimeBoundariesObserver.onPeriodCleared(value.type, value.period);
                    if (cancelSignal.isCancelled()) {
                        return; // Previous call has stopped streams due to a side-effect
                    }
                    self.trigger("periodStreamCleared", value);
                },
                bitrateEstimationChange: function (value) {
                    return self.trigger("bitrateEstimationChange", value);
                },
                addedSegment: function (value) { return self.trigger("addedSegment", value); },
                needsMediaSourceReload: function (payload) {
                    var _a, _b;
                    var lastObservation = streamObserver.getReference().getValue();
                    var currentPosition = (_a = lastObservation.position.pending) !== null && _a !== void 0 ? _a : streamObserver.getCurrentTime();
                    var isPaused = (_b = lastObservation.paused.pending) !== null && _b !== void 0 ? _b : streamObserver.getIsPaused();
                    var position = currentPosition + payload.timeOffset;
                    if (payload.minimumPosition !== undefined) {
                        position = Math.max(payload.minimumPosition, position);
                    }
                    if (payload.maximumPosition !== undefined) {
                        position = Math.min(payload.maximumPosition, position);
                    }
                    onReloadOrder({ position: position, autoPlay: !isPaused });
                },
                needsDecipherabilityFlush: function () {
                    var _a, _b, _c;
                    var keySystem = getKeySystemConfiguration(mediaElement);
                    if (shouldReloadMediaSourceOnDecipherabilityUpdate(keySystem === null || keySystem === void 0 ? void 0 : keySystem[0])) {
                        var lastObservation = streamObserver.getReference().getValue();
                        var position = (_a = lastObservation.position.pending) !== null && _a !== void 0 ? _a : streamObserver.getCurrentTime();
                        var isPaused = (_b = lastObservation.paused.pending) !== null && _b !== void 0 ? _b : streamObserver.getIsPaused();
                        onReloadOrder({ position: position, autoPlay: !isPaused });
                    }
                    else {
                        var lastObservation = streamObserver.getReference().getValue();
                        var position = (_c = lastObservation.position.pending) !== null && _c !== void 0 ? _c : streamObserver.getCurrentTime();
                        // simple seek close to the current position
                        // to flush the buffers
                        if (position + 0.001 < lastObservation.duration) {
                            playbackObserver.setCurrentTime(mediaElement.currentTime + 0.001);
                        }
                        else {
                            playbackObserver.setCurrentTime(position);
                        }
                    }
                },
                encryptionDataEncountered: function (value) {
                    for (var _i = 0, value_1 = value; _i < value_1.length; _i++) {
                        var protectionData = value_1[_i];
                        protectionRef.setValue(protectionData);
                        if (cancelSignal.isCancelled()) {
                            return; // Previous call has stopped streams due to a side-effect
                        }
                    }
                },
                error: function (err) { return self._onFatalError(err); },
            };
        }
    };
    /**
     * Creates a `ContentTimeBoundariesObserver`, a class indicating various
     * events related to media time (such as duration updates, period changes,
     * warnings about being out of the Manifest time boundaries or "endOfStream"
     * management), handle those events and returns the class.
     *
     * Various methods from that class need then to be called at various events
     * (see `ContentTimeBoundariesObserver`).
     * @param {Object} manifest
     * @param {MediaSource} mediaSource
     * @param {Object} streamObserver
     * @param {Object} segmentBuffersStore
     * @param {Object} cancelSignal
     * @returns {Object}
     */
    MediaSourceContentInitializer.prototype._createContentTimeBoundariesObserver = function (manifest, mediaSource, streamObserver, segmentBuffersStore, cancelSignal) {
        var _this = this;
        /** Maintains the MediaSource's duration up-to-date with the Manifest */
        var mediaSourceDurationUpdater = new MediaSourceDurationUpdater(mediaSource);
        cancelSignal.register(function () {
            mediaSourceDurationUpdater.stopUpdating();
        });
        /** Allows to cancel a pending `end-of-stream` operation. */
        var endOfStreamCanceller = null;
        var contentTimeBoundariesObserver = new ContentTimeBoundariesObserver(manifest, streamObserver, segmentBuffersStore.getBufferTypes());
        cancelSignal.register(function () {
            contentTimeBoundariesObserver.dispose();
        });
        contentTimeBoundariesObserver.addEventListener("warning", function (err) {
            return _this.trigger("warning", err);
        });
        contentTimeBoundariesObserver.addEventListener("periodChange", function (period) {
            _this.trigger("activePeriodChanged", { period: period });
        });
        contentTimeBoundariesObserver.addEventListener("endingPositionChange", function (x) { return mediaSourceDurationUpdater.updateDuration(x.endingPosition, x.isEnd); });
        contentTimeBoundariesObserver.addEventListener("endOfStream", function () {
            if (endOfStreamCanceller === null) {
                endOfStreamCanceller = new TaskCanceller();
                endOfStreamCanceller.linkToSignal(cancelSignal);
                log.debug("Init: end-of-stream order received.");
                maintainEndOfStream(mediaSource, endOfStreamCanceller.signal);
            }
        });
        contentTimeBoundariesObserver.addEventListener("resumeStream", function () {
            if (endOfStreamCanceller !== null) {
                log.debug("Init: resume-stream order received.");
                endOfStreamCanceller.cancel();
                endOfStreamCanceller = null;
            }
        });
        var endInfo = contentTimeBoundariesObserver.getCurrentEndingTime();
        mediaSourceDurationUpdater.updateDuration(endInfo.endingPosition, endInfo.isEnd);
        return contentTimeBoundariesObserver;
    };
    /**
     * Creates a `RebufferingController`, a class trying to avoid various stalling
     * situations (such as rebuffering periods), and returns it.
     *
     * Various methods from that class need then to be called at various events
     * (see `RebufferingController` definition).
     *
     * This function also handles the `RebufferingController`'s events:
     *   - emit "stalled" events when stalling situations cannot be prevented,
     *   - emit "unstalled" events when we could get out of one,
     *   - emit "warning" on various rebuffering-related minor issues
     *     like discontinuity skipping.
     * @param {Object} playbackObserver
     * @param {Object} manifest
     * @param {Object} speed
     * @param {Object} cancelSignal
     * @returns {Object}
     */
    MediaSourceContentInitializer.prototype._createRebufferingController = function (playbackObserver, manifest, segmentBuffersStore, speed, cancelSignal) {
        var _this = this;
        var rebufferingController = new RebufferingController(playbackObserver, manifest, segmentBuffersStore, speed);
        // Bubble-up events
        rebufferingController.addEventListener("stalled", function (evt) { return _this.trigger("stalled", evt); });
        rebufferingController.addEventListener("unstalled", function () { return _this.trigger("unstalled", null); });
        rebufferingController.addEventListener("warning", function (err) { return _this.trigger("warning", err); });
        cancelSignal.register(function () { return rebufferingController.destroy(); });
        rebufferingController.start();
        return rebufferingController;
    };
    return MediaSourceContentInitializer;
}(ContentInitializer));
export default MediaSourceContentInitializer;
