rx-player
Version:
Canal+ HTML5 Video Player
297 lines (296 loc) • 11 kB
JavaScript
/**
* 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.
*/
// XML-Schema
// <http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd>
import log from "../../../../../log";
import { base64ToBytes } from "../../../../../utils/base64";
import isNonEmptyString from "../../../../../utils/is_non_empty_string";
const iso8601Duration = /^P(([\d.]*)Y)?(([\d.]*)M)?(([\d.]*)D)?T?(([\d.]*)H)?(([\d.]*)M)?(([\d.]*)S)?/;
const rangeRe = /([0-9]+)-([0-9]+)/;
/**
* Parse MPD boolean attributes.
*
* The returned value is a tuple of two elements where:
* 1. the first value is the parsed boolean - or `null` if we could not parse
* it
* 2. the second value is a possible error encountered while parsing this
* value - set to `null` if no error was encountered.
* @param {string} val - The value to parse
* @param {string} displayName - The name of the property. Used for error
* formatting.
* @returns {Array.<Boolean | Error | null>}
*/
function parseBoolean(val, displayName) {
if (val === "true") {
return [true, null];
}
if (val === "false") {
return [false, null];
}
const error = new MPDError(`\`${displayName}\` property is not a boolean value but "${val}"`);
return [false, error];
}
/**
* Parse MPD integer attributes.
*
* The returned value is a tuple of two elements where:
* 1. the first value is the parsed boolean - or `null` if we could not parse
* it
* 2. the second value is a possible error encountered while parsing this
* value - set to `null` if no error was encountered.
* @param {string} val - The value to parse
* @param {string} displayName - The name of the property. Used for error
* formatting.
* @returns {Array.<number | Error | null>}
*/
function parseMPDInteger(val, displayName) {
const toInt = parseInt(val, 10);
if (isNaN(toInt)) {
const error = new MPDError(`\`${displayName}\` property is not an integer value but "${val}"`);
return [null, error];
}
return [toInt, null];
}
/**
* Parse MPD float attributes.
*
* The returned value is a tuple of two elements where:
* 1. the first value is the parsed boolean - or `null` if we could not parse
* it
* 2. the second value is a possible error encountered while parsing this
* value - set to `null` if no error was encountered.
* @param {string} val - The value to parse
* @param {string} displayName - The name of the property. Used for error
* formatting.
* @returns {Array.<number | Error | null>}
*/
function parseMPDFloat(val, displayName) {
if (val === "INF") {
return [Infinity, null];
}
const toInt = parseFloat(val);
if (isNaN(toInt)) {
const error = new MPDError(`\`${displayName}\` property is invalid: "${val}"`);
return [null, error];
}
return [toInt, null];
}
/**
* Parse MPD attributes which are either integer or boolean values.
*
* The returned value is a tuple of two elements where:
* 1. the first value is the parsed value - or `null` if we could not parse
* it
* 2. the second value is a possible error encountered while parsing this
* value - set to `null` if no error was encountered.
* @param {string} val - The value to parse
* @param {string} displayName - The name of the property. Used for error
* formatting.
* @returns {Array.<Boolean | number | Error | null>}
*/
function parseIntOrBoolean(val, displayName) {
if (val === "true") {
return [true, null];
}
if (val === "false") {
return [false, null];
}
const toInt = parseInt(val, 10);
if (isNaN(toInt)) {
const error = new MPDError(`\`${displayName}\` property is not a boolean nor an integer but "${val}"`);
return [null, error];
}
return [toInt, null];
}
/**
* Parse MPD date attributes.
*
* The returned value is a tuple of two elements where:
* 1. the first value is the parsed value - or `null` if we could not parse
* it
* 2. the second value is a possible error encountered while parsing this
* value - set to `null` if no error was encountered.
* @param {string} val - The value to parse
* @param {string} displayName - The name of the property. Used for error
* formatting.
* @returns {Array.<Date | null | Error>}
*/
function parseDateTime(val, displayName) {
const parsed = Date.parse(val);
if (isNaN(parsed)) {
const error = new MPDError(`\`${displayName}\` is in an invalid date format: "${val}"`);
return [null, error];
}
return [new Date(Date.parse(val)).getTime() / 1000, null];
}
/**
* Parse MPD ISO8601 duration attributes into seconds.
*
* The returned value is a tuple of two elements where:
* 1. the first value is the parsed value - or `null` if we could not parse
* it
* 2. the second value is a possible error encountered while parsing this
* value - set to `null` if no error was encountered.
* @param {string} val - The value to parse
* @param {string} displayName - The name of the property. Used for error
* formatting.
* @returns {Array.<number | Error | null>}
*/
function parseDuration(val, displayName) {
if (!isNonEmptyString(val)) {
const error = new MPDError(`\`${displayName}\` property is empty`);
return [0, error];
}
const match = iso8601Duration.exec(val);
if (match === null) {
const error = new MPDError(`\`${displayName}\` property has an unrecognized format "${val}"`);
return [null, error];
}
const duration = parseFloat(isNonEmptyString(match[2]) ? match[2] : "0") * 365 * 24 * 60 * 60 +
parseFloat(isNonEmptyString(match[4]) ? match[4] : "0") * 30 * 24 * 60 * 60 +
parseFloat(isNonEmptyString(match[6]) ? match[6] : "0") * 24 * 60 * 60 +
parseFloat(isNonEmptyString(match[8]) ? match[8] : "0") * 60 * 60 +
parseFloat(isNonEmptyString(match[10]) ? match[10] : "0") * 60 +
parseFloat(isNonEmptyString(match[12]) ? match[12] : "0");
return [duration, null];
}
/**
* Parse MPD byterange attributes into arrays of two elements: the start and
* the end.
*
* The returned value is a tuple of two elements where:
* 1. the first value is the parsed value - or `null` if we could not parse
* it
* 2. the second value is a possible error encountered while parsing this
* value - set to `null` if no error was encountered.
* @param {string} val
* @param {string} displayName
* @returns {Array.<Array.<number> | Error | null>}
*/
function parseByteRange(val, displayName) {
const match = rangeRe.exec(val);
if (match === null) {
const error = new MPDError(`\`${displayName}\` property has an unrecognized format "${val}"`);
return [null, error];
}
else {
return [[+match[1], +match[2]], null];
}
}
/**
* Parse MPD base64 attribute into an Uint8Array.
* the end.
*
* The returned value is a tuple of two elements where:
* 1. the first value is the parsed value - or `null` if we could not parse
* it
* 2. the second value is a possible error encountered while parsing this
* value - set to `null` if no error was encountered.
* @param {string} val
* @param {string} displayName
* @returns {Uint8Array | Error | null>}
*/
function parseBase64(val, displayName) {
try {
return [base64ToBytes(val), null];
}
catch (_) {
const error = new MPDError(`\`${displayName}\` is not a valid base64 string: "${val}"`);
return [null, error];
}
}
/**
* Some values in the MPD can be expressed as divisions of integers (e.g. frame
* rates).
* This function tries to convert it to a floating point value.
* @param {string} val
* @param {string} displayName
* @returns {Array.<number | Error | null>}
*/
function parseMaybeDividedNumber(val, displayName) {
const matches = /^(\d+)\/(\d+)$/.exec(val);
if (matches !== null) {
// No need to check, we know both are numbers
return [+matches[1] / +matches[2], null];
}
return parseMPDFloat(val, displayName);
}
/**
* @param {Element} root
* @returns {Object}
*/
function parseScheme(root) {
let schemeIdUri;
let value;
for (let i = 0; i < root.attributes.length; i++) {
const attribute = root.attributes[i];
switch (attribute.name) {
case "schemeIdUri":
schemeIdUri = attribute.value;
break;
case "value":
value = attribute.value;
break;
}
}
return { schemeIdUri, value };
}
/**
* Create a function to factorize the MPD parsing logic.
* @param {Object} dest - The destination object which will contain the parsed
* values.
* @param {Array.<Error>} warnings - An array which will contain every parsing
* error encountered.
* @return {Function}
*/
function ValueParser(dest, warnings) {
/**
* Parse a single value and add it to the `dest` objects.
* If an error arised while parsing, add it at the end of the `warnings` array.
* @param {string} objKey - The key which will be added to the `dest` object.
* @param {string} val - The value found in the MPD which we should parse.
* @param {Function} parsingFn - The parsing function adapted for this value.
* @param {string} displayName - The name of the key as it appears in the MPD.
* This is used only in error formatting,
*/
return function (val, { asKey, parser, dashName, }) {
const [parsingResult, parsingError] = parser(val, dashName);
if (parsingError !== null) {
log.warn(parsingError.message);
warnings.push(parsingError);
}
if (parsingResult !== null) {
dest[asKey] = parsingResult;
}
};
}
/**
* Error arising when parsing the MPD.
* @class MPDError
* @extends Error
*/
class MPDError extends Error {
/**
* @param {string} message
*/
constructor(message) {
super(message);
// @see https://stackoverflow.com/questions/41102060/typescript-extending-error-class
Object.setPrototypeOf(this, MPDError.prototype);
this.name = "MPDError";
}
}
export { MPDError, ValueParser, parseBase64, parseBoolean, parseByteRange, parseDateTime, parseDuration, parseIntOrBoolean, parseMaybeDividedNumber, parseMPDFloat, parseMPDInteger, parseScheme, };