UNPKG

rx-player

Version:
1,011 lines 134 kB
"use strict"; /** * Copyright 2015 CANAL+ Group * * Licensed under the Apache License, Version 2.0 (the "License");publicapi * 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."); }; 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; }; Object.defineProperty(exports, "__esModule", { value: true }); var can_rely_on_video_visibility_and_size_1 = require("../../compat/can_rely_on_video_visibility_and_size"); var event_listeners_1 = require("../../compat/event_listeners"); var get_start_date_1 = require("../../compat/get_start_date"); var has_mse_in_worker_1 = require("../../compat/has_mse_in_worker"); var has_worker_api_1 = require("../../compat/has_worker_api"); var config_1 = require("../../config"); var errors_1 = require("../../errors"); var worker_initialization_error_1 = require("../../errors/worker_initialization_error"); var features_1 = require("../../features"); var log_1 = require("../../log"); var manifest_1 = require("../../manifest"); var media_element_playback_observer_1 = require("../../playback_observer/media_element_playback_observer"); var array_find_1 = require("../../utils/array_find"); var array_includes_1 = require("../../utils/array_includes"); var assert_1 = require("../../utils/assert"); var event_emitter_1 = require("../../utils/event_emitter"); var global_scope_1 = require("../../utils/global_scope"); var id_generator_1 = require("../../utils/id_generator"); var is_null_or_undefined_1 = require("../../utils/is_null_or_undefined"); var monotonic_timestamp_1 = require("../../utils/monotonic_timestamp"); var object_assign_1 = require("../../utils/object_assign"); var ranges_1 = require("../../utils/ranges"); var reference_1 = require("../../utils/reference"); var task_canceller_1 = require("../../utils/task_canceller"); var decrypt_1 = require("../decrypt"); var render_thumbnail_1 = require("../render_thumbnail"); var tracks_store_1 = require("../tracks_store"); var option_utils_1 = require("./option_utils"); var utils_1 = require("./utils"); /* eslint-disable @typescript-eslint/naming-convention */ // Enable debug mode as soon as `RX_PLAYER_DEBUG_MODE__` is set to `true`: var globals = global_scope_1.default; var isDebugModeEnabled = typeof globals.__RX_PLAYER_DEBUG_MODE__ === "boolean" && globals.__RX_PLAYER_DEBUG_MODE__; try { Object.defineProperty(globals, "__RX_PLAYER_DEBUG_MODE__", { get: function () { return isDebugModeEnabled; }, set: function (val) { isDebugModeEnabled = val; if (val) { Player.LogLevel = "DEBUG"; Player.LogFormat = "full"; } }, }); } catch (_err) { // Ignore, maybe we're in some jsdom thing, maybe the current target does not // authorize setting globals that way etc. } if (isDebugModeEnabled) { log_1.default.setLevel("DEBUG", "full"); } else if (0 /* __ENVIRONMENT__.CURRENT_ENV */ === 1 /* __ENVIRONMENT__.DEV */) { log_1.default.setLevel("NONE" /* __LOGGER_LEVEL__.CURRENT_LEVEL */, "standard"); } var generateContentId = (0, id_generator_1.default)(); /** * Options of a `loadVideo` call which are for now not supported when running * in a "multithread" mode. * * TODO support those? */ var MULTI_THREAD_UNSUPPORTED_LOAD_VIDEO_OPTIONS = [ "manifestLoader", "segmentLoader", ]; /** * @class Player * @extends EventEmitter */ var Player = /** @class */ (function (_super) { __extends(Player, _super); /** * @constructor * @param {Object} options */ function Player(options) { if (options === void 0) { options = {}; } var _this = _super.call(this) || this; var _a = (0, option_utils_1.parseConstructorOptions)(options), baseBandwidth = _a.baseBandwidth, videoResolutionLimit = _a.videoResolutionLimit, maxBufferAhead = _a.maxBufferAhead, maxBufferBehind = _a.maxBufferBehind, throttleVideoBitrateWhenHidden = _a.throttleVideoBitrateWhenHidden, videoElement = _a.videoElement, wantedBufferAhead = _a.wantedBufferAhead, maxVideoBufferSize = _a.maxVideoBufferSize; // Workaround to support Firefox autoplay on FF 42. // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624 videoElement.preload = "auto"; _this.version = /* PLAYER_VERSION */ "4.4.1"; _this.log = log_1.default; _this.state = "STOPPED"; _this.videoElement = videoElement; Player._priv_registerVideoElement(_this.videoElement); var destroyCanceller = new task_canceller_1.default(); _this._destroyCanceller = destroyCanceller; _this._priv_pictureInPictureRef = (0, event_listeners_1.getPictureOnPictureStateRef)(videoElement, destroyCanceller.signal); _this._priv_speed = new reference_1.default(videoElement.playbackRate, _this._destroyCanceller.signal); _this._priv_preferTrickModeTracks = false; _this._priv_contentLock = new reference_1.default(false, _this._destroyCanceller.signal); _this._priv_bufferOptions = { wantedBufferAhead: new reference_1.default(wantedBufferAhead, _this._destroyCanceller.signal), maxBufferAhead: new reference_1.default(maxBufferAhead, _this._destroyCanceller.signal), maxBufferBehind: new reference_1.default(maxBufferBehind, _this._destroyCanceller.signal), maxVideoBufferSize: new reference_1.default(maxVideoBufferSize, _this._destroyCanceller.signal), }; _this._priv_bitrateInfos = { lastBitrates: { audio: baseBandwidth, video: baseBandwidth }, }; _this._priv_throttleVideoBitrateWhenHidden = throttleVideoBitrateWhenHidden; _this._priv_videoResolutionLimit = videoResolutionLimit; _this._priv_currentError = null; _this._priv_contentInfos = null; _this._priv_contentEventsMemory = {}; _this._priv_reloadingMetadata = {}; _this._priv_lastAutoPlay = false; _this._priv_worker = null; var onVolumeChange = function () { _this.trigger("volumeChange", { volume: videoElement.volume, muted: videoElement.muted, }); }; videoElement.addEventListener("volumechange", onVolumeChange); destroyCanceller.signal.register(function () { videoElement.removeEventListener("volumechange", onVolumeChange); }); return _this; } Object.defineProperty(Player, "ErrorTypes", { /** All possible Error types emitted by the RxPlayer. */ get: function () { return errors_1.ErrorTypes; }, enumerable: false, configurable: true }); Object.defineProperty(Player, "ErrorCodes", { /** All possible Error codes emitted by the RxPlayer. */ get: function () { return errors_1.ErrorCodes; }, enumerable: false, configurable: true }); Object.defineProperty(Player, "LogLevel", { /** * Current log level. * Update current log level. * Should be either (by verbosity ascending): * - "NONE" * - "ERROR" * - "WARNING" * - "INFO" * - "DEBUG" * Any other value will be translated to "NONE". */ get: function () { return log_1.default.getLevel(); }, set: function (logLevel) { log_1.default.setLevel(logLevel, log_1.default.getFormat()); }, enumerable: false, configurable: true }); Object.defineProperty(Player, "LogFormat", { /** * Current log format. * Should be either (by verbosity ascending): * - "standard": Regular log messages. * - "full": More verbose format, including a timestamp and a namespace. * Any other value will be translated to "standard". */ get: function () { return log_1.default.getFormat(); }, set: function (format) { log_1.default.setLevel(log_1.default.getLevel(), format); }, enumerable: false, configurable: true }); /** * Add feature(s) to the RxPlayer. * @param {Array.<Object>} featureList - Features wanted. */ Player.addFeatures = function (featureList) { (0, features_1.addFeatures)(featureList); }; /** * Register the video element to the set of elements currently in use. * @param videoElement the video element to register. * @throws Error - Throws if the element is already used by another player instance. */ Player._priv_registerVideoElement = function (videoElement) { if (Player._priv_currentlyUsedVideoElements.has(videoElement)) { var errorMessage = "The video element is already attached to another RxPlayer instance." + "\nMake sure to dispose the previous instance with player.dispose() before creating" + " a new player instance attaching that video element."; // eslint-disable-next-line no-console console.warn(errorMessage); /* * TODO: for next major version 5.0: this need to throw an error instead of just logging * this was not done for minor version as it could be considerated a breaking change. * * throw new Error(errorMessage); */ } Player._priv_currentlyUsedVideoElements.add(videoElement); }; /** * Deregister the video element of the set of elements currently in use. * @param videoElement the video element to deregister. */ Player._priv_deregisterVideoElement = function (videoElement) { if (Player._priv_currentlyUsedVideoElements.has(videoElement)) { Player._priv_currentlyUsedVideoElements.delete(videoElement); } }; /** * TODO returns promise? * @param {Object} workerSettings */ Player.prototype.attachWorker = function (workerSettings) { var _this = this; return new Promise(function (res, rej) { var _a; if (!(0, has_worker_api_1.default)()) { log_1.default.warn("API", "Cannot rely on a WebWorker: Worker API unavailable"); return rej(new worker_initialization_error_1.default("INCOMPATIBLE_ERROR", "Worker unavailable")); } // check if the user already attach worker before // terminate the previous worker to release the resources if (_this._priv_worker !== null) { if (_this.state !== "STOPPED") { log_1.default.warn("API", "Cannot attach a new worker while a content is playing, please stop the player first."); return rej(new worker_initialization_error_1.default("SETUP_ERROR", "Cannot attach a new worker while a content is playing")); } else { _this._priv_worker.terminate(); } } if (typeof workerSettings.workerUrl === "string") { _this._priv_worker = new Worker(workerSettings.workerUrl); } else { var blobUrl = URL.createObjectURL(workerSettings.workerUrl); _this._priv_worker = new Worker(blobUrl); URL.revokeObjectURL(blobUrl); } _this._priv_worker.onerror = function (evt) { if (_this._priv_worker !== null) { _this._priv_worker.terminate(); _this._priv_worker = null; } log_1.default.error("API", "Unexpected worker error", evt.error instanceof Error ? evt.error : undefined); rej(new worker_initialization_error_1.default("UNKNOWN_ERROR", 'Unexpected Worker "error" event')); }; var handleInitMessages = function (msg) { var msgData = msg.data; if (msgData.type === "init-error" /* WorkerMessageType.InitError */) { log_1.default.warn("API", "Processing InitError worker message: detaching worker"); if (_this._priv_worker !== null) { _this._priv_worker.removeEventListener("message", handleInitMessages); _this._priv_worker.terminate(); _this._priv_worker = null; } rej(new worker_initialization_error_1.default("SETUP_ERROR", "Worker parser initialization failed: " + msgData.value.errorMessage)); } else if (msgData.type === "init-success" /* WorkerMessageType.InitSuccess */) { log_1.default.info("API", "InitSuccess received from worker."); if (_this._priv_worker !== null) { _this._priv_worker.removeEventListener("message", handleInitMessages); } res(); } }; _this._priv_worker.addEventListener("message", handleInitMessages); log_1.default.debug("M-->C", "Sending message", { name: "init" /* MainThreadMessageType.Init */ }); _this._priv_worker.postMessage({ type: "init" /* MainThreadMessageType.Init */, value: { dashWasmUrl: workerSettings.dashWasmUrl, logLevel: log_1.default.getLevel(), logFormat: log_1.default.getFormat(), sendBackLogs: isDebugModeEnabled, date: Date.now(), timestamp: (0, monotonic_timestamp_1.default)(), hasVideo: ((_a = _this.videoElement) === null || _a === void 0 ? void 0 : _a.nodeName.toLowerCase()) === "video", }, }); log_1.default.addEventListener("onLogLevelChange", function (logInfo) { if (_this._priv_worker === null) { return; } log_1.default.debug("M-->C", "Sending message", { name: "log-level-update" /* MainThreadMessageType.LogLevelUpdate */, }); _this._priv_worker.postMessage({ type: "log-level-update" /* MainThreadMessageType.LogLevelUpdate */, value: { logLevel: logInfo.level, logFormat: logInfo.format, sendBackLogs: isDebugModeEnabled, }, }); }, _this._destroyCanceller.signal); var sendConfigUpdates = function (updates) { if (_this._priv_worker === null) { return; } log_1.default.debug("M-->C", "Sending message:", { name: "config-update" /* MainThreadMessageType.ConfigUpdate */, }); _this._priv_worker.postMessage({ type: "config-update" /* MainThreadMessageType.ConfigUpdate */, value: updates, }); }; if (config_1.default.updated) { sendConfigUpdates(config_1.default.getCurrent()); } config_1.default.addEventListener("update", sendConfigUpdates, _this._destroyCanceller.signal); }); }; /** * Returns information on which "mode" the RxPlayer is running for the current * content (e.g. main logic running in a WebWorker or not, are we in * directfile mode...). * * Returns `null` if no content is loaded. * @returns {Object|null} */ Player.prototype.getCurrentModeInformation = function () { if (this._priv_contentInfos === null) { return null; } return { isDirectFile: this._priv_contentInfos.isDirectFile, useWorker: this._priv_contentInfos.useWorker, }; }; /** * Register a new callback for a player event event. * * @param {string} evt - The event to register a callback to * @param {Function} fn - The callback to call as that event is triggered. * The callback will take as argument the eventual payload of the event * (single argument). */ Player.prototype.addEventListener = function (evt, fn) { // The EventEmitter's `addEventListener` method takes an optional third // argument that we do not want to expose in the public API. // We thus overwrite that function to remove any possible usage of that // third argument. return _super.prototype.addEventListener.call(this, evt, fn); }; /** * Stop the playback for the current content. */ Player.prototype.stop = function () { if (this._priv_contentInfos !== null) { this._priv_contentInfos.currentContentCanceller.cancel(); } this._priv_cleanUpCurrentContentState(); if (this.state !== "STOPPED" /* PLAYER_STATES.STOPPED */) { this._priv_setPlayerState("STOPPED" /* PLAYER_STATES.STOPPED */); } }; /** * Free the resources used by the player. * /!\ The player cannot be "used" anymore after this method has been called. */ Player.prototype.dispose = function () { // free resources linked to the loaded content this.stop(); if (this.videoElement !== null) { Player._priv_deregisterVideoElement(this.videoElement); // free resources used for decryption management (0, decrypt_1.disposeDecryptionResources)(this.videoElement).catch(function (err) { var message = err instanceof Error ? err.message : "Unknown error"; log_1.default.error("API", "Could not dispose decryption resources: " + message); }); } // free resources linked to the Player instance this._destroyCanceller.cancel(); this._priv_reloadingMetadata = {}; // un-attach video element this.videoElement = null; if (this._priv_worker !== null) { this._priv_worker.terminate(); this._priv_worker = null; } }; /** * Load a new video. * @param {Object} opts */ Player.prototype.loadVideo = function (opts) { var options = (0, option_utils_1.parseLoadVideoOptions)(opts); log_1.default.info("API", "Calling loadvideo", { url: options.url, transport: options.transport, }); this._priv_reloadingMetadata = { options: options }; this._priv_initializeContentPlayback(options); this._priv_lastAutoPlay = options.autoPlay; }; /** * Reload the last loaded content. * @param {Object} reloadOpts */ Player.prototype.reload = function (reloadOpts) { var _a, _b, _c; var _d = this._priv_reloadingMetadata, options = _d.options, manifest = _d.manifest, reloadPosition = _d.reloadPosition, reloadInPause = _d.reloadInPause; if (options === undefined) { throw new Error("API: Can't reload without having previously loaded a content."); } (0, option_utils_1.checkReloadOptions)(reloadOpts); var startAt; if (((_a = reloadOpts === null || reloadOpts === void 0 ? void 0 : reloadOpts.reloadAt) === null || _a === void 0 ? void 0 : _a.position) !== undefined) { startAt = { position: reloadOpts.reloadAt.position }; } else if (((_b = reloadOpts === null || reloadOpts === void 0 ? void 0 : reloadOpts.reloadAt) === null || _b === void 0 ? void 0 : _b.relative) !== undefined) { if (reloadPosition === undefined) { throw new Error("Can't reload to a relative position when previous content was not loaded."); } else { startAt = { position: reloadOpts.reloadAt.relative + reloadPosition }; } } else if (reloadPosition !== undefined) { startAt = { position: reloadPosition }; } var autoPlay; if ((reloadOpts === null || reloadOpts === void 0 ? void 0 : reloadOpts.autoPlay) !== undefined) { autoPlay = reloadOpts.autoPlay; } else if (reloadInPause !== undefined) { autoPlay = !reloadInPause; } var keySystems; if ((reloadOpts === null || reloadOpts === void 0 ? void 0 : reloadOpts.keySystems) !== undefined) { keySystems = reloadOpts.keySystems; } else if (((_c = this._priv_reloadingMetadata.options) === null || _c === void 0 ? void 0 : _c.keySystems) !== undefined) { keySystems = this._priv_reloadingMetadata.options.keySystems; } var newOptions = __assign(__assign({}, options), { initialManifest: manifest }); if (startAt !== undefined) { newOptions.startAt = startAt; } if (autoPlay !== undefined) { newOptions.autoPlay = autoPlay; } if (keySystems !== undefined) { newOptions.keySystems = keySystems; } this._priv_initializeContentPlayback(newOptions); }; Player.prototype.createDebugElement = function (element) { if (features_1.default.createDebugElement === null) { throw new Error("Feature `DEBUG_ELEMENT` not added to the RxPlayer"); } var canceller = new task_canceller_1.default(); features_1.default.createDebugElement(element, this, canceller.signal); return { dispose: function () { canceller.cancel(); }, }; }; /** * Returns an array decribing the various thumbnail tracks that can be * encountered at the wanted time or Period. * @param {Object} arg * @param {number|undefined} [arg.time] - The position to check for thumbnail * tracks, in seconds. * @param {string|undefined} [arg.periodId] - The Period to check for * thumbnail tracks. * If not set and if `arg.time` is also not set, the current Period will be * considered. * @returns {Array.<Object>} */ Player.prototype.getAvailableThumbnailTracks = function (_a) { var _b = _a === void 0 ? {} : _a, time = _b.time, periodId = _b.periodId; if (this._priv_contentInfos === null || this._priv_contentInfos.manifest === null) { return []; } var manifest = this._priv_contentInfos.manifest; var period; if (time !== undefined) { period = (0, manifest_1.getPeriodForTime)(this._priv_contentInfos.manifest, time); if (period === undefined || period.thumbnailTracks.length === 0) { return []; } } else if (periodId !== undefined) { period = (0, array_find_1.default)(manifest.periods, function (p) { return p.id === periodId; }); if (period === undefined) { log_1.default.error("API", "getAvailableThumbnailTracks: periodId not found", { periodId: periodId }); return []; } } else { var currentPeriod = this._priv_contentInfos.currentPeriod; if (currentPeriod === null) { return []; } period = currentPeriod; } return period.thumbnailTracks.map(function (t) { return { id: t.id, width: Math.floor(t.width / t.horizontalTiles), height: Math.floor(t.height / t.verticalTiles), mimeType: t.mimeType, }; }); }; /** * Render inside the given `container` the thumbnail corresponding to the * given time. * * If no thumbnail is available at that time or if the RxPlayer does not succeed * to load or render it, reject the corresponding Promise and remove the * potential previous thumbnail from the container. * * If a new `renderThumbnail` call is made with the same `container` before it * had time to finish, the Promise is also rejected but the previous thumbnail * potentially found in the container is untouched. * * @param {Object|undefined} options * @returns {Promise} */ Player.prototype.renderThumbnail = function (options) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { if ((0, is_null_or_undefined_1.default)(options.time)) { throw new Error("You have to provide a `time` property to `renderThumbnail`, indicating the wanted thumbnail time in seconds."); } if ((0, is_null_or_undefined_1.default)(options.container)) { throw new Error("You have to provide a `container` property to `renderThumbnail`, specifying the HTML Element in which the thumbnail should be inserted."); } return [2 /*return*/, (0, render_thumbnail_1.default)(this._priv_contentInfos, options)]; }); }); }; /** * From given options, initialize content playback. * @param {Object} options */ Player.prototype._priv_initializeContentPlayback = function (options) { var _this = this; var _a, _b, _c, _d, _f, _g; var autoPlay = options.autoPlay, cmcd = options.cmcd, defaultAudioTrackSwitchingMode = options.defaultAudioTrackSwitchingMode, enableFastSwitching = options.enableFastSwitching, initialManifest = options.initialManifest, keySystems = options.keySystems, lowLatencyMode = options.lowLatencyMode, minimumManifestUpdateInterval = options.minimumManifestUpdateInterval, requestConfig = options.requestConfig, onCodecSwitch = options.onCodecSwitch, startAt = options.startAt, transport = options.transport, checkMediaSegmentIntegrity = options.checkMediaSegmentIntegrity, checkManifestIntegrity = options.checkManifestIntegrity, manifestLoader = options.manifestLoader, referenceDateTime = options.referenceDateTime, segmentLoader = options.segmentLoader, serverSyncInfos = options.serverSyncInfos, mode = options.mode, experimentalOptions = options.experimentalOptions, __priv_manifestUpdateUrl = options.__priv_manifestUpdateUrl, __priv_patchLastSegmentInSidx = options.__priv_patchLastSegmentInSidx, url = options.url, onAudioTracksNotPlayable = options.onAudioTracksNotPlayable, onVideoTracksNotPlayable = options.onVideoTracksNotPlayable; // Perform multiple checks on the given options if (this.videoElement === null) { throw new Error("the attached video element is disposed"); } var isDirectFile = transport === "directfile"; /** Emit to stop the current content. */ var currentContentCanceller = new task_canceller_1.default(); var videoElement = this.videoElement; var initializer; var useWorker = false; var mediaElementTracksStore = null; if (!isDirectFile) { /** Interface used to load and refresh the Manifest. */ var manifestRequestSettings = { lowLatencyMode: lowLatencyMode, maxRetry: (_a = requestConfig.manifest) === null || _a === void 0 ? void 0 : _a.maxRetry, requestTimeout: (_b = requestConfig.manifest) === null || _b === void 0 ? void 0 : _b.timeout, connectionTimeout: (_c = requestConfig.manifest) === null || _c === void 0 ? void 0 : _c.connectionTimeout, minimumManifestUpdateInterval: minimumManifestUpdateInterval, initialManifest: initialManifest, }; var relyOnVideoVisibilityAndSize = (0, can_rely_on_video_visibility_and_size_1.default)(); var throttlers = { throttleBitrate: {}, limitResolution: {}, }; if (this._priv_throttleVideoBitrateWhenHidden) { if (!relyOnVideoVisibilityAndSize) { log_1.default.warn("API", "Can't apply throttleVideoBitrateWhenHidden because " + "browser can't be trusted for visibility."); } else { throttlers.throttleBitrate = { video: (0, reference_1.createMappedReference)((0, event_listeners_1.getVideoVisibilityRef)(this._priv_pictureInPictureRef, currentContentCanceller.signal), function (isActive) { return (isActive ? Infinity : 0); }, currentContentCanceller.signal), }; } } if (this._priv_videoResolutionLimit === "videoElement") { if (!relyOnVideoVisibilityAndSize) { log_1.default.warn("API", "Can't apply videoResolutionLimit because browser can't be " + "trusted for video size."); } else { throttlers.limitResolution = { video: (0, event_listeners_1.getElementResolutionRef)(videoElement, this._priv_pictureInPictureRef, currentContentCanceller.signal), }; } } else if (this._priv_videoResolutionLimit === "screen") { throttlers.limitResolution = { video: (0, event_listeners_1.getScreenResolutionRef)(currentContentCanceller.signal), }; } /** Options used by the adaptive logic. */ var adaptiveOptions = { initialBitrates: this._priv_bitrateInfos.lastBitrates, lowLatencyMode: lowLatencyMode, throttlers: throttlers, }; /** Options used by the TextTrack SegmentSink. */ var textTrackOptions = options.textTrackMode === "native" ? { textTrackMode: "native" } : { textTrackMode: "html", textTrackElement: options.textTrackElement, }; var bufferOptions = (0, object_assign_1.default)({ enableFastSwitching: enableFastSwitching, onCodecSwitch: onCodecSwitch }, this._priv_bufferOptions); var segmentRequestOptions = { lowLatencyMode: lowLatencyMode, maxRetry: (_d = requestConfig.segment) === null || _d === void 0 ? void 0 : _d.maxRetry, requestTimeout: (_f = requestConfig.segment) === null || _f === void 0 ? void 0 : _f.timeout, connectionTimeout: (_g = requestConfig.segment) === null || _g === void 0 ? void 0 : _g.connectionTimeout, }; var canRunInMultiThread = features_1.default.multithread !== null && this._priv_worker !== null && this.videoElement.FORCED_MEDIA_SOURCE === undefined && transport === "dash" && MULTI_THREAD_UNSUPPORTED_LOAD_VIDEO_OPTIONS.every(function (option) { return (0, is_null_or_undefined_1.default)(options[option]); }) && typeof options.representationFilter !== "function"; if (mode === "main" || (mode === "auto" && !canRunInMultiThread)) { if (features_1.default.mainThreadMediaSourceInit === null) { throw new Error("Cannot load video, neither in a WebWorker nor with the " + "`MEDIA_SOURCE_MAIN` feature"); } var transportFn = features_1.default.transports[transport]; if (typeof transportFn !== "function") { // Stop previous content and reset its state this.stop(); this._priv_currentError = null; throw new Error("transport \"".concat(transport, "\" not supported")); } var representationFilter = typeof options.representationFilter === "string" ? (0, manifest_1.createRepresentationFilterFromFnString)(options.representationFilter) : options.representationFilter; log_1.default.info("API", "Initializing MediaSource mode in the main thread"); var transportPipelines = transportFn({ lowLatencyMode: lowLatencyMode, checkMediaSegmentIntegrity: checkMediaSegmentIntegrity, checkManifestIntegrity: checkManifestIntegrity, manifestLoader: manifestLoader, referenceDateTime: referenceDateTime, representationFilter: representationFilter, segmentLoader: segmentLoader, serverSyncInfos: serverSyncInfos, __priv_manifestUpdateUrl: __priv_manifestUpdateUrl, __priv_patchLastSegmentInSidx: __priv_patchLastSegmentInSidx, }); initializer = new features_1.default.mainThreadMediaSourceInit({ adaptiveOptions: adaptiveOptions, autoPlay: autoPlay, bufferOptions: bufferOptions, cmcd: cmcd, enableRepresentationAvoidance: experimentalOptions.enableRepresentationAvoidance, keySystems: keySystems, lowLatencyMode: lowLatencyMode, transport: transportPipelines, manifestRequestSettings: manifestRequestSettings, segmentRequestOptions: segmentRequestOptions, speed: this._priv_speed, startAt: startAt, textTrackOptions: textTrackOptions, url: url, }); } else { if (features_1.default.multithread === null) { throw new Error("Cannot load video in multithread mode: `MULTI_THREAD` " + "feature not imported."); } else if (this._priv_worker === null) { throw new Error("Cannot load video in multithread mode: `attachWorker` " + "method not called."); } (0, assert_1.default)(typeof options.representationFilter !== "function"); useWorker = true; log_1.default.info("API", "Initializing MediaSource mode in a WebWorker"); var transportOptions = { lowLatencyMode: lowLatencyMode, checkMediaSegmentIntegrity: checkMediaSegmentIntegrity, checkManifestIntegrity: checkManifestIntegrity, referenceDateTime: referenceDateTime, serverSyncInfos: serverSyncInfos, manifestLoader: undefined, segmentLoader: undefined, representationFilter: options.representationFilter, __priv_manifestUpdateUrl: __priv_manifestUpdateUrl, __priv_patchLastSegmentInSidx: __priv_patchLastSegmentInSidx, }; initializer = new features_1.default.multithread.init({ adaptiveOptions: adaptiveOptions, autoPlay: autoPlay, bufferOptions: bufferOptions, cmcd: cmcd, enableRepresentationAvoidance: experimentalOptions.enableRepresentationAvoidance, keySystems: keySystems, lowLatencyMode: lowLatencyMode, transportOptions: transportOptions, manifestRequestSettings: manifestRequestSettings, segmentRequestOptions: segmentRequestOptions, speed: this._priv_speed, startAt: startAt, textTrackOptions: textTrackOptions, worker: this._priv_worker, url: url, useMseInWorker: has_mse_in_worker_1.default, }); } } else { if (features_1.default.directfile === null) { this.stop(); this._priv_currentError = null; throw new Error("DirectFile feature not activated in your build."); } else if ((0, is_null_or_undefined_1.default)(url)) { throw new Error("No URL for a DirectFile content"); } log_1.default.info("API", "Initializing DirectFile mode in the main thread"); mediaElementTracksStore = this._priv_initializeMediaElementTracksStore(currentContentCanceller.signal); if (currentContentCanceller.isUsed()) { return; } initializer = new features_1.default.directfile.initDirectFile({ autoPlay: autoPlay, keySystems: keySystems, speed: this._priv_speed, startAt: startAt, url: url, }); } /** Global "playback observer" which will emit playback conditions */ var playbackObserver = new media_element_playback_observer_1.default({ withMediaSource: !isDirectFile, lowLatencyMode: lowLatencyMode, }); /* * We want to block seeking operations until we know the media element is * ready for it. */ playbackObserver.blockSeeking(); currentContentCanceller.signal.register(function () { playbackObserver.stop(); }); /** Future `this._priv_contentInfos` related to this content. */ var contentInfos = { contentId: generateContentId(), originalUrl: url, playbackObserver: playbackObserver, currentContentCanceller: currentContentCanceller, defaultAudioTrackSwitchingMode: defaultAudioTrackSwitchingMode, initializer: initializer, isDirectFile: isDirectFile, manifest: null, currentPeriod: null, activeAdaptations: null, activeRepresentations: null, tracksStore: null, mediaElementTracksStore: mediaElementTracksStore, useWorker: useWorker, segmentSinkMetricsCallback: null, fetchThumbnailDataCallback: null, thumbnailRequestsInfo: { pendingRequests: new WeakMap(), lastResponse: null, }, onAudioTracksNotPlayable: onAudioTracksNotPlayable, onVideoTracksNotPlayable: onVideoTracksNotPlayable, }; // Bind events initializer.addEventListener("error", function (error) { _this._priv_onFatalError(error, contentInfos); }); initializer.addEventListener("warning", function (error) { var formattedError = (0, errors_1.formatError)(error, { defaultCode: "NONE", defaultReason: "An unknown error happened.", }); log_1.default.warn("API", "Sending warning:", formattedError); _this.trigger("warning", formattedError); }); initializer.addEventListener("reloadingMediaSource", function (payload) { if (contentInfos.tracksStore !== null) { contentInfos.tracksStore.resetPeriodObjects(); } if (_this._priv_contentInfos !== null) { _this._priv_contentInfos.segmentSinkMetricsCallback = null; } _this._priv_lastAutoPlay = payload.autoPlay; }); initializer.addEventListener("inbandEvents", function (inbandEvents) { return _this.trigger("inbandEvents", inbandEvents); }); initializer.addEventListener("streamEvent", function (streamEvent) { return _this.trigger("streamEvent", streamEvent); }); initializer.addEventListener("streamEventSkip", function (streamEventSkip) { return _this.trigger("streamEventSkip", streamEventSkip); }); initializer.addEventListener("activePeriodChanged", function (periodInfo) { return _this._priv_onActivePeriodChanged(contentInfos, periodInfo); }); initializer.addEventListener("periodStreamReady", function (periodReadyInfo) { return _this._priv_onPeriodStreamReady(contentInfos, periodReadyInfo); }); initializer.addEventListener("periodStreamCleared", function (periodClearedInfo) { return _this._priv_onPeriodStreamCleared(contentInfos, periodClearedInfo); }); initializer.addEventListener("representationChange", function (representationInfo) { return _this._priv_onRepresentationChange(contentInfos, representationInfo); }); initializer.addEventListener("adaptationChange", function (adaptationInfo) { return _this._priv_onAdaptationChange(contentInfos, adaptationInfo); }); initializer.addEventListener("bitrateEstimateChange", function (bitrateEstimateInfo) { return _this._priv_onBitrateEstimateChange(bitrateEstimateInfo); }); initializer.addEventListener("manifestReady", function (manifest) { return _this._priv_onManifestReady(contentInfos, manifest); }); initializer.addEventListener("manifestUpdate", function (updates) { return _this._priv_onManifestUpdate(contentInfos, updates); }); initializer.addEventListener("codecSupportUpdate", function () { return _this._priv_onCodecSupportUpdate(contentInfos); }); initializer.addEventListener("decipherabilityUpdate", function (updates) { return _this._priv_onDecipherabilityUpdate(contentInfos, updates); }); initializer.addEventListener("loaded", function (evt) { if (_this._priv_contentInfos !== null) { _this._priv_contentInfos.segmentSinkMetricsCallback = evt.getSegmentSinkMetrics; _this._priv_contentInfos.fetchThumbnailDataCallback = evt.getThumbnailData; } }); // Now, that most events are linked, prepare the next content. initializer.prepare(); // Now that the content is prepared, stop previous content and reset state // This is done after content preparation as `stop` could technically have // a long and synchronous blocking time. // Note that this call is done **synchronously** after all events linking. // This is **VERY** important so: // - the `STOPPED` state is switched to synchronously after loading a new // content. // - we can avoid involontarily catching events linked to the previous // content. this.stop(); playbackObserver.attachMediaElement(videoElement); // Update the RxPlayer's state at the right events var playerStateRef = (0, utils_1.constructPlayerStateReference)(initializer, videoElement, playbackObserver, isDirectFile, currentContentCanceller.signal); currentContentCanceller.signal.register(function () { initializer.dispose(); }); /** * Function updating `this._priv_reloadingMetadata` in function of the * current state and playback conditions. * To call when either might change. * @param {string} state - The player state we're about to switch to. */ var updateReloadingMetadata = function (state) { switch (state) { case "STOPPED": case "RELOADING": case "LOADING": break; // keep previous metadata case "ENDED": _this._priv_reloadingMetadata.reloadInPause = true; _this._priv_reloadingMetadata.reloadPosition = playbackObserver .getReference() .getValue() .position.getPolled(); break; default: { var o = playbackObserver.getReference().getValue(); _this._priv_reloadingMetadata.reloadInPause = o.paused; _this._priv_reloadingMetadata.reloadPosition = o.position.getWanted(); break; } } }; /** * `TaskCanceller` allowing to stop emitting `"play"` and `"pause"` * events. * `null` when such events are not emitted currently. */ var playPauseEventsCanceller = null; /** * Callback emitting `"play"` and `"pause`" events once the content is * loaded, starting from the state indicated in argument. * @param {boolea