rx-player
Version:
Canal+ HTML5 Video Player
137 lines (127 loc) • 5.1 kB
text/typescript
/**
* 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.
*/
import config from "../../../config";
import type { IMetaPlaylist } from "../../../parsers/manifest/metaplaylist";
import isNonEmptyString from "../../../utils/is_non_empty_string";
import request from "../../../utils/request/xhr";
import TaskCanceller from "../../../utils/task_canceller";
const iso8601Duration =
/^P(([\d.]*)Y)?(([\d.]*)M)?(([\d.]*)D)?T?(([\d.]*)H)?(([\d.]*)M)?(([\d.]*)S)?/;
/**
* 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
* @returns {number | null}
*/
function parseDuration(val: string): number | null {
if (!isNonEmptyString(val)) {
return null;
}
const match = iso8601Duration.exec(val) as RegExpExecArray;
if (match === null) {
return null;
}
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;
}
/**
* Load manifest and get duration from it.
* @param {String} url
* @param {String} transport
* @returns {Promise.<number>}
*/
async function getDurationFromManifest(
url: string,
transport: "dash" | "smooth" | "metaplaylist",
): Promise<number> {
if (transport !== "dash" && transport !== "smooth" && transport !== "metaplaylist") {
throw new Error("createMetaplaylist: Unknown transport type.");
}
if (transport === "dash" || transport === "smooth") {
const response = await request({
url,
responseType: "document",
timeout: config.getCurrent().DEFAULT_REQUEST_TIMEOUT,
connectionTimeout: config.getCurrent().DEFAULT_CONNECTION_TIMEOUT,
// We won't cancel
cancelSignal: new TaskCanceller().signal,
});
const { responseData } = response;
const root = responseData.documentElement;
if (transport === "dash") {
const dashDurationAttribute = root.getAttribute("mediaPresentationDuration");
if (dashDurationAttribute === null) {
throw new Error("createMetaplaylist: No duration on DASH content.");
}
const periodElements = root.getElementsByTagName("Period");
const firstDASHStartAttribute = periodElements[0]?.getAttribute("start");
const firstDASHStart =
firstDASHStartAttribute !== null ? parseDuration(firstDASHStartAttribute) : 0;
const dashDuration = parseDuration(dashDurationAttribute);
if (firstDASHStart === null || dashDuration === null) {
throw new Error(
"createMetaplaylist: Cannot parse " + "the duration from a DASH content.",
);
}
return dashDuration - firstDASHStart;
}
// smooth
const smoothDurationAttribute = root.getAttribute("Duration");
const smoothTimeScaleAttribute = root.getAttribute("TimeScale");
if (smoothDurationAttribute === null) {
throw new Error("createMetaplaylist: No duration on smooth content.");
}
const timescale =
smoothTimeScaleAttribute !== null
? parseInt(smoothTimeScaleAttribute, 10)
: 10000000;
return parseInt(smoothDurationAttribute, 10) / timescale;
}
// metaplaylist
const response = await request({
url,
responseType: "text",
timeout: config.getCurrent().DEFAULT_REQUEST_TIMEOUT,
connectionTimeout: config.getCurrent().DEFAULT_CONNECTION_TIMEOUT,
// We won't cancel
cancelSignal: new TaskCanceller().signal,
});
const { responseData } = response;
const metaplaylist = JSON.parse(responseData) as IMetaPlaylist;
if (
metaplaylist.contents === undefined ||
metaplaylist.contents.length === undefined ||
metaplaylist.contents.length === 0
) {
throw new Error("createMetaplaylist: No duration on Metaplaylist content.");
}
const { contents } = metaplaylist;
const lastEnd = contents[contents.length - 1].endTime;
const firstStart = contents[0].startTime;
return lastEnd - firstStart;
}
export default getDurationFromManifest;