rx-player
Version:
Canal+ HTML5 Video Player
323 lines (322 loc) • 14.4 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 __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 });
exports.default = parseMetaPlaylist;
var log_1 = require("../../../log");
var manifest_1 = require("../../../manifest");
var classes_1 = require("../../../manifest/classes");
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 url_utils_1 = require("../../../utils/url-utils");
var representation_index_1 = require("./representation_index");
/**
* Parse playlist string to JSON.
* Returns an array of contents.
* @param {string} data
* @param {Object} parserOptions
* @returns {Object}
*/
function parseMetaPlaylist(data, parserOptions) {
var e_1, _a;
var parsedData;
if (typeof data === "object" && data !== null) {
parsedData = data;
}
else if (typeof data === "string") {
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
parsedData = JSON.parse(data);
}
catch (_error) {
throw new Error("MPL Parser: Bad MetaPlaylist file. Expected JSON.");
}
}
else {
throw new Error("MPL Parser: Parser input must be either a string " +
"or the MetaPlaylist data directly.");
}
var _b = parsedData, contents = _b.contents, version = _b.version, type = _b.type;
if (type !== "MPL") {
throw new Error("MPL Parser: Bad MetaPlaylist. " + "The `type` property is not set to `MPL`");
}
if (version !== "0.1") {
throw new Error("MPL Parser: Bad MetaPlaylist version");
}
// quick checks
if ((0, is_null_or_undefined_1.default)(contents) || contents.length === 0) {
throw new Error("MPL Parser: No content found.");
}
var ressources = [];
try {
for (var contents_1 = __values(contents), contents_1_1 = contents_1.next(); !contents_1_1.done; contents_1_1 = contents_1.next()) {
var content = contents_1_1.value;
if ((0, is_null_or_undefined_1.default)(content.url) ||
(0, is_null_or_undefined_1.default)(content.startTime) ||
(0, is_null_or_undefined_1.default)(content.endTime) ||
(0, is_null_or_undefined_1.default)(content.transport)) {
throw new Error("MPL Parser: Malformed content.");
}
ressources.push({ url: content.url, transportType: content.transport });
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (contents_1_1 && !contents_1_1.done && (_a = contents_1.return)) _a.call(contents_1);
}
finally { if (e_1) throw e_1.error; }
}
var metaPlaylist = parsedData;
return {
type: "needs-manifest-loader",
value: {
ressources: ressources,
continue: function parseWholeMPL(loadedRessources) {
var parsedManifest = createManifest(metaPlaylist, loadedRessources, parserOptions);
return { type: "done", value: parsedManifest };
},
},
};
}
/**
* From several parsed manifests, generate a single bigger manifest.
* Each content presents a start and end time, so that periods
* boudaries could be adapted.
* @param {Object} mplData
* @param {Array<Object>} manifests
* @param {Object} parserOptions
* @returns {Object}
*/
function createManifest(mplData, manifests, parserOptions) {
var url = parserOptions.url, serverSyncInfos = parserOptions.serverSyncInfos;
var clockOffset = serverSyncInfos !== undefined
? serverSyncInfos.serverTimestamp - serverSyncInfos.clientTime
: undefined;
var generateAdaptationID = (0, id_generator_1.default)();
var generateRepresentationID = (0, id_generator_1.default)();
var contents = mplData.contents;
var minimumTime = contents.length > 0 ? contents[0].startTime : 0;
var maximumTime = contents.length > 0 ? contents[contents.length - 1].endTime : 0;
var isDynamic = mplData.dynamic === true;
var firstStart = null;
var lastEnd = null;
var periods = [];
var _loop_1 = function (iMan) {
var content = contents[iMan];
firstStart =
firstStart !== null ? Math.min(firstStart, content.startTime) : content.startTime;
lastEnd = lastEnd !== null ? Math.max(lastEnd, content.endTime) : content.endTime;
var currentManifest = manifests[iMan];
if (currentManifest.periods.length <= 0) {
return "continue";
}
var contentOffset = content.startTime - currentManifest.periods[0].start;
var contentEnd = content.endTime;
var manifestPeriods = [];
var _loop_2 = function (iPer) {
var _a;
var currentPeriod = currentManifest.periods[iPer];
var adaptations = manifest_1.SUPPORTED_ADAPTATIONS_TYPE.reduce(function (acc, type) {
var currentAdaptations = currentPeriod.adaptations[type];
if ((0, is_null_or_undefined_1.default)(currentAdaptations)) {
return acc;
}
var adaptationsForCurrentType = [];
for (var iAda = 0; iAda < currentAdaptations.length; iAda++) {
var currentAdaptation = currentAdaptations[iAda];
var representations = [];
for (var iRep = 0; iRep < currentAdaptation.representations.length; iRep++) {
var currentRepresentation = currentAdaptation.representations[iRep];
var baseContentMetadata = {
isLive: currentManifest.isLive,
manifestPublishTime: currentManifest.publishTime,
periodStart: currentPeriod.start,
periodEnd: currentPeriod.end,
};
var newIndex = new representation_index_1.default(currentRepresentation.index, [contentOffset, contentEnd], content.transport, baseContentMetadata);
var supplementalCodecs = void 0;
if (currentRepresentation.codecs.length > 1) {
if (currentRepresentation.codecs.length > 2) {
log_1.default.warn("MP: MetaPlaylist relying on more than 2 groups of " +
"codecs with retro-compatibility");
}
supplementalCodecs = currentRepresentation.codecs[0];
}
var codecs = currentRepresentation.codecs[currentRepresentation.codecs.length - 1];
representations.push({
bitrate: currentRepresentation.bitrate,
index: newIndex,
cdnMetadata: currentRepresentation.cdnMetadata,
id: currentRepresentation.id,
height: currentRepresentation.height,
width: currentRepresentation.width,
mimeType: currentRepresentation.mimeType,
frameRate: currentRepresentation.frameRate,
codecs: codecs,
supplementalCodecs: supplementalCodecs,
isSpatialAudio: currentRepresentation.isSpatialAudio,
contentProtections: currentRepresentation.contentProtections,
});
}
adaptationsForCurrentType.push({
id: currentAdaptation.id,
representations: representations,
type: currentAdaptation.type,
audioDescription: currentAdaptation.isAudioDescription,
closedCaption: currentAdaptation.isClosedCaption,
isDub: currentAdaptation.isDub,
language: currentAdaptation.language,
isSignInterpreted: currentAdaptation.isSignInterpreted,
});
acc[type] = adaptationsForCurrentType;
}
return acc;
}, {});
// TODO only first period?
var textTracks = content.textTracks === undefined ? [] : content.textTracks;
var newTextAdaptations = textTracks.map(function (track) {
var adaptationID = "gen-text-ada-" + generateAdaptationID();
var representationID = "gen-text-rep-" + generateRepresentationID();
var indexOfFilename = (0, url_utils_1.getFilenameIndexInUrl)(track.url);
var cdnUrl = track.url.substring(0, indexOfFilename);
var filename = track.url.substring(indexOfFilename);
return {
id: adaptationID,
type: "text",
language: track.language,
closedCaption: track.closedCaption,
manuallyAdded: true,
representations: [
{
bitrate: 0,
cdnMetadata: [{ baseUrl: cdnUrl }],
id: representationID,
mimeType: track.mimeType,
codecs: track.codecs,
index: new classes_1.StaticRepresentationIndex({ media: filename }),
},
],
};
}, []);
if (newTextAdaptations.length > 0) {
if ((0, is_null_or_undefined_1.default)(adaptations.text)) {
adaptations.text = newTextAdaptations;
}
else {
(_a = adaptations.text).push.apply(_a, __spreadArray([], __read(newTextAdaptations), false));
}
}
var newPeriod = {
id: formatId(currentManifest.id) + "_" + formatId(currentPeriod.id),
adaptations: adaptations,
duration: currentPeriod.duration,
start: contentOffset + currentPeriod.start,
thumbnailTracks: currentPeriod.thumbnailTracks,
};
manifestPeriods.push(newPeriod);
};
for (var iPer = 0; iPer < currentManifest.periods.length; iPer++) {
_loop_2(iPer);
}
for (var i = manifestPeriods.length - 1; i >= 0; i--) {
var period = manifestPeriods[i];
if (period.start >= content.endTime) {
manifestPeriods.splice(i, 1);
}
else if (!(0, is_null_or_undefined_1.default)(period.duration)) {
if (period.start + period.duration > content.endTime) {
period.duration = content.endTime - period.start;
}
}
else if (i === manifestPeriods.length - 1) {
period.duration = content.endTime - period.start;
}
}
periods.push.apply(periods, __spreadArray([], __read(manifestPeriods), false));
};
for (var iMan = 0; iMan < contents.length; iMan++) {
_loop_1(iMan);
}
var time = (0, monotonic_timestamp_1.default)();
var isLastPeriodKnown = !isDynamic ||
(mplData.pollInterval === undefined &&
(manifests.length <= 0 || manifests[manifests.length - 1].isLastPeriodKnown));
var manifest = {
availabilityStartTime: 0,
clockOffset: clockOffset,
suggestedPresentationDelay: 10,
periods: periods,
transportType: "metaplaylist",
isLive: isDynamic,
isDynamic: isDynamic,
isLastPeriodKnown: isLastPeriodKnown,
uris: (0, is_null_or_undefined_1.default)(url) ? [] : [url],
// TODO more precize time bounds?
timeBounds: {
minimumSafePosition: minimumTime,
timeshiftDepth: null,
maximumTimeData: {
isLinear: false,
maximumSafePosition: maximumTime,
livePosition: undefined,
time: time,
},
},
lifetime: mplData.pollInterval,
};
return manifest;
}
function formatId(str) {
return str.replace(/_/g, "_");
}