-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
storage_compatibility_unit.js
344 lines (297 loc) · 11.8 KB
/
storage_compatibility_unit.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
/*! @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
});