UNPKG

rx-player

Version:
335 lines (334 loc) 16.4 kB
"use strict"; 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 __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 features_1 = require("../../features"); var log_1 = require("../../log"); var main_media_source_interface_1 = require("../../mse/main_media_source_interface"); var worker_media_source_interface_1 = require("../../mse/worker_media_source_interface"); var id_generator_1 = require("../../utils/id_generator"); var task_canceller_1 = require("../../utils/task_canceller"); var adaptive_1 = require("../adaptive"); var cmcd_1 = require("../cmcd"); var fetchers_1 = require("../fetchers"); var cdn_prioritizer_1 = require("../fetchers/cdn_prioritizer"); var thumbnail_fetcher_1 = require("../fetchers/thumbnails/thumbnail_fetcher"); var segment_sinks_1 = require("../segment_sinks"); var core_text_displayer_interface_1 = require("./core_text_displayer_interface"); var FreezeResolver_1 = require("./FreezeResolver"); var track_choice_setter_1 = require("./track_choice_setter"); var utils_1 = require("./utils"); /** Function allowing to associate a unique identifier to all created `MediaSource` */ var generateMediaSourceId = (0, id_generator_1.default)(); /** * Class facilitating the workflows behind loading a new content for the * RxPlayer Core: * * - Handle Manifest fetching and Manifest updates. * * - Handle the `MediaSource`'s creation and indirectly of its `SourceBuffer`s * as well as handling "MediaSource reloading". * * - initialize various modules (`segmentQueueCreator`, CmcdDataBuilder`, * `RepresentationEstimator`) linked to the initialized content. * * You can start loading a content through the `initializeNewContent` method. * * When a content is linked to the `ContentPreparer` you can inspect the * different initialized modules by calling its `getCurrentContent` method. * * @class ContentPreparer */ var ContentPreparer = /** @class */ (function () { /** * @param {Object} capabilities * @param {boolean} capabilities.hasVideo - If `true`, we're playing on an * element which has video capabilities. * If `false`, we're only able to play audio, optionally with subtitles. * * Typically this boolean is `true` for `<video>` HTMLElement and `false` for * `<audio>` HTMLElement. */ function ContentPreparer(_a) { var hasVideo = _a.hasVideo; this._currentContent = null; this._currentMediaSourceCanceller = new task_canceller_1.default("ContentPreparer MediaSource"); this._hasVideo = hasVideo; var contentCanceller = new task_canceller_1.default("ContentPreparer"); this._contentCanceller = contentCanceller; } /** * Start fetching the wanted content's Manifest and initializing the various * modules stored by the `ContentPreparer` linked to that content. * * The returned Promise resolves with the parsed Manifest when those modules * are all ready and you can thus begin to load the content. * * Reject if it failed to do so. * @param {Object} context - Information on the content that should be * initialized. * @param {Object} corePlugins - Callbacks that may have been registered by * the application if it loaded the core independently as a worker. * @returns {Promise.<Object>} */ ContentPreparer.prototype.initializeNewContent = function (sendMessage, context, /** Allows to filter which Representations can be choosen. */ throttlers, corePlugins) { var _this = this; return new Promise(function (res, rej) { var _a, _b; _this.disposeCurrentContent("new init"); var contentCanceller = _this._contentCanceller; var currentMediaSourceCanceller = new task_canceller_1.default("ContentPreparer MediaSource"); _this._currentMediaSourceCanceller = currentMediaSourceCanceller; currentMediaSourceCanceller.linkToSignal(contentCanceller.signal); var contentId = context.contentId, url = context.url, hasText = context.hasText, transportOptions = context.transportOptions, useMseInWorker = context.useMseInWorker, enableRepresentationAvoidance = context.enableRepresentationAvoidance, transport = context.transport; var manifest = null; var transportFn = features_1.default.transports[transport]; if (typeof transportFn !== "function") { rej(new Error("transport \"".concat(transport, "\" not supported. ") + "Did you add the corresponding feature?")); return; } var transportPipelines = transportFn(__assign(__assign({}, transportOptions), (0, utils_1.extractExternalPlugins)(transportOptions, corePlugins))); var cmcdDataBuilder = context.cmcd === undefined ? null : new cmcd_1.default(context.cmcd); var manifestFetcher = new fetchers_1.ManifestFetcher(url === undefined ? undefined : [url], transportPipelines, __assign({ cmcdDataBuilder: cmcdDataBuilder }, context.manifestRetryOptions)); var representationEstimator = (0, adaptive_1.default)({ initialBitrates: { audio: (_a = context.initialAudioBitrate) !== null && _a !== void 0 ? _a : 0, video: (_b = context.initialVideoBitrate) !== null && _b !== void 0 ? _b : 0, }, lowLatencyMode: transportOptions.lowLatencyMode, throttlers: throttlers, }); var unbindRejectOnCancellation = currentMediaSourceCanceller.signal.register(function (error) { rej(error); }); var cdnPrioritizer = new cdn_prioritizer_1.default(contentCanceller.signal); var segmentQueueCreator = new fetchers_1.SegmentQueueCreator(transportPipelines, cdnPrioritizer, cmcdDataBuilder, context.segmentRetryOptions); var fetchThumbnailData = (0, thumbnail_fetcher_1.default)(transportPipelines.thumbnails, cdnPrioritizer); var trackChoiceSetter = new track_choice_setter_1.default(); var _c = __read(createMediaSourceInterfaceAndSegmentSinksStore(sendMessage, contentId, { useMseInWorker: useMseInWorker, hasVideo: _this._hasVideo, hasText: hasText, }, currentMediaSourceCanceller.signal), 3), mediaSource = _c[0], segmentSinksStore = _c[1], coreTextSender = _c[2]; var freezeResolver = new FreezeResolver_1.default(segmentSinksStore); _this._currentContent = { cmcdDataBuilder: cmcdDataBuilder, contentId: contentId, enableRepresentationAvoidance: enableRepresentationAvoidance, freezeResolver: freezeResolver, mediaSource: mediaSource, manifest: null, manifestFetcher: manifestFetcher, representationEstimator: representationEstimator, segmentSinksStore: segmentSinksStore, segmentQueueCreator: segmentQueueCreator, fetchThumbnailData: fetchThumbnailData, coreTextSender: coreTextSender, trackChoiceSetter: trackChoiceSetter, useMseInWorker: useMseInWorker, }; mediaSource.addEventListener("mediaSourceOpen", function () { checkIfReadyAndValidate(); }, currentMediaSourceCanceller.signal); contentCanceller.signal.register(function (err) { manifestFetcher.dispose(err.reason); }); manifestFetcher.addEventListener("warning", function (err) { sendMessage({ type: "warning" /* CoreMessageType.Warning */, contentId: contentId, value: (0, utils_1.formatErrorForSender)(err), }); }, contentCanceller.signal); manifestFetcher.addEventListener("manifestReady", function (man) { if (manifest !== null) { log_1.default.warn("Core", "Multiple `manifestReady` events, ignoring"); return; } manifest = man; if (_this._currentContent !== null) { _this._currentContent.manifest = manifest; } checkIfReadyAndValidate(); }, currentMediaSourceCanceller.signal); manifestFetcher.addEventListener("error", function (err) { rej(err); }, contentCanceller.signal); manifestFetcher.start(); function checkIfReadyAndValidate() { if (manifest === null || mediaSource.readyState === "closed" || currentMediaSourceCanceller.isUsed()) { return; } (0, utils_1.updateCodecSupportInWorkerMode)(manifest); manifest.addEventListener("manifestUpdate", function (updates) { if (manifest === null) { // TODO log warn? return; } sendMessage({ type: "manifest-update" /* CoreMessageType.ManifestUpdate */, contentId: contentId, value: { manifest: manifest, updates: updates }, }); }, contentCanceller.signal); unbindRejectOnCancellation(); res(manifest); } }); }; /** * Get information on the current content prepared through the * `initializeNewContent` method, or `null` if no content is currently * prepared. * @returns {Object|null} */ ContentPreparer.prototype.getCurrentContent = function () { return this._currentContent; }; /** * Schedule an update for the Manifest file, * * Do nothing if no content is currently prepared. * @param {Object} settings - Various settings to configure the ways and * moment at which the Manifest will be refreshed. */ ContentPreparer.prototype.scheduleManifestRefresh = function (settings) { var _a; (_a = this._currentContent) === null || _a === void 0 ? void 0 : _a.manifestFetcher.scheduleManualRefresh(settings); }; /** * Change the MediaSource attached for the current content. * It is assumed that main thread is already notified that such a reload is * happening. * * The returned Promise resolves when it restarts being ready. * @param {Function} sendMessage * @returns {Promise} */ ContentPreparer.prototype.reloadMediaSource = function (sendMessage) { var _this = this; var _a; this._currentMediaSourceCanceller.cancel("ContentPreparer MediaSource reload"); if (this._currentContent === null) { return Promise.reject(new Error("CP: No content anymore")); } this._currentContent.trackChoiceSetter.reset(); (_a = this._currentContent.coreTextSender) === null || _a === void 0 ? void 0 : _a.stop("ContentPreparer MediaSource reload"); this._currentMediaSourceCanceller = new task_canceller_1.default("ContentPreparer MediaSource"); this._currentMediaSourceCanceller.linkToSignal(this._contentCanceller.signal); var _b = __read(createMediaSourceInterfaceAndSegmentSinksStore(sendMessage, this._currentContent.contentId, { useMseInWorker: this._currentContent.useMseInWorker, hasVideo: this._hasVideo, hasText: this._currentContent.coreTextSender !== null, }, this._currentMediaSourceCanceller.signal), 3), mediaSourceInterface = _b[0], segmentSinksStore = _b[1], coreTextSender = _b[2]; this._currentContent.mediaSource = mediaSourceInterface; this._currentContent.segmentSinksStore = segmentSinksStore; this._currentContent.freezeResolver = new FreezeResolver_1.default(segmentSinksStore); this._currentContent.coreTextSender = coreTextSender; return new Promise(function (res, rej) { mediaSourceInterface.addEventListener("mediaSourceOpen", function () { res(); }, _this._currentMediaSourceCanceller.signal); mediaSourceInterface.addEventListener("mediaSourceClose", function () { rej(new Error("MediaSource ReadyState changed to close during init.")); }, _this._currentMediaSourceCanceller.signal); _this._currentMediaSourceCanceller.signal.register(function (error) { rej(error); }); }); }; /** * Dispose all resources linked to the currently preopared content if one and * stop linking it to this `ContentPreparer`. * @param {string | undefined} reason - Human-inspectable reason behind the * dispose. Used for debugging matters, especially for debug log * inspection. */ ContentPreparer.prototype.disposeCurrentContent = function (reason) { this._contentCanceller.cancel(reason); this._contentCanceller = new task_canceller_1.default("ContentPreparer"); }; return ContentPreparer; }()); exports.default = ContentPreparer; /** * @param {Function} sendMessage * @param {string} contentId * @param {Object} capabilities * @param {boolean} capabilities.useMseInWorker * @param {boolean} capabilities.hasVideo * @param {boolean} capabilities.hasText * @param {Object} cancelSignal * @returns {Array.<Object>} */ function createMediaSourceInterfaceAndSegmentSinksStore(sendMessage, contentId, capabilities, cancelSignal) { var mediaSourceInterface; if (capabilities.useMseInWorker) { var mainMediaSource = new main_media_source_interface_1.default(generateMediaSourceId()); mediaSourceInterface = mainMediaSource; var sentMediaSourceLink = void 0; var handle = mainMediaSource.handle; if (handle.type === "handle") { sentMediaSourceLink = { type: "handle", value: handle.value }; } else { var url_1 = URL.createObjectURL(handle.value); sentMediaSourceLink = { type: "url", value: url_1 }; cancelSignal.register(function () { URL.revokeObjectURL(url_1); }); } sendMessage({ type: "attach-media-source" /* CoreMessageType.AttachMediaSource */, contentId: contentId, value: sentMediaSourceLink, mediaSourceId: mediaSourceInterface.id, }, // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion [handle.value]); } else { mediaSourceInterface = new worker_media_source_interface_1.default(generateMediaSourceId(), contentId, sendMessage); } var textSender = capabilities.hasText ? new core_text_displayer_interface_1.default(contentId, sendMessage) : null; var hasVideo = capabilities.hasVideo; var segmentSinksStore = new segment_sinks_1.default(mediaSourceInterface, hasVideo, textSender); cancelSignal.register(function (err) { segmentSinksStore.disposeAll(err.reason); textSender === null || textSender === void 0 ? void 0 : textSender.stop(err.reason); mediaSourceInterface.dispose(err.reason); }); return [mediaSourceInterface, segmentSinksStore, textSender]; }