rx-player
Version:
Canal+ HTML5 Video Player
151 lines (150 loc) • 6.4 kB
JavaScript
import { OtherError } from "../../errors";
import globalScope from "../../utils/global_scope";
import isNullOrUndefined from "../../utils/is_null_or_undefined";
import TaskCanceller from "../../utils/task_canceller";
import checkISOBMFFIntegrity from "../utils/check_isobmff_integrity";
import inferSegmentContainer from "../utils/infer_segment_container";
/**
* Add multiple checks on the response given by the `segmentLoader` in argument.
* If the response appear to be corrupted, the returned Promise will reject with
* an error with an `INTEGRITY_ERROR` code.
* @param {Function} segmentLoader
* @returns {Function}
*/
export function addSegmentIntegrityChecks(segmentLoader) {
return (url, context, loaderOptions, initialCancelSignal, callbacks) => {
return new Promise((resolve, reject) => {
const requestCanceller = new TaskCanceller();
const unlinkCanceller = requestCanceller.linkToSignal(initialCancelSignal);
requestCanceller.signal.register(reject);
segmentLoader(url, context, loaderOptions, requestCanceller.signal, Object.assign(Object.assign({}, callbacks), { onNewChunk(data) {
try {
throwOnIntegrityError(data);
callbacks.onNewChunk(data);
}
catch (err) {
// Do not reject with a `CancellationError` after cancelling the request
cleanUpCancellers();
// Cancel the request
requestCanceller.cancel();
// Reject with thrown error
reject(err);
}
} })).then((info) => {
cleanUpCancellers();
if (requestCanceller.isUsed()) {
return;
}
if (info.resultType === "segment-loaded") {
try {
throwOnIntegrityError(info.resultData.responseData);
}
catch (err) {
reject(err);
return;
}
}
resolve(info);
}, (err) => {
cleanUpCancellers();
reject(err);
});
function cleanUpCancellers() {
requestCanceller.signal.deregister(reject);
unlinkCanceller();
}
});
/**
* If the data's seems to be corrupted, throws an `INTEGRITY_ERROR` error.
* @param {*} data
*/
function throwOnIntegrityError(data) {
if ((!(data instanceof ArrayBuffer) && !(data instanceof Uint8Array)) ||
inferSegmentContainer(context.type, context.mimeType) !== "mp4") {
return;
}
checkISOBMFFIntegrity(new Uint8Array(data), context.segment.isInit);
}
};
}
/**
* Add multiple checks on the response given by the `manifestLoader` in argument.
* If the response appear to be corrupted, the returned Promise will reject with
* an error with an `INTEGRITY_ERROR` code.
* @param {Function} manifestLoader
* @returns {Function}
*/
export function addManifestIntegrityChecks(manifestLoader) {
return async (url, options, initialCancelSignal) => {
const res = await manifestLoader(url, options, initialCancelSignal);
throwOnIntegrityError(res.responseData);
return res;
/**
* If the data's seems to be corrupted, throws an `INTEGRITY_ERROR` error.
* @param {*} data
*/
function throwOnIntegrityError(data) {
if (typeof data === "string") {
let currOffset = data.length - 1;
const expectedStrings = ["</", "MPD", ">"];
for (let i = expectedStrings.length - 1; i >= 0; i--) {
const currentExpectedStr = expectedStrings[i];
while (isCharXmlWhiteSpace(data[currOffset])) {
currOffset--;
}
for (let j = currentExpectedStr.length - 1; j >= 0; j--) {
if (data[currOffset] !== currentExpectedStr[j]) {
throw new Error("INTEGRITY_ERROR MPD does not end with </MPD>");
}
else {
currOffset--;
}
}
}
}
else if (data instanceof ArrayBuffer) {
let currOffset = data.byteLength - 1;
const dv = new DataView(data);
const expectedCharGroups = [[0x3c, 0x2f], [0x4d, 0x50, 0x44], [0x3e]];
for (let i = expectedCharGroups.length - 1; i >= 0; i--) {
const currentExpectedCharGroup = expectedCharGroups[i];
while (isUtf8XmlWhiteSpace(dv.getUint8(currOffset))) {
currOffset--;
}
for (let j = currentExpectedCharGroup.length - 1; j >= 0; j--) {
if (dv.getUint8(currOffset) !== currentExpectedCharGroup[j]) {
throw new Error("INTEGRITY_ERROR MPD does not end with </MPD>");
}
else {
currOffset--;
}
}
}
}
else if (!isNullOrUndefined(globalScope.Document) &&
data instanceof globalScope.Document) {
if (data.documentElement.nodeName !== "MPD") {
throw new OtherError("INTEGRITY_ERROR", "MPD does not end with </MPD>");
}
}
}
};
}
/**
* Returns `true` if the character given can be considered as
* whitespace according to the XML spec.
* @param {string} char
* @returns {boolean}
*/
function isCharXmlWhiteSpace(char) {
return char === " " || char === "\t" || char === "\r" || char === "\n";
}
/**
* Returns `true` if the character given can be considered as an ASCII
* whitespace according to the HTML spec.
* @param {string} char
* @returns {boolean}
*/
function isUtf8XmlWhiteSpace(char) {
return char === 0x20 || char === 0x9 || char === 0xd || char === 0xa;
}