UNPKG

rx-player

Version:
1,510 lines 72.1 kB
"use strict";
/**
 * 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 __read = (this && this.__read) || function (o, n) {
    var m = typeof Symbol === "function" && o[Symbol.iterator];
    if (!m) return o;
    var i = m.call(o), r, ar = [], e;
    try {
        while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
    }
    catch (error) { e = { error: error }; }
    finally {
        try {
            if (r && !r.done && (m = i["return"])) m.call(i);
        }
        finally { if (e) throw e.error; }
    }
    return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
    if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
        if (ar || !(i in from)) {
            if (!ar) ar = Array.prototype.slice.call(from, 0, i);
            ar[i] = from[i];
        }
    }
    return to.concat(ar || Array.prototype.slice.call(from));
};
var __values = (this && this.__values) || function(o) {
    var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
    if (m) return m.call(o);
    if (o && typeof o.length === "number") return {
        next: function () {
            if (o && i >= o.length) o = void 0;
            return { value: o && o[i++], done: !o };
        }
    };
    throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
Object.defineProperty(exports, "__esModule", { value: true });
/**
 * This file is used to abstract the notion of text, audio and video tracks
 * switching for an easier API management.
 */
var config_1 = require("../../config");
var errors_1 = require("../../errors");
var log_1 = require("../../log");
var manifest_1 = require("../../manifest");
var array_find_1 = require("../../utils/array_find");
var assert_1 = require("../../utils/assert");
var event_emitter_1 = require("../../utils/event_emitter");
var is_null_or_undefined_1 = require("../../utils/is_null_or_undefined");
var object_assign_1 = require("../../utils/object_assign");
var reference_1 = require("../../utils/reference");
var track_dispatcher_1 = require("./track_dispatcher");
/**
 * Class helping with the management of the audio, video and text tracks and
 * qualities.
 *
 * The `TracksStore` allows to choose a track and qualities for different types
 * of media through a simpler API.
 *
 * @class TracksStore
 */
var TracksStore = /** @class */ (function (_super) {
    __extends(TracksStore, _super);
    function TracksStore(args) {
        var _a;
        var _this = _super.call(this) || this;
        _this._handledTrackTypes = args.handledTrackTypes;
        _this._storedPeriodInfo = [];
        _this._isDisposed = false;
        _this._cachedPeriodInfo = new WeakMap();
        _this._isTrickModeTrackEnabled = args.preferTrickModeTracks;
        _this._defaultAudioTrackSwitchingMode =
            (_a = args.defaultAudioTrackSwitchingMode) !== null && _a !== void 0 ? _a : config_1.default.getCurrent().DEFAULT_AUDIO_TRACK_SWITCHING_MODE;
        _this.onTracksNotPlayableForType = args.onTracksNotPlayableForType;
        return _this;
    }
    /**
     * Return Array of Period information, to allow an outside application to
     * modify the track of any Period.
     * @returns {Array.<Object>}
     */
    TracksStore.prototype.getAvailablePeriods = function () {
        return this._storedPeriodInfo.reduce(function (acc, p) {
            if (p.isPeriodAdvertised) {
                acc.push(toExposedPeriod(p.period));
            }
            return acc;
        }, []);
    };
    /**
     * Callack that needs to be called as codec support is either first known or
     * updated on the Manifest.
     */
    TracksStore.prototype.onManifestCodecSupportUpdate = function () {
        this._selectInitialTrackIfNeeded();
    };
    /**
     * Check that a period has a supported track for the given track type.
     * Trigger an error with `MANIFEST_INCOMPATIBLE_CODECS_ERROR` if no codecs are
     * supported and `onTracksNotPlayableForType[ttype]` is "error".
     *
     * @param period - The period to check
     * @param ttype - The track type.
     */
    TracksStore.prototype.checkPeriodHasSupportedTrack = function (period, ttype) {
        var _a, _b;
        var adaptationsForType = period.adaptations[ttype];
        var audioVideoAdaptations = __spreadArray(__spreadArray([], __read(((_a = period.adaptations.audio) !== null && _a !== void 0 ? _a : [])), false), __read(((_b = period.adaptations.video) !== null && _b !== void 0 ? _b : [])), false);
        var periodHasAdaptationForType = adaptationsForType !== undefined && adaptationsForType.length > 0;
        if (!periodHasAdaptationForType) {
            // The content does not have audio/video media, e.g. it's an audio only content.
            // there is no track of this type to select.
            return;
        }
        var isTypeUnsupported = adaptationsForType.every(function (adapt) { return adapt.supportStatus.hasSupportedCodec === false; });
        var isAudioAndVideoUnsupported = audioVideoAdaptations.every(function (adapt) { return adapt.supportStatus.hasSupportedCodec === false; });
        if (isTypeUnsupported) {
            var err = new errors_1.MediaError("MANIFEST_INCOMPATIBLE_CODECS_ERROR", "No supported " + ttype + " adaptations", { tracks: undefined });
            if (isAudioAndVideoUnsupported) {
                // Both video and audio are unsupported or the content is audio/video only.
                // The content cannot be played without this media type as there is no other media type.
                this.trigger("error", err);
                // The previous event trigger could have had side-effects, so we
                // re-check if we're still mostly in the same state
                if (this._isDisposed) {
                    return; // Someone disposed the `TracksStore` on the previous side-effect
                }
                this.dispose();
                return;
            }
            else if (this.onTracksNotPlayableForType[ttype] === "continue") {
                // audio or video is not playable, but let's continue the playback without audio
                // or without video because of the option was set to "continue".
                this.trigger("warning", err);
            }
            else {
                this.trigger("error", err);
                // The previous event trigger could have had side-effects, so we
                // re-check if we're still mostly in the same state
                if (this._isDisposed) {
                    return; // Someone disposed the `TracksStore` on the previous side-effect
                }
                this.dispose();
                return;
            }
        }
    };
    /**
     * Update the list of Periods handled by the TracksStore and make a
     * track choice decision for each of them.
     * @param {Object} manifest - The new Manifest object
     */
    TracksStore.prototype.onManifestUpdate = function (manifest) {
        var e_1, _a, _b, e_2, _c;
        var _this = this;
        var _d, _e, _f;
        var periods = manifest.periods;
        // We assume that they are always sorted chronologically
        // In dev mode, perform a runtime check that this is the case
        // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
        if (0 /* __ENVIRONMENT__.CURRENT_ENV */ === 1 /* __ENVIRONMENT__.DEV */) {
            for (var i = 1; i < periods.length; i++) {
                (0, assert_1.default)(periods[i - 1].start <= periods[i].start);
            }
        }
        /** Periods which have just been added. */
        var addedPeriods = [];
        var _loop_1 = function (period) {
            ["audio", "video"].forEach(function (ttype) {
                _this.checkPeriodHasSupportedTrack(period, ttype);
            });
            if (this_1._isDisposed) {
                return { value: void 0 };
            }
        };
        var this_1 = this;
        try {
            for (var periods_1 = __values(periods), periods_1_1 = periods_1.next(); !periods_1_1.done; periods_1_1 = periods_1.next()) {
                var period = periods_1_1.value;
                var state_1 = _loop_1(period);
                if (typeof state_1 === "object")
                    return state_1.value;
            }
        }
        catch (e_1_1) { e_1 = { error: e_1_1 }; }
        finally {
            try {
                if (periods_1_1 && !periods_1_1.done && (_a = periods_1.return)) _a.call(periods_1);
            }
            finally { if (e_1) throw e_1.error; }
        }
        var newPListIdx = 0;
        for (var i = 0; i < this._storedPeriodInfo.length; i++) {
            var oldPeriod = this._storedPeriodInfo[i].period;
            var newPeriod = periods[newPListIdx];
            if (newPeriod === undefined) {
                // We reached the end of the new Periods, remove remaining old Periods
                for (var j = this._storedPeriodInfo.length - 1; j >= i; j--) {
                    this._storedPeriodInfo[j].inManifest = false;
                    if (isPeriodItemRemovable(this._storedPeriodInfo[j])) {
                        this._removePeriodObject(j);
                    }
                }
            }
            else if (oldPeriod === newPeriod) {
                newPListIdx++;
                this.resetSelectedTrackIfNotAvailableAnymore(this._storedPeriodInfo[i], newPeriod, "video");
                this.resetSelectedTrackIfNotAvailableAnymore(this._storedPeriodInfo[i], newPeriod, "audio");
                this.resetSelectedTrackIfNotAvailableAnymore(this._storedPeriodInfo[i], newPeriod, "text");
                // (If not, what do?)
            }
            else if (oldPeriod.start <= newPeriod.start) {
                // This old Period does not exist anymore.
                this._storedPeriodInfo[i].inManifest = false;
                if (isPeriodItemRemovable(this._storedPeriodInfo[i])) {
                    this._removePeriodObject(i);
                    i--;
                }
            }
            else {
                var newPeriodInfo = generatePeriodInfo(newPeriod, true);
                // oldPeriod.start > newPeriod.start: insert newPeriod before
                this._storedPeriodInfo.splice(i, 0, newPeriodInfo);
                addedPeriods.push(newPeriodInfo);
                newPListIdx++;
                // Note: we don't increment `i` on purpose here, as we want to check the
                // same oldPeriod at the next loop iteration
            }
        }
        if (newPListIdx < periods.length) {
            // Add further new Period
            var periodsToAdd = periods
                .slice(newPListIdx)
                .map(function (p) { return generatePeriodInfo(p, true); });
            (_b = this._storedPeriodInfo).push.apply(_b, __spreadArray([], __read(periodsToAdd), false));
            addedPeriods.push.apply(addedPeriods, __spreadArray([], __read(periodsToAdd), false));
        }
        try {
            for (var _g = __values(this._storedPeriodInfo), _h = _g.next(); !_h.done; _h = _g.next()) {
                var storedPeriodInfo = _h.value;
                (_d = storedPeriodInfo.audio.dispatcher) === null || _d === void 0 ? void 0 : _d.refresh();
                (_e = storedPeriodInfo.video.dispatcher) === null || _e === void 0 ? void 0 : _e.refresh();
                (_f = storedPeriodInfo.text.dispatcher) === null || _f === void 0 ? void 0 : _f.refresh();
            }
        }
        catch (e_2_1) { e_2 = { error: e_2_1 }; }
        finally {
            try {
                if (_h && !_h.done && (_c = _g.return)) _c.call(_g);
            }
            finally { if (e_2) throw e_2.error; }
        }
    };
    TracksStore.prototype.onDecipherabilityUpdates = function () {
        var e_3, _a;
        var _b, _c, _d;
        try {
            for (var _e = __values(this._storedPeriodInfo), _f = _e.next(); !_f.done; _f = _e.next()) {
                var storedPeriodInfo = _f.value;
                (_b = storedPeriodInfo.audio.dispatcher) === null || _b === void 0 ? void 0 : _b.refresh();
                (_c = storedPeriodInfo.video.dispatcher) === null || _c === void 0 ? void 0 : _c.refresh();
                (_d = storedPeriodInfo.text.dispatcher) === null || _d === void 0 ? void 0 : _d.refresh();
            }
        }
        catch (e_3_1) { e_3 = { error: e_3_1 }; }
        finally {
            try {
                if (_f && !_f.done && (_a = _e.return)) _a.call(_e);
            }
            finally { if (e_3) throw e_3.error; }
        }
    };
    /**
     * Reset the stored settings if the track is not available anymore in
     * the new manifest.
     * @param periodInfo
     * @param newPeriod
     * @param type
     * @returns
     */
    TracksStore.prototype.resetSelectedTrackIfNotAvailableAnymore = function (periodInfo, newPeriod, type) {
        var _a;
        var wantedTrack = periodInfo[type].storedSettings;
        if ((0, is_null_or_undefined_1.default)(wantedTrack)) {
            return;
        }
        var supportedAdaptations = (0, manifest_1.getSupportedAdaptations)(newPeriod, type);
        var stillHere = supportedAdaptations.some(function (a) { return a.id === wantedTrack.adaptation.id; });
        if (stillHere) {
            // The track still exists, it don't need to be resetted.
            return;
        }
        log_1.default.warn("Track", "Chosen ".concat(type, " Adaptation not available anymore"));
        if (type === "video") {
            periodInfo.video.storedSettings = this.getDefaultStoredSettingsForAdaptation(supportedAdaptations, "video");
        }
        else if (type === "audio") {
            periodInfo.audio.storedSettings = this.getDefaultStoredSettingsForAdaptation(supportedAdaptations, "audio");
        }
        else if (type === "text") {
            periodInfo.text.storedSettings = this.getDefaultStoredSettingsForAdaptation(supportedAdaptations, "text");
        }
        this.trigger("trackUpdate", {
            period: toExposedPeriod(newPeriod),
            trackType: type,
            reason: "missing",
        });
        // The previous event trigger could have had side-effects, so we
        // re-check if we're still mostly in the same state
        if (this._isDisposed) {
            return; // The current TracksStore is disposed, we can abort
        }
        var periodItem = getPeriodItem(this._storedPeriodInfo, periodInfo.period.id);
        if (periodItem !== undefined &&
            periodItem.isPeriodAdvertised &&
            periodItem[type].storedSettings === null) {
            (_a = periodItem[type].dispatcher) === null || _a === void 0 ? void 0 : _a.updateTrack(null);
        }
    };
    TracksStore.prototype.getDefaultStoredSettingsForAdaptation = function (supportedAdaptations, type) {
        var DEFAULT_VIDEO_TRACK_SWITCHING_MODE = config_1.default.getCurrent().DEFAULT_VIDEO_TRACK_SWITCHING_MODE;
        if (type === "text") {
            return null;
        }
        if (supportedAdaptations.length === 0) {
            return null;
        }
        switch (type) {
            case "text":
                return null;
            case "video": {
                var adaptationBase = supportedAdaptations[0];
                var adaptation = getRightVideoTrack(adaptationBase, this._isTrickModeTrackEnabled);
                var lockedRepresentations = new reference_1.default(null);
                return {
                    adaptationBase: adaptationBase,
                    adaptation: adaptation,
                    switchingMode: DEFAULT_VIDEO_TRACK_SWITCHING_MODE,
                    lockedRepresentations: lockedRepresentations,
                };
            }
            case "audio": {
                return {
                    adaptation: supportedAdaptations[0],
                    switchingMode: this._defaultAudioTrackSwitchingMode,
                    lockedRepresentations: new reference_1.default(null),
                };
            }
            default: {
                (0, assert_1.assertUnreachable)(type);
            }
        }
    };
    /**
     * Add shared reference to choose Adaptation for new "audio", "video" or
     * "text" Period.
     *
     * Note that such reference has to be removed through `removeTrackReference`
     * so ressources can be freed.
     * @param {string} bufferType - The concerned buffer type
     * @param {Period} period - The concerned Period.
     * @param {Object} adaptationRef - A reference through which
     * the choice will be given.
     */
    TracksStore.prototype.addTrackReference = function (bufferType, period, adaptationRef) {
        var e_4, _a;
        var _this = this;
        log_1.default.debug("Track", "Adding Track Reference", { bufferType: bufferType, periodId: period.id });
        var periodObj = getPeriodItem(this._storedPeriodInfo, period.id);
        if (periodObj === undefined) {
            // The Period has not yet been added.
            periodObj = generatePeriodInfo(period, false);
            var found = false;
            for (var i = 0; i < this._storedPeriodInfo.length; i++) {
                if (this._storedPeriodInfo[i].period.start > period.start) {
                    this._storedPeriodInfo.splice(i, 0, periodObj);
                    found = true;
                }
            }
            if (!found) {
                this._storedPeriodInfo.push(periodObj);
            }
        }
        if (periodObj[bufferType].dispatcher !== null) {
            log_1.default.error("Track", "Subject already added for type and Period", {
                bufferType: bufferType,
                periodId: period.id,
            });
            periodObj[bufferType].dispatcher.dispose("TrackDispatcher double add issue");
        }
        var dispatcher = new track_dispatcher_1.default(adaptationRef);
        periodObj[bufferType].dispatcher = dispatcher;
        dispatcher.addEventListener("noPlayableRepresentation", function () {
            _this.handleMissingOrUnplayableTrack(period, bufferType, false);
        });
        dispatcher.addEventListener("noPlayableLockedRepresentation", function () {
            // TODO check that it doesn't already lead to segment loading or MediaSource
            // reloading
            if (periodObj === undefined) {
                return;
            }
            _this.unlockVideoRepresentations(periodObj);
            _this.trigger("brokenRepresentationsLock", {
                period: { id: period.id, start: period.start, end: period.end },
                trackType: bufferType,
            });
        });
        this._selectInitialTrackIfNeeded();
        // Ensure `newAvailablePeriods` is sent
        if (this._shouldAdvertisePeriod(periodObj)) {
            periodObj.isPeriodAdvertised = true;
            this.trigger("newAvailablePeriods", [
                {
                    id: period.id,
                    start: period.start,
                    end: period.end,
                },
            ]);
            if (this._isDisposed) {
                return;
            }
        }
        // Ensure the initial track is set for each type now)
        var trackTypes = ["audio", "video", "text"];
        try {
            for (var trackTypes_1 = __values(trackTypes), trackTypes_1_1 = trackTypes_1.next(); !trackTypes_1_1.done; trackTypes_1_1 = trackTypes_1.next()) {
                var ttype = trackTypes_1_1.value;
                var trackObj = periodObj[ttype];
                if (periodObj.isPeriodAdvertised &&
                    trackObj.dispatcher !== null &&
                    !trackObj.dispatcher.hasSetTrack() &&
                    trackObj.storedSettings !== undefined) {
                    trackObj.dispatcher.updateTrack(trackObj.storedSettings);
                }
                if (this._isDisposed) {
                    return;
                }
            }
        }
        catch (e_4_1) { e_4 = { error: e_4_1 }; }
        finally {
            try {
                if (trackTypes_1_1 && !trackTypes_1_1.done && (_a = trackTypes_1.return)) _a.call(trackTypes_1);
            }
            finally { if (e_4) throw e_4.error; }
        }
    };
    /**
     * Throws an error if neither audio nor video tracks are selected for the given period.
     *
     * This indicates that the application or user has not selected any tracks for playback,
     * which is considered an invalid state.
     *
     * @param {object} period - The period to check for selected tracks.
     * @returns {void}
     * @throws {erorr} If no audio or video tracks are set for the period.
     */
    TracksStore.prototype.throwIfTracksAreNotSetForPeriod = function (period) {
        var periodItem = getPeriodItem(this._storedPeriodInfo, period.id);
        if (periodItem !== undefined) {
            var hasNoTrackAtAll = ["audio", "video"].every(function (ttype) { return periodItem[ttype].storedSettings === null; });
            if (hasNoTrackAtAll) {
                var err = new errors_1.MediaError("NO_AUDIO_VIDEO_TRACKS", "No audio and no video tracks are set.");
                this.trigger("error", err);
                this.dispose();
            }
        }
    };
    /**
     * Handles the case when no playable representations are available, or when no initial track has been set,
     * for a given period and track type (e.g., `'video'` or `'audio'`).
     *
     * It attempts to fall back to another available track.
     *
     * If no fallback tracks are available, the function may either:
     * - Throw an error
     * - Continue playback by disabling the affected media type (audio/video)
     *
     * The behavior depends on how the `onVideoTracksNotPlayable` and `onAudioTracksNotPlayable` options are configured.
     *
     * @param {Object} period - The period object containing the adaptations.
     * @param {string} trackType - The type of media track (e.g., `'video'` or `'audio'`) that became unplayable.
     *@param {boolean} isInitialSelection - Indicates if this occurs during initial track selection.
     * @returns {void}
     */
    TracksStore.prototype.handleMissingOrUnplayableTrack = function (period, trackType, isInitialSelection) {
        var _a, _b, _c, _d;
        var _e = getFallbackTrack(period, trackType), fallbackTrack = _e.fallbackTrack, noSourceMedia = _e.noSourceMedia;
        var typeInfo = (_a = getPeriodItem(this._storedPeriodInfo, period.id)) === null || _a === void 0 ? void 0 : _a[trackType];
        if (typeInfo === undefined) {
            log_1.default.warn("Track", "Could not find period", { periodId: period.id });
            return;
        }
        var initialStoredSettings = typeInfo.storedSettings;
        var hasStoredSettingsChanged = function () {
            return typeInfo.storedSettings !== initialStoredSettings;
        };
        if (fallbackTrack !== null) {
            var switchingMode = void 0;
            if (trackType === "audio") {
                switchingMode = this._defaultAudioTrackSwitchingMode;
            }
            else if (trackType === "text") {
                switchingMode = "direct";
            }
            else {
                switchingMode = "reload";
            }
            var storedSettings = {
                adaptation: fallbackTrack,
                switchingMode: switchingMode,
                lockedRepresentations: new reference_1.default(null),
            };
            typeInfo.storedSettings = storedSettings;
            if (!isInitialSelection) {
                // "trackUpdate" events are not sent for the initial track.
                // See documentation: #Player_Events.md, section ###trackUpdate
                this.trigger("trackUpdate", {
                    period: toExposedPeriod(period),
                    trackType: trackType,
                    reason: "no-playable-representation",
                });
            }
            // The previous event trigger could have had side-effects, so we
            // re-check if we're still mostly in the same state
            if (this._isDisposed) {
                return; // Someone disposed the `TracksStore` on the previous side-effect
            }
            // Check again that no track change occurred in the meantime
            if (typeInfo.storedSettings === storedSettings) {
                (_b = typeInfo.dispatcher) === null || _b === void 0 ? void 0 : _b.updateTrack(storedSettings);
            }
        }
        else if (fallbackTrack === null && !noSourceMedia) {
            this.trigger("noPlayableTrack", {
                trackType: trackType,
                period: { id: period.id, start: period.start, end: period.end },
            });
            // The previous event trigger could have had side-effects, so we
            // re-check if we're still mostly in the same state
            if (this._isDisposed) {
                return; // Someone disposed the `TracksStore` on the previous side-effect
            }
            var fallbackBehavior = this.onTracksNotPlayableForType[trackType];
            if (hasStoredSettingsChanged()) {
                // The previous "noPlayableTrack" event might have caused changes,
                // so we re-check to see if the selected track has been updated.
                // If it has, we exit early because the API consumer likely adjusted the settings,
                // and throwing an error now would be out of sync with their changes.
            }
            else if (fallbackBehavior === "continue") {
                log_1.default.warn("Track", "No playable ".concat(trackType, ", continuing without ").concat(trackType));
                typeInfo.storedSettings = null;
                if (!isInitialSelection) {
                    // "trackUpdate" events are not sent for the initial track.
                    // See documentation: #Player_Events.md, section ###trackUpdate
                    this.trigger("trackUpdate", {
                        period: toExposedPeriod(period),
                        trackType: trackType,
                        reason: "no-playable-representation",
                    });
                }
                if (typeInfo.storedSettings !== null || this._isDisposed) {
                    // The previous "trackUpdate" event might have caused changes,
                    // so we re-check to see if the selected track has been updated.
                    // If it has, we exit early because the API consumer likely adjusted the settings,
                    // and throwing an error now would be out of sync with their changes.
                }
                else {
                    (_c = typeInfo.dispatcher) === null || _c === void 0 ? void 0 : _c.updateTrack(null);
                }
            }
            else if (fallbackBehavior === "error") {
                var noRepErr = new errors_1.MediaError("NO_PLAYABLE_REPRESENTATION", "No ".concat(trackType, " Representation can be played"), { tracks: undefined });
                this.trigger("error", noRepErr);
            }
        }
        else if (fallbackTrack === null && noSourceMedia) {
            log_1.default.debug("Track", "The period does not have adaptation for ".concat(trackType, " there is no track to choose"));
            typeInfo.storedSettings = null;
            if (!isInitialSelection) {
                // "trackUpdate" events are not sent for the initial track.
                // See documentation: #Player_Events.md, section ###trackUpdate
                this.trigger("trackUpdate", {
                    period: toExposedPeriod(period),
                    trackType: trackType,
                    reason: "no-playable-representation",
                });
            }
            if (typeInfo.storedSettings !== null || this._isDisposed) {
                // The previous "trackUpdate" event might have caused changes,
                // so we re-check to see if the selected track has been updated.
                // If it has, we exit early because the API consumer likely adjusted the settings,
                // and throwing an error now would be out of sync with their changes.
            }
            else {
                (_d = typeInfo.dispatcher) === null || _d === void 0 ? void 0 : _d.updateTrack(null);
            }
        }
        // The previous event trigger could have had side-effects, so we
        // re-check if we're still mostly in the same state
        if (this._isDisposed) {
            return; // Someone disposed the `TracksStore` on the previous side-effect
        }
        if (trackType !== "text") {
            // Allow missing text tracks; only enforce for audio/video
            this.throwIfTracksAreNotSetForPeriod(period);
        }
    };
    /**
     * Remove shared reference to choose an "audio", "video" or "text" Adaptation
     * for a Period.
     * @param {string} bufferType - The concerned buffer type
     * @param {string} periodId - The concerned Period's `id`.
     */
    TracksStore.prototype.removeTrackReference = function (bufferType, periodId) {
        log_1.default.debug("Track", "Removing Track Reference", { bufferType: bufferType, periodId: periodId });
        var periodIndex;
        for (var i = 0; i < this._storedPeriodInfo.length; i++) {
            var periodI = this._storedPeriodInfo[i];
            if (periodI.period.id === periodId) {
                periodIndex = i;
                break;
            }
        }
        if (periodIndex === undefined) {
            log_1.default.warn("Track", "type not found for period", { bufferType: bufferType, periodId: periodId });
            return;
        }
        var periodObj = this._storedPeriodInfo[periodIndex];
        var choiceItem = periodObj[bufferType];
        if ((choiceItem === null || choiceItem === void 0 ? void 0 : choiceItem.dispatcher) === null) {
            log_1.default.warn("Track", "TrackDispatcher already removed for type and Period", {
                bufferType: bufferType,
                periodId: periodId,
            });
            return;
        }
        choiceItem.dispatcher.dispose("TrackDispatcher reference removal");
        choiceItem.dispatcher = null;
        if (isPeriodItemRemovable(periodObj)) {
            this._removePeriodObject(periodIndex);
        }
    };
    /**
     * Allows to recuperate a "Period Object" - used in get/set methods of the
     * `TracksStore` - by giving the Period itself.
     *
     * This method should be preferred when possible over `getPeriodObjectFromId`
     * because it is able to fallback on an internal cache in case the
     * corresponding Period is not stored anymore.
     * This for example could happen when a Period has been removed from the
     * Manifest yet may still be needed (e.g. because its linked segments might
     * still live in the buffers).
     *
     * Note however that this cache-retrieval logic is based on a Map whose key
     * is the Period's JavaScript reference. As such, the cache won't be used if
     * `Period` corresponds to a copy of the original `Period` object.
     *
     * @param {Object} period
     * @returns {Object}
     */
    TracksStore.prototype.getPeriodObjectFromPeriod = function (period) {
        var periodObj = getPeriodItem(this._storedPeriodInfo, period.id);
        if (periodObj === undefined && period !== undefined) {
            return this._cachedPeriodInfo.get(period);
        }
        return periodObj;
    };
    /**
     * Allows to recuperate a "Period Object" - used in get/set methods of the
     * `TracksStore` - by giving the Period's id.
     *
     * Note that unlike `getPeriodObjectFromPeriod` this method is only going to look
     * into currently stored Period and as such old Periods not in the Manifest
     * anymore might not be retrievable.
     * If you want to retrieve Period objects linked to such Period, you might
     * prefer to use `getPeriodObjectFromPeriod` (which necessitates the original
     * Period object).
     *
     * @param {string} periodId - The concerned Period's id
     * @returns {Object}
     */
    TracksStore.prototype.getPeriodObjectFromId = function (periodId) {
        return getPeriodItem(this._storedPeriodInfo, periodId);
    };
    TracksStore.prototype.disableVideoTrickModeTracks = function () {
        if (!this._isTrickModeTrackEnabled) {
            return;
        }
        this._isTrickModeTrackEnabled = false;
        this._resetVideoTrackChoices("trickmode-disabled");
    };
    TracksStore.prototype.enableVideoTrickModeTracks = function () {
        if (this._isTrickModeTrackEnabled) {
            return;
        }
        this._isTrickModeTrackEnabled = true;
        this._resetVideoTrackChoices("trickmode-enabled");
    };
    /**
     * Reset the TracksStore's Period objects:
     *   - All Period which are not in the manifest currently will be removed.
     *   - All References used to communicate the wanted track will be removed.
     *
     * You might want to call this API when restarting playback.
     */
    TracksStore.prototype.resetPeriodObjects = function () {
        var _a, _b, _c;
        log_1.default.debug("Track", "Resetting Period Objects");
        for (var i = this._storedPeriodInfo.length - 1; i >= 0; i--) {
            var storedObj = this._storedPeriodInfo[i];
            (_a = storedObj.audio.dispatcher) === null || _a === void 0 ? void 0 : _a.dispose("TrackDispatcher reset");
            storedObj.audio.dispatcher = null;
            (_b = storedObj.video.dispatcher) === null || _b === void 0 ? void 0 : _b.dispose("TrackDispatcher reset");
            storedObj.video.dispatcher = null;
            (_c = storedObj.text.dispatcher) === null || _c === void 0 ? void 0 : _c.dispose("TrackDispatcher reset");
            storedObj.text.dispatcher = null;
            if (!storedObj.inManifest) {
                this._removePeriodObject(i);
            }
        }
    };
    /**
     * @returns {boolean}
     */
    TracksStore.prototype.isTrickModeEnabled = function () {
        return this._isTrickModeTrackEnabled;
    };
    /**
     * Set audio track based on the ID of its Adaptation for a given added Period.
     * @param {Object} params
     * @param {Object} params.periodRef - The concerned Period's object.
     * @param {string} params.trackId - adaptation id of the wanted track.
     * @param {string} params.switchingMode - Behavior when replacing the track by
     * another.
     * @param {Object|null} params.lockedRepresentations - Audio Representations
     * that should be locked after switching to that track.
     * `null` if no Audio Representation should be locked.
     * @param {number} params.relativeResumingPosition
     */
    TracksStore.prototype.setAudioTrack = function (payload) {
        var periodRef = payload.periodRef, trackId = payload.trackId, switchingMode = payload.switchingMode, lockedRepresentations = payload.lockedRepresentations, relativeResumingPosition = payload.relativeResumingPosition;
        return this._setAudioOrTextTrack({
            bufferType: "audio",
            periodRef: periodRef,
            trackId: trackId,
            switchingMode: switchingMode !== null && switchingMode !== void 0 ? switchingMode : this._defaultAudioTrackSwitchingMode,
            lockedRepresentations: lockedRepresentations,
            relativeResumingPosition: relativeResumingPosition,
        });
    };
    /**
     * Set text track based on the ID of its Adaptation for a given added Period.
     * @param {Object} periodObj - The concerned Period's object.
     * @param {string} wantedId - adaptation id of the wanted track.
     */
    TracksStore.prototype.setTextTrack = function (periodObj, wantedId) {
        return this._setAudioOrTextTrack({
            bufferType: "text",
            periodRef: periodObj,
            trackId: wantedId,
            switchingMode: "direct",
            lockedRepresentations: null,
            relativeResumingPosition: undefined,
        });
    };
    /**
     * Set audio track based on the ID of its Adaptation for a given added Period.
     * @param {Object} params
     * @param {string} params.bufferType
     * @param {Object} params.periodRef - The concerned Period's object.
     * @param {string} params.trackId - adaptation id of the wanted track.
     * @param {string} params.switchingMode - Behavior when replacing the track by
     * another.
     * @param {Array.<string>|null} params.lockedRepresentations - Audio
     * Representations that should be locked after switchingMode to that track.
     * `null` if no Audio Representation should be locked.
     * @param {number|undefined} params.relativeResumingPosition
     */
    TracksStore.prototype._setAudioOrTextTrack = function (_a) {
        var _b, _c;
        var bufferType = _a.bufferType, periodRef = _a.periodRef, trackId = _a.trackId, switchingMode = _a.switchingMode, lockedRepresentations = _a.lockedRepresentations, relativeResumingPosition = _a.relativeResumingPosition;
        if (!periodRef.isPeriodAdvertised) {
            throw new Error("Wanted Period not yet advertised.");
        }
        var period = periodRef.period;
        var wantedAdaptation = (0, array_find_1.default)((_b = period.adaptations[bufferType]) !== null && _b !== void 0 ? _b : [], function (_a) {
            var id = _a.id, supportStatus = _a.supportStatus;
            return supportStatus.hasSupportedCodec !== false &&
                supportStatus.isDecipherable !== false &&
                id === trackId;
        });
        if (wantedAdaptation === undefined) {
            throw new Error("Wanted ".concat(bufferType, " track not found."));
        }
        var typeInfo = periodRef[bufferType];
        var lockedRepresentationsRef;
        if (lockedRepresentations === null) {
            lockedRepresentationsRef = new reference_1.default(null);
        }
        else {
            var representationsToLock = this._getRepresentationsToLock(wantedAdaptation, lockedRepresentations);
            var repSwitchingMode = bufferType === "audio"
                ? this._defaultAudioTrackSwitchingMode
                : "direct";
            lockedRepresentationsRef = new reference_1.default({
                representationIds: representationsToLock,
                switchingMode: repSwitchingMode,
            });
        }
        var storedSettings = {
            adaptation: wantedAdaptation,
            switchingMode: switchingMode,
            lockedRepresentations: lockedRepresentationsRef,
            relativeResumingPosition: relativeResumingPosition,
        };
        typeInfo.storedSettings = storedSettings;
        this.trigger("trackUpdate", {
            period: toExposedPeriod(period),
            trackType: bufferType,
            reason: "manual",
        });
        // The previous event trigger could have had side-effects, so we
        // re-check if we're still mostly in the same state
        if (this._isDisposed) {
            return; // Someone disposed the `TracksStore` on the previous side-effect
        }
        var newPeriodItem = getPeriodItem(this._storedPeriodInfo, period.id);
        if (newPeriodItem !== undefined &&
            newPeriodItem[bufferType].storedSettings === storedSettings) {
            (_c = newPeriodItem[bufferType].dispatcher) === null || _c === void 0 ? void 0 : _c.updateTrack(storedSettings);
        }
    };
    /**
     * Set video track based on the ID of its Adaptation for a given added Period.
     * @param {Object} params
     * @param {Object} params.periodRef - The concerned Period's object.
     * @param {string} params.trackId - adaptation id of the wanted track.
     * @param {string} params.switchingMode - Behavior when replacing the track by
     * another.
     * @param {Array.<string>|null} params.lockedRepresentations - Video
     * Representations that should be locked after switching to that track.
     * `null` if no Video Representation should be locked.
     * @param {number|undefined} params.relativeResumingPosition
     */
    TracksStore.prototype.setVideoTrack = function (payload) {
        var _a, _b;
        var periodRef = payload.periodRef, trackId = payload.trackId, switchingMode = payload.switchingMode, lockedRepresentations = payload.lockedRepresentations, relativeResumingPosition = payload.relativeResumingPosition;
        if (!periodRef.isPeriodAdvertised) {
            throw new Error("Wanted Period not yet advertised.");
        }
        var period = periodRef.period;
        var wantedAdaptation = (0, array_find_1.default)((_a = period.adaptations.video) !== null && _a !== void 0 ? _a : [], function (_a) {
            var id = _a.id, supportStatus = _a.supportStatus;
            return supportStatus.isDecipherable !== false &&
                supportStatus.hasSupportedCodec !== false &&
                id === trackId;
        });
        if (wantedAdaptation === undefined) {
            throw new Error("Wanted video track not found.");
        }
        var DEFAULT_VIDEO_TRACK_SWITCHING_MODE = config_1.default.getCurrent().DEFAULT_VIDEO_TRACK_SWITCHING_MODE;
        var typeInfo = periodRef.video;
        var newAdaptation = getRightVideoTrack(wantedAdaptation, this._isTrickModeTrackEnabled);
        var lockedRepresentationsRef;
        if (lockedRepresentations === null) {
            lockedRepresentationsRef = new reference_1.default(null);
        }
        else {
            var representationsToLock = this._getRepresentationsToLock(wantedAdaptation, lockedRepresentations);
            var repSwitchingMode = DEFAULT_VIDEO_TRACK_SWITCHING_MODE;
            lockedRepresentationsRef = new reference_1.default({
                representationIds: representationsToLock,
                switchingMode: repSwitchingMode,
            });
        }
        var storedSettings = {
            adaptationBase: wantedAdaptation,
            switchingMode: switchingMode !== null && switchingMode !== void 0 ? switchingMode : DEFAULT_VIDEO_TRACK_SWITCHING_MODE,
            adaptation: newAdaptation,
            relativeResumingPosition: relativeResumingPosition,
            lockedRepresentations: lockedRepresentationsRef,
        };
        typeInfo.storedSettings = storedSettings;
        this.trigger("trackUpdate", {
            period: toExposedPeriod(period),
            trackType: "video",
            reason: "manual",
        });
        // The previous event trigger could have had side-effects, so we
        // re-check if we're still mostly in the same state
        if (this._isDisposed) {
            return; // Someone disposed the `TracksStore` on the previous side-effect
        }
        var newPeriodItem = getPeriodItem(this._storedPeriodInfo, period.id);
        if (newPeriodItem !== undefined &&
            newPeriodItem.video.storedSettings === storedSettings) {
            (_b = newPeriodItem.video.dispatcher) === null || _b === void 0 ? void 0 : _b.updateTrack(storedSettings);
        }
    };
    /**
     * Disable the current text track for a given period.
     *
     * @param {Object} periodObj - The concerned Period's object
     * @param {string} bufferType - The type of track to disable.
     * @throws Error - Throws if the period given has not been added
     */
    TracksStore.prototype.disableTrack = function (periodObj, bufferType) {
        var _a, _b;
        if (!periodObj.isPeriodAdvertised) {
            throw new Error("Wanted Period not yet advertised.");
        }
        var trackInfo = periodObj[bufferType];
        if (trackInfo.storedSettings === null) {
            return;
        }
        if (bufferType !== "text") {
            // Potentially unneeded, but let's be clean
            (_a = periodObj[bufferType].storedSettings) === null || _a === void 0 ? void 0 : _a.lockedRepresentations.finish();
        }
        trackInfo.storedSettings = null;
        this.trigger("trackUpdate", {
            period: toExposedPeriod(periodObj.period),
            trackType: bufferType,
            reason: "manual",
        });
        // The previous event trigger could have had side-effects, so we
        // re-check if we're still mostly in the same state
        if (this._isDisposed) {
            return; // Someone disposed the `TracksStore` on the previous side-effect
        }
        var newPeriodItem = getPeriodItem(this._storedPeriodInfo, periodObj.period.id);
        if (newPeriodItem !== undefined &&
            newPeriodItem[bufferType].storedSettings === null) {
            (_b = newPeriodItem[bufferType].dispatcher) === null || _b === void 0 ? void 0 : _b.updateTrack(null);
        }
        if (newPeriodItem !== undefined) {
            var hasNoTrackAtAll = ["audio", "video"].every(function (ttype) { return newPeriodItem[ttype].storedSettings === null; });
            if (hasNoTrackAtAll) {
                var err = new errors_1.MediaError("NO_AUDIO_VIDEO_TRACKS", "No audio and no video tracks are set.");
                this.trigger("error", err);
                this.dispose();
                return;
            }
        }
    };
    /**
     * Returns an object describing the chosen audio track for the given audio
     * Period.
     *
     * Returns `null` is the the current audio track is disabled or not
     * set yet.
     *
     * Returns `undefined` if the given Period's id is not currently found in the
     * `TracksStore`. The cause being most probably that the corresponding
     * Period is not available anymore.
     * If you're in that case and if still have the corresponding JavaScript
     * reference to the wanted Period, you can call `getOldAudioTrack` with it. It
     * will try retrieving the choice it made from its cache.
     * @param {Object} periodObj - The concerned Period's object
     * @returns {Object|null|undefined} - The audio track chosen for this Period.
     * `null` if audio tracks were disabled and `undefined` if the Period is not
     * known.
     */
    TracksStore.prototype.getChosenAudioTrack = function (periodObj, filterPlayableRepresentations) {
        return (0, is_null_or_undefined_1.default)(periodObj.audio.storedSettings)
            ? null
            : (0, manifest_1.toAudioTrack)(periodObj.audio.storedSettings.adaptation, filterPlayableRepresentations);
    };
    /**
     * Returns an object describing the chosen text track for the given text
     * Period.
     *
     * Returns null is the the current text track is disabled or not
     * set yet.
     *
     * @param {Object} periodObj - The concerned Period's object
     * @returns {Object|null} - The text track chosen for this Period
     */
    TracksStore.prototype.getChosenTextTrack = function (periodObj) {
        return (0, is_null_or_undefined_1.default)(periodObj.text.storedSettings)
            ? null
            : (0, manifest_1.toTextTrack)(periodObj.text.storedSettings.adaptation);
    };
    /**
     * Returns an object describing the chosen video track for the given video
     * Period.
     *
     * Returns null is the the current video track is disabled or not
     * set yet.
     *
     * @param {Object} periodObj - The concerned Period's object
     * @returns {Object|null} - The video track chosen for this Period
     */
    TracksStore.prototype.getChosenVideoTrack = function (periodObj, filterPlayableRepresentations) {
        if ((0, is_null_or_undefined_1.default)(periodObj.video.storedSettings)) {
            return null;
        }
        return (0, manifest_1.toVideoTrack)(periodObj.video.storedSettings.adaptation, filterPlayableRepresentations);
    };
    /**
     * Returns all available audio tracks for a given Period, as an array of
     * objects.
     *
     * Returns `undefined` if the given Period's id is not known.
     *
     * @param {Object} periodObj - The concerned Period's object
     * @param {boolean} filterPlayableRepresentations - If `true`, only
     * representations considered to be "playable" will be included in the
     * returned response.
     * If `false`, the response should contain all linked representations.
     * @returns {Array.<Object>}
     */
    TracksStore.prototype.getAvailableAudioTracks = function (periodObj, filterPlayableRepresentations) {
        var storedSettings = periodObj.audio.storedSettings;
        var currentId = !(0, is_null_or_undefined_1.default)(storedSettings)
            ? storedSettings.adaptation.id
            : null;
        var adaptations = (0, manifest_1.getSupportedAdaptations)(periodObj.period, "audio");
        return adaptations.map(function (adaptation) {
            var active = currentId === null ? false : currentId === adaptation.id;
            return (0, object_assign_1.default)((0, manifest_1.toAudioTrack)(adaptation, filterPlayableRepresentations), {
                active: active,
            });
        });
    };
    /**
     * Returns all available text tracks for a given Period, as an array of
     * objects.
     *
     * Returns `undefined` if the given Period's id is not known.
     *
     * @param {Object} periodObj - The concerned Period's object
     * @returns {Array.<Object>}
     */
    TracksStore.prototype.getAvailableTextTracks = function (periodObj) {
        var storedSettings = periodObj.text.storedSettings;
        var currentId = !(0, is_null_or_undefined_1.default)(storedSettings)
            ? storedSettings.adaptation.id
            : null;
        var adaptations = (0, manifest_1.getSupportedAdaptations)(periodObj.period, "text");
        return adaptations.map(function (adaptation) {
            var active = currentId === null ? false : currentId === adaptation.id;
            return (0, object_assign_1.default)((0, manifest_1.toTextTrack)(adaptation), { active: active });
        });
    };
    /**
     * Returns all available video tracks for a given Period, as an array of
     * objects.
     *
     * Returns `undefined` if the given Period's id is not known.
     *
     * @param {Object} periodObj - The concerned Period's object
     * @param {boolean} filterPlayableRepresentations - If `true`, only
     * representations considered to be "playable" will be included in the
     * returned response.
     * If `false`, the response should contain all linked representations.
     * @returns {Array.<Object>}
     */
    TracksStore.prototype.getAvailableVideoTracks = function (periodObj, filterPlayableRepresentations) {
        var storedSettings = periodObj.video.storedSettings;
        var currentId = (0, is_null_or_undefined_1.default)(storedSettings)
            ? undefined
            : storedSettings.adaptation.id;
        var adaptations = (0, manifest_1.getSupportedAdaptations)(periodObj.period, "video");
        return adaptations.map(function (adaptation) {
            var active = currentId === null ? false : currentId === adaptation.id;
            var track = (0, manifest_1.toVideoTrack)(adaptation, filterPlayableRepresentations);
            var trickModeTracks = track.trickModeTracks !== undefined
                ? track.trickModeTracks.map(function (trickModeAdaptation) {
                    var isActive = currentId === null ? false : currentId === trickModeAdaptation.id;
                    return (0, object_assign_1.default)(trickModeAdaptation, { active: isActive });
                })
                : [];
            var availableTrack = (0, object_assign_1.default)(track, { active: active });
            if (trickModeTracks !== undefined) {
                availableTrack.trickModeTracks = trickModeTracks;
            }
            return availableTrack;
        });
    };
    TracksStore.prototype.getLockedAudioRepresentations = function (periodObj) {
        var storedSettings = periodObj.audio.storedSettings;
        if ((0, is_null_or_undefined_1.default)(storedSettings)) {
            return null;
        }
        var lastLockedSettings = storedSettings.lockedRepresentations.getValue();
        return lastLockedSettings === null ? null : lastLockedSettings.representationIds;
    };
    TracksStore.prototype.getLockedVideoRepresentations = function (periodObj) {
        var storedSettings = periodObj.video.storedSettings;
        if ((0, is_null_or_undefined_1.default)(storedSettings)) {
            return null;
        }
        var lastLockedSettings = storedSettings.lockedRepresentations.getValue();
        return lastLockedSettings === null ? null : lastLockedSettings.representationIds;
    };
    TracksStore.prototype.lockAudioRepresentations = function (periodObj, lockSettings) {
        var _a;
        var storedSettings = periodObj.audio.storedSettings;
        if ((0, is_null_or_undefined_1.default)(storedSettings)) {
            return;
        }
        var DEFAULT_AUDIO_REPRESENTATIONS_SWITCHING_MODE = config_1.default.getCurrent().DEFAULT_AUDIO_REPRESENTATIONS_SWITCHING_MODE;
        var filtered = this._getRepresentationsToLock(storedSettings.adaptation, lockSettings.representations);
        var switchingMode = (_a = lockSettings.switchingMode) !== null && _a !== void 0 ? _a : DEFAULT_AUDIO_REPRESENTATIONS_SWITCHING_MODE;
        storedSettings.lockedRepresentations.setValue({
            representationIds: filtered,
            switchingMode: switchingMode,
        });
    };
    TracksStore.prototype.lockVideoRepresentations = function (periodObj, lockSettings) {
        var _a;
        var storedSettings = periodObj.video.storedSettings;
        if ((0, is_null_or_undefined_1.default)(storedSettings)) {
            return;
        }
        var DEFAULT_VIDEO_REPRESENTATIONS_SWITCHING_MODE = config_1.default.getCurrent().DEFAULT_VIDEO_REPRESENTATIONS_SWITCHING_MODE;
        var filtered = this._getRepresentationsToLock(storedSettings.adaptation, lockSettings.representations);
        var switchingMode = (_a = lockSettings.switchingMode) !== null && _a !== void 0 ? _a : DEFAULT_VIDEO_REPRESENTATIONS_SWITCHING_MODE;
        storedSettings.lockedRepresentations.setValue({
            representationIds: filtered,
            switchingMode: switchingMode,
        });
    };
    TracksStore.prototype.unlockAudioRepresentations = function (periodObj) {
        var storedSettings = periodObj.audio.storedSettings;
        if ((0, is_null_or_undefined_1.default)(storedSettings) ||
            storedSettings.lockedRepresentations.getValue() === null) {
            return;
        }
        storedSettings.lockedRepresentations.setValue(null);
    };
    TracksStore.prototype.unlockVideoRepresentations = function (periodObj) {
        var storedSettings = periodObj.video.storedSettings;
        if ((0, is_null_or_undefined_1.default)(storedSettings) ||
            storedSettings.lockedRepresentations.getValue() === null) {
            return;
        }
        storedSettings.lockedRepresentations.setValue(null);
    };
    TracksStore.prototype.dispose = function () {
        this._isDisposed = true;
        while (true) {
            var lastPeriod = this._storedPeriodInfo.pop();
            if (lastPeriod === undefined) {
                return;
            }
            lastPeriod.isRemoved = true;
        }
    };
    TracksStore.prototype._resetVideoTrackChoices = function (reason) {
        var _a;
        for (var i = 0; i < this._storedPeriodInfo.length; i++) {
            var periodObj = this._storedPeriodInfo[i];
            if (!(0, is_null_or_undefined_1.default)(periodObj.video.storedSettings)) {
                var chosenBaseTrack = periodObj.video.storedSettings.adaptationBase;
                if (chosenBaseTrack !== null) {
                    var chosenTrack = getRightVideoTrack(chosenBaseTrack, this._isTrickModeTrackEnabled);
                    periodObj.video.storedSettings.adaptationBase = chosenBaseTrack;
                    periodObj.video.storedSettings.adaptation = chosenTrack;
                }
            }
        }
        // Clone the current Period list to not be influenced if Periods are removed
        // or added while the loop is running.
        var sliced = this._storedPeriodInfo.slice();
        for (var i = 0; i < sliced.length; i++) {
            var period = sliced[i].period;
            var videoItem = sliced[i].video;
            var storedSettings = videoItem.storedSettings;
            if (storedSettings !== undefined) {
                this.trigger("trackUpdate", {
                    period: toExposedPeriod(period),
                    trackType: "video",
                    reason: reason,
                });
                // The previous event trigger could have had side-effects, so we
                // re-check if we're still mostly in the same state
                if (this._isDisposed) {
                    return; // Someone disposed the `TracksStore` on the previous side-effect
                }
                var newPeriodItem = getPeriodItem(this._storedPeriodInfo, period.id);
                if (newPeriodItem !== undefined &&
                    newPeriodItem.isPeriodAdvertised &&
                    newPeriodItem.video.storedSettings === storedSettings) {
                    (_a = newPeriodItem.video.dispatcher) === null || _a === void 0 ? void 0 : _a.updateTrack(storedSettings);
                }
            }
        }
    };
    TracksStore.prototype._removePeriodObject = function (index) {
        (0, assert_1.default)(index < this._storedPeriodInfo.length);
        var oldPeriodItem = this._storedPeriodInfo[index];
        this._storedPeriodInfo[index].isRemoved = true;
        this._storedPeriodInfo.splice(index, 1);
        this._cachedPeriodInfo.set(oldPeriodItem.period, oldPeriodItem);
    };
    TracksStore.prototype._getRepresentationsToLock = function (adaptation, representationIds) {
        var filtered = representationIds.reduce(function (acc, repId) {
            var foundRep = (0, array_find_1.default)(adaptation.representations, function (r) {
                return r.id === repId;
            });
            if (foundRep === undefined) {
                log_1.default.warn("Track", "Wanted locked Representation not found.");
            }
            else {
                acc.push(foundRep.id);
            }
            return acc;
        }, []);
        if (filtered.length === 0) {
            throw new Error("Cannot lock Representations: " + "None of the given Representation id are found");
        }
        return filtered;
    };
    /**
     * Check or re-check all Periods for which both an initial track can be chosen
     * and for which the `newAvailablePeriods` event can be triggered.
     */
    TracksStore.prototype._selectInitialTrackIfNeeded = function () {
        var e_5, _a, e_6, _b, e_7, _c;
        var _d, _e, _f, _g;
        var DEFAULT_VIDEO_TRACK_SWITCHING_MODE = config_1.default.getCurrent().DEFAULT_VIDEO_TRACK_SWITCHING_MODE;
        var periodsToAdvertise = [];
        var toDispatchTrack = [];
        var _loop_2 = function (trackStorePeriod) {
            var period = trackStorePeriod.period;
            if (trackStorePeriod.audio.storedSettings !== undefined &&
                trackStorePeriod.video.storedSettings !== undefined &&
                trackStorePeriod.text.storedSettings !== undefined) {
                return "continue";
            }
            var adaptations = __spreadArray(__spreadArray([], __read(((_d = period.adaptations.audio) !== null && _d !== void 0 ? _d : [])), false), __read(((_e = period.adaptations.video) !== null && _e !== void 0 ? _e : [])), false);
            var hasCodecWithUndefinedSupport = adaptations.every(function (a) { return a.supportStatus.hasCodecWithUndefinedSupport; });
            if (adaptations.length > 0 && hasCodecWithUndefinedSupport) {
                return "continue";
            }
            var audioAdaptation = (0, manifest_1.getSupportedAdaptations)(period, "audio")[0];
            if (audioAdaptation === undefined) {
                trackStorePeriod.audio.storedSettings = null;
                this_2.handleMissingOrUnplayableTrack(period, "audio", true);
                if (this_2._isDisposed) {
                    return { value: void 0 };
                }
            }
            else {
                trackStorePeriod.audio.storedSettings = {
                    adaptation: audioAdaptation,
                    switchingMode: this_2._defaultAudioTrackSwitchingMode,
                    lockedRepresentations: new reference_1.default(null),
                };
            }
            var baseVideoAdaptation = (0, manifest_1.getSupportedAdaptations)(period, "video")[0];
            if (baseVideoAdaptation === undefined) {
                trackStorePeriod.video.storedSettings = null;
                this_2.handleMissingOrUnplayableTrack(period, "video", true);
                if (this_2._isDisposed) {
                    return { value: void 0 };
                }
            }
            else {
                var videoAdaptation = getRightVideoTrack(baseVideoAdaptation, this_2._isTrickModeTrackEnabled);
                trackStorePeriod.video.storedSettings = {
                    adaptation: videoAdaptation,
                    adaptationBase: baseVideoAdaptation,
                    switchingMode: DEFAULT_VIDEO_TRACK_SWITCHING_MODE,
                    lockedRepresentations: new reference_1.default(null),
                };
            }
            var textAdaptation = null;
            var forcedSubtitles = ((_f = period.adaptations.text) !== null && _f !== void 0 ? _f : []).filter(function (ad) { return ad.isForcedSubtitles === true; });
            if (forcedSubtitles.length > 0) {
                if (audioAdaptation !== null && audioAdaptation !== undefined) {
                    var sameLanguage = (0, array_find_1.default)(forcedSubtitles, function (f) { return f.normalizedLanguage === audioAdaptation.normalizedLanguage; });
                    if (sameLanguage !== undefined) {
                        textAdaptation = sameLanguage;
                    }
                }
                if (textAdaptation === null) {
                    textAdaptation =
                        (_g = (0, array_find_1.default)(forcedSubtitles, function (f) { return f.normalizedLanguage === undefined; })) !== null && _g !== void 0 ? _g : null;
                }
            }
            trackStorePeriod.text.storedSettings =
                textAdaptation === null
                    ? null
                    : {
                        adaptation: textAdaptation,
                        switchingMode: "direct",
                        lockedRepresentations: new reference_1.default(null),
                    };
            toDispatchTrack.push(trackStorePeriod);
            if (this_2._shouldAdvertisePeriod(trackStorePeriod)) {
                trackStorePeriod.isPeriodAdvertised = true;
                periodsToAdvertise.push({ id: period.id, start: period.start, end: period.end });
            }
        };
        var this_2 = this;
        try {
            for (var _h = __values(this._storedPeriodInfo), _j = _h.next(); !_j.done; _j = _h.next()) {
                var trackStorePeriod = _j.value;
                var state_2 = _loop_2(trackStorePeriod);
                if (typeof state_2 === "object")
                    return state_2.value;
            }
        }
        catch (e_5_1) { e_5 = { error: e_5_1 }; }
        finally {
            try {
                if (_j && !_j.done && (_a = _h.return)) _a.call(_h);
            }
            finally { if (e_5) throw e_5.error; }
        }
        if (periodsToAdvertise.length > 0) {
            this.trigger("newAvailablePeriods", periodsToAdvertise);
            if (this._isDisposed) {
                return;
            }
        }
        try {
            for (var toDispatchTrack_1 = __values(toDispatchTrack), toDispatchTrack_1_1 = toDispatchTrack_1.next(); !toDispatchTrack_1_1.done; toDispatchTrack_1_1 = toDispatchTrack_1.next()) {
                var trackStorePeriod = toDispatchTrack_1_1.value;
                if (!trackStorePeriod.isPeriodAdvertised) {
                    continue;
                }
                var bufferTypes = ["audio", "video", "text"];
                try {
                    for (var bufferTypes_1 = (e_7 = void 0, __values(bufferTypes)), bufferTypes_1_1 = bufferTypes_1.next(); !bufferTypes_1_1.done; bufferTypes_1_1 = bufferTypes_1.next()) {
                        var bufferType = bufferTypes_1_1.value;
                        var trackInfo = trackStorePeriod[bufferType];
                        if (trackInfo.dispatcher !== null &&
                            trackInfo.storedSettings !== undefined &&
                            !trackInfo.dispatcher.hasSetTrack()) {
                            trackInfo.dispatcher.updateTrack(trackInfo.storedSettings);
                            if (this._isDisposed) {
                                return;
                            }
                        }
                    }
                }
                catch (e_7_1) { e_7 = { error: e_7_1 }; }
                finally {
                    try {
                        if (bufferTypes_1_1 && !bufferTypes_1_1.done && (_c = bufferTypes_1.return)) _c.call(bufferTypes_1);
                    }
                    finally { if (e_7) throw e_7.error; }
                }
            }
        }
        catch (e_6_1) { e_6 = { error: e_6_1 }; }
        finally {
            try {
                if (toDispatchTrack_1_1 && !toDispatchTrack_1_1.done && (_b = toDispatchTrack_1.return)) _b.call(toDispatchTrack_1);
            }
            finally { if (e_6) throw e_6.error; }
        }
    };
    /**
     * Returns `true` once a Period can be advertised through a `newAvailablePeriods`
     * event, after which track can begin to be set and updated.
     * @param {Object} periodObj
     * @returns {boolean}
     */
    TracksStore.prototype._shouldAdvertisePeriod = function (periodObj) {
        return (!periodObj.isPeriodAdvertised &&
            this._isPeriodTypeReadyForAdvertisement(periodObj, "text") &&
            this._isPeriodTypeReadyForAdvertisement(periodObj, "video") &&
            this._isPeriodTypeReadyForAdvertisement(periodObj, "audio"));
    };
    /**
     * Returns `true` if the given type is ready for Period advertisement.
     *
     * We only wait for dispatchers that can actually be created in the current
     * runtime. Types disabled through feature selection or media element
     * capabilities should not keep the Period hidden forever.
     */
    TracksStore.prototype._isPeriodTypeReadyForAdvertisement = function (periodObj, bufferType) {
        return (!this._handledTrackTypes[bufferType] || periodObj[bufferType].dispatcher !== null);
    };
    return TracksStore;
}(event_emitter_1.default));
exports.default = TracksStore;
/**
 * Returns element in the given `periods` Array that corresponds to the
 * `period` given.
 * Returns `undefined` if that `period` is not found.
 * @param {Object} periods
 * @param {string} periodId
 * @returns {Object|undefined}
 */
function getPeriodItem(periods, periodId) {
    for (var i = 0; i < periods.length; i++) {
        var periodI = periods[i];
        if (periodI.period.id === periodId) {
            return periodI;
        }
    }
}
/**
 * A `ITSPeriodObject` should only be removed once all References linked to it
 * do not exist anymore, to keep the possibility of making track choices.
 * @param {Object} periodObj
 * @returns {boolean}
 */
function isPeriodItemRemovable(periodObj) {
    var _a, _b, _c;
    return (!periodObj.inManifest &&
        ((_a = periodObj.text) === null || _a === void 0 ? void 0 : _a.dispatcher) === null &&
        ((_b = periodObj.audio) === null || _b === void 0 ? void 0 : _b.dispatcher) === null &&
        ((_c = periodObj.video) === null || _c === void 0 ? void 0 : _c.dispatcher) === null);
}
function getRightVideoTrack(adaptation, isTrickModeEnabled) {
    var _a;
    if (isTrickModeEnabled && ((_a = adaptation.trickModeTracks) === null || _a === void 0 ? void 0 : _a[0]) !== undefined) {
        return adaptation.trickModeTracks[0];
    }
    return adaptation;
}
/**
 * Generate an `ITSPeriodObject` object for the given Period, selecting the
 * default track for each type.
 * @param {Object} period
 * @param {boolean} inManifest
 * @returns {object}
 */
function generatePeriodInfo(period, inManifest) {
    return {
        period: period,
        inManifest: inManifest,
        isPeriodAdvertised: false,
        isRemoved: false,
        audio: { storedSettings: undefined, dispatcher: null },
        video: { storedSettings: undefined, dispatcher: null },
        text: { storedSettings: undefined, dispatcher: null },
    };
}
function toExposedPeriod(p) {
    return { start: p.start, end: p.end, id: p.id };
}
/**
 * Retrieves a fallback track for the given period and track type.
 *
 * This function is used when the current track becomes unavailable,
 * and a fallback track must be selected based on the period and media type.
 *
 * @param {Object} period - The period object that contains information about the available tracks.
 * @param {string} trackType - The type of media track to fallback to (e.g., "audio", "video", "text").
 * @returns {object|null} A fallback track matching the type, or `null` if none is available.
 */
function getFallbackTrack(period, trackType) {
    var periodHasAdaptationForType = period.adaptations[trackType] !== undefined &&
        period.adaptations[trackType].length > 0;
    if (!periodHasAdaptationForType) {
        return {
            fallbackTrack: null,
            noSourceMedia: true,
        };
    }
    var firstPlayableAdaptation = findFirstPlayableAdaptation(period, trackType);
    return {
        fallbackTrack: firstPlayableAdaptation !== null && firstPlayableAdaptation !== void 0 ? firstPlayableAdaptation : null,
        noSourceMedia: false,
    };
}
function findFirstPlayableAdaptation(period, type) {
    var _a;
    var firstPlayableAdaptation = (0, array_find_1.default)((_a = period.adaptations[type]) !== null && _a !== void 0 ? _a : [], function (adaptation) {
        if (adaptation.supportStatus.hasSupportedCodec === false ||
            adaptation.supportStatus.isDecipherable === false) {
            return false;
        }
        var playableRepresentations = adaptation.representations.filter(function (r) { return (0, manifest_1.isRepresentationPlayable)(r) === true; });
        return playableRepresentations.length > 0;
    });
    return firstPlayableAdaptation;
}