/** * 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 isNullOrUndefined from "../../../../../utils/is_null_or_undefined"; import type { ITNode } from "../../../../../utils/xml-parser"; import type { IAdaptationSetAttributes, IAdaptationSetChildren, IAdaptationSetIntermediateRepresentation, } from "../../node_parser_types"; import parseBaseURL from "./BaseURL"; import parseContentComponent from "./ContentComponent"; import parseContentProtection from "./ContentProtection"; import { createRepresentationIntermediateRepresentation } from "./Representation"; import parseSegmentBase from "./SegmentBase"; import parseSegmentList from "./SegmentList"; import parseSegmentTemplate from "./SegmentTemplate"; import { parseBoolean, parseIntOrBoolean, parseMaybeDividedNumber, parseMPDFloat, parseMPDInteger, parseScheme, textContent, ValueParser, } from "./utils"; /** * Parse child nodes from an AdaptationSet. * @param {Array.} adaptationSetChildren - The AdaptationSet child nodes. * @returns {Array.} */ function parseAdaptationSetChildren( adaptationSetChildren: Array, ): [IAdaptationSetChildren, Error[]] { const children: IAdaptationSetChildren = { baseURLs: [], representations: [], }; const contentProtections = []; let warnings: Error[] = []; for (let i = 0; i < adaptationSetChildren.length; i++) { const currentNode = adaptationSetChildren[i]; if (typeof currentNode === "string") { continue; } switch (currentNode.tagName) { case "Accessibility": if (children.accessibilities === undefined) { children.accessibilities = [parseScheme(currentNode)]; } else { children.accessibilities.push(parseScheme(currentNode)); } break; case "BaseURL": { const [baseURLObj, baseURLWarnings] = parseBaseURL(currentNode); if (baseURLObj !== undefined) { children.baseURLs.push(baseURLObj); } if (baseURLWarnings.length > 0) { warnings = warnings.concat(baseURLWarnings); } break; } case "ContentComponent": children.contentComponent = parseContentComponent(currentNode); break; case "EssentialProperty": if (isNullOrUndefined(children.essentialProperties)) { children.essentialProperties = [parseScheme(currentNode)]; } else { children.essentialProperties.push(parseScheme(currentNode)); } break; case "InbandEventStream": if (children.inbandEventStreams === undefined) { children.inbandEventStreams = []; } children.inbandEventStreams.push(parseScheme(currentNode)); break; case "Label": { const label = textContent(currentNode.children); if (label !== null && label !== undefined) { children.label = label; } break; } case "Representation": { const [representation, representationWarnings] = createRepresentationIntermediateRepresentation(currentNode); children.representations.push(representation); if (representationWarnings.length > 0) { warnings = warnings.concat(representationWarnings); } break; } case "Role": if (isNullOrUndefined(children.roles)) { children.roles = [parseScheme(currentNode)]; } else { children.roles.push(parseScheme(currentNode)); } break; case "SupplementalProperty": if (isNullOrUndefined(children.supplementalProperties)) { children.supplementalProperties = [parseScheme(currentNode)]; } else { children.supplementalProperties.push(parseScheme(currentNode)); } break; case "SegmentBase": { const [segmentBase, segmentBaseWarnings] = parseSegmentBase(currentNode); children.segmentBase = segmentBase; if (segmentBaseWarnings.length > 0) { warnings = warnings.concat(segmentBaseWarnings); } break; } case "SegmentList": { const [segmentList, segmentListWarnings] = parseSegmentList(currentNode); children.segmentList = segmentList; if (segmentListWarnings.length > 0) { warnings = warnings.concat(segmentListWarnings); } break; } case "SegmentTemplate": { const [segmentTemplate, segmentTemplateWarnings] = parseSegmentTemplate(currentNode); children.segmentTemplate = segmentTemplate; if (segmentTemplateWarnings.length > 0) { warnings = warnings.concat(segmentTemplateWarnings); } break; } case "ContentProtection": { const [contentProtection, contentProtectionWarnings] = parseContentProtection(currentNode); if (contentProtectionWarnings.length > 0) { warnings = warnings.concat(contentProtectionWarnings); } if (contentProtection !== undefined) { contentProtections.push(contentProtection); } break; } // case "Rating": // children.rating = currentNode; // break; // case "Viewpoint": // children.viewpoint = currentNode; // break; } } if (contentProtections.length > 0) { children.contentProtections = contentProtections; } return [children, warnings]; } /** * Parse every attributes from an AdaptationSet root element into a simple JS * object. * @param {Object} root - The AdaptationSet root element. * @returns {Array.} */ function parseAdaptationSetAttributes(root: ITNode): [IAdaptationSetAttributes, Error[]] { const parsedAdaptation: IAdaptationSetAttributes = {}; const warnings: Error[] = []; const parseValue = ValueParser(parsedAdaptation, warnings); for (const attributeName of Object.keys(root.attributes)) { const attributeVal = root.attributes[attributeName]; if (isNullOrUndefined(attributeVal)) { continue; } switch (attributeName) { case "id": parsedAdaptation.id = attributeVal; break; case "group": parseValue(attributeVal, { asKey: "group", parser: parseMPDInteger, dashName: "group", }); break; case "lang": parsedAdaptation.language = attributeVal; break; case "contentType": parsedAdaptation.contentType = attributeVal; break; case "par": parsedAdaptation.par = attributeVal; break; case "minBandwidth": parseValue(attributeVal, { asKey: "minBitrate", parser: parseMPDInteger, dashName: "minBandwidth", }); break; case "maxBandwidth": parseValue(attributeVal, { asKey: "maxBitrate", parser: parseMPDInteger, dashName: "maxBandwidth", }); break; case "minWidth": parseValue(attributeVal, { asKey: "minWidth", parser: parseMPDInteger, dashName: "minWidth", }); break; case "maxWidth": parseValue(attributeVal, { asKey: "maxWidth", parser: parseMPDInteger, dashName: "maxWidth", }); break; case "minHeight": parseValue(attributeVal, { asKey: "minHeight", parser: parseMPDInteger, dashName: "minHeight", }); break; case "maxHeight": parseValue(attributeVal, { asKey: "maxHeight", parser: parseMPDInteger, dashName: "maxHeight", }); break; case "minFrameRate": parseValue(attributeVal, { asKey: "minFrameRate", parser: parseMaybeDividedNumber, dashName: "minFrameRate", }); break; case "maxFrameRate": parseValue(attributeVal, { asKey: "maxFrameRate", parser: parseMaybeDividedNumber, dashName: "maxFrameRate", }); break; case "selectionPriority": parseValue(attributeVal, { asKey: "selectionPriority", parser: parseMPDInteger, dashName: "selectionPriority", }); break; case "segmentAlignment": parseValue(attributeVal, { asKey: "segmentAlignment", parser: parseIntOrBoolean, dashName: "segmentAlignment", }); break; case "subsegmentAlignment": parseValue(attributeVal, { asKey: "subsegmentAlignment", parser: parseIntOrBoolean, dashName: "subsegmentAlignment", }); break; case "bitstreamSwitching": parseValue(attributeVal, { asKey: "bitstreamSwitching", parser: parseBoolean, dashName: "bitstreamSwitching", }); break; case "audioSamplingRate": parsedAdaptation.audioSamplingRate = attributeVal; break; case "codecs": parsedAdaptation.codecs = attributeVal; break; case "scte214:supplementalCodecs": parsedAdaptation.supplementalCodecs = attributeVal; break; case "codingDependency": parseValue(attributeVal, { asKey: "codingDependency", parser: parseBoolean, dashName: "codingDependency", }); break; case "frameRate": parseValue(attributeVal, { asKey: "frameRate", parser: parseMaybeDividedNumber, dashName: "frameRate", }); break; case "height": parseValue(attributeVal, { asKey: "height", parser: parseMPDInteger, dashName: "height", }); break; case "maxPlayoutRate": parseValue(attributeVal, { asKey: "maxPlayoutRate", parser: parseMPDFloat, dashName: "maxPlayoutRate", }); break; case "maximumSAPPeriod": parseValue(attributeVal, { asKey: "maximumSAPPeriod", parser: parseMPDFloat, dashName: "maximumSAPPeriod", }); break; case "mimeType": parsedAdaptation.mimeType = attributeVal; break; case "profiles": parsedAdaptation.profiles = attributeVal; break; case "segmentProfiles": parsedAdaptation.segmentProfiles = attributeVal; break; case "width": parseValue(attributeVal, { asKey: "width", parser: parseMPDInteger, dashName: "width", }); break; case "availabilityTimeOffset": parseValue(attributeVal, { asKey: "availabilityTimeOffset", parser: parseMPDFloat, dashName: "availabilityTimeOffset", }); break; case "availabilityTimeComplete": parseValue(attributeVal, { asKey: "availabilityTimeComplete", parser: parseBoolean, dashName: "availabilityTimeComplete", }); break; } } return [parsedAdaptation, warnings]; } /** * Parse an AdaptationSet element into an AdaptationSet intermediate * representation. * @param {Object} adaptationSetElement - The AdaptationSet root element. * @returns {Array.} */ export function createAdaptationSetIntermediateRepresentation( adaptationSetElement: ITNode, ): [IAdaptationSetIntermediateRepresentation, Error[]] { const childNodes = adaptationSetElement.children; const [children, childrenWarnings] = parseAdaptationSetChildren(childNodes); const [attributes, attrsWarnings] = parseAdaptationSetAttributes(adaptationSetElement); const warnings = childrenWarnings.concat(attrsWarnings); return [{ children, attributes }, warnings]; }