shaka-player 3.3.10 → 3.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/build-and-test.yaml +42 -22
- package/.github/workflows/compute-incremental-coverage.py +14 -3
- package/.github/workflows/custom-actions/set-commit-status/action.yaml +1 -5
- package/.github/workflows/deflake.yaml +28 -0
- package/.github/workflows/demo-version-index.yaml +9 -1
- package/.github/workflows/nightly-demo.yaml +1 -1
- package/.github/workflows/release-please.yaml +4 -4
- package/.github/workflows/report-incremental-coverage.yaml +1 -1
- package/.github/workflows/selenium-lab-tests.yaml +21 -10
- package/AUTHORS +3 -0
- package/CHANGELOG.md +25 -0
- package/CONTRIBUTORS +2 -0
- package/dist/deps.js +3 -3
- package/dist/locales.js +30 -30
- package/dist/shaka-player.compiled.d.ts +1 -0
- package/dist/shaka-player.compiled.debug.d.ts +1 -0
- package/dist/shaka-player.compiled.debug.externs.js +1 -0
- package/dist/shaka-player.compiled.debug.js +133 -131
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +1 -0
- package/dist/shaka-player.compiled.js +712 -711
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +1 -0
- package/dist/shaka-player.ui.debug.d.ts +1 -0
- package/dist/shaka-player.ui.debug.externs.js +1 -0
- package/dist/shaka-player.ui.debug.js +197 -195
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +1 -0
- package/dist/shaka-player.ui.js +830 -829
- package/dist/shaka-player.ui.map +4 -4
- package/docs/jsdoc-template/publish.js +33 -0
- package/docs/jsdoc-template/tmpl/container.tmpl +4 -1
- package/docs/jsdoc.conf.json +1 -0
- package/docs/tutorials/welcome.md +2 -2
- package/lib/ads/client_side_ad_manager.js +5 -2
- package/lib/cast/cast_sender.js +39 -14
- package/lib/cea/cea608_memory.js +4 -0
- package/lib/media/drm_engine.js +4 -2
- package/lib/media/streaming_engine.js +41 -4
- package/lib/offline/indexeddb/storage_mechanism.js +24 -0
- package/lib/player.js +1 -1
- package/lib/polyfill/media_capabilities.js +1 -0
- package/lib/text/ttml_text_parser.js +69 -87
- package/lib/util/error.js +7 -0
- package/lib/util/periods.js +9 -9
- package/lib/util/platform.js +34 -0
- package/lib/util/stream_utils.js +2 -1
- package/lib/util/xml_utils.js +45 -6
- package/maintained-branches.md +2 -2
- package/package.json +1 -1
- package/test/cea/cea608_memory_unit.js +88 -0
- package/test/media/streaming_engine_integration.js +8 -7
- package/test/offline/offline_integration.js +3 -3
- package/test/offline/storage_integration.js +30 -0
- package/test/test/util/ttml_utils.js +0 -2
- package/test/text/ttml_text_parser_unit.js +46 -0
- package/test/ui/text_displayer_layout_unit.js +17 -2
- package/test/util/periods_unit.js +149 -11
- package/test/util/xml_utils_unit.js +64 -0
package/lib/util/platform.js
CHANGED
|
@@ -163,6 +163,40 @@ shaka.util.Platform = class {
|
|
|
163
163
|
return shaka.util.Platform.userAgentContains_('CrKey');
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Check if the current platform is a Android-based Cast devices.
|
|
168
|
+
*
|
|
169
|
+
* @return {boolean}
|
|
170
|
+
*/
|
|
171
|
+
static isAndroidCastDevice() {
|
|
172
|
+
return shaka.util.Platform.isChromecast() &&
|
|
173
|
+
shaka.util.Platform.userAgentContains_('Android');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Returns a major version number for Chrome, or Chromium-based browsers.
|
|
178
|
+
*
|
|
179
|
+
* For example:
|
|
180
|
+
* - Chrome 106.0.5249.61 returns 106.
|
|
181
|
+
* - Edge 106.0.1370.34 returns 106 (since this is based on Chromium).
|
|
182
|
+
* - Safari returns null (since this is independent of Chromium).
|
|
183
|
+
*
|
|
184
|
+
* @return {?number} A major version number or null if not Chromium-based.
|
|
185
|
+
*/
|
|
186
|
+
static chromeVersion() {
|
|
187
|
+
if (!shaka.util.Platform.userAgentContains_('Chrome')) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Looking for something like "Chrome/106.0.0.0".
|
|
192
|
+
const match = navigator.userAgent.match(/Chrome\/(\d+)/);
|
|
193
|
+
if (match) {
|
|
194
|
+
return parseInt(match[1], /* base= */ 10);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
|
|
166
200
|
/**
|
|
167
201
|
* Check if the current platform is Google Chrome.
|
|
168
202
|
*
|
package/lib/util/stream_utils.js
CHANGED
|
@@ -467,7 +467,8 @@ shaka.util.StreamUtils = class {
|
|
|
467
467
|
if (shaka.util.Platform.isXboxOne() && video &&
|
|
468
468
|
((video.width && video.width > 1920) ||
|
|
469
469
|
(video.height && video.height > 1080)) &&
|
|
470
|
-
video.codecs.includes('avc1.')
|
|
470
|
+
(video.codecs.includes('avc1.') ||
|
|
471
|
+
video.codecs.includes('avc3.'))) {
|
|
471
472
|
shaka.log.debug('Dropping variant - not compatible with platform',
|
|
472
473
|
shaka.util.StreamUtils.getVariantSummaryString_(variant));
|
|
473
474
|
return false;
|
package/lib/util/xml_utils.js
CHANGED
|
@@ -8,6 +8,7 @@ goog.provide('shaka.util.XmlUtils');
|
|
|
8
8
|
|
|
9
9
|
goog.require('goog.asserts');
|
|
10
10
|
goog.require('shaka.log');
|
|
11
|
+
goog.require('shaka.util.Lazy');
|
|
11
12
|
goog.require('shaka.util.StringUtils');
|
|
12
13
|
|
|
13
14
|
|
|
@@ -348,19 +349,21 @@ shaka.util.XmlUtils = class {
|
|
|
348
349
|
*/
|
|
349
350
|
static parseXmlString(xmlString, expectedRootElemName) {
|
|
350
351
|
const parser = new DOMParser();
|
|
351
|
-
|
|
352
|
+
const unsafeXmlString =
|
|
353
|
+
shaka.util.XmlUtils.trustedHTMLFromString_.value()(xmlString);
|
|
354
|
+
let unsafeXml = null;
|
|
352
355
|
try {
|
|
353
|
-
|
|
356
|
+
unsafeXml = parser.parseFromString(unsafeXmlString, 'text/xml');
|
|
354
357
|
} catch (exception) {
|
|
355
358
|
shaka.log.error('XML parsing exception:', exception);
|
|
356
359
|
return null;
|
|
357
360
|
}
|
|
358
361
|
|
|
359
362
|
// According to MDN, parseFromString never returns null.
|
|
360
|
-
goog.asserts.assert(
|
|
363
|
+
goog.asserts.assert(unsafeXml, 'Parsed XML document cannot be null!');
|
|
361
364
|
|
|
362
365
|
// Check for empty documents.
|
|
363
|
-
const rootElem =
|
|
366
|
+
const rootElem = unsafeXml.documentElement;
|
|
364
367
|
if (!rootElem) {
|
|
365
368
|
shaka.log.error('XML document was empty!');
|
|
366
369
|
return null;
|
|
@@ -374,13 +377,28 @@ shaka.util.XmlUtils = class {
|
|
|
374
377
|
}
|
|
375
378
|
|
|
376
379
|
// The top-level element in the loaded XML should have the name we expect.
|
|
377
|
-
if (
|
|
380
|
+
if (rootElem.tagName != expectedRootElemName) {
|
|
378
381
|
shaka.log.error(
|
|
379
382
|
`XML tag name does not match expected "${expectedRootElemName}":`,
|
|
380
|
-
|
|
383
|
+
rootElem.tagName);
|
|
381
384
|
return null;
|
|
382
385
|
}
|
|
383
386
|
|
|
387
|
+
// SECURITY: Verify that the document does not contain elements from the
|
|
388
|
+
// HTML or SVG namespaces, which could trigger script execution and XSS.
|
|
389
|
+
const iterator = document.createNodeIterator(
|
|
390
|
+
unsafeXml,
|
|
391
|
+
NodeFilter.SHOW_ALL,
|
|
392
|
+
);
|
|
393
|
+
let currentNode;
|
|
394
|
+
while (currentNode = iterator.nextNode()) {
|
|
395
|
+
if (currentNode instanceof HTMLElement ||
|
|
396
|
+
currentNode instanceof SVGElement) {
|
|
397
|
+
shaka.log.error('XML document embeds unsafe content!');
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
384
402
|
return rootElem;
|
|
385
403
|
}
|
|
386
404
|
|
|
@@ -402,3 +420,24 @@ shaka.util.XmlUtils = class {
|
|
|
402
420
|
}
|
|
403
421
|
}
|
|
404
422
|
};
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Promote a string to TrustedHTML. This function is security-sensitive and
|
|
426
|
+
* should only be used with security approval where the string is guaranteed not
|
|
427
|
+
* to cause an XSS vulnerability.
|
|
428
|
+
*
|
|
429
|
+
* @private {!shaka.util.Lazy.<function(!string): (!TrustedHTML|!string)>}
|
|
430
|
+
*/
|
|
431
|
+
shaka.util.XmlUtils.trustedHTMLFromString_ = new shaka.util.Lazy(() => {
|
|
432
|
+
if (typeof trustedTypes !== 'undefined') {
|
|
433
|
+
// Create a Trusted Types policy for promoting the string to TrustedHTML.
|
|
434
|
+
// The Lazy wrapper ensures this policy is only created once.
|
|
435
|
+
const policy = trustedTypes.createPolicy('shaka-player#xml', {
|
|
436
|
+
createHTML: (s) => s,
|
|
437
|
+
});
|
|
438
|
+
return (s) => policy.createHTML(s);
|
|
439
|
+
}
|
|
440
|
+
// Fall back to strings in environments that don't support Trusted Types.
|
|
441
|
+
return (s) => s;
|
|
442
|
+
});
|
|
443
|
+
|
package/maintained-branches.md
CHANGED
package/package.json
CHANGED
|
@@ -295,5 +295,93 @@ describe('Cea608Memory', () => {
|
|
|
295
295
|
const caption = memory.forceEmit(startTime, endTime);
|
|
296
296
|
expect(caption).toEqual(expectedCaption);
|
|
297
297
|
});
|
|
298
|
+
|
|
299
|
+
it('does not move rows if source row index is negative', () => {
|
|
300
|
+
const startTime = 1;
|
|
301
|
+
const endTime = 2;
|
|
302
|
+
const text = 'test';
|
|
303
|
+
|
|
304
|
+
// Add the text to the buffer, each character on separate rows.
|
|
305
|
+
// At this point, the memory looks like:
|
|
306
|
+
// [1]: t
|
|
307
|
+
// [2]: e
|
|
308
|
+
// [3]: s
|
|
309
|
+
// [4]: t
|
|
310
|
+
for (const c of text) {
|
|
311
|
+
memory.addChar(CharSet.BASIC_NORTH_AMERICAN,
|
|
312
|
+
c.charCodeAt(0));
|
|
313
|
+
memory.setRow(memory.getRow() + 1); // increment row
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const srcRowIdx = -1;
|
|
317
|
+
const dstRowIdx = 2;
|
|
318
|
+
const rowsToMove = 3;
|
|
319
|
+
memory.moveRows(dstRowIdx, srcRowIdx, rowsToMove);
|
|
320
|
+
|
|
321
|
+
// Expected text is 't\ne\ns\nt'
|
|
322
|
+
const topLevelCue = new shaka.text.Cue(startTime, endTime, '');
|
|
323
|
+
topLevelCue.nestedCues = [
|
|
324
|
+
CeaUtils.createDefaultCue(startTime, endTime, 't'),
|
|
325
|
+
CeaUtils.createLineBreakCue(startTime, endTime),
|
|
326
|
+
CeaUtils.createDefaultCue(startTime, endTime, 'e'),
|
|
327
|
+
CeaUtils.createLineBreakCue(startTime, endTime),
|
|
328
|
+
CeaUtils.createDefaultCue(startTime, endTime, 's'),
|
|
329
|
+
CeaUtils.createLineBreakCue(startTime, endTime),
|
|
330
|
+
CeaUtils.createDefaultCue(startTime, endTime, 't'),
|
|
331
|
+
];
|
|
332
|
+
|
|
333
|
+
const expectedCaption = {
|
|
334
|
+
stream,
|
|
335
|
+
cue: topLevelCue,
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// Force out the new memory.
|
|
339
|
+
const caption = memory.forceEmit(startTime, endTime);
|
|
340
|
+
expect(caption).toEqual(expectedCaption);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('does not move rows if destination row index is negative', () => {
|
|
344
|
+
const startTime = 1;
|
|
345
|
+
const endTime = 2;
|
|
346
|
+
const text = 'test';
|
|
347
|
+
|
|
348
|
+
// Add the text to the buffer, each character on separate rows.
|
|
349
|
+
// At this point, the memory looks like:
|
|
350
|
+
// [1]: t
|
|
351
|
+
// [2]: e
|
|
352
|
+
// [3]: s
|
|
353
|
+
// [4]: t
|
|
354
|
+
for (const c of text) {
|
|
355
|
+
memory.addChar(CharSet.BASIC_NORTH_AMERICAN,
|
|
356
|
+
c.charCodeAt(0));
|
|
357
|
+
memory.setRow(memory.getRow() + 1); // increment row
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const srcRowIdx = 1;
|
|
361
|
+
const dstRowIdx = -2;
|
|
362
|
+
const rowsToMove = 3;
|
|
363
|
+
memory.moveRows(dstRowIdx, srcRowIdx, rowsToMove);
|
|
364
|
+
|
|
365
|
+
// Expected text is 't\ne\ns\nt'
|
|
366
|
+
const topLevelCue = new shaka.text.Cue(startTime, endTime, '');
|
|
367
|
+
topLevelCue.nestedCues = [
|
|
368
|
+
CeaUtils.createDefaultCue(startTime, endTime, 't'),
|
|
369
|
+
CeaUtils.createLineBreakCue(startTime, endTime),
|
|
370
|
+
CeaUtils.createDefaultCue(startTime, endTime, 'e'),
|
|
371
|
+
CeaUtils.createLineBreakCue(startTime, endTime),
|
|
372
|
+
CeaUtils.createDefaultCue(startTime, endTime, 's'),
|
|
373
|
+
CeaUtils.createLineBreakCue(startTime, endTime),
|
|
374
|
+
CeaUtils.createDefaultCue(startTime, endTime, 't'),
|
|
375
|
+
];
|
|
376
|
+
|
|
377
|
+
const expectedCaption = {
|
|
378
|
+
stream,
|
|
379
|
+
cue: topLevelCue,
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
// Force out the new memory.
|
|
383
|
+
const caption = memory.forceEmit(startTime, endTime);
|
|
384
|
+
expect(caption).toEqual(expectedCaption);
|
|
385
|
+
});
|
|
298
386
|
});
|
|
299
387
|
});
|
|
@@ -90,17 +90,17 @@ describe('StreamingEngine', () => {
|
|
|
90
90
|
|
|
91
91
|
segmentAvailability = {
|
|
92
92
|
start: 0,
|
|
93
|
-
end:
|
|
93
|
+
end: 40,
|
|
94
94
|
};
|
|
95
95
|
|
|
96
96
|
timeline = shaka.test.StreamingEngineUtil.createFakePresentationTimeline(
|
|
97
97
|
segmentAvailability,
|
|
98
|
-
/* presentationDuration= */
|
|
98
|
+
/* presentationDuration= */ 40,
|
|
99
99
|
/* maxSegmentDuration= */ metadata.video.segmentDuration,
|
|
100
100
|
/* isLive= */ false);
|
|
101
101
|
|
|
102
102
|
setupNetworkingEngine(
|
|
103
|
-
/* presentationDuration= */
|
|
103
|
+
/* presentationDuration= */ 40,
|
|
104
104
|
{
|
|
105
105
|
audio: metadata.audio.segmentDuration,
|
|
106
106
|
video: metadata.video.segmentDuration,
|
|
@@ -108,8 +108,8 @@ describe('StreamingEngine', () => {
|
|
|
108
108
|
|
|
109
109
|
setupManifest(
|
|
110
110
|
/* firstPeriodStartTime= */ 0,
|
|
111
|
-
/* secondPeriodStartTime= */
|
|
112
|
-
/* presentationDuration= */
|
|
111
|
+
/* secondPeriodStartTime= */ 20,
|
|
112
|
+
/* presentationDuration= */ 40);
|
|
113
113
|
|
|
114
114
|
setupPlayhead();
|
|
115
115
|
|
|
@@ -263,10 +263,11 @@ describe('StreamingEngine', () => {
|
|
|
263
263
|
streamingEngine.switchVariant(variant);
|
|
264
264
|
await streamingEngine.start();
|
|
265
265
|
video.play();
|
|
266
|
-
// The overall test timeout is 120 seconds, and the content is
|
|
266
|
+
// The overall test timeout is 120 seconds, and the content is 40
|
|
267
267
|
// seconds. It should be possible to complete this test in 100 seconds,
|
|
268
268
|
// and if not, we want the error thrown to be within the overall test's
|
|
269
|
-
// timeout window.
|
|
269
|
+
// timeout window. Note that we have seen some devices fail to play at
|
|
270
|
+
// full speed for reasons beyond our control, so we plan for >= 0.5x.
|
|
270
271
|
await waiter.timeoutAfter(100).waitForEnd(video);
|
|
271
272
|
});
|
|
272
273
|
|
|
@@ -68,7 +68,7 @@ filterDescribe('Offline', supportsStorage, () => {
|
|
|
68
68
|
await player.load(contentUri);
|
|
69
69
|
|
|
70
70
|
video.play();
|
|
71
|
-
await playTo(/* end= */ 3, /* timeout= */
|
|
71
|
+
await playTo(/* end= */ 3, /* timeout= */ 20);
|
|
72
72
|
await player.unload();
|
|
73
73
|
await storage.remove(contentUri);
|
|
74
74
|
});
|
|
@@ -104,7 +104,7 @@ filterDescribe('Offline', supportsStorage, () => {
|
|
|
104
104
|
await player.load(contentUri);
|
|
105
105
|
|
|
106
106
|
video.play();
|
|
107
|
-
await playTo(/* end= */ 3, /* timeout= */
|
|
107
|
+
await playTo(/* end= */ 3, /* timeout= */ 20);
|
|
108
108
|
await player.unload();
|
|
109
109
|
await storage.remove(contentUri);
|
|
110
110
|
});
|
|
@@ -147,7 +147,7 @@ filterDescribe('Offline', supportsStorage, () => {
|
|
|
147
147
|
await player.load(contentUri);
|
|
148
148
|
|
|
149
149
|
video.play();
|
|
150
|
-
await playTo(/* end= */ 3, /* timeout= */
|
|
150
|
+
await playTo(/* end= */ 3, /* timeout= */ 20);
|
|
151
151
|
await player.unload();
|
|
152
152
|
await storage.remove(contentUri);
|
|
153
153
|
});
|
|
@@ -1034,6 +1034,36 @@ filterDescribe('Storage', storageSupport, () => {
|
|
|
1034
1034
|
}
|
|
1035
1035
|
});
|
|
1036
1036
|
|
|
1037
|
+
/**
|
|
1038
|
+
* In some situations, indexedDB.open() can just hang, and call neither the
|
|
1039
|
+
* 'success' nor the 'error' callbacks.
|
|
1040
|
+
* I'm not sure what causes it, but it seems to happen consistently between
|
|
1041
|
+
* reloads when it does so it might be a browser-based issue.
|
|
1042
|
+
* In that case, we should time out with an error, instead of also hanging.
|
|
1043
|
+
*/
|
|
1044
|
+
it('throws an error if indexedDB open times out', async () => {
|
|
1045
|
+
const oldOpen = window.indexedDB.open;
|
|
1046
|
+
window.indexedDB.open = () => {
|
|
1047
|
+
// Just return a dummy object.
|
|
1048
|
+
return /** @type {!IDBOpenDBRequest} */ ({
|
|
1049
|
+
onsuccess: (event) => {},
|
|
1050
|
+
onerror: (error) => {},
|
|
1051
|
+
});
|
|
1052
|
+
};
|
|
1053
|
+
|
|
1054
|
+
/** @type {!shaka.offline.StorageMuxer} */
|
|
1055
|
+
const muxer = new shaka.offline.StorageMuxer();
|
|
1056
|
+
const expectedError = shaka.test.Util.jasmineError(new shaka.util.Error(
|
|
1057
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
1058
|
+
shaka.util.Error.Category.STORAGE,
|
|
1059
|
+
shaka.util.Error.Code.INDEXED_DB_INIT_TIMED_OUT));
|
|
1060
|
+
|
|
1061
|
+
await expectAsync(muxer.init())
|
|
1062
|
+
.toBeRejectedWith(expectedError);
|
|
1063
|
+
|
|
1064
|
+
window.indexedDB.open = oldOpen;
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1037
1067
|
it('throws an error if the content is a live stream', async () => {
|
|
1038
1068
|
const expected = Util.jasmineError(new shaka.util.Error(
|
|
1039
1069
|
shaka.util.Error.Severity.CRITICAL,
|
|
@@ -1535,6 +1535,52 @@ describe('TtmlTextParser', () => {
|
|
|
1535
1535
|
{startTime: 1, endTime: 2});
|
|
1536
1536
|
});
|
|
1537
1537
|
|
|
1538
|
+
// Regression test for #4468
|
|
1539
|
+
it('defaults the body background to transparent', () => {
|
|
1540
|
+
verifyHelper(
|
|
1541
|
+
// One cue, don't care about the details
|
|
1542
|
+
[{}],
|
|
1543
|
+
'<tt xmlns:tts="http://www.w3.org/ns/ttml#styling">' +
|
|
1544
|
+
'<head>' +
|
|
1545
|
+
'<styling>' +
|
|
1546
|
+
'<style xml:id="s1" tts:backgroundColor="black" />' +
|
|
1547
|
+
'</styling>' +
|
|
1548
|
+
'</head>' +
|
|
1549
|
+
'<body><div>' +
|
|
1550
|
+
'<p begin="00:01.00" end="00:02.00">' +
|
|
1551
|
+
'<span style="s1">Test</span>' +
|
|
1552
|
+
'</p>' +
|
|
1553
|
+
'</div></body></tt>',
|
|
1554
|
+
{periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0},
|
|
1555
|
+
// The body must have these properties:
|
|
1556
|
+
{backgroundColor: 'transparent'},
|
|
1557
|
+
// The div must have these properties:
|
|
1558
|
+
{}); // don't care
|
|
1559
|
+
});
|
|
1560
|
+
|
|
1561
|
+
// Regression test for #4468
|
|
1562
|
+
it('allows the body background color to be set', () => {
|
|
1563
|
+
verifyHelper(
|
|
1564
|
+
// One cue, don't care about the details
|
|
1565
|
+
[{}],
|
|
1566
|
+
'<tt xmlns:tts="http://www.w3.org/ns/ttml#styling">' +
|
|
1567
|
+
'<head>' +
|
|
1568
|
+
'<styling>' +
|
|
1569
|
+
'<style xml:id="s1" tts:backgroundColor="black" />' +
|
|
1570
|
+
'</styling>' +
|
|
1571
|
+
'</head>' +
|
|
1572
|
+
'<body style="s1"><div>' +
|
|
1573
|
+
'<p begin="00:01.00" end="00:02.00">' +
|
|
1574
|
+
'<span>Test</span>' +
|
|
1575
|
+
'</p>' +
|
|
1576
|
+
'</div></body></tt>',
|
|
1577
|
+
{periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0},
|
|
1578
|
+
// The body must have these properties:
|
|
1579
|
+
{backgroundColor: 'black'},
|
|
1580
|
+
// The div must have these properties:
|
|
1581
|
+
{}); // don't care
|
|
1582
|
+
});
|
|
1583
|
+
|
|
1538
1584
|
it('parses wrapping option', () => {
|
|
1539
1585
|
verifyHelper(
|
|
1540
1586
|
[
|
|
@@ -219,6 +219,21 @@ filterDescribe('TextDisplayer layout', supportsScreenshots, () => {
|
|
|
219
219
|
|
|
220
220
|
/** @param {string} prefix Prepended to screenshot names */
|
|
221
221
|
function defineTests(prefix) {
|
|
222
|
+
// Due to a Safari implementation bug, the browser only does the correct
|
|
223
|
+
// thing for "end-time-edge-case" on Safari 16+. Skip the tests on earlier
|
|
224
|
+
// versions.
|
|
225
|
+
const safariVersion = shaka.util.Platform.safariVersion();
|
|
226
|
+
if (prefix == 'native' && safariVersion && safariVersion < 16) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Due to updates in the rendering and/or default styles in Chrome, the
|
|
231
|
+
// screenshots for native rendering only match in Chrome 106+.
|
|
232
|
+
const chromeVersion = shaka.util.Platform.chromeVersion();
|
|
233
|
+
if (prefix == 'native' && chromeVersion && chromeVersion < 106) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
222
237
|
it('basic cue', async () => {
|
|
223
238
|
textDisplayer.append([
|
|
224
239
|
new shaka.text.Cue(0, 1, 'Captain\'s log, stardate 41636.9'),
|
|
@@ -262,8 +277,8 @@ filterDescribe('TextDisplayer layout', supportsScreenshots, () => {
|
|
|
262
277
|
|
|
263
278
|
// Regression test for #3151
|
|
264
279
|
// Only one cue should be displayed. Note, however, that we don't control
|
|
265
|
-
// this in a browser's native display. As of
|
|
266
|
-
// the right thing in native text display. Chrome
|
|
280
|
+
// this in a browser's native display. As of Sep 2022, both Firefox and
|
|
281
|
+
// Safari>=16 do the right thing in native text display. Chrome and Edge
|
|
267
282
|
// show both cues in this edge case. When we control the display of text
|
|
268
283
|
// through the UI & DOM, we can always get the timing right.
|
|
269
284
|
it('cues ending exactly now', async () => {
|
|
@@ -12,6 +12,14 @@ describe('PeriodCombiner', () => {
|
|
|
12
12
|
/** @type {shaka.util.PeriodCombiner} */
|
|
13
13
|
let combiner;
|
|
14
14
|
|
|
15
|
+
const makeAudioStreamWithRoles = (id, roles, primary = true) => {
|
|
16
|
+
const stream = makeAudioStream('en');
|
|
17
|
+
stream.originalId = id;
|
|
18
|
+
stream.roles = roles;
|
|
19
|
+
stream.primary = primary;
|
|
20
|
+
return stream;
|
|
21
|
+
};
|
|
22
|
+
|
|
15
23
|
beforeEach(() => {
|
|
16
24
|
combiner = new shaka.util.PeriodCombiner();
|
|
17
25
|
});
|
|
@@ -1127,20 +1135,149 @@ describe('PeriodCombiner', () => {
|
|
|
1127
1135
|
const variants = combiner.getVariants();
|
|
1128
1136
|
|
|
1129
1137
|
expect(variants).toEqual(jasmine.arrayWithExactContents([
|
|
1130
|
-
makeAVVariant(1080, 'en'),
|
|
1131
|
-
makeAVVariant(1080, 'en'),
|
|
1138
|
+
makeAVVariant(1080, 'en', 2, ['role1', 'role2']),
|
|
1139
|
+
makeAVVariant(1080, 'en', 2, ['role1']),
|
|
1132
1140
|
]));
|
|
1133
1141
|
|
|
1134
1142
|
// We can use the originalId field to see what each track is composed of.
|
|
1135
|
-
|
|
1136
|
-
expect(
|
|
1137
|
-
|
|
1143
|
+
expect(variants[0].audio.originalId).toBe('stream1,stream3');
|
|
1144
|
+
expect(variants[1].audio.originalId).toBe('stream2,stream4');
|
|
1145
|
+
});
|
|
1138
1146
|
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1147
|
+
it('Matches streams with roles in common', async () => {
|
|
1148
|
+
/** @type {!Array.<shaka.util.PeriodCombiner.Period>} */
|
|
1149
|
+
const periods = [
|
|
1150
|
+
{
|
|
1151
|
+
id: '1',
|
|
1152
|
+
videoStreams: [
|
|
1153
|
+
makeVideoStream(720),
|
|
1154
|
+
makeVideoStream(1080),
|
|
1155
|
+
],
|
|
1156
|
+
audioStreams: [
|
|
1157
|
+
makeAudioStreamWithRoles('stream1', ['main']),
|
|
1158
|
+
makeAudioStreamWithRoles('stream2', ['description'], false),
|
|
1159
|
+
],
|
|
1160
|
+
textStreams: [],
|
|
1161
|
+
imageStreams: [],
|
|
1162
|
+
},
|
|
1163
|
+
{
|
|
1164
|
+
id: '2',
|
|
1165
|
+
videoStreams: [
|
|
1166
|
+
makeVideoStream(720),
|
|
1167
|
+
makeVideoStream(1080),
|
|
1168
|
+
],
|
|
1169
|
+
audioStreams: [
|
|
1170
|
+
makeAudioStreamWithRoles('stream1', ['main']),
|
|
1171
|
+
makeAudioStreamWithRoles('stream2', ['description'], false),
|
|
1172
|
+
],
|
|
1173
|
+
textStreams: [],
|
|
1174
|
+
imageStreams: [],
|
|
1175
|
+
},
|
|
1176
|
+
];
|
|
1177
|
+
|
|
1178
|
+
await combiner.combinePeriods(periods, /* isDynamic= */ false);
|
|
1179
|
+
const variants = combiner.getVariants();
|
|
1180
|
+
|
|
1181
|
+
console.log(variants);
|
|
1182
|
+
|
|
1183
|
+
expect(variants.length).toBe(4);
|
|
1184
|
+
|
|
1185
|
+
expect(variants).toEqual(jasmine.arrayWithExactContents([
|
|
1186
|
+
makeAVVariant(720, 'en', 2, ['main']),
|
|
1187
|
+
makeAVVariant(1080, 'en', 2, ['main']),
|
|
1188
|
+
makeAVVariant(720, 'en', 2, ['description']),
|
|
1189
|
+
makeAVVariant(1080, 'en', 2, ['description']),
|
|
1190
|
+
]));
|
|
1191
|
+
|
|
1192
|
+
// We can use the originalId field to see what each track is composed of.
|
|
1193
|
+
expect(variants[0].audio.originalId).toBe('stream1,stream1');
|
|
1194
|
+
expect(variants[1].audio.originalId).toBe('stream1,stream1');
|
|
1195
|
+
expect(variants[2].audio.originalId).toBe('stream2,stream2');
|
|
1196
|
+
expect(variants[3].audio.originalId).toBe('stream2,stream2');
|
|
1142
1197
|
});
|
|
1143
1198
|
|
|
1199
|
+
it('Matches streams with mismatched roles', async () => {
|
|
1200
|
+
/** @type {!Array.<shaka.util.PeriodCombiner.Period>} */
|
|
1201
|
+
const periods = [
|
|
1202
|
+
{
|
|
1203
|
+
id: '0',
|
|
1204
|
+
videoStreams: [
|
|
1205
|
+
makeVideoStream(720),
|
|
1206
|
+
makeVideoStream(1080),
|
|
1207
|
+
],
|
|
1208
|
+
audioStreams: [
|
|
1209
|
+
makeAudioStreamWithRoles('stream1', ['main']),
|
|
1210
|
+
],
|
|
1211
|
+
textStreams: [],
|
|
1212
|
+
imageStreams: [],
|
|
1213
|
+
},
|
|
1214
|
+
{
|
|
1215
|
+
id: '1',
|
|
1216
|
+
videoStreams: [
|
|
1217
|
+
makeVideoStream(720),
|
|
1218
|
+
makeVideoStream(1080),
|
|
1219
|
+
],
|
|
1220
|
+
audioStreams: [
|
|
1221
|
+
makeAudioStreamWithRoles('stream1', ['main']),
|
|
1222
|
+
makeAudioStreamWithRoles('stream2', ['description'], false),
|
|
1223
|
+
],
|
|
1224
|
+
textStreams: [],
|
|
1225
|
+
imageStreams: [],
|
|
1226
|
+
},
|
|
1227
|
+
{
|
|
1228
|
+
id: '2',
|
|
1229
|
+
videoStreams: [
|
|
1230
|
+
makeVideoStream(720),
|
|
1231
|
+
makeVideoStream(1080),
|
|
1232
|
+
],
|
|
1233
|
+
audioStreams: [
|
|
1234
|
+
makeAudioStreamWithRoles('stream1', ['main']),
|
|
1235
|
+
],
|
|
1236
|
+
textStreams: [],
|
|
1237
|
+
imageStreams: [],
|
|
1238
|
+
},
|
|
1239
|
+
{
|
|
1240
|
+
id: '3',
|
|
1241
|
+
videoStreams: [
|
|
1242
|
+
makeVideoStream(720),
|
|
1243
|
+
makeVideoStream(1080),
|
|
1244
|
+
],
|
|
1245
|
+
audioStreams: [
|
|
1246
|
+
makeAudioStreamWithRoles('stream1', ['main']),
|
|
1247
|
+
makeAudioStreamWithRoles('stream2', ['description'], false),
|
|
1248
|
+
],
|
|
1249
|
+
textStreams: [],
|
|
1250
|
+
imageStreams: [],
|
|
1251
|
+
},
|
|
1252
|
+
];
|
|
1253
|
+
|
|
1254
|
+
await combiner.combinePeriods(periods, /* isDynamic= */ false);
|
|
1255
|
+
const variants = combiner.getVariants();
|
|
1256
|
+
|
|
1257
|
+
console.log(variants);
|
|
1258
|
+
|
|
1259
|
+
expect(variants.length).toBe(4);
|
|
1260
|
+
|
|
1261
|
+
expect(variants).toEqual(jasmine.arrayWithExactContents([
|
|
1262
|
+
makeAVVariant(720, 'en', 2, ['main']),
|
|
1263
|
+
makeAVVariant(1080, 'en', 2, ['main']),
|
|
1264
|
+
makeAVVariant(720, 'en', 2, ['description', 'main']),
|
|
1265
|
+
makeAVVariant(1080, 'en', 2, ['description', 'main']),
|
|
1266
|
+
]));
|
|
1267
|
+
|
|
1268
|
+
// We can use the originalId field to see what each track is composed of.
|
|
1269
|
+
expect(variants[0].audio.originalId)
|
|
1270
|
+
.toBe('stream1,stream1,stream1,stream1');
|
|
1271
|
+
|
|
1272
|
+
expect(variants[1].audio.originalId)
|
|
1273
|
+
.toBe('stream1,stream1,stream1,stream1');
|
|
1274
|
+
|
|
1275
|
+
expect(variants[2].audio.originalId)
|
|
1276
|
+
.toBe('stream1,stream2,stream1,stream2');
|
|
1277
|
+
|
|
1278
|
+
expect(variants[3].audio.originalId)
|
|
1279
|
+
.toBe('stream1,stream2,stream1,stream2');
|
|
1280
|
+
});
|
|
1144
1281
|
|
|
1145
1282
|
it('The number of variants stays stable after many periods ' +
|
|
1146
1283
|
'when going between similar content and varying ads', async () => {
|
|
@@ -1148,7 +1285,7 @@ describe('PeriodCombiner', () => {
|
|
|
1148
1285
|
// https://github.com/shaka-project/shaka-player/issues/2716
|
|
1149
1286
|
// that used to cause our period flattening logic to keep
|
|
1150
1287
|
// creating new variants for every new period added.
|
|
1151
|
-
// It's ok to create a few additional
|
|
1288
|
+
// It's ok to create a few additional variants/streams,
|
|
1152
1289
|
// but we should stabilize eventually and keep the number
|
|
1153
1290
|
// of variants from growing indefinitely.
|
|
1154
1291
|
|
|
@@ -1334,7 +1471,7 @@ describe('PeriodCombiner', () => {
|
|
|
1334
1471
|
expect(isCandidateBetter).toBe(WORSE);
|
|
1335
1472
|
|
|
1336
1473
|
// Make sure it works correctly whether it's the candidate or the best
|
|
1337
|
-
// value that is
|
|
1474
|
+
// value that is equal to the output.
|
|
1338
1475
|
isCandidateBetter = PeriodCombiner.compareClosestPreferLower(
|
|
1339
1476
|
/* output= */ 5, /* bestValue= */ 3, /* candidateValue= */ 5);
|
|
1340
1477
|
expect(isCandidateBetter).toBe(BETTER);
|
|
@@ -1461,11 +1598,12 @@ describe('PeriodCombiner', () => {
|
|
|
1461
1598
|
* @param {number=} channels
|
|
1462
1599
|
* @return {shaka.extern.Variant}
|
|
1463
1600
|
*/
|
|
1464
|
-
function makeAVVariant(height, language, channels = 2) {
|
|
1601
|
+
function makeAVVariant(height, language, channels = 2, roles = []) {
|
|
1465
1602
|
const variant = jasmine.objectContaining({
|
|
1466
1603
|
language,
|
|
1467
1604
|
audio: jasmine.objectContaining({
|
|
1468
1605
|
language,
|
|
1606
|
+
roles,
|
|
1469
1607
|
channelsCount: channels,
|
|
1470
1608
|
}),
|
|
1471
1609
|
video: jasmine.objectContaining({
|