UNPKG

rx-player

Version:
346 lines (345 loc) 17.5 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)); }; 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."); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = parseMpdIr; var config_1 = require("../../../../config"); var log_1 = require("../../../../log"); var array_find_1 = require("../../../../utils/array_find"); var is_null_or_undefined_1 = require("../../../../utils/is_null_or_undefined"); var monotonic_timestamp_1 = require("../../../../utils/monotonic_timestamp"); var url_utils_1 = require("../../../../utils/url-utils"); var content_protection_parser_1 = require("./content_protection_parser"); var get_clock_offset_1 = require("./get_clock_offset"); var get_http_utc_timing_url_1 = require("./get_http_utc-timing_url"); var get_minimum_and_maximum_positions_1 = require("./get_minimum_and_maximum_positions"); var manifest_bounds_calculator_1 = require("./manifest_bounds_calculator"); var parse_availability_start_time_1 = require("./parse_availability_start_time"); var parse_periods_1 = require("./parse_periods"); var resolve_base_urls_1 = require("./resolve_base_urls"); /** * Checks if xlinks needs to be loaded before actually parsing the manifest. * @param {Object} mpdIR * @param {Object} args * @param {boolean} hasLoadedClock * @param {Array.<Object>} warnings * @returns {Object} */ function parseMpdIr(mpdIR, args, warnings, hasLoadedClock, xlinkInfos) { if (xlinkInfos === void 0) { xlinkInfos = new WeakMap(); } var rootChildren = mpdIR.children, rootAttributes = mpdIR.attributes; if ((0, is_null_or_undefined_1.default)(args.externalClockOffset)) { var isDynamic = rootAttributes.type === "dynamic"; var directTiming = (0, array_find_1.default)(rootChildren.utcTimings, function (utcTiming) { return (utcTiming.schemeIdUri === "urn:mpeg:dash:utc:direct:2014" && !(0, is_null_or_undefined_1.default)(utcTiming.value)); }); var clockOffsetFromDirectUTCTiming = !(0, is_null_or_undefined_1.default)(directTiming) && !(0, is_null_or_undefined_1.default)(directTiming.value) ? (0, get_clock_offset_1.default)(directTiming.value) : undefined; var clockOffset = !(0, is_null_or_undefined_1.default)(clockOffsetFromDirectUTCTiming) && !isNaN(clockOffsetFromDirectUTCTiming) ? clockOffsetFromDirectUTCTiming : undefined; if (!(0, is_null_or_undefined_1.default)(clockOffset) && hasLoadedClock !== true) { args.externalClockOffset = clockOffset; } else if (isDynamic && hasLoadedClock !== true) { var UTCTimingHTTPURL = (0, get_http_utc_timing_url_1.default)(mpdIR); if (!(0, is_null_or_undefined_1.default)(UTCTimingHTTPURL) && UTCTimingHTTPURL.length > 0) { // TODO fetch UTCTiming and XLinks at the same time return { type: "needs-clock", value: { url: UTCTimingHTTPURL, continue: function continueParsingMPD(responseDataClock) { if (!responseDataClock.success) { warnings.push(responseDataClock.error); log_1.default.warn("DASH Parser: Error on fetching the clock ressource", responseDataClock.error); return parseMpdIr(mpdIR, args, warnings, true); } args.externalClockOffset = (0, get_clock_offset_1.default)(responseDataClock.data); return parseMpdIr(mpdIR, args, warnings, true); }, }, }; } } } var xlinksToLoad = []; for (var i = 0; i < rootChildren.periods.length; i++) { var _a = rootChildren.periods[i].attributes, xlinkHref = _a.xlinkHref, xlinkActuate = _a.xlinkActuate; if (!(0, is_null_or_undefined_1.default)(xlinkHref) && xlinkActuate === "onLoad") { xlinksToLoad.push({ index: i, ressource: xlinkHref }); } } if (xlinksToLoad.length === 0) { return parseCompleteIntermediateRepresentation(mpdIR, args, warnings, xlinkInfos); } return { type: "needs-xlinks", value: { xlinksUrls: xlinksToLoad.map(function (_a) { var ressource = _a.ressource; return ressource; }), continue: function continueParsingMPD(loadedRessources) { var e_1, _a, _b; if (loadedRessources.length !== xlinksToLoad.length) { throw new Error("DASH parser: wrong number of loaded ressources."); } // Note: It is important to go from the last index to the first index in // the resulting array, as we will potentially add elements to the array for (var i = loadedRessources.length - 1; i >= 0; i--) { var index = xlinksToLoad[i].index; var _c = loadedRessources[i], periodsIR = _c.parsed, parsingWarnings = _c.warnings, receivedTime = _c.receivedTime, sendingTime = _c.sendingTime, url = _c.url; if (parsingWarnings.length > 0) { warnings.push.apply(warnings, __spreadArray([], __read(parsingWarnings), false)); } try { for (var periodsIR_1 = (e_1 = void 0, __values(periodsIR)), periodsIR_1_1 = periodsIR_1.next(); !periodsIR_1_1.done; periodsIR_1_1 = periodsIR_1.next()) { var periodIR = periodsIR_1_1.value; xlinkInfos.set(periodIR, { receivedTime: receivedTime, sendingTime: sendingTime, url: url }); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (periodsIR_1_1 && !periodsIR_1_1.done && (_a = periodsIR_1.return)) _a.call(periodsIR_1); } finally { if (e_1) throw e_1.error; } } // replace original "xlinked" periods by the real deal (_b = rootChildren.periods).splice.apply(_b, __spreadArray([index, 1], __read(periodsIR), false)); } return parseMpdIr(mpdIR, args, warnings, hasLoadedClock, xlinkInfos); }, }, }; } /** * Parse the MPD intermediate representation into a regular Manifest. * @param {Object} mpdIR * @param {Object} args * @param {Array.<Object>} warnings * @param {Object} xlinkInfos * @returns {Object} */ function parseCompleteIntermediateRepresentation(mpdIR, args, warnings, xlinkInfos) { var _a, _b, _c, _d, _e; var rootChildren = mpdIR.children, rootAttributes = mpdIR.attributes; var isDynamic = rootAttributes.type === "dynamic"; var initialBaseUrl = args.url !== undefined ? [{ url: args.url.substring(0, (0, url_utils_1.getFilenameIndexInUrl)(args.url)) }] : []; var mpdBaseUrls = (0, resolve_base_urls_1.default)(initialBaseUrl, rootChildren.baseURLs); var availabilityStartTime = (0, parse_availability_start_time_1.default)(rootAttributes, args.referenceDateTime); var timeShiftBufferDepth = rootAttributes.timeShiftBufferDepth; var maxSegmentDuration = rootAttributes.maxSegmentDuration; var clockOffset = args.externalClockOffset, unsafelyBaseOnPreviousManifest = args.unsafelyBaseOnPreviousManifest; var externalClockOffset = args.externalClockOffset; var manifestBoundsCalculator = new manifest_bounds_calculator_1.default({ availabilityStartTime: availabilityStartTime, isDynamic: isDynamic, timeShiftBufferDepth: timeShiftBufferDepth, serverTimestampOffset: externalClockOffset, }); var contentProtectionParser = new content_protection_parser_1.default(); contentProtectionParser.addReferences((_a = rootChildren.contentProtections) !== null && _a !== void 0 ? _a : []); var manifestInfos = { availabilityStartTime: availabilityStartTime, baseURLs: mpdBaseUrls, clockOffset: clockOffset, contentProtectionParser: contentProtectionParser, duration: rootAttributes.duration, isDynamic: isDynamic, manifestBoundsCalculator: manifestBoundsCalculator, manifestProfiles: mpdIR.attributes.profiles, receivedTime: args.manifestReceivedTime, unsafelyBaseOnPreviousManifest: unsafelyBaseOnPreviousManifest, xlinkInfos: xlinkInfos, xmlNamespaces: mpdIR.attributes.namespaces, }; var parsedPeriods = (0, parse_periods_1.default)(rootChildren.periods, manifestInfos); contentProtectionParser.finalize(); var mediaPresentationDuration = rootAttributes.duration; var lifetime; var minimumTime; var timeshiftDepth = null; var maximumTimeData; if (rootAttributes.minimumUpdatePeriod !== undefined && rootAttributes.minimumUpdatePeriod >= 0) { lifetime = rootAttributes.minimumUpdatePeriod === 0 ? config_1.default.getCurrent().DASH_FALLBACK_LIFETIME_WHEN_MINIMUM_UPDATE_PERIOD_EQUAL_0 : rootAttributes.minimumUpdatePeriod; } var _f = (0, get_minimum_and_maximum_positions_1.default)(parsedPeriods), minimumSafePosition = _f.minimumSafePosition, maximumSafePosition = _f.maximumSafePosition, maximumUnsafePosition = _f.maximumUnsafePosition; var now = (0, monotonic_timestamp_1.default)(); if (!isDynamic) { minimumTime = minimumSafePosition; if (minimumTime === undefined) { minimumTime = (_c = (_b = parsedPeriods[0]) === null || _b === void 0 ? void 0 : _b.start) !== null && _c !== void 0 ? _c : 0; } var finalMaximumSafePosition = mediaPresentationDuration !== null && mediaPresentationDuration !== void 0 ? mediaPresentationDuration : Infinity; if (parsedPeriods[parsedPeriods.length - 1] !== undefined) { var lastPeriod = parsedPeriods[parsedPeriods.length - 1]; var lastPeriodEnd = (_d = lastPeriod.end) !== null && _d !== void 0 ? _d : (lastPeriod.duration !== undefined ? lastPeriod.start + lastPeriod.duration : undefined); if (lastPeriodEnd !== undefined && lastPeriodEnd < finalMaximumSafePosition) { finalMaximumSafePosition = lastPeriodEnd; } } if (maximumSafePosition !== undefined && maximumSafePosition < finalMaximumSafePosition) { finalMaximumSafePosition = maximumSafePosition; } maximumTimeData = { isLinear: false, maximumSafePosition: finalMaximumSafePosition, livePosition: undefined, time: now, }; } else { // Determine the maximum seekable position var finalMaximumSafePosition = void 0; if (maximumSafePosition !== undefined) { finalMaximumSafePosition = maximumSafePosition; } else { if (externalClockOffset === undefined) { log_1.default.warn("DASH Parser: use system clock to define maximum position"); finalMaximumSafePosition = Date.now() / 1000 - availabilityStartTime; } else { var serverTime = (0, monotonic_timestamp_1.default)() + externalClockOffset; finalMaximumSafePosition = serverTime / 1000 - availabilityStartTime; } } // Determine live edge (what position corresponds to live content, can be // inferior or superior to the maximum anounced position in some specific // scenarios). However, the `timeShiftBufferDepth` should be based on it. var livePosition = manifestBoundsCalculator.getEstimatedLiveEdge(); if (livePosition === undefined) { if (maximumUnsafePosition !== undefined) { livePosition = maximumUnsafePosition; } else { livePosition = finalMaximumSafePosition; } // manifestBoundsCalculator.forceLiveEdge(livePosition); } maximumTimeData = { isLinear: true, maximumSafePosition: finalMaximumSafePosition, livePosition: livePosition, time: now, }; // if the minimum calculated time is even below the buffer depth, perhaps we // can go even lower in terms of depth minimumTime = minimumSafePosition; timeshiftDepth = timeShiftBufferDepth !== null && timeShiftBufferDepth !== void 0 ? timeShiftBufferDepth : null; if (timeshiftDepth !== null) { // The DASH spec implies that a segment is still available after a given // `timeShiftBufferDepth` for a time equal to its duration // (What I interpret from "ISO/IEC 23009-1 fifth edition 2022-08 // A.3.4 Media Segment list restrictions). // // This `timeshiftDepth` property is global for the whole Manifest (and // not per segment), thus we cannot do exactly that, but we can take the // anounced `maxSegmentDuration` by default instead. This may be a little // too optimistic, but would in reality not lead to a lot of issues as // this `timeshiftDepth` property is not the one that should be relied on // to know which segment can or cannot be requested anymore. timeshiftDepth += maxSegmentDuration !== null && maxSegmentDuration !== void 0 ? maxSegmentDuration : 0; } if (timeshiftDepth !== null && minimumTime !== undefined && livePosition - minimumTime > timeshiftDepth) { timeshiftDepth = livePosition - minimumTime; } } // `isLastPeriodKnown` should be `true` in two cases for DASH contents: // 1. When the content is static, because we know that no supplementary // Period will be added. // 2. If the content is dynamic, only when both the duration is known and // the `minimumUpdatePeriod` is not set. This corresponds to the case // explained in "4.6.4. Transition Phase between Live and On-Demand" of // the DASH-IF IOP v4.3 for live contents transitionning to on-demand. var isLastPeriodKnown = !isDynamic || (mpdIR.attributes.minimumUpdatePeriod === undefined && (((_e = parsedPeriods[parsedPeriods.length - 1]) === null || _e === void 0 ? void 0 : _e.end) !== undefined || mpdIR.attributes.duration !== undefined)); var parsedMPD = { availabilityStartTime: availabilityStartTime, clockOffset: args.externalClockOffset, isDynamic: isDynamic, isLive: isDynamic, isLastPeriodKnown: isLastPeriodKnown, periods: parsedPeriods, publishTime: rootAttributes.publishTime, suggestedPresentationDelay: rootAttributes.suggestedPresentationDelay, transportType: "dash", timeBounds: { minimumSafePosition: minimumTime, timeshiftDepth: timeshiftDepth, maximumTimeData: maximumTimeData, }, lifetime: lifetime, uris: (0, is_null_or_undefined_1.default)(args.url) ? rootChildren.locations : __spreadArray([args.url], __read(rootChildren.locations), false), }; return { type: "done", value: { parsed: parsedMPD, warnings: warnings } }; }