Files
shaka-player/lib/offline/indexeddb/v1_storage_cell.js
T
Joey Parrish f539147d48 fix: Correct license headers in compiled output
This fixes all the license headers in the main library, which corrects
the appearance of the main license in the compiled output.

It seems that the `!` in the header forces the compiler to keep it in
the output.  I believe older compiler releases did this purely based
on `@license`.

Issue #2638

Change-Id: I7f0e918caad10c9af689c9d07672b7fe9be7b2f3
2020-06-09 16:05:09 -07:00

329 lines
10 KiB
JavaScript

/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
goog.provide('shaka.offline.indexeddb.V1StorageCell');
goog.require('goog.asserts');
goog.require('shaka.log');
goog.require('shaka.offline.indexeddb.BaseStorageCell');
goog.require('shaka.util.Error');
goog.require('shaka.util.ManifestParserUtils');
goog.require('shaka.util.PeriodCombiner');
goog.require('shaka.util.PublicPromise');
/**
* The V1StorageCell is for all stores that follow the shaka.externs V1 offline
* types, introduced in Shaka Player v2.0 and deprecated in v2.3.
*
* @implements {shaka.extern.StorageCell}
*/
shaka.offline.indexeddb.V1StorageCell = class
extends shaka.offline.indexeddb.BaseStorageCell {
/** @override */
async updateManifestExpiration(key, newExpiration) {
const op = this.connection_.startReadWriteOperation(this.manifestStore_);
/** @type {IDBObjectStore} */
const store = op.store();
/** @type {!shaka.util.PublicPromise} */
const p = new shaka.util.PublicPromise();
store.get(key).onsuccess = (event) => {
// Make sure a defined value was found. Indexeddb treats "no value found"
// as a success with an undefined result.
const manifest = /** @type {shaka.extern.ManifestDBV1} */(
event.target.result);
// Indexeddb does not fail when you get a value that is not in the
// database. It will return an undefined value. However, we expect
// the value to never be null, so something is wrong if we get a
// falsey value.
if (manifest) {
// Since this store's scheme uses in-line keys, we don't specify the key
// with |put|. This difference is why we must override the base class.
goog.asserts.assert(
manifest.key == key,
'With in-line keys, the keys should match');
manifest.expiration = newExpiration;
store.put(manifest);
p.resolve();
} else {
p.reject(new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.STORAGE,
shaka.util.Error.Code.KEY_NOT_FOUND,
'Could not find values for ' + key));
}
};
await Promise.all([op.promise(), p]);
}
/**
* @override
* @param {shaka.extern.ManifestDBV1} old
* @return {!Promise.<shaka.extern.ManifestDB>}
*/
async convertManifest(old) {
const V1StorageCell = shaka.offline.indexeddb.V1StorageCell;
const streamsPerPeriod = [];
for (let i = 0; i < old.periods.length; ++i) {
// The last period ends at the end of the presentation.
const periodEnd = i == old.periods.length - 1 ?
old.duration : old.periods[i + 1].startTime;
const duration = periodEnd - old.periods[i].startTime;
const streams = V1StorageCell.convertPeriod_(old.periods[i], duration);
streamsPerPeriod.push(streams);
}
const streams = await shaka.util.PeriodCombiner.combineDbStreams(
streamsPerPeriod);
return {
creationTime: 0,
originalManifestUri: old.originalManifestUri,
duration: old.duration,
size: old.size,
expiration: old.expiration == null ? Infinity : old.expiration,
streams,
sessionIds: old.sessionIds,
drmInfo: old.drmInfo,
appMetadata: old.appMetadata,
};
}
/**
* @param {shaka.extern.PeriodDBV1} old
* @param {number} periodDuration
* @return {!Array.<shaka.extern.StreamDB>}
* @private
*/
static convertPeriod_(old, periodDuration) {
const V1StorageCell = shaka.offline.indexeddb.V1StorageCell;
// In the case that this is really old (like really old, like dinosaurs
// roaming the Earth old) there may be no variants, so we need to add those.
V1StorageCell.fillMissingVariants_(old);
for (const stream of old.streams) {
const message = 'After filling in missing variants, ' +
'each stream should have variant ids';
goog.asserts.assert(stream.variantIds, message);
}
return old.streams.map((stream) => V1StorageCell.convertStream_(
stream, old.startTime, periodDuration));
}
/**
* @param {shaka.extern.StreamDBV1} old
* @param {number} periodStart
* @param {number} periodDuration
* @return {shaka.extern.StreamDB}
* @private
*/
static convertStream_(old, periodStart, periodDuration) {
const V1StorageCell = shaka.offline.indexeddb.V1StorageCell;
const initSegmentKey = old.initSegmentUri ?
V1StorageCell.getKeyFromSegmentUri_(old.initSegmentUri) : null;
// timestampOffset in the new format is the inverse of
// presentationTimeOffset in the old format. Also, PTO did not include the
// period start, while TO does.
const timestampOffset = periodStart + old.presentationTimeOffset;
const appendWindowStart = periodStart;
const appendWindowEnd = periodStart + periodDuration;
return {
id: old.id,
originalId: null,
primary: old.primary,
type: old.contentType,
mimeType: old.mimeType,
codecs: old.codecs,
frameRate: old.frameRate,
pixelAspectRatio: undefined,
kind: old.kind,
language: old.language,
label: old.label,
width: old.width,
height: old.height,
initSegmentKey: initSegmentKey,
encrypted: old.encrypted,
keyIds: new Set([old.keyId]),
segments: old.segments.map((segment) => V1StorageCell.convertSegment_(
segment, initSegmentKey, appendWindowStart, appendWindowEnd,
timestampOffset)),
variantIds: old.variantIds,
roles: [],
audioSamplingRate: null,
channelsCount: null,
closedCaptions: null,
};
}
/**
* @param {shaka.extern.SegmentDBV1} old
* @param {?number} initSegmentKey
* @param {number} appendWindowStart
* @param {number} appendWindowEnd
* @param {number} timestampOffset
* @return {shaka.extern.SegmentDB}
* @private
*/
static convertSegment_(
old, initSegmentKey, appendWindowStart, appendWindowEnd,
timestampOffset) {
const V1StorageCell = shaka.offline.indexeddb.V1StorageCell;
// Since we don't want to use the uri anymore, we need to parse the key
// from it.
const dataKey = V1StorageCell.getKeyFromSegmentUri_(old.uri);
return {
startTime: appendWindowStart + old.startTime,
endTime: appendWindowStart + old.endTime,
dataKey,
initSegmentKey,
appendWindowStart,
appendWindowEnd,
timestampOffset,
};
}
/**
* @override
* @param {shaka.extern.SegmentDataDBV1} old
* @return {shaka.extern.SegmentDataDB}
*/
convertSegmentData(old) {
return {data: old.data};
}
/**
* @param {string} uri
* @return {number}
* @private
*/
static getKeyFromSegmentUri_(uri) {
let parts = null;
// Try parsing the uri as the original Shaka Player 2.0 uri.
parts = /^offline:[0-9]+\/[0-9]+\/([0-9]+)$/.exec(uri);
if (parts) {
return Number(parts[1]);
}
// Just before Shaka Player 2.3 the uri format was changed to remove some
// of the un-used information from the uri and make the segment uri and
// manifest uri follow a similar format. However the old storage system
// was still in place, so it is possible for Storage V1 Cells to have
// Storage V2 uris.
parts = /^offline:segment\/([0-9]+)$/.exec(uri);
if (parts) {
return Number(parts[1]);
}
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.STORAGE,
shaka.util.Error.Code.MALFORMED_OFFLINE_URI,
'Could not parse uri ' + uri);
}
/**
* Take a period and check if the streams need to have variants generated.
* Before Shaka Player moved to its variants model, there were no variants.
* This will fill missing variants into the given object.
*
* @param {shaka.extern.PeriodDBV1} period
* @private
*/
static fillMissingVariants_(period) {
const AUDIO = shaka.util.ManifestParserUtils.ContentType.AUDIO;
const VIDEO = shaka.util.ManifestParserUtils.ContentType.VIDEO;
// There are three cases:
// 1. All streams' variant ids are null
// 2. All streams' variant ids are non-null
// 3. Some streams' variant ids are null and other are non-null
// Case 3 is invalid and should never happen in production.
const audio = period.streams.filter((s) => s.contentType == AUDIO);
const video = period.streams.filter((s) => s.contentType == VIDEO);
// Case 2 - There is nothing we need to do, so let's just get out of here.
if (audio.every((s) => s.variantIds) && video.every((s) => s.variantIds)) {
return;
}
// Case 3... We don't want to be in case three.
goog.asserts.assert(
audio.every((s) => !s.variantIds),
'Some audio streams have variant ids and some do not.');
goog.asserts.assert(
video.every((s) => !s.variantIds),
'Some video streams have variant ids and some do not.');
// Case 1 - Populate all the variant ids (putting us back to case 2).
// Since all the variant ids are null, we need to first make them into
// valid arrays.
for (const s of audio) {
s.variantIds = [];
}
for (const s of video) {
s.variantIds = [];
}
let nextId = 0;
// It is not possible in Shaka Player's pre-variant world to have audio-only
// and video-only content mixed in with audio-video content. So we can
// assume that there is only audio-only or video-only if one group is empty.
// Everything is video-only content - so each video stream gets to be its
// own variant.
if (video.length && !audio.length) {
shaka.log.debug('Found video-only content. Creating variants for video.');
const variantId = nextId++;
for (const s of video) {
s.variantIds.push(variantId);
}
}
// Everything is audio-only content - so each audio stream gets to be its
// own variant.
if (!video.length && audio.length) {
shaka.log.debug('Found audio-only content. Creating variants for audio.');
const variantId = nextId++;
for (const s of audio) {
s.variantIds.push(variantId);
}
}
// Everything is audio-video content.
if (video.length && audio.length) {
shaka.log.debug('Found audio-video content. Creating variants.');
for (const a of audio) {
for (const v of video) {
const variantId = nextId++;
a.variantIds.push(variantId);
v.variantIds.push(variantId);
}
}
}
}
};