rx-player
Version:
Canal+ HTML5 Video Player
342 lines (341 loc) • 16.6 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 __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 __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 });
exports.default = parsePeriods;
var log_1 = require("../../../../log");
var flat_map_1 = require("../../../../utils/flat_map");
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_values_1 = require("../../../../utils/object_values");
var string_parsing_1 = require("../../../../utils/string_parsing");
var flatten_overlapping_periods_1 = require("./flatten_overlapping_periods");
var get_periods_time_infos_1 = require("./get_periods_time_infos");
var parse_adaptation_sets_1 = require("./parse_adaptation_sets");
var resolve_base_urls_1 = require("./resolve_base_urls");
var generatePeriodID = (0, id_generator_1.default)();
/**
* Process intermediate periods to create final parsed periods.
* @param {Array.<Object>} periodsIR
* @param {Object} context
* @returns {Array.<Object>}
*/
function parsePeriods(periodsIR, context) {
var _a, _b, _c, _d, _e;
var parsedPeriods = [];
var periodsTimeInformation = (0, get_periods_time_infos_1.default)(periodsIR, context);
if (periodsTimeInformation.length !== periodsIR.length) {
throw new Error("MPD parsing error: the time information are incoherent.");
}
var isDynamic = context.isDynamic, manifestBoundsCalculator = context.manifestBoundsCalculator;
if (!isDynamic && !(0, is_null_or_undefined_1.default)(context.duration)) {
manifestBoundsCalculator.setLastPosition(context.duration);
}
var _loop_1 = function (i) {
var isLastPeriod = i === periodsIR.length - 1;
var periodIR = periodsIR[i];
var xlinkInfos = context.xlinkInfos.get(periodIR);
var periodBaseURLs = (0, resolve_base_urls_1.default)(context.baseURLs, periodIR.children.baseURLs);
var _g = periodsTimeInformation[i], periodStart = _g.periodStart, periodDuration = _g.periodDuration, periodEnd = _g.periodEnd;
var periodID;
if ((0, is_null_or_undefined_1.default)(periodIR.attributes.id)) {
periodID = "gen-dash-period-" + generatePeriodID();
log_1.default.warn("dash", "No usable id found in the Period. Generating one.", {
periodId: periodID,
});
}
else {
periodID = periodIR.attributes.id;
}
// Avoid duplicate IDs
while (parsedPeriods.some(function (p) { return p.id === periodID; })) {
periodID += "-dup";
}
var receivedTime = xlinkInfos !== undefined ? xlinkInfos.receivedTime : context.receivedTime;
var unsafelyBaseOnPreviousPeriod = (_b = (_a = context.unsafelyBaseOnPreviousManifest) === null || _a === void 0 ? void 0 : _a.getPeriod(periodID)) !== null && _b !== void 0 ? _b : null;
var availabilityTimeComplete = periodIR.attributes.availabilityTimeComplete;
var availabilityTimeOffset = periodIR.attributes.availabilityTimeOffset;
var manifestProfiles = context.manifestProfiles, contentProtectionParser = context.contentProtectionParser;
var segmentTemplate = periodIR.children.segmentTemplate;
contentProtectionParser.addReferences((_c = periodIR.children.contentProtections) !== null && _c !== void 0 ? _c : []);
var adapCtxt = {
availabilityTimeComplete: availabilityTimeComplete,
availabilityTimeOffset: availabilityTimeOffset,
baseURLs: periodBaseURLs,
contentProtectionParser: contentProtectionParser,
manifestBoundsCalculator: manifestBoundsCalculator,
end: periodEnd,
isDynamic: isDynamic,
isLastPeriod: isLastPeriod,
manifestProfiles: manifestProfiles,
receivedTime: receivedTime,
segmentTemplate: segmentTemplate,
start: periodStart,
unsafelyBaseOnPreviousPeriod: unsafelyBaseOnPreviousPeriod,
};
var _h = (0, parse_adaptation_sets_1.default)(periodIR.children.adaptations, adapCtxt), adaptations = _h.adaptations, thumbnailTracks = _h.thumbnailTracks;
var namespaces = ((_d = context.xmlNamespaces) !== null && _d !== void 0 ? _d : []).concat((_e = periodIR.attributes.namespaces) !== null && _e !== void 0 ? _e : []);
var streamEvents = generateStreamEvents(periodIR.children.eventStreams, periodStart, namespaces);
var parsedPeriod = {
id: periodID,
start: periodStart,
end: periodEnd,
duration: periodDuration,
thumbnailTracks: thumbnailTracks,
adaptations: adaptations,
streamEvents: streamEvents,
};
parsedPeriods.unshift(parsedPeriod);
if (!manifestBoundsCalculator.lastPositionIsKnown()) {
var lastPosition = getMaximumLastPosition(adaptations);
if (!isDynamic) {
if (typeof lastPosition === "number") {
manifestBoundsCalculator.setLastPosition(lastPosition);
}
}
else {
if (typeof lastPosition === "number") {
var positionTime = (0, monotonic_timestamp_1.default)() / 1000;
manifestBoundsCalculator.setLastPosition(lastPosition, positionTime);
}
else {
var guessedLastPositionFromClock = guessLastPositionFromClock(context, periodStart);
if (guessedLastPositionFromClock !== undefined) {
var _j = __read(guessedLastPositionFromClock, 2), guessedLastPosition = _j[0], guessedPositionTime = _j[1];
manifestBoundsCalculator.setLastPosition(guessedLastPosition, guessedPositionTime);
}
}
}
}
};
// We parse it in reverse because we might need to deduce the buffer depth from
// the last Periods' indexes
for (var i = periodsIR.length - 1; i >= 0; i--) {
_loop_1(i);
}
if (context.isDynamic && !manifestBoundsCalculator.lastPositionIsKnown()) {
// Guess a last time the last position
var guessedLastPositionFromClock = guessLastPositionFromClock(context, 0);
if (guessedLastPositionFromClock !== undefined) {
var _f = __read(guessedLastPositionFromClock, 2), lastPosition = _f[0], positionTime = _f[1];
manifestBoundsCalculator.setLastPosition(lastPosition, positionTime);
}
}
return (0, flatten_overlapping_periods_1.default)(parsedPeriods);
}
/**
* Try to guess the "last position", which is the last position
* available in the manifest in seconds, and the "position time", the
* monotonically-raising timestamp used by the RxPlayer, at which the
* last position was collected.
*
* These values allows to retrieve at any time in the future the new last
* position, by substracting the position time to the last position, and
* adding to it the new monotonically-raising timestamp.
*
* The last position and position time are returned by this function if and only if
* it would indicate a last position superior to the `minimumTime` given.
*
* This last part allows for example to detect which Period is likely to be the
* "current" one in multi-periods contents. By giving the Period's start as a
* `minimumTime`, you ensure that you will get a value only if the current time
* is in that period.
*
* This is useful as guessing the live time from the clock can be seen as a last
* resort. By detecting that the current time is before the currently considered
* Period, we can just parse and look at the previous Period. If we can guess
* the live time more directly from that previous one, we might be better off
* than just using the clock.
*
* @param {Object} context
* @param {number} minimumTime
* @returns {Array.<number|undefined>}
*/
function guessLastPositionFromClock(context, minimumTime) {
if (!(0, is_null_or_undefined_1.default)(context.clockOffset)) {
var lastPosition = context.clockOffset / 1000 - context.availabilityStartTime;
var positionTime = (0, monotonic_timestamp_1.default)() / 1000;
var timeInSec = positionTime + lastPosition;
if (timeInSec >= minimumTime) {
return [timeInSec, positionTime];
}
}
else {
var now = Date.now() / 1000;
if (now >= minimumTime) {
log_1.default.warn("dash", "no clock synchronization mechanism found. Using the system clock instead.");
var lastPosition = now - context.availabilityStartTime;
var positionTime = (0, monotonic_timestamp_1.default)() / 1000;
return [lastPosition, positionTime];
}
}
return undefined;
}
/**
* Try to extract the last position declared for any segments in a Period:
* - If at least a single index' last position is defined, take the maximum
* among them.
* - If segments are available but we cannot define the last position
* return undefined.
* - If no segment are available in that period, return null
* @param {Object} adaptationsPerType
* @returns {number|null|undefined}
*/
function getMaximumLastPosition(adaptationsPerType) {
var e_1, _a, e_2, _b;
var maxEncounteredPosition = null;
var allIndexAreEmpty = true;
var adaptationsVal = (0, object_values_1.default)(adaptationsPerType).filter(function (ada) { return !(0, is_null_or_undefined_1.default)(ada); });
var allAdaptations = (0, flat_map_1.default)(adaptationsVal, function (adaptationsForType) { return adaptationsForType; });
try {
for (var allAdaptations_1 = __values(allAdaptations), allAdaptations_1_1 = allAdaptations_1.next(); !allAdaptations_1_1.done; allAdaptations_1_1 = allAdaptations_1.next()) {
var adaptation = allAdaptations_1_1.value;
var representations = adaptation.representations;
try {
for (var representations_1 = (e_2 = void 0, __values(representations)), representations_1_1 = representations_1.next(); !representations_1_1.done; representations_1_1 = representations_1.next()) {
var representation = representations_1_1.value;
var position = representation.index.getLastAvailablePosition();
if (position !== null) {
allIndexAreEmpty = false;
if (typeof position === "number") {
maxEncounteredPosition = (0, is_null_or_undefined_1.default)(maxEncounteredPosition)
? position
: Math.max(maxEncounteredPosition, position);
}
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (representations_1_1 && !representations_1_1.done && (_b = representations_1.return)) _b.call(representations_1);
}
finally { if (e_2) throw e_2.error; }
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (allAdaptations_1_1 && !allAdaptations_1_1.done && (_a = allAdaptations_1.return)) _a.call(allAdaptations_1);
}
finally { if (e_1) throw e_1.error; }
}
if (!(0, is_null_or_undefined_1.default)(maxEncounteredPosition)) {
return maxEncounteredPosition;
}
else if (allIndexAreEmpty) {
return null;
}
return undefined;
}
/**
* Generate parsed "eventStream" objects from a `StreamEvent` node's
* intermediate Representation.
* @param {Array.<Object>} baseIr - The array of every encountered StreamEvent's
* intermediate representations for a given Period.
* @param {number} periodStart - The time in seconds at which this corresponding
* Period starts.
* @returns {Array.<Object>} - The parsed objects.
*/
function generateStreamEvents(baseIr, periodStart, xmlNamespaces) {
var e_3, _a, e_4, _b;
var _c, _d;
var res = [];
try {
for (var baseIr_1 = __values(baseIr), baseIr_1_1 = baseIr_1.next(); !baseIr_1_1.done; baseIr_1_1 = baseIr_1.next()) {
var eventStreamIr = baseIr_1_1.value;
var _e = eventStreamIr.attributes, _f = _e.schemeIdUri, schemeIdUri = _f === void 0 ? "" : _f, _g = _e.timescale, timescale = _g === void 0 ? 1 : _g;
var allNamespaces = xmlNamespaces.concat((_c = eventStreamIr.attributes.namespaces) !== null && _c !== void 0 ? _c : []);
try {
for (var _h = (e_4 = void 0, __values(eventStreamIr.children.events)), _j = _h.next(); !_j.done; _j = _h.next()) {
var eventIr = _j.value;
if (eventIr.eventStreamData !== undefined) {
var start = ((_d = eventIr.presentationTime) !== null && _d !== void 0 ? _d : 0) / timescale + periodStart;
var end = eventIr.duration === undefined
? undefined
: start + eventIr.duration / timescale;
var element = void 0;
var xmlData = void 0;
try {
xmlData = {
namespaces: allNamespaces,
data: typeof eventIr.eventStreamData === "string"
? eventIr.eventStreamData
: (0, string_parsing_1.utf8ToStr)(new Uint8Array(eventIr.eventStreamData)),
};
}
catch (err) {
log_1.default.error("dash", "Error while parsing event-stream:", err instanceof Error ? err.message : "Unknown error");
}
res.push({
start: start,
end: end,
id: eventIr.id,
data: {
type: "dash-event-stream",
value: { schemeIdUri: schemeIdUri, timescale: timescale, element: element, xmlData: xmlData },
},
});
}
}
}
catch (e_4_1) { e_4 = { error: e_4_1 }; }
finally {
try {
if (_j && !_j.done && (_b = _h.return)) _b.call(_h);
}
finally { if (e_4) throw e_4.error; }
}
}
}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
finally {
try {
if (baseIr_1_1 && !baseIr_1_1.done && (_a = baseIr_1.return)) _a.call(baseIr_1);
}
finally { if (e_3) throw e_3.error; }
}
return res;
}