UNPKG

rx-player

Version:
884 lines 62.5 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 __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; 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 = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["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 }; } }; 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 }); var is_codec_supported_1 = require("../../compat/is_codec_supported"); var may_media_element_fail_on_undecipherable_data_1 = require("../../compat/may_media_element_fail_on_undecipherable_data"); var should_reload_media_source_on_decipherability_update_1 = require("../../compat/should_reload_media_source_on_decipherability_update"); var config_1 = require("../../config"); var adaptive_1 = require("../../core/adaptive"); var cmcd_1 = require("../../core/cmcd"); var fetchers_1 = require("../../core/fetchers"); var create_content_time_boundaries_observer_1 = require("../../core/main/common/create_content_time_boundaries_observer"); var FreezeResolver_1 = require("../../core/main/common/FreezeResolver"); var get_thumbnail_data_1 = require("../../core/main/common/get_thumbnail_data"); var synchronize_sinks_on_observation_1 = require("../../core/main/common/synchronize_sinks_on_observation"); var segment_sinks_1 = require("../../core/segment_sinks"); var stream_1 = require("../../core/stream"); var errors_1 = require("../../errors"); var features_1 = require("../../features"); var log_1 = require("../../log"); var are_arrays_of_numbers_equal_1 = require("../../utils/are_arrays_of_numbers_equal"); var assert_1 = require("../../utils/assert"); var create_cancellable_promise_1 = require("../../utils/create_cancellable_promise"); var is_null_or_undefined_1 = require("../../utils/is_null_or_undefined"); var noop_1 = require("../../utils/noop"); var object_assign_1 = require("../../utils/object_assign"); var sync_or_async_1 = require("../../utils/sync_or_async"); var task_canceller_1 = require("../../utils/task_canceller"); var decrypt_1 = require("../decrypt"); var types_1 = require("./types"); var create_core_playback_observer_1 = require("./utils/create_core_playback_observer"); var create_media_source_1 = require("./utils/create_media_source"); var get_initial_time_1 = require("./utils/get_initial_time"); var get_loaded_reference_1 = require("./utils/get_loaded_reference"); var initial_seek_and_play_1 = require("./utils/initial_seek_and_play"); var initialize_content_decryption_1 = require("./utils/initialize_content_decryption"); var main_thread_text_displayer_interface_1 = require("./utils/main_thread_text_displayer_interface"); var rebuffering_controller_1 = require("./utils/rebuffering_controller"); var stream_events_emitter_1 = require("./utils/stream_events_emitter"); var throw_on_media_error_1 = require("./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._initSettings = settings; _this._initCanceller = new task_canceller_1.default(); _this._manifest = null; _this._decryptionCapabilities = { status: "uninitialized", value: null }; var urls = settings.url === undefined ? undefined : [settings.url]; _this._cmcdDataBuilder = settings.cmcd === undefined ? null : new cmcd_1.default(settings.cmcd); _this._manifestFetcher = new fetchers_1.ManifestFetcher(urls, settings.transport, __assign(__assign({}, settings.manifestRequestSettings), { lowLatencyMode: settings.lowLatencyMode, cmcdDataBuilder: _this._cmcdDataBuilder })); 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._manifest !== null) { return; } this._manifest = sync_or_async_1.default.createAsync((0, create_cancellable_promise_1.default)(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. */ (0, throw_on_media_error_1.default)(mediaElement, function (error) { return _this._onFatalError(error); }, this._initCanceller.signal); this._setupInitialMediaSourceAndDecryption(mediaElement) .then(function (initResult) { return _this._onInitialMediaSourceReady(mediaElement, initResult.mediaSource, playbackObserver, initResult.drmSystemId, 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); }; /** * Stop content and free all resources linked to this * `MediaSourceContentInitializer`. */ MediaSourceContentInitializer.prototype.dispose = function () { this._initCanceller.cancel(); }; /** * Callback called when an error interrupting playback arised. * @param {*} err */ MediaSourceContentInitializer.prototype._onFatalError = function (err) { if (this._initCanceller.isUsed()) { return; } this._initCanceller.cancel(); this.trigger("error", err); }; /** * Initialize decryption mechanisms if needed and begin creating and relying * on the initial `MediaSourceInterface` for this content. * @param {HTMLMediaElement|null} mediaElement * @returns {Promise.<Object>} */ MediaSourceContentInitializer.prototype._setupInitialMediaSourceAndDecryption = function (mediaElement) { var _this = this; var initCanceller = this._initCanceller; return (0, create_cancellable_promise_1.default)(initCanceller.signal, function (resolve) { var keySystems = _this._initSettings.keySystems; /** Initialize decryption capabilities. */ var _a = (0, initialize_content_decryption_1.default)(mediaElement, keySystems, { onWarning: function (err) { return _this.trigger("warning", err); }, onError: function (err) { return _this._onFatalError(err); }, onBlackListProtectionData: function (val) { // Ugly IIFE workaround to allow async event listener (function () { return __awaiter(_this, void 0, void 0, function () { var manifest, _a; var _b; return __generator(this, function (_c) { switch (_c.label) { case 0: if (this._manifest === null) { return [2 /*return*/]; } if (!((_b = this._manifest.syncValue) !== null && _b !== void 0)) return [3 /*break*/, 1]; _a = _b; return [3 /*break*/, 3]; case 1: return [4 /*yield*/, this._manifest.getValueAsAsync()]; case 2: _a = (_c.sent()); _c.label = 3; case 3: manifest = _a; blackListProtectionDataOnManifest(manifest, val); return [2 /*return*/]; } }); }); })().catch(noop_1.default); }, onKeyIdsCompatibilityUpdate: function (updates) { // Ugly IIFE workaround to allow async event listener (function () { return __awaiter(_this, void 0, void 0, function () { var manifest, _a; var _b; return __generator(this, function (_c) { switch (_c.label) { case 0: if (this._manifest === null) { return [2 /*return*/]; } if (!((_b = this._manifest.syncValue) !== null && _b !== void 0)) return [3 /*break*/, 1]; _a = _b; return [3 /*break*/, 3]; case 1: return [4 /*yield*/, this._manifest.getValueAsAsync()]; case 2: _a = (_c.sent()); _c.label = 3; case 3: manifest = _a; updateKeyIdsDecipherabilityOnManifest(manifest, updates.whitelistedKeyIds, updates.blacklistedKeyIds, updates.delistedKeyIds); return [2 /*return*/]; } }); }); })().catch(noop_1.default); }, onCodecSupportUpdate: function () { var _a, _b; var syncManifest = (_a = _this._manifest) === null || _a === void 0 ? void 0 : _a.syncValue; if ((0, is_null_or_undefined_1.default)(syncManifest)) { // The Manifest is not yet fetched, but we will be able to check // the codecs once it is the case (_b = _this._manifest) === null || _b === void 0 ? void 0 : _b.getValueAsAsync().then(function (loadedManifest) { if (_this._initCanceller.isUsed()) { return; } _this._refreshManifestCodecSupport(loadedManifest); }, noop_1.default); } else { _this._refreshManifestCodecSupport(syncManifest); } }, }, initCanceller.signal), drmInitRef = _a.statusRef, contentDecryptor = _a.contentDecryptor; if (contentDecryptor.enabled) { _this._decryptionCapabilities = { status: "enabled", value: contentDecryptor.value, }; } else { _this._decryptionCapabilities = { status: "disabled", value: contentDecryptor.value, }; } drmInitRef.onUpdate(function (drmStatus, stopListeningToDrmUpdates) { if (drmStatus.initializationState.type === "uninitialized") { return; } stopListeningToDrmUpdates(); var mediaSourceCanceller = new task_canceller_1.default(); mediaSourceCanceller.linkToSignal(initCanceller.signal); (0, create_media_source_1.default)(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, initialMediaSourceCanceller) { return __awaiter(this, void 0, void 0, function () { var _a, adaptiveOptions, autoPlay, bufferOptions, lowLatencyMode, segmentRequestOptions, speed, startAt, textTrackOptions, transport, initCanceller, manifest, _b, _e_1, initialTime, representationEstimator, subBufferOptions, cdnPrioritizer, segmentQueueCreator; var _this = this; var _c; return __generator(this, function (_d) { switch (_d.label) { case 0: _a = this._initSettings, 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; (0, assert_1.default)(this._manifest !== null); _d.label = 1; case 1: _d.trys.push([1, 5, , 6]); if (!((_c = this._manifest.syncValue) !== null && _c !== void 0)) return [3 /*break*/, 2]; _b = _c; return [3 /*break*/, 4]; case 2: return [4 /*yield*/, this._manifest.getValueAsAsync()]; case 3: _b = (_d.sent()); _d.label = 4; case 4: manifest = _b; return [3 /*break*/, 6]; case 5: _e_1 = _d.sent(); return [2 /*return*/]; // The error should already have been processed through an event listener case 6: manifest.addEventListener("manifestUpdate", function (updates) { _this.trigger("manifestUpdate", updates); _this._refreshManifestCodecSupport(manifest); }, initCanceller.signal); manifest.addEventListener("decipherabilityUpdate", function (elts) { _this.trigger("decipherabilityUpdate", elts); }, initCanceller.signal); manifest.addEventListener("supportUpdate", function () { _this.trigger("codecSupportUpdate", null); }, initCanceller.signal); log_1.default.debug("Init: Calculating initial time"); initialTime = (0, get_initial_time_1.default)(manifest, lowLatencyMode, startAt); log_1.default.debug("Init: Initial time calculated:", initialTime); representationEstimator = (0, adaptive_1.default)(adaptiveOptions); subBufferOptions = (0, object_assign_1.default)({ textTrackOptions: textTrackOptions, drmSystemId: drmSystemId }, bufferOptions); cdnPrioritizer = new fetchers_1.CdnPrioritizer(initCanceller.signal); segmentQueueCreator = new fetchers_1.SegmentQueueCreator(transport, cdnPrioritizer, this._cmcdDataBuilder, segmentRequestOptions); this._refreshManifestCodecSupport(manifest); this.trigger("manifestReady", manifest); if (initCanceller.isUsed()) { return [2 /*return*/]; } // handle initial load and reloads this._setupContentWithNewMediaSource({ mediaElement: mediaElement, playbackObserver: playbackObserver, mediaSource: initialMediaSource, initialTime: initialTime, autoPlay: autoPlay, manifest: manifest, representationEstimator: representationEstimator, cdnPrioritizer: cdnPrioritizer, segmentQueueCreator: segmentQueueCreator, speed: speed, bufferOptions: subBufferOptions, }, initialMediaSourceCanceller); return [2 /*return*/]; } }); }); }; /** * 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 {Object} args * @param {Object} currentCanceller */ MediaSourceContentInitializer.prototype._setupContentWithNewMediaSource = function (args, currentCanceller) { this._startLoadingContentOnMediaSource(args, this._createReloadMediaSourceCallback(args, currentCanceller), currentCanceller.signal); }; /** * Create `IReloadMediaSourceCallback` allowing to handle reload orders. * @param {Object} args * @param {Object} currentCanceller */ MediaSourceContentInitializer.prototype._createReloadMediaSourceCallback = function (args, currentCanceller) { var _this = this; var initCanceller = this._initCanceller; return function (reloadOrder) { currentCanceller.cancel(); if (initCanceller.isUsed()) { return; } _this.trigger("reloadingMediaSource", reloadOrder); if (initCanceller.isUsed()) { return; } var newCanceller = new task_canceller_1.default(); newCanceller.linkToSignal(initCanceller.signal); (0, create_media_source_1.default)(args.mediaElement, newCanceller.signal) .then(function (newMediaSource) { _this._setupContentWithNewMediaSource(__assign(__assign({}, args), { mediaSource: newMediaSource, initialTime: reloadOrder.position, autoPlay: reloadOrder.autoPlay }), newCanceller); }) .catch(function (err) { if (newCanceller.isUsed()) { return; } _this._onFatalError(err); }); }; }; /** * Buffer the content on the given MediaSource. * @param {Object} args * @param {function} onReloadOrder * @param {Object} cancelSignal */ MediaSourceContentInitializer.prototype._startLoadingContentOnMediaSource = function (args, onReloadOrder, cancelSignal) { var _this = this; var _a, _b; var autoPlay = args.autoPlay, bufferOptions = args.bufferOptions, initialTime = args.initialTime, manifest = args.manifest, mediaElement = args.mediaElement, mediaSource = args.mediaSource, playbackObserver = args.playbackObserver, representationEstimator = args.representationEstimator, cdnPrioritizer = args.cdnPrioritizer, segmentQueueCreator = args.segmentQueueCreator, speed = args.speed; var transport = this._initSettings.transport; var initialPeriod = (_a = manifest.getPeriodForTime(initialTime)) !== null && _a !== void 0 ? _a : manifest.getNextPeriod(initialTime); if (initialPeriod === undefined) { var error = new errors_1.MediaError("MEDIA_STARTING_TIME_NOT_FOUND", "Wanted starting time not found in the Manifest."); return this._onFatalError(error); } var textDisplayerInterface = null; var textDisplayer = createTextDisplayer(mediaElement, this._initSettings.textTrackOptions); if (textDisplayer !== null) { var sender_1 = new main_thread_text_displayer_interface_1.default(textDisplayer); textDisplayerInterface = sender_1; cancelSignal.register(function () { sender_1.stop(); textDisplayer === null || textDisplayer === void 0 ? void 0 : textDisplayer.stop(); }); } /** Interface to create media buffers. */ var segmentSinksStore = new segment_sinks_1.default(mediaSource, mediaElement.nodeName === "VIDEO", textDisplayerInterface); cancelSignal.register(function () { segmentSinksStore.disposeAll(); }); var _c = (0, initial_seek_and_play_1.default)({ mediaElement: mediaElement, playbackObserver: playbackObserver, startTime: initialTime, mustAutoPlay: autoPlay, onWarning: function (err) { _this.trigger("warning", err); }, isDirectfile: false, }, cancelSignal), autoPlayResult = _c.autoPlayResult, initialPlayPerformed = _c.initialPlayPerformed; if (cancelSignal.isCancelled()) { return; } initialPlayPerformed.onUpdate(function (isPerformed, stopListening) { if (isPerformed) { stopListening(); var streamEventsEmitter_1 = new stream_events_emitter_1.default(manifest, playbackObserver); manifest.addEventListener("manifestUpdate", function () { streamEventsEmitter_1.onManifestUpdate(manifest); }, cancelSignal); 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 coreObserver = (0, create_core_playback_observer_1.default)(playbackObserver, { autoPlay: autoPlay, manifest: manifest, mediaSource: mediaSource, textDisplayer: textDisplayer, initialPlayPerformed: initialPlayPerformed, speed: speed, }, cancelSignal); (_b = this._cmcdDataBuilder) === null || _b === void 0 ? void 0 : _b.startMonitoringPlayback(coreObserver); cancelSignal.register(function () { var _a; (_a = _this._cmcdDataBuilder) === null || _a === void 0 ? void 0 : _a.stopMonitoringPlayback(); }); var rebufferingController = this._createRebufferingController(playbackObserver, manifest, speed, cancelSignal); var freezeResolver = new FreezeResolver_1.default(segmentSinksStore); if (may_media_element_fail_on_undecipherable_data_1.default) { // On some devices, just reload immediately when data become undecipherable manifest.addEventListener("decipherabilityUpdate", function (elts) { if (elts.some(function (e) { return e.representation.decipherable !== true; })) { reloadMediaSource(0, undefined, undefined); } }, cancelSignal); } coreObserver.listen(function (observation) { (0, synchronize_sinks_on_observation_1.default)(observation, segmentSinksStore); var freezeResolution = freezeResolver.onNewObservation(observation); if (freezeResolution === null) { return; } // TODO: The following method looks generic, we may be able to factorize // it with other reload handlers after some work. var triggerReload = function () { var _a; var lastObservation = playbackObserver.getReference().getValue(); var position = lastObservation.position.isAwaitingFuturePosition() ? lastObservation.position.getWanted() : ((_a = coreObserver.getCurrentTime()) !== null && _a !== void 0 ? _a : lastObservation.position.getPolled()); var autoplay = initialPlayPerformed.getValue() ? !playbackObserver.getIsPaused() : autoPlay; onReloadOrder({ position: position, autoPlay: autoplay }); }; handleFreezeResolution(freezeResolution, { enableRepresentationAvoidance: _this._initSettings.enableRepresentationAvoidance, manifest: manifest, triggerReload: triggerReload, playbackObserver: playbackObserver, }); }, { clearSignal: cancelSignal }); var contentTimeBoundariesObserver = (0, create_content_time_boundaries_observer_1.default)(manifest, mediaSource, coreObserver, segmentSinksStore, { onWarning: function (err) { return _this.trigger("warning", err); }, onPeriodChanged: function (period) { return _this.trigger("activePeriodChanged", { period: period }); }, }, 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 () { (0, get_loaded_reference_1.default)(playbackObserver, false, cancelSignal).onUpdate(function (isLoaded, stopListening) { if (isLoaded) { stopListening(); _this.trigger("loaded", { getSegmentSinkMetrics: function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, new Promise(function (resolve) { return resolve(segmentSinksStore.getSegmentSinksMetrics()); })]; }); }); }, getThumbnailData: function (periodId, thumbnailTrackId, time) { return __awaiter(_this, void 0, void 0, function () { var fetchThumbnails; return __generator(this, function (_a) { fetchThumbnails = (0, fetchers_1.createThumbnailFetcher)(transport.thumbnails, cdnPrioritizer); return [2 /*return*/, (0, get_thumbnail_data_1.default)(fetchThumbnails, manifest, periodId, thumbnailTrackId, time)]; }); }); }, }); } }, { 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; (0, stream_1.default)({ manifest: manifest, initialPeriod: initialPeriod }, coreObserver, representationEstimator, segmentSinksStore, segmentQueueCreator, 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 (payload) { var _a; var wantedSeekingTime; var lastObservation = playbackObserver.getReference().getValue(); var currentTime = lastObservation.position.isAwaitingFuturePosition() ? lastObservation.position.getWanted() : mediaElement.currentTime; var relativeResumingPosition = (_a = payload === null || payload === void 0 ? void 0 : payload.relativeResumingPosition) !== null && _a !== void 0 ? _a : 0; var canBeApproximateSeek = Boolean(payload === null || payload === void 0 ? void 0 : payload.relativePosHasBeenDefaulted); if (relativeResumingPosition === 0 && canBeApproximateSeek) { // in case relativeResumingPosition is 0, we still perform // a tiny seek to be sure that the browser will correclty reload the video. wantedSeekingTime = currentTime + 0.001; } else { wantedSeekingTime = currentTime + relativeResumingPosition; } playbackObserver.setCurrentTime(wantedSeekingTime); // 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, we have no buffer but we're already advancing obs.position.getPolled() > wantedSeekingTime + 0.1) { stopListening(); playbackObserver.setCurrentTime(obs.position.getWanted() + 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_1.default.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", { type: value.type, periodId: value.period.id, }); }, bitrateEstimateChange: function (value) { var _a; (_a = self._cmcdDataBuilder) === null || _a === void 0 ? void 0 : _a.updateThroughput(value.type, value.bitrate); self.trigger("bitrateEstimateChange", value); }, needsMediaSourceReload: function (payload) { reloadMediaSource(payload.timeOffset, payload.minimumPosition, payload.maximumPosition); }, needsDecipherabilityFlush: function () { var _a, _b, _c, _d; var keySystem = (0, decrypt_1.getKeySystemConfiguration)(mediaElement); if ((0, should_reload_media_source_on_decipherability_update_1.default)(keySystem === null || keySystem === void 0 ? void 0 : keySystem[0])) { var lastObservation = coreObserver.getReference().getValue(); var position = lastObservation.position.isAwaitingFuturePosition() ? lastObservation.position.getWanted() : ((_a = coreObserver.getCurrentTime()) !== null && _a !== void 0 ? _a : lastObservation.position.getPolled()); var isPaused = (_c = (_b = lastObservation.paused.pending) !== null && _b !== void 0 ? _b : coreObserver.getIsPaused()) !== null && _c !== void 0 ? _c : lastObservation.paused.last; onReloadOrder({ position: position, autoPlay: !isPaused }); } else { var lastObservation = coreObserver.getReference().getValue(); var position = lastObservation.position.isAwaitingFuturePosition() ? lastObservation.position.getWanted() : ((_d = coreObserver.getCurrentTime()) !== null && _d !== void 0 ? _d : lastObservation.position.getPolled()); // 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) { var e_1, _a; if (self._decryptionCapabilities.status === "disabled") { self._onFatalError(self._decryptionCapabilities.value); return; } else if (self._decryptionCapabilities.status === "uninitialized") { // Should never happen log_1.default.error("Init: received encryption data without known decryption capabilities"); return; } try { for (var value_1 = __values(value), value_1_1 = value_1.next(); !value_1_1.done; value_1_1 = value_1.next()) { var protectionData = value_1_1.value; self._decryptionCapabilities.value.onInitializationData(protectionData); if (cancelSignal.isCancelled()) { return; // Previous call has stopped streams due to a side-effect } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (value_1_1 && !value_1_1.done && (_a = value_1.return)) _a.call(value_1); } finally { if (e_1) throw e_1.error; } } }, error: function (err) { return self._onFatalError(err); }, }; } /** * Callback allowing to reload the current content. * @param {number} deltaPosition - Position you want to seek to after * reloading, as a delta in seconds from the last polled playing position. * @param {number|undefined} minimumPosition - If set, minimum time bound * in seconds after `deltaPosition` has been applied. * @param {number|undefined} maximumPosition - If set, minimum time bound * in seconds after `deltaPosition` has been applied. */ function reloadMediaSource(deltaPosition, minimumPosition, maximumPosition) { var _a, _b, _c; var lastObservation = coreObserver.getReference().getValue(); var currentPosition = lastObservation.position.isAwaitingFuturePosition() ? lastObservation.position.getWanted() : ((_a = coreObserver.getCurrentTime()) !== null && _a !== void 0 ? _a : lastObservation.position.getPolled()); var isPaused = (_c = (_b = lastObservation.paused.pending) !== null && _b !== void 0 ? _b : coreObserver.getIsPaused()) !== null && _c !== void 0 ? _c : lastObservation.paused.last; var position = currentPosition + deltaPosition; if (minimumPosition !== undefined) { position = Math.max(minimumPosition, position); } if (maximumPosition !== undefined) { position = Math.min(maximumPosition, position); } onReloadOrder({ position: position, autoPlay: !isPaused }); } }; /** * 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, speed, cancelSignal) { var _this = this; var rebufferingController = new rebuffering_controller_1.default(playbackObserver, manifest, 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; }; /** * Evaluates a list of codecs to determine their support status. * * @param {Array} codecsToCheck - The list of codecs to check. * @returns {A