UNPKG

shaka-player

Version:
345 lines (297 loc) 12 kB
/*! @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.minBufferTime = 2; 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 });