UNPKG

rx-player

Version:
324 lines (301 loc) 12.1 kB
/** * 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" >;