rx-player
Version:
Canal+ HTML5 Video Player
571 lines (570 loc) • 24.8 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 __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 __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;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
var errors_1 = require("../../errors");
var log_1 = require("../../log");
var update_manifest_codec_support_1 = require("../../main_thread/init/utils/update_manifest_codec_support");
var array_find_1 = require("../../utils/array_find");
var event_emitter_1 = require("../../utils/event_emitter");
var id_generator_1 = require("../../utils/id_generator");
var warn_once_1 = require("../../utils/warn_once");
var utils_1 = require("../utils");
var codec_support_cache_1 = require("./codec_support_cache");
var period_1 = require("./period");
var types_1 = require("./types");
var update_periods_1 = require("./update_periods");
var generateNewManifestId = (0, id_generator_1.default)();
/**
* Normalized Manifest structure.
*
* Details the current content being played:
* - the duration of the content
* - the available tracks
* - the available qualities
* - the segments defined in those qualities
* - ...
* while staying agnostic of the transport protocol used (Smooth, DASH etc.).
*
* The Manifest and its contained information can evolve over time (like when
* updating a dynamic manifest or when right management forbid some tracks from
* being played).
* To perform actions on those changes, any module using this Manifest can
* listen to its sent events and react accordingly.
*
* @class Manifest
*/
var Manifest = /** @class */ (function (_super) {
__extends(Manifest, _super);
/**
* Construct a Manifest instance from a parsed Manifest object (as returned by
* Manifest parsers) and options.
*
* Some minor errors can arise during that construction. `warnings`
* will contain all such errors, in the order they have been encountered.
* @param {Object} parsedManifest
* @param {Object} options
* @param {Array.<Object>} warnings - After construction, will be optionally
* filled by errors expressing minor issues seen while parsing the Manifest.
*/
function Manifest(parsedManifest, options, warnings) {
var _a;
var _this = _super.call(this) || this;
var representationFilter = options.representationFilter, manifestUpdateUrl = options.manifestUpdateUrl;
_this.manifestFormat = 0 /* ManifestMetadataFormat.Class */;
_this.id = generateNewManifestId();
_this.expired = (_a = parsedManifest.expired) !== null && _a !== void 0 ? _a : null;
_this.transport = parsedManifest.transportType;
_this.clockOffset = parsedManifest.clockOffset;
_this._cachedCodecSupport = new codec_support_cache_1.default([]);
var unsupportedAdaptations = [];
_this.periods = parsedManifest.periods
.map(function (parsedPeriod) {
var period = new period_1.default(parsedPeriod, unsupportedAdaptations, _this._cachedCodecSupport, representationFilter);
return period;
})
.sort(function (a, b) { return a.start - b.start; });
if (unsupportedAdaptations.length > 0) {
var error = new errors_1.MediaError("MANIFEST_INCOMPATIBLE_CODECS_ERROR", "An Adaptation contains only incompatible codecs.", { tracks: unsupportedAdaptations.map(utils_1.toTaggedTrack) });
warnings.push(error);
}
/**
* @deprecated It is here to ensure compatibility with the way the
* v3.x.x manages adaptations at the Manifest level
*/
_this.adaptations = _this.periods[0] === undefined ? {} : _this.periods[0].adaptations;
_this.timeBounds = parsedManifest.timeBounds;
_this.isDynamic = parsedManifest.isDynamic;
_this.isLive = parsedManifest.isLive;
_this.isLastPeriodKnown = parsedManifest.isLastPeriodKnown;
_this.uris = parsedManifest.uris === undefined ? [] : parsedManifest.uris;
_this.updateUrl = manifestUpdateUrl;
_this.lifetime = parsedManifest.lifetime;
_this.clockOffset = parsedManifest.clockOffset;
_this.suggestedPresentationDelay = parsedManifest.suggestedPresentationDelay;
_this.availabilityStartTime = parsedManifest.availabilityStartTime;
_this.publishTime = parsedManifest.publishTime;
return _this;
}
/**
* Some environments (e.g. in a WebWorker) may not have the capability to know
* if a mimetype+codec combination is supported on the current platform.
*
* Calling `updateCodecSupport` manually once the codecs supported are known
* by the current environnement allows to work-around this issue.
*
* @param {Array<Object>} [updatedCodecSupportInfo]
* @returns {Error|null} - Refreshing codec support might reveal that some
* `Adaptation` don't have any of their `Representation`s supported.
* In that case, an error object will be created and returned, so you can
* e.g. later emit it as a warning through the RxPlayer API.
*/
Manifest.prototype.updateCodecSupport = function (updatedCodecSupportInfo) {
var e_1, _a;
if (updatedCodecSupportInfo === void 0) { updatedCodecSupportInfo = []; }
if (updatedCodecSupportInfo.length === 0) {
return null;
}
this._cachedCodecSupport.addCodecs(updatedCodecSupportInfo);
var unsupportedAdaptations = [];
try {
for (var _b = __values(this.periods), _c = _b.next(); !_c.done; _c = _b.next()) {
var period = _c.value;
period.refreshCodecSupport(unsupportedAdaptations, this._cachedCodecSupport);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_1) throw e_1.error; }
}
this.trigger("supportUpdate", null);
if (unsupportedAdaptations.length > 0) {
return new errors_1.MediaError("MANIFEST_INCOMPATIBLE_CODECS_ERROR", "An Adaptation contains only incompatible codecs.", { tracks: unsupportedAdaptations.map(utils_1.toTaggedTrack) });
}
return null;
};
/**
* Returns the Period corresponding to the given `id`.
* Returns `undefined` if there is none.
* @param {string} id
* @returns {Object|undefined}
*/
Manifest.prototype.getPeriod = function (id) {
return (0, array_find_1.default)(this.periods, function (period) {
return id === period.id;
});
};
/**
* Returns the Period encountered at the given time.
* Returns `undefined` if there is no Period exactly at the given time.
* @param {number} time
* @returns {Object|undefined}
*/
Manifest.prototype.getPeriodForTime = function (time) {
return (0, utils_1.getPeriodForTime)(this, time);
};
/**
* Returns the first Period starting strictly after the given time.
* Returns `undefined` if there is no Period starting after that time.
* @param {number} time
* @returns {Object|undefined}
*/
Manifest.prototype.getNextPeriod = function (time) {
return (0, array_find_1.default)(this.periods, function (period) {
return period.start > time;
});
};
/**
* Returns the Period coming chronologically just after another given Period.
* Returns `undefined` if not found.
* @param {Object} period
* @returns {Object|null}
*/
Manifest.prototype.getPeriodAfter = function (period) {
return (0, utils_1.getPeriodAfter)(this, period);
};
/**
* Returns the most important URL from which the Manifest can be refreshed.
* `undefined` if no URL is found.
* @returns {Array.<string>}
*/
Manifest.prototype.getUrls = function () {
return this.uris;
};
/**
* Update the current Manifest properties by giving a new updated version.
* This instance will be updated with the new information coming from it.
* @param {Object} newManifest
*/
Manifest.prototype.replace = function (newManifest) {
this._performUpdate(newManifest, types_1.MANIFEST_UPDATE_TYPE.Full);
};
/**
* Update the current Manifest properties by giving a new but shorter version
* of it.
* This instance will add the new information coming from it and will
* automatically clean old Periods that shouldn't be available anymore.
*
* /!\ Throws if the given Manifest cannot be used or is not sufficient to
* update the Manifest.
* @param {Object} newManifest
*/
Manifest.prototype.update = function (newManifest) {
this._performUpdate(newManifest, types_1.MANIFEST_UPDATE_TYPE.Partial);
};
/**
* Returns the theoretical minimum playable position on the content
* regardless of the current Adaptation chosen, as estimated at parsing
* time.
* @returns {number}
*/
Manifest.prototype.getMinimumSafePosition = function () {
return (0, utils_1.getMinimumSafePosition)(this);
};
/**
* Get the position of the live edge - that is, the position of what is
* currently being broadcasted, in seconds.
* @returns {number|undefined}
*/
Manifest.prototype.getLivePosition = function () {
return (0, utils_1.getLivePosition)(this);
};
/**
* Returns the theoretical maximum playable position on the content
* regardless of the current Adaptation chosen, as estimated at parsing
* time.
*/
Manifest.prototype.getMaximumSafePosition = function () {
return (0, utils_1.getMaximumSafePosition)(this);
};
Manifest.prototype.updateCodecSupportList = function (cachedCodecSupport) {
this._cachedCodecSupport = cachedCodecSupport;
};
/**
* Look in the Manifest for Representations linked to the given key ID,
* and mark them as being impossible to decrypt.
* Then trigger a "decipherabilityUpdate" event to notify everyone of the
* changes performed.
* @param {Function} isDecipherableCb
*/
Manifest.prototype.updateRepresentationsDeciperability = function (isDecipherableCb) {
var updates = updateDeciperability(this, isDecipherableCb);
if (updates.length > 0) {
this.trigger("decipherabilityUpdate", updates);
}
};
/**
* Indicate that some `Representation` needs to be avoided due to playback
* issues.
* @param {Array.<Object>} items
*/
Manifest.prototype.addRepresentationsToAvoid = function (items) {
var e_2, _a;
var updates = [];
try {
for (var items_1 = __values(items), items_1_1 = items_1.next(); !items_1_1.done; items_1_1 = items_1.next()) {
var item = items_1_1.value;
var period = this.getPeriod(item.period.id);
if (period === undefined) {
continue;
}
var adaptation = period.getAdaptation(item.adaptation.id);
if (adaptation === undefined) {
continue;
}
var representation = adaptation.getRepresentation(item.representation.id);
if (representation === undefined) {
continue;
}
representation.shouldBeAvoided = true;
updates.push({
manifest: this,
period: period,
adaptation: adaptation,
representation: representation,
});
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (items_1_1 && !items_1_1.done && (_a = items_1.return)) _a.call(items_1);
}
finally { if (e_2) throw e_2.error; }
}
if (updates.length > 0) {
this.trigger("representationAvoidanceUpdate", updates);
}
};
/**
* @deprecated only returns adaptations for the first period
* @returns {Array.<Object>}
*/
Manifest.prototype.getAdaptations = function () {
(0, warn_once_1.default)("manifest.getAdaptations() is deprecated." +
" Please use manifest.period[].getAdaptations() instead");
var firstPeriod = this.periods[0];
if (firstPeriod === undefined) {
return [];
}
var adaptationsByType = firstPeriod.adaptations;
var adaptationsList = [];
for (var adaptationType in adaptationsByType) {
if (Object.prototype.hasOwnProperty.call(adaptationsByType, adaptationType)) {
var adaptations = adaptationsByType[adaptationType];
adaptationsList.push.apply(adaptationsList, __spreadArray([], __read(adaptations), false));
}
}
return adaptationsList;
};
/**
* @deprecated only returns adaptations for the first period
* @returns {Array.<Object>}
*/
Manifest.prototype.getAdaptationsForType = function (adaptationType) {
(0, warn_once_1.default)("manifest.getAdaptationsForType(type) is deprecated." +
" Please use manifest.period[].getAdaptationsForType(type) instead");
var firstPeriod = this.periods[0];
if (firstPeriod === undefined) {
return [];
}
var adaptationsForType = firstPeriod.adaptations[adaptationType];
return adaptationsForType === undefined ? [] : adaptationsForType;
};
/**
* @deprecated only returns adaptations for the first period
* @returns {Array.<Object>}
*/
Manifest.prototype.getAdaptation = function (wantedId) {
(0, warn_once_1.default)("manifest.getAdaptation(id) is deprecated." +
" Please use manifest.period[].getAdaptation(id) instead");
return (0, array_find_1.default)(this.getAdaptations(), function (_a) {
var id = _a.id;
return wantedId === id;
});
};
/**
* Format the current `Manifest`'s properties into a
* `IManifestMetadata` format which can better be communicated through
* another thread.
*
* Please bear in mind however that the returned object will not be updated
* when the current `Manifest` instance is updated, it is only a
* snapshot at the current time.
*
* If you want to keep that data up-to-date with the current `Manifest`
* instance, you will have to do it yourself.
*
* @returns {Object}
*/
Manifest.prototype.getMetadataSnapshot = function () {
var e_3, _a;
var periods = [];
try {
for (var _b = __values(this.periods), _c = _b.next(); !_c.done; _c = _b.next()) {
var period = _c.value;
periods.push(period.getMetadataSnapshot());
}
}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_3) throw e_3.error; }
}
return {
manifestFormat: 1 /* ManifestMetadataFormat.MetadataObject */,
id: this.id,
periods: periods,
isDynamic: this.isDynamic,
isLive: this.isLive,
isLastPeriodKnown: this.isLastPeriodKnown,
suggestedPresentationDelay: this.suggestedPresentationDelay,
clockOffset: this.clockOffset,
uris: this.uris,
availabilityStartTime: this.availabilityStartTime,
timeBounds: this.timeBounds,
};
};
/**
* Returns a list of all codecs that the support is not known yet.
* If a representation with (`isSupported`) is undefined, we consider the
* codec support as unknown.
*
* This function iterates through all periods, adaptations, and representations,
* and collects unknown codecs.
*
* @returns {Array} The list of codecs with unknown support status.
*/
Manifest.prototype.getCodecsWithUnknownSupport = function () {
return (0, update_manifest_codec_support_1.getCodecsWithUnknownSupport)(this);
};
/**
* @param {Object} newManifest
* @param {number} updateType
*/
Manifest.prototype._performUpdate = function (newManifest, updateType) {
this.availabilityStartTime = newManifest.availabilityStartTime;
this.expired = newManifest.expired;
this.isDynamic = newManifest.isDynamic;
this.isLive = newManifest.isLive;
this.isLastPeriodKnown = newManifest.isLastPeriodKnown;
this.lifetime = newManifest.lifetime;
this.clockOffset = newManifest.clockOffset;
this.suggestedPresentationDelay = newManifest.suggestedPresentationDelay;
this.transport = newManifest.transport;
this.publishTime = newManifest.publishTime;
var updatedPeriodsResult;
if (updateType === types_1.MANIFEST_UPDATE_TYPE.Full) {
this.timeBounds = newManifest.timeBounds;
this.uris = newManifest.uris;
updatedPeriodsResult = (0, update_periods_1.replacePeriods)(this.periods, newManifest.periods);
}
else {
this.timeBounds.maximumTimeData = newManifest.timeBounds.maximumTimeData;
this.updateUrl = newManifest.uris[0];
updatedPeriodsResult = (0, update_periods_1.updatePeriods)(this.periods, newManifest.periods);
// Partial updates do not remove old Periods.
// This can become a memory problem when playing a content long enough.
// Let's clean manually Periods behind the minimum possible position.
var min = this.getMinimumSafePosition();
while (this.periods.length > 0) {
var period = this.periods[0];
if (period.end === undefined || period.end > min) {
break;
}
this.periods.shift();
}
}
this.updateCodecSupport();
// Re-set this.adaptations for retro-compatibility in v3.x.x
this.adaptations = this.periods[0] === undefined ? {} : this.periods[0].adaptations;
// Let's trigger events at the end, as those can trigger side-effects.
// We do not want the current Manifest object to be incomplete when those
// happen.
this.trigger("manifestUpdate", updatedPeriodsResult);
};
return Manifest;
}(event_emitter_1.default));
exports.default = Manifest;
/**
* Update `decipherable` property of every `Representation` found in the
* Manifest based on the result of a `isDecipherable` callback:
* - When that callback returns `true`, update `decipherable` to `true`
* - When that callback returns `false`, update `decipherable` to `false`
* - When that callback returns `undefined`, update `decipherable` to
* `undefined`
* @param {Manifest} manifest
* @param {Function} isDecipherable
* @returns {Array.<Object>}
*/
function updateDeciperability(manifest, isDecipherable) {
var e_4, _a, e_5, _b, e_6, _c;
var updates = [];
try {
for (var _d = __values(manifest.periods), _e = _d.next(); !_e.done; _e = _d.next()) {
var period = _e.value;
try {
for (var _f = (e_5 = void 0, __values(period.getAdaptations())), _g = _f.next(); !_g.done; _g = _f.next()) {
var adaptation = _g.value;
var hasOnlyUndecipherableRepresentations = true;
try {
for (var _h = (e_6 = void 0, __values(adaptation.representations)), _j = _h.next(); !_j.done; _j = _h.next()) {
var representation = _j.value;
var content = { manifest: manifest, period: period, adaptation: adaptation, representation: representation };
var result = isDecipherable(content);
if (result !== false) {
hasOnlyUndecipherableRepresentations = false;
}
if (result !== representation.decipherable) {
updates.push(content);
representation.decipherable = result;
if (result === true) {
adaptation.supportStatus.isDecipherable = true;
}
else if (result === undefined &&
adaptation.supportStatus.isDecipherable === false) {
adaptation.supportStatus.isDecipherable = undefined;
}
log_1.default.debug("Decipherability changed for \"".concat(representation.id, "\""), "(".concat(representation.bitrate, ")"), String(representation.decipherable));
}
}
}
catch (e_6_1) { e_6 = { error: e_6_1 }; }
finally {
try {
if (_j && !_j.done && (_c = _h.return)) _c.call(_h);
}
finally { if (e_6) throw e_6.error; }
}
if (hasOnlyUndecipherableRepresentations) {
adaptation.supportStatus.isDecipherable = false;
}
}
}
catch (e_5_1) { e_5 = { error: e_5_1 }; }
finally {
try {
if (_g && !_g.done && (_b = _f.return)) _b.call(_f);
}
finally { if (e_5) throw e_5.error; }
}
}
}
catch (e_4_1) { e_4 = { error: e_4_1 }; }
finally {
try {
if (_e && !_e.done && (_a = _d.return)) _a.call(_d);
}
finally { if (e_4) throw e_4.error; }
}
return updates;
}