rx-player
Version:
Canal+ HTML5 Video Player
1,011 lines • 134 kB
JavaScript
"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