UNPKG

rx-player

Version:
509 lines (508 loc) 22.9 kB
"use strict"; /** * 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. */ var __values = (this && this.__values) || function(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); }; var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = parseAdaptationSets; var log_1 = require("../../../../log"); var manifest_1 = require("../../../../manifest"); var array_find_1 = require("../../../../utils/array_find"); var array_find_index_1 = require("../../../../utils/array_find_index"); var array_includes_1 = require("../../../../utils/array_includes"); var is_non_empty_string_1 = require("../../../../utils/is_non_empty_string"); var is_null_or_undefined_1 = require("../../../../utils/is_null_or_undefined"); var attach_trickmode_track_1 = require("./attach_trickmode_track"); var infer_adaptation_type_1 = require("./infer_adaptation_type"); var parse_representations_1 = require("./parse_representations"); var resolve_base_urls_1 = require("./resolve_base_urls"); /** * Detect if the accessibility given defines an adaptation for the visually * impaired. * Based on DVB Document A168 (DVB-DASH) and DASH-IF 4.3. * @param {Object} accessibility * @returns {Boolean} */ function isVisuallyImpaired(accessibility) { if (accessibility === undefined) { return false; } var isVisuallyImpairedAudioDvbDash = accessibility.schemeIdUri === "urn:tva:metadata:cs:AudioPurposeCS:2007" && accessibility.value === "1"; var isVisuallyImpairedDashIf = accessibility.schemeIdUri === "urn:mpeg:dash:role:2011" && accessibility.value === "description"; return isVisuallyImpairedAudioDvbDash || isVisuallyImpairedDashIf; } /** * Detect if the accessibility given defines an adaptation for the hard of * hearing. * Based on DVB Document A168 (DVB-DASH) and DASH specification. * @param {Array.<Object>} accessibilities * @param {Array.<Object>} roles * @returns {Boolean} */ function isCaptionning(accessibilities, roles) { if (accessibilities !== undefined) { var hasDvbClosedCaptionSignaling = accessibilities.some(function (accessibility) { return accessibility.schemeIdUri === "urn:tva:metadata:cs:AudioPurposeCS:2007" && accessibility.value === "2"; }); if (hasDvbClosedCaptionSignaling) { return true; } } if (roles !== undefined) { var hasDashCaptionSinaling = roles.some(function (role) { return role.schemeIdUri === "urn:mpeg:dash:role:2011" && role.value === "caption"; }); if (hasDashCaptionSinaling) { return true; } } return false; } /** * Detect if the accessibility given defines an AdaptationSet containing a sign * language interpretation. * Based on DASH-IF 4.3. * @param {Object} accessibility * @returns {Boolean} */ function hasSignLanguageInterpretation(accessibility) { if (accessibility === undefined) { return false; } return (accessibility.schemeIdUri === "urn:mpeg:dash:role:2011" && accessibility.value === "sign"); } /** * Contruct Adaptation ID from the information we have. * @param {Object} adaptation * @param {Object} infos * @returns {string} */ function getAdaptationID(adaptation, infos) { if ((0, is_non_empty_string_1.default)(adaptation.attributes.id)) { return adaptation.attributes.id; } var isClosedCaption = infos.isClosedCaption, isForcedSubtitle = infos.isForcedSubtitle, isAudioDescription = infos.isAudioDescription, isSignInterpreted = infos.isSignInterpreted, isTrickModeTrack = infos.isTrickModeTrack, type = infos.type; var idString = type; if ((0, is_non_empty_string_1.default)(adaptation.attributes.language)) { idString += "-".concat(adaptation.attributes.language); } if (isClosedCaption === true) { idString += "-cc"; } if (isForcedSubtitle === true) { idString += "-cc"; } if (isAudioDescription === true) { idString += "-ad"; } if (isSignInterpreted === true) { idString += "-si"; } if (isTrickModeTrack) { idString += "-trickMode"; } if ((0, is_non_empty_string_1.default)(adaptation.attributes.contentType)) { idString += "-".concat(adaptation.attributes.contentType); } if ((0, is_non_empty_string_1.default)(adaptation.attributes.codecs)) { idString += "-".concat(adaptation.attributes.codecs); } if ((0, is_non_empty_string_1.default)(adaptation.attributes.mimeType)) { idString += "-".concat(adaptation.attributes.mimeType); } if (adaptation.attributes.frameRate !== undefined) { idString += "-".concat(String(adaptation.attributes.frameRate)); } return idString; } /** * Returns a list of ID this adaptation can be seamlessly switched to * @param {Object} adaptation * @returns {Array.<string>} */ function getAdaptationSetSwitchingIDs(adaptation) { var e_1, _a; if (!(0, is_null_or_undefined_1.default)(adaptation.children.supplementalProperties)) { var supplementalProperties = adaptation.children.supplementalProperties; try { for (var supplementalProperties_1 = __values(supplementalProperties), supplementalProperties_1_1 = supplementalProperties_1.next(); !supplementalProperties_1_1.done; supplementalProperties_1_1 = supplementalProperties_1.next()) { var supplementalProperty = supplementalProperties_1_1.value; if (supplementalProperty.schemeIdUri === "urn:mpeg:dash:adaptation-set-switching:2016" && !(0, is_null_or_undefined_1.default)(supplementalProperty.value)) { return supplementalProperty.value .split(",") .map(function (id) { return id.trim(); }) .filter(function (id) { return (0, is_non_empty_string_1.default)(id); }); } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (supplementalProperties_1_1 && !supplementalProperties_1_1.done && (_a = supplementalProperties_1.return)) _a.call(supplementalProperties_1); } finally { if (e_1) throw e_1.error; } } } return []; } /** * Process AdaptationSets intermediate representations to return under its final * form. * Note that the AdaptationSets returned are sorted by priority (from the most * priority to the least one). * @param {Array.<Object>} adaptationsIR * @param {Object} context * @returns {Array.<Object>} */ function parseAdaptationSets(adaptationsIR, context) { var e_2, _a; var _b, _c, _d, _e, _f, _g, _h; var parsedAdaptations = { video: [], audio: [], text: [] }; var parsedThumbnailTracks = []; var trickModeAdaptations = []; var adaptationSwitchingInfos = {}; var parsedAdaptationsIDs = []; for (var adaptationIdx = 0; adaptationIdx < adaptationsIR.length; adaptationIdx++) { var adaptation = adaptationsIR[adaptationIdx]; var adaptationChildren = adaptation.children; var essentialProperties = adaptationChildren.essentialProperties, roles = adaptationChildren.roles, label = adaptationChildren.label; var isMainAdaptation = Array.isArray(roles) && roles.some(function (role) { return role.value === "main"; }) && roles.some(function (role) { return role.schemeIdUri === "urn:mpeg:dash:role:2011"; }); var representationsIR = adaptation.children.representations; var availabilityTimeComplete = (_b = adaptation.attributes.availabilityTimeComplete) !== null && _b !== void 0 ? _b : context.availabilityTimeComplete; var availabilityTimeOffset = void 0; if (adaptation.attributes.availabilityTimeOffset !== undefined || context.availabilityTimeOffset !== undefined) { availabilityTimeOffset = ((_c = adaptation.attributes.availabilityTimeOffset) !== null && _c !== void 0 ? _c : 0) + ((_d = context.availabilityTimeOffset) !== null && _d !== void 0 ? _d : 0); } var type = (0, infer_adaptation_type_1.default)(adaptation, representationsIR); if (type === undefined) { continue; } var priority = (_e = adaptation.attributes.selectionPriority) !== null && _e !== void 0 ? _e : 1; var originalID = adaptation.attributes.id; var adaptationSetSwitchingIDs = getAdaptationSetSwitchingIDs(adaptation); var parentSegmentTemplates = []; if (context.segmentTemplate !== undefined) { parentSegmentTemplates.push(context.segmentTemplate); } if (adaptation.children.segmentTemplate !== undefined) { parentSegmentTemplates.push(adaptation.children.segmentTemplate); } var reprCtxt = { availabilityTimeComplete: availabilityTimeComplete, availabilityTimeOffset: availabilityTimeOffset, baseURLs: (0, resolve_base_urls_1.default)(context.baseURLs, adaptationChildren.baseURLs), contentProtectionParser: context.contentProtectionParser, manifestBoundsCalculator: context.manifestBoundsCalculator, end: context.end, isDynamic: context.isDynamic, isLastPeriod: context.isLastPeriod, manifestProfiles: context.manifestProfiles, parentSegmentTemplates: parentSegmentTemplates, receivedTime: context.receivedTime, start: context.start, unsafelyBaseOnPreviousAdaptation: null, }; var trickModeProperty = Array.isArray(essentialProperties) ? (0, array_find_1.default)(essentialProperties, function (scheme) { return scheme.schemeIdUri === "http://dashif.org/guidelines/trickmode"; }) : undefined; var trickModeAttachedAdaptationIds = (_f = trickModeProperty === null || trickModeProperty === void 0 ? void 0 : trickModeProperty.value) === null || _f === void 0 ? void 0 : _f.split(" "); var isTrickModeTrack = trickModeAttachedAdaptationIds !== undefined; var accessibilities = adaptationChildren.accessibilities; var isDub = void 0; if (roles !== undefined && roles.some(function (role) { return role.value === "dub"; })) { isDub = true; } var isClosedCaption = void 0; if (type !== "text") { isClosedCaption = false; } else { isClosedCaption = isCaptionning(accessibilities, roles); } var isForcedSubtitle = void 0; if (type === "text" && roles !== undefined && roles.some(function (role) { return role.value === "forced-subtitle" || role.value === "forced_subtitle"; })) { isForcedSubtitle = true; } var isAudioDescription = void 0; if (type !== "audio") { isAudioDescription = false; } else if (accessibilities !== undefined) { isAudioDescription = accessibilities.some(isVisuallyImpaired); } var isSignInterpreted = void 0; if (type !== "video") { isSignInterpreted = false; } else if (accessibilities !== undefined) { isSignInterpreted = accessibilities.some(hasSignLanguageInterpretation); } var adaptationID = getAdaptationID(adaptation, { isAudioDescription: isAudioDescription, isForcedSubtitle: isForcedSubtitle, isClosedCaption: isClosedCaption, isSignInterpreted: isSignInterpreted, isTrickModeTrack: isTrickModeTrack, type: type, }); // Avoid duplicate IDs while ((0, array_includes_1.default)(parsedAdaptationsIDs, adaptationID)) { adaptationID += "-dup"; } var newID = adaptationID; parsedAdaptationsIDs.push(adaptationID); reprCtxt.unsafelyBaseOnPreviousAdaptation = (_h = (_g = context.unsafelyBaseOnPreviousPeriod) === null || _g === void 0 ? void 0 : _g.getAdaptation(adaptationID)) !== null && _h !== void 0 ? _h : null; var representations = (0, parse_representations_1.default)(representationsIR, adaptation, reprCtxt); if (type === "thumbnails") { var track = createThumbnailTracks(adaptation, representations); if (track !== null) { parsedThumbnailTracks.push.apply(parsedThumbnailTracks, __spreadArray([], __read(track), false)); } continue; } var parsedAdaptationSet = { id: adaptationID, representations: representations, type: type, isTrickModeTrack: isTrickModeTrack, }; if (!(0, is_null_or_undefined_1.default)(adaptation.attributes.language)) { parsedAdaptationSet.language = adaptation.attributes.language; } if (!(0, is_null_or_undefined_1.default)(isClosedCaption)) { parsedAdaptationSet.closedCaption = isClosedCaption; } if (!(0, is_null_or_undefined_1.default)(isAudioDescription)) { parsedAdaptationSet.audioDescription = isAudioDescription; } if (isDub === true) { parsedAdaptationSet.isDub = true; } if (isForcedSubtitle !== undefined) { parsedAdaptationSet.forcedSubtitles = isForcedSubtitle; } if (isSignInterpreted === true) { parsedAdaptationSet.isSignInterpreted = true; } if (label !== undefined) { parsedAdaptationSet.label = label; } if (trickModeAttachedAdaptationIds !== undefined) { trickModeAdaptations.push({ adaptation: parsedAdaptationSet, trickModeAttachedAdaptationIds: trickModeAttachedAdaptationIds, }); } else { // look if we have to merge this into another Adaptation var mergedIntoIdx = -1; var _loop_1 = function (id) { var _j; var switchingInfos = adaptationSwitchingInfos[id]; if (switchingInfos !== undefined && switchingInfos.newID !== newID && (0, array_includes_1.default)(switchingInfos.adaptationSetSwitchingIDs, originalID)) { mergedIntoIdx = (0, array_find_index_1.default)(parsedAdaptations[type], function (a) { return a[0].id === id; }); var mergedInto = parsedAdaptations[type][mergedIntoIdx]; if (mergedInto !== undefined && mergedInto[0].audioDescription === parsedAdaptationSet.audioDescription && mergedInto[0].closedCaption === parsedAdaptationSet.closedCaption && mergedInto[0].language === parsedAdaptationSet.language) { log_1.default.info('DASH Parser: merging "switchable" AdaptationSets', originalID, id); (_j = mergedInto[0].representations).push.apply(_j, __spreadArray([], __read(parsedAdaptationSet.representations), false)); mergedInto[1] = { priority: Math.max(priority, mergedInto[1].priority), isMainAdaptation: isMainAdaptation || mergedInto[1].isMainAdaptation, indexInMpd: Math.min(adaptationIdx, mergedInto[1].indexInMpd), }; return "break"; } } }; try { for (var adaptationSetSwitchingIDs_1 = (e_2 = void 0, __values(adaptationSetSwitchingIDs)), adaptationSetSwitchingIDs_1_1 = adaptationSetSwitchingIDs_1.next(); !adaptationSetSwitchingIDs_1_1.done; adaptationSetSwitchingIDs_1_1 = adaptationSetSwitchingIDs_1.next()) { var id = adaptationSetSwitchingIDs_1_1.value; var state_1 = _loop_1(id); if (state_1 === "break") break; } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (adaptationSetSwitchingIDs_1_1 && !adaptationSetSwitchingIDs_1_1.done && (_a = adaptationSetSwitchingIDs_1.return)) _a.call(adaptationSetSwitchingIDs_1); } finally { if (e_2) throw e_2.error; } } if (mergedIntoIdx < 0) { parsedAdaptations[type].push([ parsedAdaptationSet, { priority: priority, isMainAdaptation: isMainAdaptation, indexInMpd: adaptationIdx }, ]); } } if (!(0, is_null_or_undefined_1.default)(originalID) && (0, is_null_or_undefined_1.default)(adaptationSwitchingInfos[originalID])) { adaptationSwitchingInfos[originalID] = { newID: newID, adaptationSetSwitchingIDs: adaptationSetSwitchingIDs, }; } } var adaptationsPerType = manifest_1.SUPPORTED_ADAPTATIONS_TYPE.reduce(function (acc, adaptationType) { var adaptationsParsedForType = parsedAdaptations[adaptationType]; if (adaptationsParsedForType.length > 0) { adaptationsParsedForType.sort(compareAdaptations); acc[adaptationType] = adaptationsParsedForType.map(function (_a) { var _b = __read(_a, 1), parsedAdaptation = _b[0]; return parsedAdaptation; }); } return acc; }, {}); parsedAdaptations.video.sort(compareAdaptations); (0, attach_trickmode_track_1.default)(adaptationsPerType, trickModeAdaptations); return { adaptations: adaptationsPerType, thumbnailTracks: parsedThumbnailTracks, }; } /** * From the given attributes, returns a parsed thumbnail track, or null if it * fails to do so. * @param {Object} adaptation * @param {Array.<Object>} representations * @returns {Object|null} */ function createThumbnailTracks(adaptation, representations) { var _a, _b; var tracks = []; for (var i = 0; i < representations.length; i++) { var representation = representations[i]; if (representation !== undefined) { if (representation.mimeType === undefined) { log_1.default.warn("DASH: Invalid thumbnails Representation, no mime-type"); continue; } var tileInfo = (0, infer_adaptation_type_1.getThumbnailAdaptationSetInfo)(adaptation, adaptation.children.representations[i]); if (tileInfo === null) { continue; } if (representation.height === undefined) { log_1.default.warn("DASH: Invalid thumbnails Representation, no height information"); continue; } if (representation.width === undefined) { log_1.default.warn("DASH: Invalid thumbnails Representation, no width information"); continue; } var start = (_a = representation.index.getFirstAvailablePosition()) !== null && _a !== void 0 ? _a : undefined; var end = (_b = representation.index.getEnd()) !== null && _b !== void 0 ? _b : undefined; var segmentDuration = void 0; var targetDuration = representation.index.getTargetSegmentDuration(); if (targetDuration !== undefined && targetDuration.isPrecize) { segmentDuration = targetDuration.duration; } else { log_1.default.warn("DASH: Cannot produce duration estimate for thumbnail track"); } tracks.push({ id: representation.id, cdnMetadata: representation.cdnMetadata, index: representation.index, mimeType: representation.mimeType, height: representation.height, width: representation.width, horizontalTiles: tileInfo.horizontalTiles, verticalTiles: tileInfo.verticalTiles, start: start, end: end, tileDuration: segmentDuration === undefined ? undefined : segmentDuration / (tileInfo.horizontalTiles * tileInfo.verticalTiles), }); } } return tracks; } /** * Compare groups of parsed AdaptationSet, alongside some ordering metadata, * allowing to easily sort them through JavaScript's `Array.prototype.sort` * method. * @param {Array.<Object>} a * @param {Array.<Object>} b * @returns {number} */ function compareAdaptations(a, b) { var priorityDiff = b[1].priority - a[1].priority; if (priorityDiff !== 0) { return priorityDiff; } if (a[1].isMainAdaptation !== b[1].isMainAdaptation) { return a[1].isMainAdaptation ? -1 : 1; } return a[1].indexInMpd - b[1].indexInMpd; }