UNPKG

shaka-player

Version:
345 lines (305 loc) 10.4 kB
/*! @license * Shaka Player * Copyright 2016 Google LLC * SPDX-License-Identifier: Apache-2.0 */ goog.provide('shaka.offline.ManifestConverter'); goog.require('goog.asserts'); goog.require('shaka.media.InitSegmentReference'); goog.require('shaka.media.ManifestParser'); goog.require('shaka.media.PresentationTimeline'); goog.require('shaka.media.SegmentIndex'); goog.require('shaka.media.SegmentReference'); goog.require('shaka.offline.OfflineUri'); goog.require('shaka.util.ManifestParserUtils'); goog.require('shaka.util.MimeUtils'); /** * Utility class for converting database manifest objects back to normal * player-ready objects. Used by the offline system to convert on-disk * objects back to the in-memory objects. */ shaka.offline.ManifestConverter = class { /** * Create a new manifest converter. Need to know the mechanism and cell that * the manifest is from so that all segments paths can be created. * * @param {string} mechanism * @param {string} cell */ constructor(mechanism, cell) { /** @private {string} */ this.mechanism_ = mechanism; /** @private {string} */ this.cell_ = cell; } /** * Convert a |shaka.extern.ManifestDB| object to a |shaka.extern.Manifest| * object. * * @param {shaka.extern.ManifestDB} manifestDB * @return {shaka.extern.Manifest} */ fromManifestDB(manifestDB) { const timeline = new shaka.media.PresentationTimeline(null, 0); timeline.setDuration(manifestDB.duration); /** @type {!Array<shaka.extern.StreamDB>} */ const audioStreams = manifestDB.streams.filter((streamDB) => this.isAudio_(streamDB)); /** @type {!Array<shaka.extern.StreamDB>} */ const videoStreams = manifestDB.streams.filter((streamDB) => this.isVideo_(streamDB)); /** @type {!Map<number, shaka.extern.Variant>} */ const variants = this.createVariants(audioStreams, videoStreams, timeline); /** @type {!Array<shaka.extern.Stream>} */ const textStreams = manifestDB.streams.filter((streamDB) => this.isText_(streamDB)) .map((streamDB) => this.fromStreamDB_(streamDB, timeline)); /** @type {!Array<shaka.extern.Stream>} */ const imageStreams = manifestDB.streams.filter((streamDB) => this.isImage_(streamDB)) .map((streamDB) => this.fromStreamDB_(streamDB, timeline)); const drmInfos = manifestDB.drmInfo ? [manifestDB.drmInfo] : []; if (manifestDB.drmInfo) { for (const variant of variants.values()) { if (variant.audio && variant.audio.encrypted) { variant.audio.drmInfos = drmInfos; } if (variant.video && variant.video.encrypted) { variant.video.drmInfos = drmInfos; } } } return { presentationTimeline: timeline, offlineSessionIds: manifestDB.sessionIds, variants: Array.from(variants.values()), textStreams: textStreams, imageStreams: imageStreams, sequenceMode: manifestDB.sequenceMode || false, ignoreManifestTimestampsInSegmentsMode: false, type: manifestDB.type || shaka.media.ManifestParser.UNKNOWN, serviceDescription: null, nextUrl: null, periodCount: 1, gapCount: 0, isLowLatency: false, startTime: null, }; } /** * Recreates Variants from audio and video StreamDB collections. * * @param {!Array<!shaka.extern.StreamDB>} audios * @param {!Array<!shaka.extern.StreamDB>} videos * @param {shaka.media.PresentationTimeline} timeline * @return {!Map<number, !shaka.extern.Variant>} */ createVariants(audios, videos, timeline) { // Get all the variant ids from all audio and video streams. /** @type {!Set<number>} */ const variantIds = new Set(); for (const streamDB of audios) { for (const id of streamDB.variantIds) { variantIds.add(id); } } for (const streamDB of videos) { for (const id of streamDB.variantIds) { variantIds.add(id); } } /** @type {!Map<number, shaka.extern.Variant>} */ const variantMap = new Map(); for (const id of variantIds) { variantMap.set(id, this.createEmptyVariant_(id)); } // Assign each audio stream to its variants. for (const audio of audios) { /** @type {shaka.extern.Stream} */ const stream = this.fromStreamDB_(audio, timeline); for (const variantId of audio.variantIds) { const variant = variantMap.get(variantId); goog.asserts.assert( !variant.audio, 'A variant should only have one audio stream'); variant.language = stream.language; variant.primary = variant.primary || stream.primary; variant.audio = stream; } } // Assign each video stream to its variants. for (const video of videos) { /** @type {shaka.extern.Stream} */ const stream = this.fromStreamDB_(video, timeline); for (const variantId of video.variantIds) { const variant = variantMap.get(variantId); goog.asserts.assert( !variant.video, 'A variant should only have one video stream'); variant.primary = variant.primary || stream.primary; variant.video = stream; } } return variantMap; } /** * @param {shaka.extern.StreamDB} streamDB * @param {shaka.media.PresentationTimeline} timeline * @return {shaka.extern.Stream} * @private */ fromStreamDB_(streamDB, timeline) { /** @type {!Array<!shaka.media.SegmentReference>} */ const segments = streamDB.segments.map( (segment, index) => this.fromSegmentDB_(index, segment, streamDB)); timeline.notifySegments(segments); /** @type {!shaka.media.SegmentIndex} */ const segmentIndex = new shaka.media.SegmentIndex(segments); /** @type {shaka.extern.Stream} */ const stream = { id: streamDB.id, originalId: streamDB.originalId, groupId: streamDB.groupId, createSegmentIndex: () => Promise.resolve(), segmentIndex, mimeType: streamDB.mimeType, codecs: streamDB.codecs, width: streamDB.width || undefined, height: streamDB.height || undefined, frameRate: streamDB.frameRate, pixelAspectRatio: streamDB.pixelAspectRatio, hdr: streamDB.hdr, colorGamut: streamDB.colorGamut, videoLayout: streamDB.videoLayout, kind: streamDB.kind, encrypted: streamDB.encrypted, drmInfos: [], keyIds: streamDB.keyIds, language: streamDB.language, originalLanguage: streamDB.originalLanguage || null, label: streamDB.label, type: streamDB.type, primary: streamDB.primary, trickModeVideo: null, dependencyStream: null, emsgSchemeIdUris: null, roles: streamDB.roles, forced: streamDB.forced, channelsCount: streamDB.channelsCount, audioSamplingRate: streamDB.audioSamplingRate, spatialAudio: streamDB.spatialAudio, closedCaptions: streamDB.closedCaptions, tilesLayout: streamDB.tilesLayout, mssPrivateData: streamDB.mssPrivateData, accessibilityPurpose: null, external: streamDB.external, fastSwitching: streamDB.fastSwitching, fullMimeTypes: new Set([shaka.util.MimeUtils.getFullType( streamDB.mimeType, streamDB.codecs)]), isAudioMuxedInVideo: false, baseOriginalId: null, }; return stream; } /** * @param {number} index * @param {shaka.extern.SegmentDB} segmentDB * @param {shaka.extern.StreamDB} streamDB * @return {!shaka.media.SegmentReference} * @private */ fromSegmentDB_(index, segmentDB, streamDB) { /** @type {!shaka.offline.OfflineUri} */ const uri = shaka.offline.OfflineUri.segment( this.mechanism_, this.cell_, segmentDB.dataKey); const initSegmentReference = segmentDB.initSegmentKey != null ? this.fromInitSegmentDB_(segmentDB.initSegmentKey) : null; const ref = new shaka.media.SegmentReference( segmentDB.startTime, segmentDB.endTime, () => [uri.toString()], /* startByte= */ 0, /* endByte= */ null, initSegmentReference, segmentDB.timestampOffset, segmentDB.appendWindowStart, segmentDB.appendWindowEnd, /* partialReferences= */ [], segmentDB.tilesLayout || ''); ref.mimeType = segmentDB.mimeType || streamDB.mimeType || ''; ref.codecs = segmentDB.codecs || streamDB.codecs || ''; if (segmentDB.thumbnailSprite) { ref.setThumbnailSprite(segmentDB.thumbnailSprite); } return ref; } /** * @param {number} key * @return {!shaka.media.InitSegmentReference} * @private */ fromInitSegmentDB_(key) { /** @type {!shaka.offline.OfflineUri} */ const uri = shaka.offline.OfflineUri.segment( this.mechanism_, this.cell_, key); return new shaka.media.InitSegmentReference( () => [uri.toString()], /* startBytes= */ 0, /* endBytes= */ null ); } /** * @param {shaka.extern.StreamDB} streamDB * @return {boolean} * @private */ isAudio_(streamDB) { const ContentType = shaka.util.ManifestParserUtils.ContentType; return streamDB.type == ContentType.AUDIO; } /** * @param {shaka.extern.StreamDB} streamDB * @return {boolean} * @private */ isVideo_(streamDB) { const ContentType = shaka.util.ManifestParserUtils.ContentType; return streamDB.type == ContentType.VIDEO; } /** * @param {shaka.extern.StreamDB} streamDB * @return {boolean} * @private */ isText_(streamDB) { const ContentType = shaka.util.ManifestParserUtils.ContentType; return streamDB.type == ContentType.TEXT; } /** * @param {shaka.extern.StreamDB} streamDB * @return {boolean} * @private */ isImage_(streamDB) { const ContentType = shaka.util.ManifestParserUtils.ContentType; return streamDB.type == ContentType.IMAGE; } /** * Creates an empty Variant. * * @param {number} id * @return {!shaka.extern.Variant} * @private */ createEmptyVariant_(id) { return { id: id, language: '', disabledUntilTime: 0, primary: false, audio: null, video: null, bandwidth: 0, allowedByApplication: true, allowedByKeySystem: true, decodingInfos: [], }; } };