shaka-player 4.2.1 → 4.2.3

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.
Files changed (97) hide show
  1. package/.github/workflows/build-and-test.yaml +42 -22
  2. package/.github/workflows/compute-incremental-coverage.py +14 -3
  3. package/.github/workflows/custom-actions/set-commit-status/action.yaml +1 -5
  4. package/.github/workflows/deflake.yaml +28 -0
  5. package/.github/workflows/demo-version-index.yaml +1 -1
  6. package/.github/workflows/nightly-demo.yaml +1 -1
  7. package/.github/workflows/release-please.yaml +5 -5
  8. package/.github/workflows/report-incremental-coverage.yaml +1 -1
  9. package/.github/workflows/selenium-lab-tests.yaml +21 -10
  10. package/AUTHORS +4 -0
  11. package/CHANGELOG.md +36 -0
  12. package/CONTRIBUTORS +4 -0
  13. package/demo/common/message_ids.js +1 -0
  14. package/demo/config.js +3 -1
  15. package/demo/locales/en.json +1 -0
  16. package/demo/locales/source.json +4 -0
  17. package/dist/demo.compiled.debug.js +7 -6
  18. package/dist/demo.compiled.debug.map +3 -3
  19. package/dist/demo.compiled.js +7 -6
  20. package/dist/demo.compiled.map +3 -3
  21. package/dist/deps.js +4 -4
  22. package/dist/locales.js +32 -32
  23. package/dist/receiver.compiled.debug.js +4 -3
  24. package/dist/receiver.compiled.debug.map +3 -3
  25. package/dist/receiver.compiled.js +4 -3
  26. package/dist/receiver.compiled.map +3 -3
  27. package/dist/shaka-player.compiled.d.ts +3 -2
  28. package/dist/shaka-player.compiled.debug.d.ts +3 -2
  29. package/dist/shaka-player.compiled.debug.externs.js +25 -24
  30. package/dist/shaka-player.compiled.debug.js +268 -259
  31. package/dist/shaka-player.compiled.debug.map +4 -4
  32. package/dist/shaka-player.compiled.externs.js +25 -24
  33. package/dist/shaka-player.compiled.js +749 -746
  34. package/dist/shaka-player.compiled.map +4 -4
  35. package/dist/shaka-player.ui.d.ts +3 -2
  36. package/dist/shaka-player.ui.debug.d.ts +3 -2
  37. package/dist/shaka-player.ui.debug.externs.js +25 -24
  38. package/dist/shaka-player.ui.debug.js +243 -234
  39. package/dist/shaka-player.ui.debug.map +4 -4
  40. package/dist/shaka-player.ui.externs.js +25 -24
  41. package/dist/shaka-player.ui.js +908 -904
  42. package/dist/shaka-player.ui.map +4 -4
  43. package/docs/jsdoc-template/publish.js +33 -0
  44. package/docs/jsdoc-template/tmpl/container.tmpl +4 -1
  45. package/docs/jsdoc.conf.json +1 -0
  46. package/docs/tutorials/welcome.md +2 -2
  47. package/externs/shaka/manifest_parser.js +4 -1
  48. package/externs/shaka/player.js +7 -1
  49. package/lib/ads/client_side_ad_manager.js +12 -2
  50. package/lib/cast/cast_sender.js +39 -14
  51. package/lib/cea/cea608_memory.js +4 -0
  52. package/lib/hls/hls_parser.js +48 -15
  53. package/lib/media/drm_engine.js +59 -7
  54. package/lib/media/media_source_engine.js +43 -15
  55. package/lib/media/playhead.js +1 -1
  56. package/lib/media/segment_index.js +4 -1
  57. package/lib/media/streaming_engine.js +79 -14
  58. package/lib/offline/indexeddb/storage_mechanism.js +24 -0
  59. package/lib/offline/storage.js +1 -0
  60. package/lib/player.js +17 -5
  61. package/lib/polyfill/media_capabilities.js +7 -6
  62. package/lib/text/ttml_text_parser.js +69 -87
  63. package/lib/text/vtt_text_parser.js +1 -1
  64. package/lib/util/error.js +7 -0
  65. package/lib/util/periods.js +9 -9
  66. package/lib/util/platform.js +19 -5
  67. package/lib/util/player_configuration.js +4 -0
  68. package/lib/util/pssh.js +2 -1
  69. package/lib/util/stream_utils.js +2 -1
  70. package/lib/util/xml_utils.js +45 -6
  71. package/package.json +1 -1
  72. package/test/cea/cea608_memory_unit.js +88 -0
  73. package/test/dash/dash_parser_content_protection_unit.js +1 -0
  74. package/test/dash/dash_parser_live_unit.js +1 -0
  75. package/test/dash/dash_parser_manifest_unit.js +1 -0
  76. package/test/dash/dash_parser_segment_base_unit.js +1 -0
  77. package/test/dash/dash_parser_segment_list_unit.js +1 -0
  78. package/test/dash/dash_parser_segment_template_unit.js +1 -0
  79. package/test/hls/hls_live_unit.js +169 -247
  80. package/test/hls/hls_parser_unit.js +60 -2
  81. package/test/media/drm_engine_unit.js +61 -0
  82. package/test/media/media_source_engine_integration.js +36 -0
  83. package/test/media/media_source_engine_unit.js +118 -1
  84. package/test/media/segment_index_unit.js +28 -2
  85. package/test/media/streaming_engine_integration.js +9 -7
  86. package/test/media/streaming_engine_unit.js +29 -0
  87. package/test/offline/offline_integration.js +3 -3
  88. package/test/offline/storage_integration.js +30 -0
  89. package/test/player_integration.js +2 -2
  90. package/test/test/util/dash_parser_util.js +2 -0
  91. package/test/test/util/ttml_utils.js +0 -2
  92. package/test/text/ttml_text_parser_unit.js +46 -0
  93. package/test/text/vtt_text_parser_unit.js +23 -1
  94. package/test/ui/text_displayer_layout_unit.js +17 -2
  95. package/test/util/periods_unit.js +149 -11
  96. package/test/util/xml_utils_unit.js +64 -0
  97. package/ui/controls.js +0 -7
@@ -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
  [
@@ -597,7 +597,7 @@ describe('VttTextParser', () => {
597
597
  [
598
598
  {startTime: 95443, endTime: 95445, payload: 'Test'},
599
599
  ],
600
- // 8589870000/900000 = 95443 sec, so expect every timestamp to be 95443
600
+ // 8589870000/90000 = 95443 sec, so expect every timestamp to be 95443
601
601
  // seconds ahead of what is specified.
602
602
  'WEBVTT\n' +
603
603
  'X-TIMESTAMP-MAP=MPEGTS:8589870000,LOCAL:00:00:00.000\n\n' +
@@ -623,6 +623,28 @@ describe('VttTextParser', () => {
623
623
  /* sequenceMode= */ true);
624
624
  });
625
625
 
626
+ // A mock-up of HLS live subs as seen in b/253104251.
627
+ it('handles timestamp rollover and negative offset in HLS live', () => {
628
+ // Similar to values seen in b/253104251, for a realistic regression test.
629
+ // When using sequence mode on live HLS, we get negative offsets that
630
+ // represent the timestamp of our first append in sequence mode.
631
+ verifyHelper(
632
+ [
633
+ {startTime: 3600, endTime: 3602, payload: 'Test'},
634
+ ],
635
+ 'WEBVTT\n' +
636
+ 'X-TIMESTAMP-MAP=MPEGTS:8355814896,LOCAL:00:00:00.000\n\n' +
637
+ '00:00:00.000 --> 00:00:02.000 line:0\n' +
638
+ 'Test',
639
+ {
640
+ periodStart: -1234567,
641
+ segmentStart: 3600,
642
+ segmentEnd: 3610,
643
+ vttOffset: -1234567,
644
+ },
645
+ /* sequenceMode= */ true);
646
+ });
647
+
626
648
  it('supports global style blocks', () => {
627
649
  const textShadow = '-1px 0 black, 0 1px black, 1px 0 black, 0 -1px black';
628
650
  verifyHelper(
@@ -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 Feb 2021, only Firefox does
266
- // the right thing in native text display. Chrome, Edge, and Safari all
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
- const audio1 = variants[0].audio;
1136
- expect(audio1.roles).toEqual(['role1', 'role2']);
1137
- expect(audio1.originalId).toBe('stream1,stream3');
1143
+ expect(variants[0].audio.originalId).toBe('stream1,stream3');
1144
+ expect(variants[1].audio.originalId).toBe('stream2,stream4');
1145
+ });
1138
1146
 
1139
- const audio2 = variants[1].audio;
1140
- expect(audio2.roles).toEqual(['role1']);
1141
- expect(audio2.originalId).toBe('stream2,stream4');
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 varinats/streams,
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 equel to the output.
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({
@@ -371,5 +371,69 @@ describe('XmlUtils', () => {
371
371
  expect(XmlUtils.parseFloat(HUGE_NUMBER_STRING)).toBe(Infinity);
372
372
  expect(XmlUtils.parseFloat('-' + HUGE_NUMBER_STRING)).toBe(-Infinity);
373
373
  });
374
+
375
+ describe('parseXmlString', () => {
376
+ it('parses a simple XML document', () => {
377
+ const xmlString = [
378
+ '<?xml version="1.0"?>',
379
+ '<Root>',
380
+ ' <Child></Child>',
381
+ '</Root>',
382
+ ].join('\n');
383
+ const doc = XmlUtils.parseXmlString(xmlString, 'Root');
384
+ expect(doc).not.toBeNull();
385
+ expect(doc.tagName).toBe('Root');
386
+ });
387
+
388
+ it('returns null on an empty XML document', () => {
389
+ const xmlString = '';
390
+ const doc = XmlUtils.parseXmlString(xmlString, 'Root');
391
+ expect(doc).toBeNull();
392
+ });
393
+
394
+ it('returns null on malformed XML', () => {
395
+ const xmlString = [
396
+ '<?xml version="1.0"?>',
397
+ '<Root>',
398
+ ' <Child</Child>',
399
+ '</Root>',
400
+ ].join('\n');
401
+ const doc = XmlUtils.parseXmlString(xmlString, 'Root');
402
+ expect(doc).toBeNull();
403
+ });
404
+
405
+ it('returns null on root element mismatch', () => {
406
+ const xmlString = [
407
+ '<?xml version="1.0"?>',
408
+ '<Root>',
409
+ ' <Child></Child>',
410
+ '</Root>',
411
+ ].join('\n');
412
+ const doc = XmlUtils.parseXmlString(xmlString, 'Document');
413
+ expect(doc).toBeNull();
414
+ });
415
+
416
+ it('returns null on XML that embeds HTML', () => {
417
+ const xmlString = [
418
+ '<?xml version="1.0"?>',
419
+ '<Root>',
420
+ ' <Child xmlns="http://www.w3.org/1999/xhtml"></Child>',
421
+ '</Root>',
422
+ ].join('\n');
423
+ const doc = XmlUtils.parseXmlString(xmlString, 'Root');
424
+ expect(doc).toBeNull();
425
+ });
426
+
427
+ it('returns null on XML that embeds SVG', () => {
428
+ const xmlString = [
429
+ '<?xml version="1.0"?>',
430
+ '<Root>',
431
+ ' <Child xmlns="http://www.w3.org/2000/svg"></Child>',
432
+ '</Root>',
433
+ ].join('\n');
434
+ const doc = XmlUtils.parseXmlString(xmlString, 'Root');
435
+ expect(doc).toBeNull();
436
+ });
437
+ });
374
438
  });
375
439
 
package/ui/controls.js CHANGED
@@ -1011,13 +1011,6 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
1011
1011
  await this.onScreenRotation_();
1012
1012
  });
1013
1013
  }
1014
-
1015
- this.eventManager_.listen(document, 'fullscreenchange', () => {
1016
- if (this.ad_) {
1017
- this.ad_.resize(
1018
- this.localVideo_.offsetWidth, this.localVideo_.offsetHeight);
1019
- }
1020
- });
1021
1014
  }
1022
1015
 
1023
1016