UNPKG

rx-player

Version:
375 lines (374 loc) 17 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 __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = default_1; var log_1 = require("../../log"); var classes_1 = require("../../manifest/classes"); var isobmff_1 = require("../../parsers/containers/isobmff"); var smooth_1 = require("../../parsers/manifest/smooth"); var global_scope_1 = require("../../utils/global_scope"); var is_null_or_undefined_1 = require("../../utils/is_null_or_undefined"); var request_1 = require("../../utils/request"); var string_parsing_1 = require("../../utils/string_parsing"); var add_query_string_1 = require("../utils/add_query_string"); var check_isobmff_integrity_1 = require("../utils/check_isobmff_integrity"); var generate_manifest_loader_1 = require("../utils/generate_manifest_loader"); var extract_timings_infos_1 = require("./extract_timings_infos"); var is_mp4_embedded_track_1 = require("./is_mp4_embedded_track"); var isobmff_2 = require("./isobmff"); var segment_loader_1 = require("./segment_loader"); var utils_1 = require("./utils"); function default_1(transportOptions) { var smoothManifestParser = (0, smooth_1.default)(transportOptions); var segmentLoader = (0, segment_loader_1.default)(transportOptions); var manifestLoaderOptions = { customManifestLoader: transportOptions.manifestLoader, }; var manifestLoader = (0, generate_manifest_loader_1.default)(manifestLoaderOptions, "text", null); var manifestPipeline = { loadManifest: manifestLoader, parseManifest: function (manifestData, parserOptions) { var _a; var url = (_a = manifestData.url) !== null && _a !== void 0 ? _a : parserOptions.originalUrl; var manifestReceivedTime = manifestData.receivedTime, responseData = manifestData.responseData; var documentData = getManifestAsString(responseData); var parserResult = smoothManifestParser(documentData, url, manifestReceivedTime); var manifest = new classes_1.default(parserResult, { representationFilter: transportOptions.representationFilter, }); return { manifest: manifest, url: url }; }, }; /** * Export functions allowing to load and parse audio and video smooth * segments. */ var audioVideoPipeline = { /** * Load a Smooth audio/video segment. * @param {Object|null} wantedCdn * @param {Object} context * @param {Object} loaderOptions * @param {Object} cancelSignal * @param {Object} callbacks * @returns {Promise} */ loadSegment: function (wantedCdn, context, loaderOptions, cancelSignal, callbacks) { var url = (0, utils_1.constructSegmentUrl)(wantedCdn, context.segment); return segmentLoader(url, context, loaderOptions, cancelSignal, callbacks); }, parseSegment: function (loadedSegment, context, initTimescale) { var _a, _b; var segment = context.segment; var data = loadedSegment.data, isChunked = loadedSegment.isChunked; if (data === null) { if (segment.isInit) { return { segmentType: "init", initializationData: null, initializationDataSize: 0, protectionData: [], initTimescale: undefined, }; } return { segmentType: "media", chunkData: null, chunkInfos: null, chunkOffset: 0, chunkSize: 0, protectionData: [], appendWindow: [undefined, undefined], }; } var responseBuffer = data instanceof Uint8Array ? data : new Uint8Array(data); if (segment.isInit) { var timescale = (_b = (_a = segment.privateInfos) === null || _a === void 0 ? void 0 : _a.smoothInitSegment) === null || _b === void 0 ? void 0 : _b.timescale; return { segmentType: "init", initializationData: data, initializationDataSize: data.byteLength, // smooth init segments are crafted by hand. // Their timescale is the one from the manifest. initTimescale: timescale, protectionData: [], }; } var timingInfos = initTimescale !== undefined ? (0, extract_timings_infos_1.default)(responseBuffer, isChunked, initTimescale, segment, context.isLive) : null; if (timingInfos === null || timingInfos.chunkInfos === null || timingInfos.scaledSegmentTime === undefined) { throw new Error("Smooth Segment without time information"); } var nextSegments = timingInfos.nextSegments, chunkInfos = timingInfos.chunkInfos, scaledSegmentTime = timingInfos.scaledSegmentTime; var chunkData = (0, isobmff_2.patchSegment)(responseBuffer, scaledSegmentTime); var predictedSegments = nextSegments.length > 0 ? nextSegments : undefined; return { segmentType: "media", chunkData: chunkData, chunkInfos: chunkInfos, chunkOffset: 0, chunkSize: chunkData.length, protectionData: [], predictedSegments: predictedSegments, appendWindow: [undefined, undefined], }; }, }; var textTrackPipeline = { loadSegment: function (wantedCdn, context, loaderOptions, cancelSignal, callbacks) { var _a, _b, _c, _d; var segment = context.segment; var url = (0, utils_1.constructSegmentUrl)(wantedCdn, segment); if (segment.isInit || url === null) { return Promise.resolve({ resultType: "segment-created", resultData: null, }); } var isMP4 = (0, is_mp4_embedded_track_1.default)(context.mimeType); if (!isMP4) { return (0, request_1.default)({ url: ((_a = loaderOptions.cmcdPayload) === null || _a === void 0 ? void 0 : _a.type) === "query" ? (0, add_query_string_1.default)(url, loaderOptions.cmcdPayload.value) : url, headers: ((_b = loaderOptions.cmcdPayload) === null || _b === void 0 ? void 0 : _b.type) === "headers" ? loaderOptions.cmcdPayload.value : undefined, responseType: "text", timeout: loaderOptions.timeout, connectionTimeout: loaderOptions.connectionTimeout, cancelSignal: cancelSignal, onProgress: callbacks.onProgress, }).then(function (data) { return ({ resultType: "segment-loaded", resultData: data, }); }); } else { return (0, request_1.default)({ url: ((_c = loaderOptions.cmcdPayload) === null || _c === void 0 ? void 0 : _c.type) === "query" ? (0, add_query_string_1.default)(url, loaderOptions.cmcdPayload.value) : url, headers: ((_d = loaderOptions.cmcdPayload) === null || _d === void 0 ? void 0 : _d.type) === "headers" ? loaderOptions.cmcdPayload.value : undefined, responseType: "arraybuffer", timeout: loaderOptions.timeout, connectionTimeout: loaderOptions.connectionTimeout, cancelSignal: cancelSignal, onProgress: callbacks.onProgress, }).then(function (data) { if (transportOptions.checkMediaSegmentIntegrity !== true) { return { resultType: "segment-loaded", resultData: data }; } var dataU8 = new Uint8Array(data.responseData); (0, check_isobmff_integrity_1.default)(dataU8, context.segment.isInit); return { resultType: "segment-loaded", resultData: __assign(__assign({}, data), { responseData: dataU8 }), }; }); } }, parseSegment: function (loadedSegment, context, initTimescale) { var _a; var segment = context.segment, language = context.language, _b = context.mimeType, mimeType = _b === void 0 ? "" : _b, _c = context.chosenCodec, chosenCodec = _c === void 0 ? "" : _c; var isMP4 = (0, is_mp4_embedded_track_1.default)(context.mimeType); var data = loadedSegment.data, isChunked = loadedSegment.isChunked; var chunkSize; if (segment.isInit) { // text init segment has no use in HSS return { segmentType: "init", initializationData: null, initializationDataSize: 0, protectionData: [], initTimescale: undefined, }; } if (data === null) { return { segmentType: "media", chunkData: null, chunkInfos: null, chunkOffset: 0, chunkSize: 0, protectionData: [], appendWindow: [undefined, undefined], }; } var nextSegments; var chunkInfos = null; var segmentStart; var segmentEnd; var _sdData; var _sdType; if (isMP4) { var chunkBytes = void 0; if (typeof data === "string") { chunkBytes = (0, string_parsing_1.strToUtf8)(data); } else { chunkBytes = data instanceof Uint8Array ? data : new Uint8Array(data); } chunkSize = chunkBytes.length; var timingInfos = initTimescale !== undefined ? (0, extract_timings_infos_1.default)(chunkBytes, isChunked, initTimescale, segment, context.isLive) : null; nextSegments = timingInfos === null || timingInfos === void 0 ? void 0 : timingInfos.nextSegments; chunkInfos = (_a = timingInfos === null || timingInfos === void 0 ? void 0 : timingInfos.chunkInfos) !== null && _a !== void 0 ? _a : null; if (chunkInfos === null) { if (isChunked) { log_1.default.warn("smooth", "Unavailable time data for current text track."); } else { segmentStart = segment.time; segmentEnd = segment.end; } } else { segmentStart = chunkInfos.time; segmentEnd = chunkInfos.duration !== undefined ? chunkInfos.time + chunkInfos.duration : segment.end; } var lcCodec = chosenCodec.toLowerCase(); if (mimeType === "application/ttml+xml+mp4" || lcCodec === "stpp" || lcCodec === "stpp.ttml" || lcCodec === "stpp.ttml.im1t") { _sdType = "ttml"; } else if (lcCodec === "wvtt") { _sdType = "vtt"; } else { throw new Error("could not find a text-track parser for the type ".concat(mimeType)); } var mdat = (0, isobmff_1.getMDAT)(chunkBytes); _sdData = mdat === null ? "" : (0, string_parsing_1.utf8ToStr)(mdat); } else { // not MP4 segmentStart = segment.time; segmentEnd = segment.end; var chunkString = void 0; if (typeof data !== "string") { var bytesData = data instanceof Uint8Array ? data : new Uint8Array(data); chunkSize = bytesData.length; chunkString = (0, string_parsing_1.utf8ToStr)(bytesData); } else { chunkString = data; } switch (mimeType) { case "application/x-sami": case "application/smil": // TODO SMIL should be its own format, no? _sdType = "sami"; break; case "application/ttml+xml": _sdType = "ttml"; break; case "text/vtt": _sdType = "vtt"; break; } if (_sdType === undefined) { var lcCodec = chosenCodec.toLowerCase(); if (lcCodec === "srt") { _sdType = "srt"; } else { throw new Error("could not find a text-track parser for the type ".concat(mimeType)); } } _sdData = chunkString; } var predictedSegments = Array.isArray(nextSegments) && nextSegments.length > 0 ? nextSegments : undefined; var chunkOffset = segmentStart !== null && segmentStart !== void 0 ? segmentStart : 0; return { segmentType: "media", chunkData: { type: _sdType, data: _sdData, start: segmentStart, end: segmentEnd, initTimescale: initTimescale !== null && initTimescale !== void 0 ? initTimescale : null, language: language, }, chunkSize: chunkSize, chunkInfos: chunkInfos, chunkOffset: chunkOffset, protectionData: [], predictedSegments: predictedSegments, appendWindow: [undefined, undefined], }; }, }; return { transportName: "smooth", manifest: manifestPipeline, audio: audioVideoPipeline, video: audioVideoPipeline, text: textTrackPipeline, thumbnails: { loadThumbnail: function () { return Promise.reject(new Error("Thumbnail tracks aren't implemented with smooth")); }, parseThumbnail: function () { throw new Error("Thumbnail tracks aren't implemented with smooth"); }, }, }; } /** * Try to convert a Manifest from an unknown format to a JS string format. * * Throws if the format cannot be converted. * @param {*} manifestSrc * @returns {Array.<Object | string>} */ function getManifestAsString(manifestSrc) { if (manifestSrc instanceof ArrayBuffer) { return (0, string_parsing_1.utf8ToStr)(new Uint8Array(manifestSrc)); } else if (typeof manifestSrc === "string") { return manifestSrc; } else if (!(0, is_null_or_undefined_1.default)(global_scope_1.default.Document) && manifestSrc instanceof global_scope_1.default.Document) { return manifestSrc.documentElement.outerHTML; } else { throw new Error("Smooth Manifest Parser: Unrecognized Manifest format"); } }