rx-player
Version:
Canal+ HTML5 Video Player
324 lines (301 loc) • 12.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 log from "../../../../log";
import type { IAdaptation } from "../../../../manifest";
import type { IHDRInformation } from "../../../../public_types";
import arrayFind from "../../../../utils/array_find";
import objectAssign from "../../../../utils/object_assign";
import type { IParsedRepresentation } from "../../types";
import type {
IAdaptationSetIntermediateRepresentation,
IRepresentationIntermediateRepresentation,
IScheme,
} from "../node_parser_types";
import type ContentProtectionParser from "./content_protection_parser";
import { convertSupplementalCodecsToRFC6381 } from "./convert_supplemental_codecs";
import { getWEBMHDRInformation } from "./get_hdr_information";
import type { IRepresentationIndexContext } from "./parse_representation_index";
import parseRepresentationIndex from "./parse_representation_index";
import resolveBaseURLs from "./resolve_base_urls";
/**
* Combine inband event streams from representation and
* adaptation data.
* @param {Object} representation
* @param {Object} adaptation
* @returns {undefined | Array.<Object>}
*/
function combineInbandEventStreams(
representation: IRepresentationIntermediateRepresentation,
adaptation: IAdaptationSetIntermediateRepresentation,
): IScheme[] | undefined {
const newSchemeId = [];
if (representation.children.inbandEventStreams !== undefined) {
newSchemeId.push(...representation.children.inbandEventStreams);
}
if (adaptation.children.inbandEventStreams !== undefined) {
newSchemeId.push(...adaptation.children.inbandEventStreams);
}
if (newSchemeId.length === 0) {
return undefined;
}
return newSchemeId;
}
/**
* Extract HDR information from manifest and codecs.
* @param {Object}
* @returns {Object | undefined}
*/
function getHDRInformation({
adaptationProfiles,
essentialProperties,
supplementalProperties,
manifestProfiles,
codecs,
}: {
adaptationProfiles?: string | undefined;
essentialProperties?: IScheme[] | undefined;
supplementalProperties?: IScheme[] | undefined;
manifestProfiles?: string | undefined;
codecs?: string | undefined;
}): undefined | IHDRInformation {
const profiles = (adaptationProfiles ?? "") + (manifestProfiles ?? "");
if (profiles.indexOf("http://dashif.org/guidelines/dash-if-uhd#hevc-hdr-pq10") !== -1) {
if (codecs === "hvc1.2.4.L153.B0" || codecs === "hev1.2.4.L153.B0") {
return { colorDepth: 10, eotf: "pq", colorSpace: "rec2020" };
}
}
const transferCharacteristicScheme = arrayFind(
[...(essentialProperties ?? []), ...(supplementalProperties ?? [])],
(p) => p.schemeIdUri === "urn:mpeg:mpegB:cicp:TransferCharacteristics",
);
if (transferCharacteristicScheme !== undefined) {
// 1: ITU-R BT.709
// 2: Unspecified
// 4: Gamma 2.2 curve
// 5: Gamma 2.8 curve
// 6: SMPTE 170M
// 7: SMPTE 240M
// 8: Linear
// 9: Logarithmic (100:1 range)
// 10: Logarithmic (100 * Sqrt(10) : 1 range)
// 11: IEC 61966-2-4
// 12: ITU-R BT.1361 Extended Colour Gamut
// 13: IEC 61966-2-1 (sRGB or sYCC)
// 14: ITU-R BT.2020 10-bit system
// 15: ITU-R BT.2020 12-bit system
// 16: SMPTE ST 2084, ITU-R BT.2100 PQ
// 17: SMPTE ST 428-1
// 18: ARIB STD-B67 (HLG)
switch (transferCharacteristicScheme.value) {
case "15":
return undefined; // SDR
case "16":
return { eotf: "pq" };
case "18":
return { eotf: "hlg" };
}
}
if (codecs !== undefined && /^vp(08|09|10)/.test(codecs)) {
return getWEBMHDRInformation(codecs);
}
}
/**
* Process intermediate representations to create final parsed representations.
* In the same order.
* @param {Array.<Object>} representationsIR
* @param {Object} context
* @returns {Array.<Object>}
*/
export default function parseRepresentations(
representationsIR: IRepresentationIntermediateRepresentation[],
adaptation: IAdaptationSetIntermediateRepresentation,
context: IRepresentationContext,
): IParsedRepresentation[] {
const parsedRepresentations: IParsedRepresentation[] = [];
for (const representation of representationsIR) {
// Compute Representation ID
let representationID =
representation.attributes.id !== undefined
? representation.attributes.id
: String(representation.attributes.bitrate) +
(representation.attributes.height !== undefined
? `-${representation.attributes.height}`
: "") +
(representation.attributes.width !== undefined
? `-${representation.attributes.width}`
: "") +
(representation.attributes.mimeType !== undefined
? `-${representation.attributes.mimeType}`
: "") +
(representation.attributes.codecs !== undefined
? `-${representation.attributes.codecs}`
: "");
// Avoid duplicate IDs
while (parsedRepresentations.some((r) => r.id === representationID)) {
representationID += "-dup";
}
// Retrieve previous version of the Representation, if one.
const unsafelyBaseOnPreviousRepresentation =
context.unsafelyBaseOnPreviousAdaptation?.getRepresentation(representationID) ??
null;
const inbandEventStreams = combineInbandEventStreams(representation, adaptation);
const availabilityTimeComplete =
representation.attributes.availabilityTimeComplete ??
context.availabilityTimeComplete;
let availabilityTimeOffset: number | undefined;
if (
representation.attributes.availabilityTimeOffset !== undefined ||
context.availabilityTimeOffset !== undefined
) {
availabilityTimeOffset =
(representation.attributes.availabilityTimeOffset ?? 0) +
(context.availabilityTimeOffset ?? 0);
}
const reprIndexCtxt = objectAssign({}, context, {
availabilityTimeOffset,
availabilityTimeComplete,
unsafelyBaseOnPreviousRepresentation,
adaptation,
inbandEventStreams,
});
const representationIndex = parseRepresentationIndex(representation, reprIndexCtxt);
// Find bitrate
let representationBitrate: number;
if (representation.attributes.bitrate === undefined) {
log.warn("dash", "No usable bitrate found in the Representation.");
representationBitrate = 0;
} else {
representationBitrate = representation.attributes.bitrate;
}
const representationBaseURLs = resolveBaseURLs(
context.baseURLs,
representation.children.baseURLs,
);
const cdnMetadata =
representationBaseURLs.length === 0
? // No BaseURL seems to be associated to this Representation, nor to the MPD,
// but underlying segments might have one. To indicate that segments should
// still be available through a CDN without giving any root CDN URL here,
// we just communicate about an empty `baseUrl`, as documented.
[{ baseUrl: "", id: undefined }]
: representationBaseURLs.map((x) => ({
baseUrl: x.url,
id: x.serviceLocation,
}));
// Construct Representation Base
const parsedRepresentation: IParsedRepresentation = {
bitrate: representationBitrate,
cdnMetadata,
index: representationIndex,
id: representationID,
};
if (
representation.children.supplementalProperties !== undefined &&
arrayFind(
representation.children.supplementalProperties,
(r) =>
r.schemeIdUri === "tag:dolby.com,2018:dash:EC3_ExtensionType:2018" &&
r.value === "JOC",
) !== undefined
) {
parsedRepresentation.isSpatialAudio = true;
}
// Add optional attributes
let codecs: string | undefined;
if (representation.attributes.codecs !== undefined) {
codecs = representation.attributes.codecs;
} else if (adaptation.attributes.codecs !== undefined) {
codecs = adaptation.attributes.codecs;
}
if (codecs !== undefined) {
codecs = codecs === "mp4a.40.02" ? "mp4a.40.2" : codecs;
parsedRepresentation.codecs = codecs;
}
let supplementalCodecs: string | undefined;
if (representation.attributes.supplementalCodecs !== undefined) {
supplementalCodecs = representation.attributes.supplementalCodecs;
} else if (adaptation.attributes.supplementalCodecs !== undefined) {
supplementalCodecs = adaptation.attributes.supplementalCodecs;
}
if (supplementalCodecs !== undefined) {
parsedRepresentation.supplementalCodecs =
convertSupplementalCodecsToRFC6381(supplementalCodecs);
}
if (representation.attributes.frameRate !== undefined) {
parsedRepresentation.frameRate = representation.attributes.frameRate;
} else if (adaptation.attributes.frameRate !== undefined) {
parsedRepresentation.frameRate = adaptation.attributes.frameRate;
}
if (representation.attributes.height !== undefined) {
parsedRepresentation.height = representation.attributes.height;
} else if (adaptation.attributes.height !== undefined) {
parsedRepresentation.height = adaptation.attributes.height;
}
if (representation.attributes.mimeType !== undefined) {
parsedRepresentation.mimeType = representation.attributes.mimeType;
} else if (adaptation.attributes.mimeType !== undefined) {
parsedRepresentation.mimeType = adaptation.attributes.mimeType;
}
if (representation.attributes.width !== undefined) {
parsedRepresentation.width = representation.attributes.width;
} else if (adaptation.attributes.width !== undefined) {
parsedRepresentation.width = adaptation.attributes.width;
}
// Content Protection parsing
{
const contentProtIrArr = [
...(adaptation.children.contentProtections ?? []),
...(representation.children.contentProtections ?? []),
];
for (const contentProtIr of contentProtIrArr) {
context.contentProtectionParser.add(parsedRepresentation, contentProtIr);
}
}
parsedRepresentation.hdrInfo = getHDRInformation({
adaptationProfiles: adaptation.attributes.profiles,
supplementalProperties: adaptation.children.supplementalProperties,
essentialProperties: adaptation.children.essentialProperties,
manifestProfiles: context.manifestProfiles,
codecs,
});
parsedRepresentations.push(parsedRepresentation);
}
return parsedRepresentations;
}
/** Supplementary context needed to parse a Representation. */
export interface IRepresentationContext extends IInheritedRepresentationIndexContext {
/** Manifest DASH profiles used for signalling some features */
manifestProfiles?: string | undefined;
/**
* The parser should take this Adaptation - which is from a previously parsed
* Manifest for the same dynamic content - as a base to speed-up the parsing
* process.
* /!\ If unexpected differences exist between both, there is a risk of
* de-synchronization with what is actually on the server,
* Use with moderation.
*/
unsafelyBaseOnPreviousAdaptation: IAdaptation | null;
/** Parses contentProtection elements. */
contentProtectionParser: ContentProtectionParser;
}
/**
* Supplementary context needed to parse a Representation common with
* `IRepresentationIndexContext`.
*/
type IInheritedRepresentationIndexContext = Omit<
IRepresentationIndexContext,
"adaptation" | "unsafelyBaseOnPreviousRepresentation" | "inbandEventStreams"
>;