shaka-player 4.1.4 → 4.1.6

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 (95) 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 +3 -0
  11. package/CHANGELOG.md +35 -0
  12. package/CONTRIBUTORS +3 -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 +6 -6
  18. package/dist/demo.compiled.debug.map +2 -2
  19. package/dist/demo.compiled.js +6 -6
  20. package/dist/demo.compiled.map +2 -2
  21. package/dist/deps.js +3 -3
  22. package/dist/locales.js +31 -31
  23. package/dist/receiver.compiled.debug.js +3 -3
  24. package/dist/receiver.compiled.debug.map +2 -2
  25. package/dist/receiver.compiled.js +3 -3
  26. package/dist/receiver.compiled.map +2 -2
  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 +233 -224
  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 +800 -797
  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 +286 -278
  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 +939 -935
  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/streaming_engine.js +79 -14
  57. package/lib/offline/indexeddb/storage_mechanism.js +24 -0
  58. package/lib/offline/storage.js +1 -0
  59. package/lib/player.js +17 -5
  60. package/lib/polyfill/media_capabilities.js +7 -6
  61. package/lib/text/ttml_text_parser.js +69 -87
  62. package/lib/text/vtt_text_parser.js +1 -1
  63. package/lib/util/error.js +7 -0
  64. package/lib/util/periods.js +9 -9
  65. package/lib/util/platform.js +19 -5
  66. package/lib/util/player_configuration.js +4 -0
  67. package/lib/util/pssh.js +2 -1
  68. package/lib/util/stream_utils.js +2 -1
  69. package/lib/util/xml_utils.js +45 -6
  70. package/package.json +1 -1
  71. package/test/cea/cea608_memory_unit.js +88 -0
  72. package/test/dash/dash_parser_content_protection_unit.js +1 -0
  73. package/test/dash/dash_parser_live_unit.js +1 -0
  74. package/test/dash/dash_parser_manifest_unit.js +1 -0
  75. package/test/dash/dash_parser_segment_base_unit.js +1 -0
  76. package/test/dash/dash_parser_segment_list_unit.js +1 -0
  77. package/test/dash/dash_parser_segment_template_unit.js +1 -0
  78. package/test/hls/hls_live_unit.js +169 -247
  79. package/test/hls/hls_parser_unit.js +60 -2
  80. package/test/media/drm_engine_unit.js +61 -0
  81. package/test/media/media_source_engine_integration.js +36 -0
  82. package/test/media/media_source_engine_unit.js +118 -1
  83. package/test/media/streaming_engine_integration.js +9 -7
  84. package/test/media/streaming_engine_unit.js +29 -0
  85. package/test/offline/offline_integration.js +3 -3
  86. package/test/offline/storage_integration.js +30 -0
  87. package/test/player_integration.js +2 -2
  88. package/test/test/util/dash_parser_util.js +2 -0
  89. package/test/test/util/ttml_utils.js +0 -2
  90. package/test/text/ttml_text_parser_unit.js +46 -0
  91. package/test/text/vtt_text_parser_unit.js +23 -1
  92. package/test/ui/text_displayer_layout_unit.js +17 -2
  93. package/test/util/periods_unit.js +149 -11
  94. package/test/util/xml_utils_unit.js +64 -0
  95. package/ui/controls.js +0 -7
@@ -75,6 +75,10 @@ shaka.util.PlayerConfiguration = class {
75
75
  updateExpirationTime: 1,
76
76
  preferredKeySystems: [],
77
77
  keySystemsMapping: {},
78
+ // The Xbox One browser does not detect DRM key changes signalled by a
79
+ // change in the PSSH in media segments. We need to parse PSSH from media
80
+ // segments to detect key changes.
81
+ parseInbandPsshEnabled: shaka.util.Platform.isXboxOne(),
78
82
  };
79
83
 
80
84
  const manifest = {
package/lib/util/pssh.js CHANGED
@@ -42,11 +42,12 @@ shaka.util.Pssh = class {
42
42
 
43
43
  new shaka.util.Mp4Parser()
44
44
  .box('moov', shaka.util.Mp4Parser.children)
45
+ .box('moof', shaka.util.Mp4Parser.children)
45
46
  .fullBox('pssh', (box) => this.parsePsshBox_(box))
46
47
  .parse(psshBox);
47
48
 
48
49
  if (this.data.length == 0) {
49
- shaka.log.warning('No pssh box found!');
50
+ shaka.log.v2('No pssh box found!');
50
51
  }
51
52
  }
52
53
 
@@ -474,7 +474,8 @@ shaka.util.StreamUtils = class {
474
474
  if (shaka.util.Platform.isXboxOne() && video &&
475
475
  ((video.width && video.width > 1920) ||
476
476
  (video.height && video.height > 1080)) &&
477
- video.codecs.includes('avc1.')) {
477
+ (video.codecs.includes('avc1.') ||
478
+ video.codecs.includes('avc3.'))) {
478
479
  shaka.log.debug('Dropping variant - not compatible with platform',
479
480
  shaka.util.StreamUtils.getVariantSummaryString_(variant));
480
481
  return false;
@@ -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
- let xml = null;
352
+ const unsafeXmlString =
353
+ shaka.util.XmlUtils.trustedHTMLFromString_.value()(xmlString);
354
+ let unsafeXml = null;
352
355
  try {
353
- xml = parser.parseFromString(xmlString, 'text/xml');
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(xml, 'Parsed XML document cannot be null!');
363
+ goog.asserts.assert(unsafeXml, 'Parsed XML document cannot be null!');
361
364
 
362
365
  // Check for empty documents.
363
- const rootElem = xml.documentElement;
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 (xml.documentElement.tagName != expectedRootElemName) {
380
+ if (rootElem.tagName != expectedRootElemName) {
378
381
  shaka.log.error(
379
382
  `XML tag name does not match expected "${expectedRootElemName}":`,
380
- xml.documentElement.tagName);
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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shaka-player",
3
3
  "description": "DASH/EME video player library",
4
- "version": "4.1.4",
4
+ "version": "4.1.6",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -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
  });
@@ -43,6 +43,7 @@ describe('DashParser ContentProtection', () => {
43
43
  isLowLatencyMode: () => false,
44
44
  isAutoLowLatencyMode: () => false,
45
45
  enableLowLatencyMode: () => {},
46
+ updateDuration: () => {},
46
47
  };
47
48
 
48
49
  const actual = await dashParser.start(
@@ -36,6 +36,7 @@ describe('DashParser Live', () => {
36
36
  isLowLatencyMode: () => false,
37
37
  isAutoLowLatencyMode: () => false,
38
38
  enableLowLatencyMode: () => {},
39
+ updateDuration: () => {},
39
40
  };
40
41
  });
41
42
 
@@ -52,6 +52,7 @@ describe('DashParser Manifest', () => {
52
52
  isLowLatencyMode: () => false,
53
53
  isAutoLowLatencyMode: () => false,
54
54
  enableLowLatencyMode: () => {},
55
+ updateDuration: () => {},
55
56
  };
56
57
  });
57
58
 
@@ -38,6 +38,7 @@ describe('DashParser SegmentBase', () => {
38
38
  isLowLatencyMode: () => false,
39
39
  isAutoLowLatencyMode: () => false,
40
40
  enableLowLatencyMode: () => {},
41
+ updateDuration: () => {},
41
42
  };
42
43
  });
43
44
 
@@ -348,6 +348,7 @@ describe('DashParser SegmentList', () => {
348
348
  isLowLatencyMode: () => false,
349
349
  isAutoLowLatencyMode: () => false,
350
350
  enableLowLatencyMode: () => {},
351
+ updateDuration: () => {},
351
352
  };
352
353
  const manifest = await dashParser.start('dummy://foo', playerInterface);
353
354
  const stream = manifest.variants[0].video;
@@ -47,6 +47,7 @@ describe('DashParser SegmentTemplate', () => {
47
47
  isLowLatencyMode: () => false,
48
48
  isAutoLowLatencyMode: () => false,
49
49
  enableLowLatencyMode: () => {},
50
+ updateDuration: () => {},
50
51
  };
51
52
  });
52
53