UNPKG

rx-player

Version:
543 lines (542 loc) 25.8 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 __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 }); var log_1 = require("../../../log"); var manifest_1 = require("../../../manifest"); var array_includes_1 = require("../../../utils/array_includes"); var assert_1 = require("../../../utils/assert"); var byte_parsing_1 = require("../../../utils/byte_parsing"); 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 monotonic_timestamp_1 = require("../../../utils/monotonic_timestamp"); var object_assign_1 = require("../../../utils/object_assign"); var string_parsing_1 = require("../../../utils/string_parsing"); var url_utils_1 = require("../../../utils/url-utils"); var isobmff_1 = require("../../containers/isobmff"); var check_manifest_ids_1 = require("../utils/check_manifest_ids"); var get_codecs_1 = require("./get_codecs"); var parse_C_nodes_1 = require("./parse_C_nodes"); var parse_protection_node_1 = require("./parse_protection_node"); var representation_index_1 = require("./representation_index"); var shared_smooth_segment_timeline_1 = require("./shared_smooth_segment_timeline"); var parseBoolean_1 = require("./utils/parseBoolean"); var reduceChildren_1 = require("./utils/reduceChildren"); var tokens_1 = require("./utils/tokens"); var DEFAULT_MIME_TYPES = { audio: "audio/mp4", video: "video/mp4", text: "application/ttml+xml", }; var MIME_TYPES = { AACL: "audio/mp4", AVC1: "video/mp4", H264: "video/mp4", TTML: "application/ttml+xml+mp4", DFXP: "application/ttml+xml+mp4", }; /** * @param {Object|undefined} parserOptions * @returns {Function} */ function createSmoothStreamingParser(parserOptions) { if (parserOptions === void 0) { parserOptions = {}; } var referenceDateTime = parserOptions.referenceDateTime === undefined ? Date.UTC(1970, 0, 1, 0, 0, 0, 0) / 1000 : parserOptions.referenceDateTime; var minRepresentationBitrate = parserOptions.minRepresentationBitrate === undefined ? 0 : parserOptions.minRepresentationBitrate; var serverSyncInfos = parserOptions.serverSyncInfos; var serverTimeOffset = serverSyncInfos !== undefined ? serverSyncInfos.serverTimestamp - serverSyncInfos.clientTime : undefined; /** * @param {Element} q * @param {string} streamType * @return {Object} */ function parseQualityLevel(q, streamType) { var customAttributes = (0, reduceChildren_1.default)(q, function (acc, qName, qNode) { if (qName === "CustomAttributes") { acc.push.apply(acc, __spreadArray([], __read((0, reduceChildren_1.default)(qNode, function (cAttrs, cName, cNode) { if (cName === "Attribute") { var name_1 = cNode.getAttribute("Name"); var value = cNode.getAttribute("Value"); if (name_1 !== null && value !== null) { cAttrs.push(name_1 + "=" + value); } } return cAttrs; }, [])), false)); } return acc; }, []); /** * @param {string} name * @returns {string|undefined} */ function getAttribute(name) { var attr = q.getAttribute(name); return attr === null ? undefined : attr; } switch (streamType) { case "audio": { var audiotag = getAttribute("AudioTag"); var bitsPerSample = getAttribute("BitsPerSample"); var channels = getAttribute("Channels"); var codecPrivateData = getAttribute("CodecPrivateData"); var fourCC = getAttribute("FourCC"); var packetSize = getAttribute("PacketSize"); var samplingRate = getAttribute("SamplingRate"); var bitrateAttr = getAttribute("Bitrate"); var bitrate = bitrateAttr === undefined ? 0 : parseInt(bitrateAttr, 10); bitrate = isNaN(bitrate) ? 0 : bitrate; if ((fourCC !== undefined && MIME_TYPES[fourCC] === undefined) || codecPrivateData === undefined) { log_1.default.warn("Smooth parser: Unsupported audio codec. Ignoring quality level."); return null; } var codecs = (0, get_codecs_1.getAudioCodecs)(codecPrivateData, fourCC); return { audiotag: audiotag !== undefined ? parseInt(audiotag, 10) : audiotag, bitrate: bitrate, bitsPerSample: bitsPerSample !== undefined ? parseInt(bitsPerSample, 10) : bitsPerSample, channels: channels !== undefined ? parseInt(channels, 10) : channels, codecPrivateData: codecPrivateData, codecs: codecs, customAttributes: customAttributes, mimeType: fourCC !== undefined ? MIME_TYPES[fourCC] : fourCC, packetSize: packetSize !== undefined ? parseInt(packetSize, 10) : packetSize, samplingRate: samplingRate !== undefined ? parseInt(samplingRate, 10) : samplingRate, }; } case "video": { var codecPrivateData = getAttribute("CodecPrivateData"); var fourCC = getAttribute("FourCC"); var width = getAttribute("MaxWidth"); var height = getAttribute("MaxHeight"); var bitrateAttr = getAttribute("Bitrate"); var bitrate = bitrateAttr === undefined ? 0 : parseInt(bitrateAttr, 10); bitrate = isNaN(bitrate) ? 0 : bitrate; if ((fourCC !== undefined && MIME_TYPES[fourCC] === undefined) || codecPrivateData === undefined) { log_1.default.warn("Smooth parser: Unsupported video codec. Ignoring quality level."); return null; } var codecs = (0, get_codecs_1.getVideoCodecs)(codecPrivateData); return { bitrate: bitrate, customAttributes: customAttributes, mimeType: fourCC !== undefined ? MIME_TYPES[fourCC] : fourCC, codecPrivateData: codecPrivateData, codecs: codecs, width: width !== undefined ? parseInt(width, 10) : undefined, height: height !== undefined ? parseInt(height, 10) : undefined, }; } case "text": { var codecPrivateData = getAttribute("CodecPrivateData"); var fourCC = getAttribute("FourCC"); var bitrateAttr = getAttribute("Bitrate"); var bitrate = bitrateAttr === undefined ? 0 : parseInt(bitrateAttr, 10); bitrate = isNaN(bitrate) ? 0 : bitrate; return { bitrate: bitrate, customAttributes: customAttributes, mimeType: fourCC !== undefined ? MIME_TYPES[fourCC] : fourCC, codecPrivateData: codecPrivateData !== null && codecPrivateData !== void 0 ? codecPrivateData : "", }; } default: log_1.default.error("Smooth Parser: Unrecognized StreamIndex type: " + streamType); return null; } } /** * Parse the adaptations (<StreamIndex>) tree containing * representations (<QualityLevels>) and timestamp indexes (<c>). * Indexes can be quite huge, and this function needs to * to be optimized. * @param {Object} args * @returns {Object} */ function parseAdaptation(args) { var root = args.root, timescale = args.timescale, baseUrl = args.baseUrl, protections = args.protections, timeShiftBufferDepth = args.timeShiftBufferDepth, manifestReceivedTime = args.manifestReceivedTime, isLive = args.isLive; var timescaleAttr = root.getAttribute("Timescale"); var _timescale = timescaleAttr === null ? timescale : +timescaleAttr; if (isNaN(_timescale)) { _timescale = timescale; } var typeAttribute = root.getAttribute("Type"); if (typeAttribute === null) { throw new Error("StreamIndex without type."); } if (!(0, array_includes_1.default)(manifest_1.SUPPORTED_ADAPTATIONS_TYPE, typeAttribute)) { log_1.default.warn("Smooth Parser: Unrecognized adaptation type:", typeAttribute); } var adaptationType = typeAttribute; var subType = root.getAttribute("Subtype"); var language = root.getAttribute("Language"); var UrlAttr = root.getAttribute("Url"); var UrlPathWithTokens = UrlAttr === null ? "" : UrlAttr; if (0 /* __ENVIRONMENT__.CURRENT_ENV */ === 1 /* __ENVIRONMENT__.DEV */) { (0, assert_1.default)(UrlPathWithTokens !== ""); } var _a = (0, reduceChildren_1.default)(root, function (res, _name, node) { switch (_name) { case "QualityLevel": { var qualityLevel = parseQualityLevel(node, adaptationType); if (qualityLevel === null) { return res; } // filter out video qualityLevels with small bitrates if (adaptationType !== "video" || qualityLevel.bitrate > minRepresentationBitrate) { res.qualityLevels.push(qualityLevel); } break; } case "c": res.cNodes.push(node); break; } return res; }, { qualityLevels: [], cNodes: [] }), qualityLevels = _a.qualityLevels, cNodes = _a.cNodes; var sharedSmoothTimeline = new shared_smooth_segment_timeline_1.default({ timeline: (0, parse_C_nodes_1.default)(cNodes), timescale: _timescale, timeShiftBufferDepth: timeShiftBufferDepth, manifestReceivedTime: manifestReceivedTime, }); // we assume that all qualityLevels have the same // codec and mimeType (0, assert_1.default)(qualityLevels.length !== 0, "Adaptation should have at least one playable representation."); var adaptationID = adaptationType + ((0, is_non_empty_string_1.default)(language) ? "_" + language : ""); var representations = qualityLevels.map(function (qualityLevel) { var media = (0, tokens_1.replaceRepresentationSmoothTokens)(UrlPathWithTokens, qualityLevel.bitrate, qualityLevel.customAttributes); var mimeType = (0, is_non_empty_string_1.default)(qualityLevel.mimeType) ? qualityLevel.mimeType : DEFAULT_MIME_TYPES[adaptationType]; var codecs = qualityLevel.codecs; var id = adaptationID + "_" + (!(0, is_null_or_undefined_1.default)(adaptationType) ? adaptationType + "-" : "") + (!(0, is_null_or_undefined_1.default)(mimeType) ? mimeType + "-" : "") + (!(0, is_null_or_undefined_1.default)(codecs) ? codecs + "-" : "") + String(qualityLevel.bitrate); var keyIDs = []; var firstProtection; if (protections.length > 0) { firstProtection = protections[0]; protections.forEach(function (protection) { keyIDs.push(protection.keyId); }); } var segmentPrivateInfos = { bitsPerSample: qualityLevel.bitsPerSample, channels: qualityLevel.channels, codecPrivateData: qualityLevel.codecPrivateData, packetSize: qualityLevel.packetSize, samplingRate: qualityLevel.samplingRate, height: qualityLevel.height, width: qualityLevel.width, // TODO set multiple protections here // instead of the first one protection: !(0, is_null_or_undefined_1.default)(firstProtection) ? { keyId: firstProtection.keyId, } : undefined, }; var reprIndex = new representation_index_1.default({ isLive: isLive, sharedSmoothTimeline: sharedSmoothTimeline, media: media, segmentPrivateInfos: segmentPrivateInfos, }); var representation = (0, object_assign_1.default)({}, qualityLevel, { index: reprIndex, cdnMetadata: [{ baseUrl: baseUrl }], mimeType: mimeType, codecs: codecs, id: id, }); if (keyIDs.length > 0 || firstProtection !== undefined) { var initDataValues = firstProtection === undefined ? [] : firstProtection.keySystems.map(function (keySystemData) { var systemId = keySystemData.systemId, privateData = keySystemData.privateData; var cleanedSystemId = systemId.replace(/-/g, ""); var pssh = createPSSHBox(cleanedSystemId, privateData); return { systemId: cleanedSystemId, data: pssh }; }); if (initDataValues.length > 0) { var initData = [{ type: "cenc", values: initDataValues }]; representation.contentProtections = { keyIds: keyIDs, initData: initData }; } else { representation.contentProtections = { keyIds: keyIDs, initData: [] }; } } return representation; }); // TODO(pierre): real ad-insert support if (subType === "ADVT") { return null; } var parsedAdaptation = { id: adaptationID, type: adaptationType, representations: representations, language: language === null ? undefined : language, }; if (adaptationType === "text" && subType === "DESC") { parsedAdaptation.closedCaption = true; } return parsedAdaptation; } function parseFromDocument(doc, url, manifestReceivedTime) { var baseUrl = ""; if (url !== undefined) { var filenameIdx = (0, url_utils_1.getFilenameIndexInUrl)(url); baseUrl = url.substring(0, filenameIdx); } var root = doc.documentElement; if ((0, is_null_or_undefined_1.default)(root) || root.nodeName !== "SmoothStreamingMedia") { throw new Error("document root should be SmoothStreamingMedia"); } var majorVersionAttr = root.getAttribute("MajorVersion"); var minorVersionAttr = root.getAttribute("MinorVersion"); if (majorVersionAttr === null || minorVersionAttr === null || !/^[2]-[0-2]$/.test(majorVersionAttr + "-" + minorVersionAttr)) { throw new Error("Version should be 2.0, 2.1 or 2.2"); } var timescaleAttr = root.getAttribute("Timescale"); var timescale = !(0, is_non_empty_string_1.default)(timescaleAttr) ? 10000000 : +timescaleAttr; if (isNaN(timescale)) { timescale = 10000000; } var _a = (0, reduceChildren_1.default)(root, function (res, name, node) { switch (name) { case "Protection": { res.protections.push((0, parse_protection_node_1.default)(node, parserOptions.keySystems)); break; } case "StreamIndex": res.adaptationNodes.push(node); break; } return res; }, { adaptationNodes: [], protections: [], }), protections = _a.protections, adaptationNodes = _a.adaptationNodes; var initialAdaptations = {}; var isLive = (0, parseBoolean_1.default)(root.getAttribute("IsLive")); var timeShiftBufferDepth; if (isLive) { var dvrWindowLength = root.getAttribute("DVRWindowLength"); if (dvrWindowLength !== null && !isNaN(+dvrWindowLength) && +dvrWindowLength !== 0) { timeShiftBufferDepth = +dvrWindowLength / timescale; } } var adaptations = adaptationNodes.reduce(function (acc, node) { var adaptation = parseAdaptation({ root: node, baseUrl: baseUrl, timescale: timescale, protections: protections, isLive: isLive, timeShiftBufferDepth: timeShiftBufferDepth, manifestReceivedTime: manifestReceivedTime, }); if (adaptation === null) { return acc; } var type = adaptation.type; var adaps = acc[type]; if (adaps === undefined) { acc[type] = [adaptation]; } else { adaps.push(adaptation); } return acc; }, initialAdaptations); var suggestedPresentationDelay; var availabilityStartTime; var minimumTime; var timeshiftDepth = null; var maximumTimeData; var firstVideoAdaptation = adaptations.video !== undefined ? adaptations.video[0] : undefined; var firstAudioAdaptation = adaptations.audio !== undefined ? adaptations.audio[0] : undefined; /** Minimum time that can be reached regardless of the StreamIndex chosen. */ var safeMinimumTime; /** Maximum time that can be reached regardless of the StreamIndex chosen. */ var safeMaximumTime; /** Maximum time that can be reached in absolute on the content. */ var unsafeMaximumTime; if (firstVideoAdaptation !== undefined || firstAudioAdaptation !== undefined) { var firstTimeReferences = []; var lastTimeReferences = []; if (firstVideoAdaptation !== undefined) { var firstVideoRepresentation = firstVideoAdaptation.representations[0]; if (firstVideoRepresentation !== undefined) { var firstVideoTimeReference = firstVideoRepresentation.index.getFirstAvailablePosition(); var lastVideoTimeReference = firstVideoRepresentation.index.getLastAvailablePosition(); if (!(0, is_null_or_undefined_1.default)(firstVideoTimeReference)) { firstTimeReferences.push(firstVideoTimeReference); } if (!(0, is_null_or_undefined_1.default)(lastVideoTimeReference)) { lastTimeReferences.push(lastVideoTimeReference); } } } if (firstAudioAdaptation !== undefined) { var firstAudioRepresentation = firstAudioAdaptation.representations[0]; if (firstAudioRepresentation !== undefined) { var firstAudioTimeReference = firstAudioRepresentation.index.getFirstAvailablePosition(); var lastAudioTimeReference = firstAudioRepresentation.index.getLastAvailablePosition(); if (!(0, is_null_or_undefined_1.default)(firstAudioTimeReference)) { firstTimeReferences.push(firstAudioTimeReference); } if (!(0, is_null_or_undefined_1.default)(lastAudioTimeReference)) { lastTimeReferences.push(lastAudioTimeReference); } } } if (firstTimeReferences.length > 0) { safeMinimumTime = Math.max.apply(Math, __spreadArray([], __read(firstTimeReferences), false)); } if (lastTimeReferences.length > 0) { safeMaximumTime = Math.min.apply(Math, __spreadArray([], __read(lastTimeReferences), false)); unsafeMaximumTime = Math.max.apply(Math, __spreadArray([], __read(lastTimeReferences), false)); } } var manifestDuration = root.getAttribute("Duration"); var duration = manifestDuration !== null && +manifestDuration !== 0 ? +manifestDuration / timescale : undefined; if (isLive) { suggestedPresentationDelay = parserOptions.suggestedPresentationDelay; availabilityStartTime = referenceDateTime; minimumTime = safeMinimumTime !== null && safeMinimumTime !== void 0 ? safeMinimumTime : availabilityStartTime; var livePosition = unsafeMaximumTime; if (livePosition === undefined) { livePosition = Date.now() / 1000 - availabilityStartTime; } var maximumSafePosition = safeMaximumTime; if (maximumSafePosition === undefined) { maximumSafePosition = livePosition; } maximumTimeData = { isLinear: true, maximumSafePosition: maximumSafePosition, livePosition: livePosition, time: (0, monotonic_timestamp_1.default)(), }; timeshiftDepth = timeShiftBufferDepth !== null && timeShiftBufferDepth !== void 0 ? timeShiftBufferDepth : null; } else { minimumTime = safeMinimumTime !== null && safeMinimumTime !== void 0 ? safeMinimumTime : 0; var maximumTime = safeMaximumTime; if (maximumTime === undefined) { maximumTime = duration !== undefined ? minimumTime + duration : Infinity; } maximumTimeData = { isLinear: false, maximumSafePosition: maximumTime, livePosition: undefined, time: (0, monotonic_timestamp_1.default)(), }; } var periodStart = isLive ? 0 : minimumTime; var periodEnd = isLive ? undefined : maximumTimeData.maximumSafePosition; var manifest = { availabilityStartTime: availabilityStartTime === undefined ? 0 : availabilityStartTime, clockOffset: serverTimeOffset, isLive: isLive, isDynamic: isLive, isLastPeriodKnown: true, timeBounds: { minimumSafePosition: minimumTime, timeshiftDepth: timeshiftDepth, maximumTimeData: maximumTimeData, }, periods: [ { adaptations: adaptations, duration: periodEnd !== undefined ? periodEnd - periodStart : duration, end: periodEnd, id: "gen-smooth-period-0", start: periodStart, thumbnailTracks: [], }, ], suggestedPresentationDelay: suggestedPresentationDelay, transportType: "smooth", uris: (0, is_null_or_undefined_1.default)(url) ? [] : [url], }; (0, check_manifest_ids_1.default)(manifest); return manifest; } return parseFromDocument; } /** * @param {string} systemId - Hex string representing the CDM, 16 bytes. * @param {Uint8Array|undefined} privateData - Data associated to protection * specific system. * @returns {Uint8Array} */ function createPSSHBox(systemId, privateData) { if (systemId.length !== 32) { throw new Error("HSS: wrong system id length"); } var version = 0; return (0, isobmff_1.createBox)("pssh", (0, byte_parsing_1.concat)([version, 0, 0, 0], (0, string_parsing_1.hexToBytes)(systemId), /** To put there KIDs if it exists (necessitate PSSH v1) */ (0, byte_parsing_1.itobe4)(privateData.length), privateData)); } exports.default = createSmoothStreamingParser;