mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-14 15:56:38 +03:00
6e55a3b21b
Fixes #2643 Happy reviewing!
344 lines
12 KiB
JavaScript
344 lines
12 KiB
JavaScript
/*! @license
|
|
* Shaka Player
|
|
* Copyright 2016 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
// All of the database dumps referenced below were originally made from the
|
|
// "Heliocentrism" content in our demo app.
|
|
// https://storage.googleapis.com/shaka-demo-assets/heliocentrism/heliocentrism.mpd
|
|
// The dumps were made with test/test/util/canned_idb.js
|
|
const compatibilityTestsMetadata = [
|
|
{
|
|
// This was our original (v1) storage format for Shaka Player v2.0,
|
|
// deprecated in v2.3. This is not the same as the storage format from
|
|
// Shaka Player v1, which is no longer supported.
|
|
name: 'v1',
|
|
dbImagePath: '/base/test/test/assets/db-dump-v1.json',
|
|
manifestKey: 0,
|
|
readOnly: true,
|
|
makeCell: (connection) => new shaka.offline.indexeddb.V1StorageCell(
|
|
connection,
|
|
/* segmentStore= */ 'segment',
|
|
/* manifestStore= */ 'manifest'),
|
|
},
|
|
{
|
|
// Two variants of v2 exist in the field. This is the initial version of
|
|
// v2, as upgraded from v1 databases. It was broken in a way that prevented
|
|
// new records from being added. This format was introduced in Shaka Player
|
|
// v2.3 and deprecated in v2.3.2.
|
|
name: 'v2-broken',
|
|
dbImagePath: '/base/test/test/assets/db-dump-v2-broken.json',
|
|
manifestKey: 0,
|
|
readOnly: true,
|
|
makeCell: (connection) => new shaka.offline.indexeddb.V2StorageCell(
|
|
connection,
|
|
/* segmentStore= */ 'segment-v2',
|
|
/* manifestStore= */ 'manifest-v2'),
|
|
},
|
|
{
|
|
// This is the "clean" version of the v2 database format, as created from
|
|
// scratch, to which new records could be added. This format was introduced
|
|
// in Shaka Player v2.3 and deprecated in v2.3.2.
|
|
name: 'v2-clean',
|
|
dbImagePath: '/base/test/test/assets/db-dump-v2-clean.json',
|
|
manifestKey: 1,
|
|
readOnly: true,
|
|
makeCell: (connection) => new shaka.offline.indexeddb.V2StorageCell(
|
|
connection,
|
|
/* segmentStore= */ 'segment-v2',
|
|
/* manifestStore= */ 'manifest-v2'),
|
|
},
|
|
{
|
|
// This is the v3 version of the database, which is actually identical to
|
|
// the "clean" version of the v2 database. The version number was
|
|
// incremented to overcome the "broken" v2 databases. This format was
|
|
// introduced in v2.3.2 and deprecated in v3.0.
|
|
name: 'v3',
|
|
dbImagePath: '/base/test/test/assets/db-dump-v3.json',
|
|
manifestKey: 1,
|
|
readOnly: true,
|
|
makeCell: (connection) => new shaka.offline.indexeddb.V2StorageCell(
|
|
connection,
|
|
/* segmentStore= */ 'segment-v3',
|
|
/* manifestStore= */ 'manifest-v3'),
|
|
},
|
|
{
|
|
// This is the v4 version of the database as written by v2.5.0 - v2.5.9. A
|
|
// bug in v2.5 caused the stream metadata from all periods to be written to
|
|
// each period. This was corrected in v2.5.10.
|
|
// See https://github.com/shaka-project/shaka-player/issues/2389
|
|
name: 'v4-broken',
|
|
dbImagePath: '/base/test/test/assets/db-dump-v4-broken.json',
|
|
manifestKey: 1,
|
|
readOnly: true,
|
|
makeCell: (connection) => new shaka.offline.indexeddb.V2StorageCell(
|
|
connection,
|
|
// V4 of the database still used the V3 store names and structures.
|
|
/* segmentStore= */ 'segment-v3',
|
|
/* manifestStore= */ 'manifest-v3'),
|
|
},
|
|
{
|
|
// This is the v5 version of the database, introduced in v3.0.
|
|
name: 'v5',
|
|
dbImagePath: '/base/test/test/assets/db-dump-v5.json',
|
|
manifestKey: 1,
|
|
readOnly: false,
|
|
makeCell: (connection) => new shaka.offline.indexeddb.V5StorageCell(
|
|
connection,
|
|
/* segmentStore= */ 'segment-v5',
|
|
/* manifestStore= */ 'manifest-v5'),
|
|
},
|
|
];
|
|
|
|
filterDescribe('Storage Compatibility', offlineSupported, () => {
|
|
for (const metadata of compatibilityTestsMetadata) {
|
|
describe(metadata.name, () => {
|
|
makeTests(metadata);
|
|
});
|
|
}
|
|
|
|
function makeTests(metadata) {
|
|
const CannedIDB = shaka.test.CannedIDB;
|
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
const Util = shaka.test.Util;
|
|
|
|
/** @type {?shaka.extern.StorageCell} */
|
|
let cell = null;
|
|
|
|
/** @type {?IDBDatabase} */
|
|
let connection = null;
|
|
|
|
/** @type {string} */
|
|
let dbImageAsString;
|
|
|
|
beforeAll(async () => {
|
|
const data = await shaka.test.Util.fetch(metadata.dbImagePath);
|
|
dbImageAsString = shaka.util.StringUtils.fromUTF8(data);
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
const dbName = 'shaka-storage-cell-test';
|
|
|
|
// Load the canned database image.
|
|
await CannedIDB.restoreJSON(
|
|
dbName, dbImageAsString, /* wipeDatabase= */ true);
|
|
|
|
// Track the connection so that we can close it when the test is over.
|
|
connection = await shaka.test.IndexedDBUtils.open(dbName);
|
|
|
|
// Create a storage cell.
|
|
cell = metadata.makeCell(connection);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
// Destroy the cell before killing the connection.
|
|
if (cell) {
|
|
await cell.destroy();
|
|
}
|
|
cell = null;
|
|
|
|
if (connection) {
|
|
connection.close();
|
|
}
|
|
connection = null;
|
|
});
|
|
|
|
if (metadata.readOnly) {
|
|
it('cannot add new manifests', async () => {
|
|
const expected = Util.jasmineError(new shaka.util.Error(
|
|
shaka.util.Error.Severity.CRITICAL,
|
|
shaka.util.Error.Category.STORAGE,
|
|
shaka.util.Error.Code.NEW_KEY_OPERATION_NOT_SUPPORTED,
|
|
jasmine.any(String)));
|
|
|
|
// There should be one manifest.
|
|
const manifests = await cell.getAllManifests();
|
|
const manifest = manifests.get(metadata.manifestKey);
|
|
expect(manifest).toBeTruthy();
|
|
|
|
// Make sure that the request fails.
|
|
await expectAsync(
|
|
cell.addManifests([manifest])).toBeRejectedWith(expected);
|
|
});
|
|
|
|
it('cannot add new segment', async () => {
|
|
const expected = Util.jasmineError(new shaka.util.Error(
|
|
shaka.util.Error.Severity.CRITICAL,
|
|
shaka.util.Error.Category.STORAGE,
|
|
shaka.util.Error.Code.NEW_KEY_OPERATION_NOT_SUPPORTED,
|
|
jasmine.any(String)));
|
|
|
|
// Update the key to what should be a free key.
|
|
const segment = {data: new ArrayBuffer(16)};
|
|
|
|
// Make sure that the request fails.
|
|
await expectAsync(
|
|
cell.addSegments([segment])).toBeRejectedWith(expected);
|
|
});
|
|
} // if (metadata.readOnly)
|
|
|
|
it('can get all manifests', async () => {
|
|
// There should be one manifest.
|
|
const map = await cell.getAllManifests();
|
|
expect(map).toBeTruthy();
|
|
expect(map.size).toBe(1);
|
|
expect(map.get(metadata.manifestKey)).toBeTruthy();
|
|
});
|
|
|
|
it('can get manifest and all segments', async () => {
|
|
// There should be one manifest.
|
|
const manifests = await cell.getManifests([metadata.manifestKey]);
|
|
const manifest = manifests[0];
|
|
expect(manifest).toBeTruthy();
|
|
|
|
// Collect all the keys for each segment.
|
|
const dataKeys = getAllSegmentKeys(manifest);
|
|
|
|
// Check that each segment was successfully retrieved.
|
|
const segmentData = await cell.getSegments(dataKeys);
|
|
expect(segmentData.length).not.toBe(0);
|
|
|
|
for (const segment of segmentData) {
|
|
expect(segment).toBeTruthy();
|
|
}
|
|
});
|
|
|
|
it('can update expiration', async () => {
|
|
const oldExpiration = Infinity;
|
|
const newExpiration = 1000;
|
|
|
|
const original = await cell.getManifests([metadata.manifestKey]);
|
|
expect(original).toBeTruthy();
|
|
expect(original[0]).toBeTruthy();
|
|
expect(original[0].expiration).toBe(oldExpiration);
|
|
|
|
await cell.updateManifestExpiration(metadata.manifestKey, newExpiration);
|
|
|
|
const updated = await cell.getManifests([metadata.manifestKey]);
|
|
expect(updated).toBeTruthy();
|
|
expect(updated[0]).toBeTruthy();
|
|
expect(updated[0].expiration).toBe(newExpiration);
|
|
});
|
|
|
|
it('can remove manifests and segments', async () => {
|
|
/** @type {!Array<number>} */
|
|
const manifestKeys = [];
|
|
/** @type {!Array<number>} */
|
|
const segmentKeys = [];
|
|
|
|
const manifests = await cell.getAllManifests();
|
|
manifests.forEach((manifest, manifestKey) => {
|
|
manifestKeys.push(manifestKey);
|
|
|
|
for (const key of getAllSegmentKeys(manifest)) {
|
|
segmentKeys.push(key);
|
|
}
|
|
});
|
|
|
|
expect(manifestKeys.length).toBe(1);
|
|
expect(segmentKeys.length).not.toBe(0);
|
|
|
|
// Remove all the segments.
|
|
const noop = () => {};
|
|
await cell.removeManifests(manifestKeys, noop);
|
|
await cell.removeSegments(segmentKeys, noop);
|
|
|
|
const expected = Util.jasmineError(new shaka.util.Error(
|
|
shaka.util.Error.Severity.CRITICAL,
|
|
shaka.util.Error.Category.STORAGE,
|
|
shaka.util.Error.Code.KEY_NOT_FOUND,
|
|
jasmine.any(String)));
|
|
const checkMissingSegment = async (key) => {
|
|
await expectAsync(cell.getSegments([key])).toBeRejectedWith(expected);
|
|
};
|
|
|
|
const checkMissingManifest = async (key) => {
|
|
await expectAsync(cell.getManifests([key])).toBeRejectedWith(expected);
|
|
};
|
|
|
|
// Need to check each key on its own to ensure that each key is missing
|
|
// and not just one of the keys is missing.
|
|
const checkMissingSegments = (keys) => {
|
|
return Promise.all(keys.map((key) => checkMissingSegment(key)));
|
|
};
|
|
const checkMissingManifests = (keys) => {
|
|
return Promise.all(keys.map((key) => checkMissingManifest(key)));
|
|
};
|
|
|
|
await checkMissingSegments(segmentKeys);
|
|
await checkMissingManifests(manifestKeys);
|
|
});
|
|
|
|
it('correctly converts to the current manifest format', async () => {
|
|
// There should be one manifest.
|
|
const manifestDb = (await cell.getManifests([metadata.manifestKey]))[0];
|
|
const converter = new shaka.offline.ManifestConverter(
|
|
'mechanism', 'cell');
|
|
const actual = converter.fromManifestDB(manifestDb);
|
|
|
|
const expected = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
manifest.anyTimeline();
|
|
|
|
manifest.addPartialVariant((variant) => {
|
|
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
|
stream.frameRate = 29.97;
|
|
stream.mime('video/webm', 'vp9');
|
|
stream.size(640, 480);
|
|
});
|
|
});
|
|
});
|
|
|
|
expect(actual).toEqual(expected);
|
|
|
|
const segmentIndex = actual.variants[0].video.segmentIndex;
|
|
goog.asserts.assert(segmentIndex != null, 'Null segmentIndex!');
|
|
const [segment0, segment1, segment2] = Array.from(segmentIndex);
|
|
|
|
expect(segment0).toEqual(jasmine.objectContaining({
|
|
startTime: 0,
|
|
endTime: Util.closeTo(2.06874),
|
|
timestampOffset: 0,
|
|
appendWindowStart: 0,
|
|
appendWindowEnd: Util.closeTo(2.06874),
|
|
}));
|
|
expect(segment1).toEqual(jasmine.objectContaining({
|
|
startTime: Util.closeTo(2.06874),
|
|
endTime: Util.closeTo(4.20413),
|
|
timestampOffset: Util.closeTo(2.06874),
|
|
appendWindowStart: Util.closeTo(2.06874),
|
|
appendWindowEnd: Util.closeTo(4.20413),
|
|
}));
|
|
expect(segment2).toEqual(jasmine.objectContaining({
|
|
startTime: Util.closeTo(4.20413),
|
|
endTime: Util.closeTo(4.904831),
|
|
timestampOffset: Util.closeTo(4.20413),
|
|
appendWindowStart: Util.closeTo(4.20413),
|
|
appendWindowEnd: Util.closeTo(4.904831),
|
|
}));
|
|
});
|
|
|
|
/**
|
|
* Get the keys for each segment. This will include the init segments.
|
|
*
|
|
* @param {shaka.extern.ManifestDB} manifest
|
|
* @return {!Array<number>}
|
|
*/
|
|
function getAllSegmentKeys(manifest) {
|
|
const keys = new Set();
|
|
|
|
for (const stream of manifest.streams) {
|
|
for (const segment of stream.segments) {
|
|
if (segment.initSegmentKey != null) {
|
|
keys.add(segment.initSegmentKey);
|
|
}
|
|
|
|
keys.add(segment.dataKey);
|
|
}
|
|
}
|
|
|
|
return Array.from(keys);
|
|
}
|
|
} // makeTests
|
|
});
|