Files
shaka-player/lib/offline/offline_utils.js
T
Jacob Trimble cfbbe485e9 Use seek range in Playhead.
Rather than using the availability window, Playhead should use the
seek range to restrict the playhead's position.

Closes #1224

Change-Id: I8612bfafb53bbb214e32aae2d71af52d748a3aee
2018-02-20 23:42:12 +00:00

417 lines
11 KiB
JavaScript

/**
* @license
* Copyright 2016 Google Inc.
*
* 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.
*/
goog.provide('shaka.offline.OfflineUtils');
goog.require('goog.asserts');
goog.require('shaka.media.InitSegmentReference');
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.MapUtils');
goog.require('shaka.util.StreamUtils');
/**
* @param {string} originalUri
* @param {shakaExtern.Manifest} manifest
* @param {number} size
* @param {!Object} metadata
* @return {shakaExtern.StoredContent}
*/
shaka.offline.OfflineUtils.createStoredContentFromManifest = function(
originalUri, manifest, size, metadata) {
goog.asserts.assert(
manifest.periods.length,
'Cannot create stored content from manifest with no periods.');
/** @type {!number} */
let expiration = manifest.expiration == undefined ?
Infinity :
manifest.expiration;
/** @type {number} */
let duration = manifest.presentationTimeline.getDuration();
/** @type {shakaExtern.Period} */
let firstPeriod = manifest.periods[0];
/** @type {!Array.<shakaExtern.Track>} */
let tracks = shaka.util.StreamUtils.getTracks(firstPeriod);
/** @type {shakaExtern.StoredContent} */
let content = {
offlineUri: null,
originalManifestUri: originalUri,
duration: duration,
size: size,
expiration: expiration,
tracks: tracks,
appMetadata: metadata
};
return content;
};
/**
* @param {string} offlineUri
* @param {shakaExtern.ManifestDB} manifestDB
* @return {shakaExtern.StoredContent}
*/
shaka.offline.OfflineUtils.createStoredContentFromManifestDB = function(
offlineUri, manifestDB) {
goog.asserts.assert(
manifestDB.periods.length,
'Cannot create stored content from manifestDB with no periods.');
/** @type {shakaExtern.PeriodDB} */
let firstPeriodDB = manifestDB.periods[0];
/** @type {!shaka.media.PresentationTimeline} */
let timeline = new shaka.media.PresentationTimeline(null, 0);
/** @type {!Array.<shakaExtern.DrmInfo>} */
let drmInfo = [];
/** @type {shakaExtern.Period} */
let firstPeriod = shaka.offline.OfflineUtils.reconstructPeriod(
firstPeriodDB, drmInfo, timeline);
/** @type {!Object} */
let metadata = manifestDB.appMetadata || {};
/** @type {!Array.<shakaExtern.Track>} */
let tracks = shaka.util.StreamUtils.getTracks(firstPeriod);
/** @type {shakaExtern.StoredContent} */
let content = {
offlineUri: offlineUri,
originalManifestUri: manifestDB.originalManifestUri,
duration: manifestDB.duration,
size: manifestDB.size,
expiration: manifestDB.expiration,
tracks: tracks,
appMetadata: metadata
};
return content;
};
/**
* Reconstructs a period object from the given database period.
*
* @param {shakaExtern.PeriodDB} period
* @param {!Array.<shakaExtern.DrmInfo>} drmInfos
* @param {shaka.media.PresentationTimeline} timeline
* @return {shakaExtern.Period}
*/
shaka.offline.OfflineUtils.reconstructPeriod = function(
period, drmInfos, timeline) {
const OfflineUtils = shaka.offline.OfflineUtils;
/** @type {!Array.<shakaExtern.StreamDB>} */
let audioStreams = period.streams.filter(OfflineUtils.isAudio_);
/** @type {!Array.<shakaExtern.StreamDB>} */
let videoStreams = period.streams.filter(OfflineUtils.isVideo_);
/** @type {!Array.<shakaExtern.Variant>} */
let variants = OfflineUtils.recreateVariants(
audioStreams, videoStreams, drmInfos);
/** @type {!Array.<shakaExtern.Stream>} */
let textStreams = period.streams
.filter(OfflineUtils.isText_)
.map(OfflineUtils.createStream_);
period.streams.forEach(function(stream, i) {
/** @type {!Array.<shaka.media.SegmentReference>} */
let refs = stream.segments.map(function(segment, index) {
return OfflineUtils.segmentDBToSegmentReference_(index, segment);
});
timeline.notifySegments(refs, i == 0);
});
return {
startTime: period.startTime,
variants: variants,
textStreams: textStreams
};
};
/**
* @param {number} index
* @param {shakaExtern.SegmentDB} segment
* @return {shaka.media.SegmentReference}
* @private
*/
shaka.offline.OfflineUtils.segmentDBToSegmentReference_ = function(index,
segment) {
const OfflineUri = shaka.offline.OfflineUri;
/** @type {number} */
let startByte = 0;
/** @type {?number} */
let endByte = null;
/** @type {number} */
let startTime = segment.startTime;
/** @type {number} */
let endTime = segment.endTime;
/** @type {string} */
let uri = OfflineUri.segmentIdToUri(segment.dataKey);
/**
* @return {!Array.<string>}
*/
let getUris = function() { return [uri]; };
return new shaka.media.SegmentReference(
index, startTime, endTime, getUris, startByte, endByte);
};
/**
* Recreates Variants from audio and video StreamDB collections.
*
* @param {!Array.<!shakaExtern.StreamDB>} audios
* @param {!Array.<!shakaExtern.StreamDB>} videos
* @param {!Array.<!shakaExtern.DrmInfo>} drmInfos
* @return {!Array.<!shakaExtern.Variant>}
*/
shaka.offline.OfflineUtils.recreateVariants = function(
audios, videos, drmInfos) {
const MapUtils = shaka.util.MapUtils;
const OfflineUtils = shaka.offline.OfflineUtils;
// Create a variant for each variant id.
/** @type {!Object.<number, shakaExtern.Variant>} */
let variantMap = {};
let allStreams = [];
allStreams.push.apply(allStreams, audios);
allStreams.push.apply(allStreams, videos);
allStreams.forEach(function(stream) {
stream.variantIds.forEach(function(id) {
if (!variantMap[id]) {
variantMap[id] = OfflineUtils.createEmptyVariant_(id, drmInfos);
}
});
});
// Assign each audio stream to its variants.
audios.forEach(function(audio) {
/** @type {shakaExtern.Stream} */
let stream = OfflineUtils.createStream_(audio);
audio.variantIds.forEach(function(id) {
let variant = variantMap[id];
OfflineUtils.addAudioToVariant_(variant, stream);
});
});
// Assign each video stream to its variants.
videos.forEach(function(video) {
/** @type {shakaExtern.Stream} */
let stream = OfflineUtils.createStream_(video);
video.variantIds.forEach(function(id) {
let variant = variantMap[id];
OfflineUtils.addVideoToVariant_(variant, stream);
});
});
return MapUtils.values(variantMap);
};
/**
* @param {shakaExtern.Variant} variant
* @param {shakaExtern.Stream} audio
* @private
*/
shaka.offline.OfflineUtils.addAudioToVariant_ = function(variant, audio) {
goog.asserts.assert(
shaka.util.StreamUtils.isAudio(audio),
'Only audio streams can be treated as audio.');
goog.asserts.assert(
!variant.audio, 'A variant should only have one audio stream');
variant.language = audio.language;
variant.primary = variant.primary || audio.primary;
variant.audio = audio;
};
/**
* @param {shakaExtern.Variant} variant
* @param {shakaExtern.Stream} video
* @private
*/
shaka.offline.OfflineUtils.addVideoToVariant_ = function(variant, video) {
goog.asserts.assert(
shaka.util.StreamUtils.isVideo(video),
'Only video streams can be treated as video.');
goog.asserts.assert(
!variant.video, 'A variant should only have one video stream');
variant.primary = variant.primary || video.primary;
variant.video = video;
};
/**
* Create an empty Variant.
*
* @param {number} id
* @param {!Array.<!shakaExtern.DrmInfo>} drmInfos
* @return {!shakaExtern.Variant}
* @private
*/
shaka.offline.OfflineUtils.createEmptyVariant_ = function(id, drmInfos) {
return {
id: id,
language: '',
primary: false,
audio: null,
video: null,
bandwidth: 0,
drmInfos: drmInfos,
allowedByApplication: true,
allowedByKeySystem: true
};
};
/**
* Creates a shakaExtern.Stream from a StreamDB.
*
* @param {shakaExtern.StreamDB} streamDB
* @return {shakaExtern.Stream}
* @private
*/
shaka.offline.OfflineUtils.createStream_ = function(streamDB) {
const OfflineUtils = shaka.offline.OfflineUtils;
/** @type {!Array.<shaka.media.SegmentReference>} */
let segments = streamDB.segments.map(function(segment, index) {
return OfflineUtils.segmentDBToSegmentReference_(index, segment);
});
/** @type {!shaka.media.SegmentIndex} */
let segmentIndex = new shaka.media.SegmentIndex(segments);
/** @type {shakaExtern.Stream} */
let stream = {
id: streamDB.id,
createSegmentIndex: Promise.resolve.bind(Promise),
findSegmentPosition: segmentIndex.find.bind(segmentIndex),
getSegmentReference: segmentIndex.get.bind(segmentIndex),
initSegmentReference: null,
presentationTimeOffset: streamDB.presentationTimeOffset,
mimeType: streamDB.mimeType,
codecs: streamDB.codecs,
width: streamDB.width || undefined,
height: streamDB.height || undefined,
frameRate: streamDB.frameRate || undefined,
kind: streamDB.kind,
encrypted: streamDB.encrypted,
keyId: streamDB.keyId,
language: streamDB.language,
label: streamDB.label || null,
type: streamDB.contentType,
primary: streamDB.primary,
trickModeVideo: null,
// TODO(modmaker): Store offline?
containsEmsgBoxes: false,
roles: [],
channelsCount: null
};
if (streamDB.initSegmentKey != null) {
stream.initSegmentReference =
OfflineUtils.createInitSegment_(streamDB.initSegmentKey);
}
return stream;
};
/**
* @param {number} initKey
* @return {!shaka.media.InitSegmentReference}
* @private
*/
shaka.offline.OfflineUtils.createInitSegment_ = function(initKey) {
const OfflineUri = shaka.offline.OfflineUri;
/** @type {string} */
let initUri = OfflineUri.segmentIdToUri(initKey);
/**
* @return {!Array.<string>}
*/
let getInitUris = function() { return [initUri]; };
/** @type {number} */
let startBytes = 0;
/** @type {?number} */
let endBytes = null;
return new shaka.media.InitSegmentReference(
getInitUris,
startBytes,
endBytes);
};
/**
* @param {shakaExtern.StreamDB} stream
* @return {boolean}
* @private
*/
shaka.offline.OfflineUtils.isAudio_ = function(stream) {
const ContentType = shaka.util.ManifestParserUtils.ContentType;
return stream.contentType == ContentType.AUDIO;
};
/**
* @param {shakaExtern.StreamDB} stream
* @return {boolean}
* @private
*/
shaka.offline.OfflineUtils.isVideo_ = function(stream) {
const ContentType = shaka.util.ManifestParserUtils.ContentType;
return stream.contentType == ContentType.VIDEO;
};
/**
* @param {shakaExtern.StreamDB} stream
* @return {boolean}
* @private
*/
shaka.offline.OfflineUtils.isText_ = function(stream) {
const ContentType = shaka.util.ManifestParserUtils.ContentType;
return stream.contentType == ContentType.TEXT;
};