rx-player
Version:
Canal+ HTML5 Video Player
370 lines (369 loc) • 16.3 kB
JavaScript
"use strict";
/**
* Copyright 2015 CANAL+ Group
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var __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 __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
Object.defineProperty(exports, "__esModule", { value: true });
var errors_1 = require("../../errors");
var log_1 = require("../../log");
var assert_1 = require("../../utils/assert");
var create_cancellable_promise_1 = require("../../utils/create_cancellable_promise");
var is_null_or_undefined_1 = require("../../utils/is_null_or_undefined");
var noop_1 = require("../../utils/noop");
var implementations_1 = require("./implementations");
var text_1 = require("./implementations/text");
var POSSIBLE_BUFFER_TYPES = ["audio", "video", "text"];
/**
* Allows to easily create and dispose SegmentSinks, which are interfaces to
* push and remove segments.
*
* Only one SegmentSink per type is allowed at the same time:
*
* - SegmentSinks linked to a "native" media buffer (relying on a
* SourceBuffer: "audio" and "video" here) are reused if one is
* re-created.
*
* - SegmentSinks for custom types (the other types of media) are aborted
* each time a new one of the same type is created.
*
* To be able to use a SegmentSink linked to a native media buffer, you
* will first need to create it, but also wait until the other one is either
* created or explicitely disabled through the `disableSegmentSink` method.
* The Promise returned by `waitForUsableBuffers` will emit when
* that is the case.
*
* @class SegmentSinksStore
*/
var SegmentSinksStore = /** @class */ (function () {
/**
* @param {Object} mediaSource
* @constructor
*/
function SegmentSinksStore(mediaSource, hasVideo, textDisplayerInterface) {
this._mediaSource = mediaSource;
this._textInterface = textDisplayerInterface;
this._hasVideo = hasVideo;
this._initializedSegmentSinks = {};
this._onNativeBufferAddedOrDisabled = [];
}
/**
* Returns true if the type is linked to a "native" media buffer (i.e. relying
* on a SourceBuffer object, native to the browser).
* Native media buffers needed for the current content must all be created
* before the content begins to be played and cannot be disposed during
* playback.
* @param {string} bufferType
* @returns {Boolean}
*/
SegmentSinksStore.isNative = function (bufferType) {
return shouldHaveNativeBuffer(bufferType);
};
/**
* Get all currently available buffer types.
* /!\ This list can evolve at runtime depending on feature switching.
* @returns {Array.<string>}
*/
SegmentSinksStore.prototype.getBufferTypes = function () {
var bufferTypes = this.getNativeBufferTypes();
if (this._textInterface !== null) {
bufferTypes.push("text");
}
return bufferTypes;
};
/**
* Get all "native" buffer types that should be created before beginning to
* push contents.
* @returns {Array.<string>}
*/
SegmentSinksStore.prototype.getNativeBufferTypes = function () {
return this._hasVideo ? ["video", "audio"] : ["audio"];
};
/**
* Returns the current "status" of the SegmentSink linked to the buffer
* type given.
*
* This function will return an object containing a key named `type` which
* can be equal to either one of those three value:
*
* - "initialized": A SegmentSink has been created for that type.
* You will in this case also have a second key, `value`, which will
* contain the related SegmentSink instance.
* Please note that you will need to wait until
* `this.waitForUsableBuffers()` has emitted before pushing segment
* data to a SegmentSink relying on a SourceBuffer.
*
* - "disabled": The SegmentSink has been explicitely disabled for this
* type.
*
* - "uninitialized": No action has yet been yet for that SegmentSink.
*
* @param {string} bufferType
* @returns {Object|null}
*/
SegmentSinksStore.prototype.getStatus = function (bufferType) {
var initializedBuffer = this._initializedSegmentSinks[bufferType];
if (initializedBuffer === undefined) {
return { type: "uninitialized" };
}
if (initializedBuffer === null) {
return { type: "disabled" };
}
return { type: "initialized", value: initializedBuffer };
};
/**
* Native media buffers (audio and video) needed for playing the current
* content need to all be created (by creating SegmentSinks linked to them)
* before any one can be used.
*
* This function will return a Promise resolving when any and all native
* SourceBuffers can be used.
*
* From https://w3c.github.io/media-source/#methods
* For example, a user agent may throw a QuotaExceededError
* exception if the media element has reached the HAVE_METADATA
* readyState. This can occur if the user agent's media engine
* does not support adding more tracks during playback.
* @param {Object} cancelWaitSignal
* @return {Promise}
*/
SegmentSinksStore.prototype.waitForUsableBuffers = function (cancelWaitSignal) {
var _this = this;
if (this._areNativeBuffersUsable()) {
return Promise.resolve();
}
return (0, create_cancellable_promise_1.default)(cancelWaitSignal, function (res) {
var onAddedOrDisabled = noop_1.default;
var removeCallback = function () {
var indexOf = _this._onNativeBufferAddedOrDisabled.indexOf(onAddedOrDisabled);
if (indexOf >= 0) {
_this._onNativeBufferAddedOrDisabled.splice(indexOf, 1);
}
};
onAddedOrDisabled = function () {
if (_this._areNativeBuffersUsable()) {
removeCallback();
res();
}
};
_this._onNativeBufferAddedOrDisabled.push(onAddedOrDisabled);
return removeCallback;
});
};
/**
* Explicitely disable the SegmentSink for a given buffer type.
* A call to this function is needed at least for unused native buffer types
* (usually "audio" and "video"), to be able to emit through
* `waitForUsableBuffers` when conditions are met.
* @param {string} bufferType
*/
SegmentSinksStore.prototype.disableSegmentSink = function (bufferType) {
var currentValue = this._initializedSegmentSinks[bufferType];
if (currentValue === null) {
log_1.default.warn("Stream", "The ".concat(bufferType, " SegmentSink was already disabled."));
return;
}
if (currentValue !== undefined) {
throw new Error("Cannot disable an active SegmentSink.");
}
this._initializedSegmentSinks[bufferType] = null;
if (SegmentSinksStore.isNative(bufferType)) {
this._onNativeBufferAddedOrDisabled.slice().forEach(function (cb) { return cb(); });
(0, assert_1.default)(this._onNativeBufferAddedOrDisabled.length === 0);
}
};
/**
* Creates a new SegmentSink associated to a type.
* Reuse an already created one if a SegmentSink for the given type
* already exists.
*
* Please note that you will need to wait until `this.waitForUsableBuffers()`
* has emitted before pushing segment data to a SegmentSink of a native
* type.
* @param {string} bufferType
* @param {string} codec
* @returns {Object}
*/
SegmentSinksStore.prototype.createSegmentSink = function (bufferType, codec) {
var memorizedSegmentSink = this._initializedSegmentSinks[bufferType];
if (shouldHaveNativeBuffer(bufferType)) {
if (!(0, is_null_or_undefined_1.default)(memorizedSegmentSink)) {
if (memorizedSegmentSink instanceof implementations_1.AudioVideoSegmentSink &&
memorizedSegmentSink.codec !== codec) {
log_1.default.warn("Stream", "Reusing native SegmentSink with codec", memorizedSegmentSink.codec, "for codec", codec);
}
else {
log_1.default.info("Stream", "Reusing native SegmentSink with codec", codec);
}
return memorizedSegmentSink;
}
log_1.default.info("Stream", "Adding native SegmentSink with codec", codec);
var sourceBufferType = bufferType === "audio" ? "audio" /* SourceBufferType.Audio */ : "video" /* SourceBufferType.Video */;
var nativeSegmentSink = new implementations_1.AudioVideoSegmentSink(sourceBufferType, codec, this._mediaSource);
this._initializedSegmentSinks[bufferType] = nativeSegmentSink;
this._onNativeBufferAddedOrDisabled.slice().forEach(function (cb) { return cb(); });
(0, assert_1.default)(this._onNativeBufferAddedOrDisabled.length === 0);
return nativeSegmentSink;
}
if (!(0, is_null_or_undefined_1.default)(memorizedSegmentSink)) {
log_1.default.info("Stream", "Reusing a previous custom SegmentSink", { bufferType: bufferType });
return memorizedSegmentSink;
}
var segmentSink;
if (bufferType === "text") {
log_1.default.info("Stream", "Creating a new text SegmentSink");
if (this._textInterface === null) {
throw new Error("HTML Text track feature not activated");
}
segmentSink = new text_1.default(this._textInterface);
this._initializedSegmentSinks.text = segmentSink;
return segmentSink;
}
log_1.default.error("Stream", "Unknown buffer type:", { bufferType: bufferType });
throw new errors_1.MediaError("BUFFER_TYPE_UNKNOWN", "The player wants to create a SegmentSink " + "of an unknown type.");
};
/**
* Dispose of the active SegmentSink for the given type.
* @param {string} bufferType
* @param {string | undefined} reason - Human-inspectable reason behind the
* dispose. Used for debugging matters, especially for debug log
* inspection.
*/
SegmentSinksStore.prototype.disposeSegmentSink = function (bufferType, reason) {
var memorizedSegmentSink = this._initializedSegmentSinks[bufferType];
if ((0, is_null_or_undefined_1.default)(memorizedSegmentSink)) {
log_1.default.warn("Stream", "Trying to dispose a SegmentSink that does not exist", {
bufferType: bufferType,
});
return;
}
log_1.default.info("Stream", "Aborting SegmentSink", { bufferType: bufferType });
memorizedSegmentSink.dispose(reason);
delete this._initializedSegmentSinks[bufferType];
};
/**
* Dispose of all SegmentSink created on this SegmentSinksStore.
* @param {string | undefined} reason - Human-inspectable reason behind the
* dispose. Used for debugging matters, especially for debug log
* inspection.
*/
SegmentSinksStore.prototype.disposeAll = function (reason) {
var _this = this;
POSSIBLE_BUFFER_TYPES.forEach(function (bufferType) {
if (_this.getStatus(bufferType).type === "initialized") {
_this.disposeSegmentSink(bufferType, reason);
}
});
};
/**
* Returns `true` when we're ready to push and decode contents to
* SourceBuffers created by SegmentSinks of a native buffer type.
*/
SegmentSinksStore.prototype._areNativeBuffersUsable = function () {
var _this = this;
var nativeBufferTypes = this.getNativeBufferTypes();
var hasUnitializedBuffers = nativeBufferTypes.some(function (sbType) { return _this._initializedSegmentSinks[sbType] === undefined; });
if (hasUnitializedBuffers) {
// one is not yet initialized/disabled
return false;
}
var areAllDisabled = nativeBufferTypes.every(function (sbType) { return _this._initializedSegmentSinks[sbType] === null; });
if (areAllDisabled) {
// they all are disabled: we can't play the content
return false;
}
return true;
};
SegmentSinksStore.prototype.createSegmentSinkMetricsForType = function (bufferType) {
var e_1, _a;
var _b, _c;
var inventory = (_b = this._initializedSegmentSinks[bufferType]) === null || _b === void 0 ? void 0 : _b.getLastKnownInventory();
var sizeEstimate;
if (inventory !== undefined) {
sizeEstimate = 0;
try {
for (var inventory_1 = __values(inventory), inventory_1_1 = inventory_1.next(); !inventory_1_1.done; inventory_1_1 = inventory_1.next()) {
var item = inventory_1_1.value;
if (item.chunkSize === undefined || sizeEstimate === undefined) {
sizeEstimate = undefined;
break;
}
sizeEstimate += item.chunkSize;
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (inventory_1_1 && !inventory_1_1.done && (_a = inventory_1.return)) _a.call(inventory_1);
}
finally { if (e_1) throw e_1.error; }
}
}
return {
bufferType: bufferType,
sizeEstimate: sizeEstimate,
codec: (_c = this._initializedSegmentSinks[bufferType]) === null || _c === void 0 ? void 0 : _c.codec,
segmentInventory: inventory === null || inventory === void 0 ? void 0 : inventory.map(function (chunk) { return (__assign(__assign({}, chunk), { infos: getChunkContextSnapshot(chunk.infos) })); }),
};
};
SegmentSinksStore.prototype.getSegmentSinksMetrics = function () {
return {
segmentSinks: {
audio: this.createSegmentSinkMetricsForType("audio"),
video: this.createSegmentSinkMetricsForType("video"),
text: this.createSegmentSinkMetricsForType("text"),
},
};
};
return SegmentSinksStore;
}());
exports.default = SegmentSinksStore;
/**
* Returns true if the given buffeType has a linked SourceBuffer implementation,
* false otherwise.
* SourceBuffers are directly added to the MediaSource.
* @param {string} bufferType
* @returns {Boolean}
*/
function shouldHaveNativeBuffer(bufferType) {
return bufferType === "audio" || bufferType === "video";
}
function getChunkContextSnapshot(context) {
return {
adaptation: context.adaptation.getMetadataSnapshot(),
period: context.period.getMetadataSnapshot(),
representation: context.representation.getMetadataSnapshot(),
};
}