shaka-player 3.1.2 → 3.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 (145) hide show
  1. package/.github/workflows/build_and_test.yaml +73 -0
  2. package/.github/workflows/release.yaml +53 -0
  3. package/AUTHORS +3 -0
  4. package/CHANGELOG.md +170 -1
  5. package/CONTRIBUTORS +3 -0
  6. package/build/checkversion.py +1 -1
  7. package/build/generateExterns.js +1 -1
  8. package/build/misspellings.txt +1 -0
  9. package/build/shakaBuildHelpers.py +6 -5
  10. package/build/subprocessWindowsPatch.py +103 -0
  11. package/build/test.py +6 -0
  12. package/demo/common/assets.js +1 -1
  13. package/dist/controls.css +2 -2
  14. package/dist/controls.css.map +1 -1
  15. package/dist/demo.compiled.debug.js +1 -1
  16. package/dist/demo.compiled.js +1 -1
  17. package/dist/deps.js +7 -7
  18. package/dist/locales.js +35 -35
  19. package/dist/receiver.compiled.debug.js +1 -1
  20. package/dist/receiver.compiled.js +1 -1
  21. package/dist/shaka-player.compiled.d.ts +172 -3
  22. package/dist/shaka-player.compiled.debug.d.ts +172 -3
  23. package/dist/shaka-player.compiled.debug.externs.js +184 -1
  24. package/dist/shaka-player.compiled.debug.js +390 -370
  25. package/dist/shaka-player.compiled.debug.map +3 -3
  26. package/dist/shaka-player.compiled.externs.js +184 -1
  27. package/dist/shaka-player.compiled.js +809 -805
  28. package/dist/shaka-player.compiled.map +3 -3
  29. package/dist/shaka-player.ui.d.ts +174 -14
  30. package/dist/shaka-player.ui.debug.d.ts +174 -14
  31. package/dist/shaka-player.ui.debug.externs.js +200 -26
  32. package/dist/shaka-player.ui.debug.js +431 -412
  33. package/dist/shaka-player.ui.debug.map +4 -4
  34. package/dist/shaka-player.ui.externs.js +200 -26
  35. package/dist/shaka-player.ui.js +934 -929
  36. package/dist/shaka-player.ui.map +4 -4
  37. package/docs/tutorials/fairplay.md +1 -1
  38. package/docs/tutorials/faq.md +14 -0
  39. package/externs/ima.js +4 -0
  40. package/externs/shaka/ads.js +2 -0
  41. package/externs/shaka/text.js +10 -0
  42. package/karma.conf.js +20 -15
  43. package/lib/ads/ad_manager.js +30 -11
  44. package/lib/ads/client_side_ad_manager.js +76 -86
  45. package/lib/ads/server_side_ad_manager.js +26 -11
  46. package/lib/cast/cast_proxy.js +6 -2
  47. package/lib/cast/cast_receiver.js +5 -1
  48. package/lib/cast/cast_sender.js +2 -2
  49. package/lib/cea/sei_processor.js +24 -17
  50. package/lib/dash/dash_parser.js +131 -24
  51. package/lib/dash/segment_list.js +1 -1
  52. package/lib/dash/segment_template.js +1 -1
  53. package/lib/hls/hls_parser.js +44 -17
  54. package/lib/hls/manifest_text_parser.js +1 -0
  55. package/lib/media/closed_caption_parser.js +3 -1
  56. package/lib/media/content_workarounds.js +12 -0
  57. package/lib/media/drm_engine.js +20 -1
  58. package/lib/media/gap_jumping_controller.js +8 -4
  59. package/lib/media/media_source_engine.js +1 -2
  60. package/lib/media/playhead.js +1 -1
  61. package/lib/media/region_observer.js +39 -33
  62. package/lib/media/region_timeline.js +17 -19
  63. package/lib/media/streaming_engine.js +8 -4
  64. package/lib/net/networking_engine.js +6 -1
  65. package/lib/offline/storage.js +10 -11
  66. package/lib/player.js +66 -30
  67. package/lib/polyfill/encryption_scheme.js +2 -0
  68. package/lib/polyfill/fullscreen.js +2 -0
  69. package/lib/polyfill/mathround.js +2 -0
  70. package/lib/polyfill/media_capabilities.js +73 -21
  71. package/lib/polyfill/mediasource.js +8 -0
  72. package/lib/polyfill/orientation.js +3 -1
  73. package/lib/polyfill/patchedmediakeys_apple.js +6 -4
  74. package/lib/polyfill/patchedmediakeys_ms.js +6 -4
  75. package/lib/polyfill/patchedmediakeys_nop.js +2 -0
  76. package/lib/polyfill/patchedmediakeys_webkit.js +6 -4
  77. package/lib/polyfill/pip_webkit.js +2 -0
  78. package/lib/polyfill/storage_estimate.js +2 -0
  79. package/lib/polyfill/video_play_promise.js +2 -0
  80. package/lib/polyfill/videoplaybackquality.js +2 -0
  81. package/lib/polyfill/vttcue.js +2 -0
  82. package/lib/text/cue.js +6 -0
  83. package/lib/text/mp4_ttml_parser.js +3 -0
  84. package/lib/text/text_engine.js +14 -2
  85. package/lib/text/ttml_text_parser.js +135 -87
  86. package/lib/text/ui_text_displayer.js +213 -80
  87. package/lib/text/vtt_text_parser.js +4 -2
  88. package/lib/util/fake_event.js +53 -7
  89. package/lib/util/fake_event_target.js +21 -1
  90. package/lib/util/mp4_box_parsers.js +1 -1
  91. package/lib/util/periods.js +4 -1
  92. package/lib/util/platform.js +13 -1
  93. package/lib/util/stream_utils.js +77 -10
  94. package/lib/util/xml_utils.js +19 -0
  95. package/package.json +3 -6
  96. package/test/cast/cast_proxy_unit.js +12 -6
  97. package/test/cast/cast_receiver_integration.js +1 -1
  98. package/test/cast/cast_utils_unit.js +1 -1
  99. package/test/cea/mp4_cea_parser_unit.js +26 -0
  100. package/test/dash/dash_parser_live_unit.js +43 -0
  101. package/test/dash/dash_parser_manifest_unit.js +222 -3
  102. package/test/hls/hls_parser_unit.js +79 -1
  103. package/test/media/closed_caption_parser_unit.js +20 -0
  104. package/test/media/drm_engine_unit.js +56 -0
  105. package/test/media/playhead_unit.js +27 -1
  106. package/test/media/region_observer_unit.js +40 -5
  107. package/test/media/region_timeline_unit.js +40 -22
  108. package/test/media/streaming_engine_integration.js +2 -1
  109. package/test/media/streaming_engine_unit.js +145 -0
  110. package/test/player_integration.js +38 -0
  111. package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
  112. package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
  113. package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
  114. package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
  115. package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
  116. package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
  117. package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
  118. package/test/test/assets/3675/dash_0.mpd +30 -0
  119. package/test/test/assets/3675/dash_1.mpd +30 -0
  120. package/test/test/assets/3675/dash_2.mpd +30 -0
  121. package/test/test/assets/3675/dash_3.mpd +30 -0
  122. package/test/test/assets/3675/dash_4.mpd +30 -0
  123. package/test/test/assets/3675/dash_5.mpd +26 -0
  124. package/test/test/assets/3675/init-stream0.m4s +0 -0
  125. package/test/test/assets/empty_caption_video_init.mp4 +0 -0
  126. package/test/test/assets/empty_caption_video_segment.mp4 +0 -0
  127. package/test/test/util/fake_ad_manager.js +4 -1
  128. package/test/test/util/ttml_utils.js +81 -0
  129. package/test/test/util/util.js +10 -7
  130. package/test/text/mp4_ttml_parser_unit.js +11 -18
  131. package/test/text/ttml_text_parser_unit.js +542 -220
  132. package/test/text/ui_text_displayer_unit.js +93 -20
  133. package/test/text/vtt_text_parser_unit.js +17 -1
  134. package/test/ui/localization_unit.js +15 -14
  135. package/test/ui/text_displayer_layout_unit.js +21 -8
  136. package/test/util/mp4_box_parsers_unit.js +28 -0
  137. package/test/util/stream_utils_unit.js +59 -2
  138. package/ui/cast_button.js +2 -3
  139. package/ui/controls.js +11 -6
  140. package/ui/less/buttons.less +0 -4
  141. package/ui/less/containers.less +6 -7
  142. package/ui/locales/ru.json +6 -6
  143. package/ui/localization.js +40 -87
  144. package/ui/pip_button.js +2 -3
  145. package/ui/text_selection.js +1 -0
@@ -337,9 +337,44 @@ shaka.util.StreamUtils = class {
337
337
  'MediaCapabilities should be valid.');
338
338
 
339
339
  await shaka.util.StreamUtils.getDecodingInfosForVariants(
340
- manifest.variants, usePersistentLicenses);
340
+ manifest.variants, usePersistentLicenses, /* srcEquals= */ false);
341
341
  manifest.variants = manifest.variants.filter((variant) => {
342
+ // See: https://github.com/google/shaka-player/issues/3860
342
343
  const video = variant.video;
344
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
345
+ if (video) {
346
+ let videoCodecs = shaka.util.StreamUtils.patchVp9(video.codecs);
347
+ // For multiplexed streams. Here we must check the audio of the
348
+ // stream to see if it is compatible.
349
+ if (video.codecs.includes(',')) {
350
+ const allCodecs = video.codecs.split(',');
351
+ videoCodecs = shaka.util.ManifestParserUtils.guessCodecs(
352
+ ContentType.VIDEO, allCodecs);
353
+ const audioCodecs = shaka.util.ManifestParserUtils.guessCodecs(
354
+ ContentType.AUDIO, allCodecs);
355
+ const audioFullType = shaka.util.MimeUtils.getFullOrConvertedType(
356
+ video.mimeType, audioCodecs, ContentType.AUDIO);
357
+ if (!MediaSource.isTypeSupported(audioFullType)) {
358
+ return false;
359
+ }
360
+ }
361
+ const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
362
+ video.mimeType, videoCodecs, ContentType.VIDEO);
363
+ if (!MediaSource.isTypeSupported(fullType)) {
364
+ return false;
365
+ }
366
+ }
367
+ const audio = variant.audio;
368
+ if (audio) {
369
+ const codecs =
370
+ shaka.util.StreamUtils.getCorrectAudioCodecs_(audio.codecs);
371
+ const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
372
+ audio.mimeType, codecs, ContentType.AUDIO);
373
+ if (!MediaSource.isTypeSupported(fullType)) {
374
+ return false;
375
+ }
376
+ }
377
+
343
378
  // See: https://github.com/google/shaka-player/issues/3380
344
379
  if (shaka.util.Platform.isXboxOne() && video &&
345
380
  ((video.width && video.width > 1920) ||
@@ -410,9 +445,11 @@ shaka.util.StreamUtils = class {
410
445
  *
411
446
  * @param {!Array.<shaka.extern.Variant>} variants
412
447
  * @param {boolean} usePersistentLicenses
448
+ * @param {boolean} srcEquals
413
449
  * @exportDoc
414
450
  */
415
- static async getDecodingInfosForVariants(variants, usePersistentLicenses) {
451
+ static async getDecodingInfosForVariants(variants, usePersistentLicenses,
452
+ srcEquals) {
416
453
  const gotDecodingInfo = variants.some((variant) =>
417
454
  variant.decodingInfos.length);
418
455
  if (gotDecodingInfo) {
@@ -436,7 +473,7 @@ shaka.util.StreamUtils = class {
436
473
  for (const variant of variants) {
437
474
  /** @type {!Array.<!MediaDecodingConfiguration>} */
438
475
  const decodingConfigs = shaka.util.StreamUtils.getDecodingConfigs_(
439
- variant, usePersistentLicenses);
476
+ variant, usePersistentLicenses, srcEquals);
440
477
 
441
478
  for (const config of decodingConfigs) {
442
479
  operations.push(getVariantDecodingInfos(variant, config));
@@ -451,17 +488,18 @@ shaka.util.StreamUtils = class {
451
488
  * results for each variant.
452
489
  * @param {!shaka.extern.Variant} variant
453
490
  * @param {boolean} usePersistentLicenses
491
+ * @param {boolean} srcEquals
454
492
  * @return {!Array.<!MediaDecodingConfiguration>}
455
493
  * @private
456
494
  */
457
- static getDecodingConfigs_(variant, usePersistentLicenses) {
495
+ static getDecodingConfigs_(variant, usePersistentLicenses, srcEquals) {
458
496
  const audio = variant.audio;
459
497
  const video = variant.video;
460
498
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
461
499
 
462
500
  /** @type {!MediaDecodingConfiguration} */
463
501
  const mediaDecodingConfig = {
464
- type: 'media-source',
502
+ type: srcEquals ? 'file' : 'media-source',
465
503
  };
466
504
 
467
505
  if (video) {
@@ -498,14 +536,23 @@ shaka.util.StreamUtils = class {
498
536
  // framerate must be greater than 0, otherwise the config is invalid.
499
537
  framerate: video.frameRate || 1,
500
538
  };
539
+ if (video.hdr) {
540
+ switch (video.hdr) {
541
+ case 'SDR':
542
+ mediaDecodingConfig.video.transferFunction = 'srgb';
543
+ break;
544
+ case 'PQ':
545
+ mediaDecodingConfig.video.transferFunction = 'pq';
546
+ break;
547
+ case 'HLG':
548
+ mediaDecodingConfig.video.transferFunction = 'hlg';
549
+ break;
550
+ }
551
+ }
501
552
  }
502
553
  if (audio) {
503
- // Some Tizen devices seem to misreport AC-3 support, but correctly
504
- // report EC-3 support. So query EC-3 as a fallback for AC-3.
505
- // See https://github.com/google/shaka-player/issues/2989 for details.
506
554
  const codecs =
507
- (audio.codecs.toLowerCase() == 'ac-3' &&
508
- shaka.util.Platform.isTizen()) ? 'ec-3' : audio.codecs;
555
+ shaka.util.StreamUtils.getCorrectAudioCodecs_(audio.codecs);
509
556
  const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
510
557
  audio.mimeType, codecs, ContentType.AUDIO);
511
558
 
@@ -616,6 +663,26 @@ shaka.util.StreamUtils = class {
616
663
  return configs;
617
664
  }
618
665
 
666
+
667
+ /**
668
+ * Generates the correct audio codec for MediaDecodingConfiguration and
669
+ * for MediaSource.isTypeSupported.
670
+ * @param {string} codecs
671
+ * @return {string}
672
+ * @private
673
+ */
674
+ static getCorrectAudioCodecs_(codecs) {
675
+ // Some Tizen devices seem to misreport AC-3 support, but correctly
676
+ // report EC-3 support. So query EC-3 as a fallback for AC-3.
677
+ // See https://github.com/google/shaka-player/issues/2989 for details.
678
+ if (shaka.util.Platform.isTizen()) {
679
+ return codecs.toLowerCase() == 'ac-3' ? 'ec-3' : codecs;
680
+ } else {
681
+ return codecs;
682
+ }
683
+ }
684
+
685
+
619
686
  /**
620
687
  * MediaCapabilities supports 'vp09...' codecs, but not 'vp9'. Translate vp9
621
688
  * codec strings into 'vp09...', to allow such content to play with
@@ -102,6 +102,25 @@ shaka.util.XmlUtils = class {
102
102
  }
103
103
 
104
104
 
105
+ /**
106
+ * Gets a namespace-qualified attribute.
107
+ * @param {!Element} elem The element to get from.
108
+ * @param {!Array.<string>} nsList The lis of namespace URIs.
109
+ * @param {string} name The local name of the attribute.
110
+ * @return {?string} The attribute's value, or null if not present.
111
+ */
112
+ static getAttributeNSList(elem, nsList, name) {
113
+ // Some browsers return the empty string when the attribute is missing,
114
+ // so check if it exists first. See: https://mzl.la/2L7F0UK
115
+ for (const ns of nsList) {
116
+ if (elem.hasAttributeNS(ns, name)) {
117
+ return elem.getAttributeNS(ns, name);
118
+ }
119
+ }
120
+ return null;
121
+ }
122
+
123
+
105
124
  /**
106
125
  * Gets the text contents of a node.
107
126
  * @param {!Node} elem The XML element.
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": "3.1.2",
4
+ "version": "3.1.6",
5
5
  "homepage": "https://github.com/google/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -11,7 +11,6 @@
11
11
  }
12
12
  ],
13
13
  "devDependencies": {
14
- "@chiragrupani/karma-chromium-edge-launcher": "^2.1.0",
15
14
  "@teppeis/clutz": "^1.0.29-4c95e12.v20190929",
16
15
  "awesomplete": "^1.1.4",
17
16
  "babel-core": "^6.26.3",
@@ -36,15 +35,12 @@
36
35
  "jsdoc": "github:joeyparrish/jsdoc#45c271fa",
37
36
  "karma": "^6.2.0",
38
37
  "karma-babel-preprocessor": "^7.0.0",
39
- "karma-chrome-launcher": "^2.2.0",
40
38
  "karma-coverage": "^1.1.2",
41
- "karma-edge-launcher": "^0.4.2",
42
- "karma-firefox-launcher": "^1.1.0",
43
39
  "karma-ie-launcher": "^1.0.0",
44
40
  "karma-jasmine": "^3.3.1",
45
41
  "karma-jasmine-ajax": "^0.1.13",
42
+ "karma-local-wd-launcher": "^1.4.1",
46
43
  "karma-opera-launcher": "^1.0.0",
47
- "karma-safari-launcher": "^1.0.0",
48
44
  "karma-sourcemap-loader": "^0.3.7",
49
45
  "karma-spec-reporter": "0.0.32",
50
46
  "karma-webdriver-launcher": "^1.0.8",
@@ -55,6 +51,7 @@
55
51
  "pwacompat": "^2.0.10",
56
52
  "rimraf": "^2.6.3",
57
53
  "sprintf-js": "^1.1.2",
54
+ "ssim.js": "^3.5.0",
58
55
  "stylelint": "^13.8.0",
59
56
  "stylelint-config-standard": "^20.0.0",
60
57
  "tippy.js": "^4.3.1",
@@ -297,7 +297,8 @@ describe('CastProxy', () => {
297
297
  'timeupdate', Util.spyFunc(proxyListener));
298
298
 
299
299
  expect(proxyListener).not.toHaveBeenCalled();
300
- const fakeEvent = new FakeEvent('timeupdate', {detail: 8675309});
300
+ const fakeEvent = new FakeEvent(
301
+ 'timeupdate', (new Map()).set('detail', 8675309));
301
302
  mockVideo.on['timeupdate'](fakeEvent);
302
303
  expect(proxyListener).toHaveBeenCalledWith(jasmine.objectContaining({
303
304
  type: 'timeupdate',
@@ -315,7 +316,8 @@ describe('CastProxy', () => {
315
316
  mockSender.hasRemoteProperties.and.returnValue(true);
316
317
 
317
318
  expect(proxyListener).not.toHaveBeenCalled();
318
- const fakeEvent = new FakeEvent('timeupdate', {detail: 8675309});
319
+ const fakeEvent = new FakeEvent(
320
+ 'timeupdate', (new Map()).set('detail', 8675309));
319
321
  mockVideo.on['timeupdate'](fakeEvent);
320
322
  expect(proxyListener).not.toHaveBeenCalled();
321
323
  });
@@ -332,7 +334,8 @@ describe('CastProxy', () => {
332
334
  mockSender.hasRemoteProperties.and.returnValue(true);
333
335
 
334
336
  expect(proxyListener).not.toHaveBeenCalled();
335
- const fakeEvent = new FakeEvent('timeupdate', {detail: 8675309});
337
+ const fakeEvent = new FakeEvent(
338
+ 'timeupdate', (new Map()).set('detail', 8675309));
336
339
  mockSender.onRemoteEvent('video', fakeEvent);
337
340
  expect(proxyListener).toHaveBeenCalledWith(jasmine.objectContaining({
338
341
  type: 'timeupdate',
@@ -446,7 +449,8 @@ describe('CastProxy', () => {
446
449
  'buffering', Util.spyFunc(proxyListener));
447
450
 
448
451
  expect(proxyListener).not.toHaveBeenCalled();
449
- const fakeEvent = new FakeEvent('buffering', {detail: 8675309});
452
+ const fakeEvent = new FakeEvent(
453
+ 'buffering', (new Map()).set('detail', 8675309));
450
454
  mockPlayer.listeners['buffering'](fakeEvent);
451
455
  expect(proxyListener).toHaveBeenCalledWith(jasmine.objectContaining({
452
456
  type: 'buffering',
@@ -464,7 +468,8 @@ describe('CastProxy', () => {
464
468
  mockSender.hasRemoteProperties.and.returnValue(true);
465
469
 
466
470
  expect(proxyListener).not.toHaveBeenCalled();
467
- const fakeEvent = new FakeEvent('buffering', {detail: 8675309});
471
+ const fakeEvent = new FakeEvent(
472
+ 'buffering', (new Map()).set('detail', 8675309));
468
473
  mockPlayer.listeners['buffering'](fakeEvent);
469
474
  expect(proxyListener).not.toHaveBeenCalled();
470
475
  });
@@ -481,7 +486,8 @@ describe('CastProxy', () => {
481
486
  mockSender.hasRemoteProperties.and.returnValue(true);
482
487
 
483
488
  expect(proxyListener).not.toHaveBeenCalled();
484
- const fakeEvent = new FakeEvent('buffering', {detail: 8675309});
489
+ const fakeEvent = new FakeEvent(
490
+ 'buffering', (new Map()).set('detail', 8675309));
485
491
  mockSender.onRemoteEvent('player', fakeEvent);
486
492
  expect(proxyListener).toHaveBeenCalledWith(jasmine.objectContaining({
487
493
  type: 'buffering',
@@ -170,7 +170,7 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
170
170
  // Check that the update message is of a reasonable size. From previous
171
171
  // testing we found that the socket would silently reject data that got
172
172
  // too big. 6KB is safely below the limit.
173
- expect(message.length).toBeLessThan(6 * 1024);
173
+ expect(message.length).toBeLessThan(7 * 1024);
174
174
  });
175
175
 
176
176
  drmIt('has reasonable average message size', async () => {
@@ -122,7 +122,7 @@ describe('CastUtils', () => {
122
122
  expect(typeof deserialized).toBe('object');
123
123
 
124
124
  // The object can be used to construct a FakeEvent.
125
- const fakeEvent = new FakeEvent(deserialized['type'], deserialized);
125
+ const fakeEvent = FakeEvent.fromRealEvent(deserialized);
126
126
 
127
127
  // The fake event has the same type and properties as the original.
128
128
  const asObj = /** @type {!Object} */ (fakeEvent);
@@ -27,6 +27,32 @@ describe('Mp4CeaParser', () => {
27
27
  ceaSegment = responses[1];
28
28
  });
29
29
 
30
+ /**
31
+ * Test only the functionality of removing EPB
32
+ * Expect removeEmu() to return the NALU with correct length
33
+ *
34
+ * Chromium VDA has a strict standard on NALU length
35
+ * It will complain about conformance if the array is malformed
36
+ *
37
+ * If EPB is removed by shifting bytes, it will return the original NALU
38
+ * length, which will fail this test
39
+ *
40
+ * Note that the CEA-608 packet in this test is incomplete
41
+ */
42
+ it('parses CEA-608 SEI data from MP4 H.264 stream', () => {
43
+ const seiProcessor = new shaka.cea.SeiProcessor();
44
+
45
+ const cea608Packet = new Uint8Array([
46
+ 0x00, 0x00, 0x03, // Emulation prevention byte
47
+ ]);
48
+
49
+ const naluData = seiProcessor.removeEmu(cea608Packet);
50
+ expect(naluData).toBeDefined();
51
+
52
+ // EPB should be removed by returning new array, not by shifting bytes
53
+ expect(naluData.length).toBe(2);
54
+ });
55
+
30
56
  it('parses cea data from mp4 stream', () => {
31
57
  const cea708Parser = new shaka.cea.Mp4CeaParser();
32
58
 
@@ -1289,6 +1289,49 @@ describe('DashParser Live', () => {
1289
1289
  .toHaveBeenCalledWith(
1290
1290
  jasmine.objectContaining({id: '2', startTime: 10, endTime: 25}));
1291
1291
  });
1292
+
1293
+ it('will not add timeline regions outside the DVR window', async () => {
1294
+ const manifest = [
1295
+ '<MPD type="dynamic" minimumUpdatePeriod="PT' + updateTime + 'S"',
1296
+ ' availabilityStartTime="1970-01-01T00:00:00Z"',
1297
+ ' suggestedPresentationDelay="PT0S"',
1298
+ ' timeShiftBufferDepth="PT30S">',
1299
+ ' <Period id="1">',
1300
+ ' <EventStream schemeIdUri="http://example.com" timescale="1">',
1301
+ ' <Event id="0" presentationTime="0" duration="10"/>',
1302
+ ' <Event id="1" presentationTime="10" duration="10"/>',
1303
+ ' <Event id="2" presentationTime="20" duration="10"/>',
1304
+ ' <Event id="3" presentationTime="30" duration="10"/>',
1305
+ ' <Event id="4" presentationTime="40" duration="10"/>',
1306
+ ' </EventStream>',
1307
+ ' <AdaptationSet mimeType="video/mp4">',
1308
+ ' <Representation bandwidth="1">',
1309
+ ' <SegmentTemplate startNumber="1" media="s$Number$.mp4"',
1310
+ ' duration="2" />',
1311
+ ' </Representation>',
1312
+ ' </AdaptationSet>',
1313
+ ' </Period>',
1314
+ '</MPD>',
1315
+ ].join('\n');
1316
+
1317
+ // 45 seconds after the epoch, which is also 45 seconds after the
1318
+ // presentation started. Only the last 30 seconds are available, from
1319
+ // time 15 to 45.
1320
+ Date.now = () => 45 * 1000;
1321
+
1322
+ fakeNetEngine.setResponseText('dummy://foo', manifest);
1323
+ await parser.start('dummy://foo', playerInterface);
1324
+
1325
+ expect(onTimelineRegionAddedSpy).toHaveBeenCalledTimes(4);
1326
+ expect(onTimelineRegionAddedSpy).toHaveBeenCalledWith(
1327
+ jasmine.objectContaining({id: '1'}));
1328
+ expect(onTimelineRegionAddedSpy).toHaveBeenCalledWith(
1329
+ jasmine.objectContaining({id: '2'}));
1330
+ expect(onTimelineRegionAddedSpy).toHaveBeenCalledWith(
1331
+ jasmine.objectContaining({id: '3'}));
1332
+ expect(onTimelineRegionAddedSpy).toHaveBeenCalledWith(
1333
+ jasmine.objectContaining({id: '4'}));
1334
+ });
1292
1335
  });
1293
1336
 
1294
1337
  it('honors clockSyncUri for in-progress recordings', async () => {
@@ -38,6 +38,14 @@ describe('DashParser Manifest', () => {
38
38
 
39
39
  /** @type {!string} */
40
40
  const thumbnailScheme = 'http://dashif.org/guidelines/thumbnail_tile';
41
+ /**
42
+ * CICP scheme. parameter must be one of the following: "ColourPrimaries",
43
+ * "TransferCharacteristics", or "MatrixCoefficients".
44
+ *
45
+ * @param {string} parameter
46
+ * @return {string}
47
+ */
48
+ const cicpScheme = (parameter) => `urn:mpeg:mpegB:cicp:${parameter}`;
41
49
 
42
50
  beforeAll(async () => {
43
51
  mp4Index = await shaka.test.Util.fetch(mp4IndexSegmentUri);
@@ -975,7 +983,8 @@ describe('DashParser Manifest', () => {
975
983
 
976
984
  it('duplicate Representation ids with live', async () => {
977
985
  const source = [
978
- '<MPD minBufferTime="PT75S" type="dynamic">',
986
+ '<MPD minBufferTime="PT75S" type="dynamic"',
987
+ ' availabilityStartTime="1970-01-01T00:00:00Z">',
979
988
  ' <Period id="1" duration="PT30S">',
980
989
  ' <AdaptationSet mimeType="video/mp4">',
981
990
  ' <Representation id="1" bandwidth="1">',
@@ -1822,7 +1831,7 @@ describe('DashParser Manifest', () => {
1822
1831
  expect(textStream.kind).toBe('caption');
1823
1832
  });
1824
1833
 
1825
- it('converts Roles element to "forced"', async () => {
1834
+ it('converts Roles element to "forced" (old role)', async () => {
1826
1835
  const manifestText = [
1827
1836
  '<MPD minBufferTime="PT75S">',
1828
1837
  ' <Period id="1" duration="PT30S">',
@@ -1854,7 +1863,40 @@ describe('DashParser Manifest', () => {
1854
1863
  expect(textStream.forced).toBe(true);
1855
1864
  });
1856
1865
 
1857
- it('supports HDR signaling', async () => {
1866
+ it('converts Roles element to "forced"', async () => {
1867
+ const manifestText = [
1868
+ '<MPD minBufferTime="PT75S">',
1869
+ ' <Period id="1" duration="PT30S">',
1870
+ ' <AdaptationSet id="1" contentType="text">',
1871
+ ' <Accessibility schemeIdUri="urn:mpeg:dash:role:2011" ',
1872
+ ' value="captions" />',
1873
+ ' <Accessibility schemeIdUri="urn:mpeg:dash:role:2011" ',
1874
+ ' value="forced-subtitle" />',
1875
+ ' <Accessibility schemeIdUri="foobar" value="bar" />',
1876
+ ' <Representation id="text-en" mimeType="text/webvtt">',
1877
+ ' <BaseURL>t-en.vtt</BaseURL>',
1878
+ ' </Representation>',
1879
+ ' </AdaptationSet>',
1880
+ ' <AdaptationSet id="1" mimeType="video/mp4">',
1881
+ ' <Representation id="video-sd" width="640" height="480">',
1882
+ ' <BaseURL>v-sd.mp4</BaseURL>',
1883
+ ' <SegmentBase indexRange="100-200" />',
1884
+ ' </Representation>',
1885
+ ' </AdaptationSet>',
1886
+ ' </Period>',
1887
+ '</MPD>',
1888
+ ].join('\n');
1889
+
1890
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
1891
+ /** @type {shaka.extern.Manifest} */
1892
+ const manifest = await parser.start('dummy://foo', playerInterface);
1893
+ const textStream = manifest.textStreams[0];
1894
+ expect(textStream.roles).toEqual(['captions', 'forced-subtitle']);
1895
+ expect(textStream.forced).toBe(true);
1896
+ });
1897
+
1898
+ it('supports HDR signaling via profiles', async () => {
1899
+ // (DASH-IF IOP v4.3 10.3.3.)
1858
1900
  const hdrProfile =
1859
1901
  'http://dashif.org/guidelines/dash-if-uhd#hevc-hdr-pq10';
1860
1902
  const manifestText = [
@@ -1885,6 +1927,110 @@ describe('DashParser Manifest', () => {
1885
1927
  expect(stream.hdr).toBe('PQ');
1886
1928
  });
1887
1929
 
1930
+ it('supports HDR signaling via SupplementalProperty', async () => {
1931
+ // (DASH-IF IOP v4.3 6.2.5.1.)
1932
+ const hdrScheme = cicpScheme('TransferCharacteristics');
1933
+ const pq = 16;
1934
+ const manifestText = [
1935
+ '<MPD minBufferTime="PT75S">',
1936
+ ' <Period id="1" duration="PT30S">',
1937
+ ' <AdaptationSet id="2" mimeType="video/mp4">',
1938
+ ` <SupplementalProperty schemeIdUri="${hdrScheme}" value="${pq}" />`,
1939
+ ' <Representation codecs="hvc1.2.4.L150.90">',
1940
+ ' <BaseURL>v-sd.mp4</BaseURL>',
1941
+ ' <SegmentBase indexRange="100-200" />',
1942
+ ' </Representation>',
1943
+ ' </AdaptationSet>',
1944
+ ' <AdaptationSet id="3" mimeType="audio/mp4">',
1945
+ ' <Representation id="audio-en">',
1946
+ ' <BaseURL>a-en.mp4</BaseURL>',
1947
+ ' <SegmentBase indexRange="100-200" />',
1948
+ ' </Representation>',
1949
+ ' </AdaptationSet>',
1950
+ ' </Period>',
1951
+ '</MPD>',
1952
+ ].join('\n');
1953
+
1954
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
1955
+
1956
+ /** @type {shaka.extern.Manifest} */
1957
+ const manifest = await parser.start('dummy://foo', playerInterface);
1958
+ expect(manifest.variants.length).toBe(1);
1959
+ const stream = manifest.variants[0].video;
1960
+ expect(stream.hdr).toBe('PQ');
1961
+ });
1962
+
1963
+ it('supports HDR signaling via EssentialProperty', async () => {
1964
+ // (DASH-IF IOP v4.3 6.2.5.1.)
1965
+ const hdrScheme = cicpScheme('TransferCharacteristics');
1966
+ const hlg = 18;
1967
+ const manifestText = [
1968
+ '<MPD minBufferTime="PT75S">',
1969
+ ' <Period id="1" duration="PT30S">',
1970
+ ' <AdaptationSet id="2" mimeType="video/mp4">',
1971
+ ` <EssentialProperty schemeIdUri="${hdrScheme}" value="${hlg}" />`,
1972
+ ' <Representation codecs="hvc1.2.4.L153.B0">',
1973
+ ' <BaseURL>v-sd.mp4</BaseURL>',
1974
+ ' <SegmentBase indexRange="100-200" />',
1975
+ ' </Representation>',
1976
+ ' </AdaptationSet>',
1977
+ ' <AdaptationSet id="3" mimeType="audio/mp4">',
1978
+ ' <Representation id="audio-en">',
1979
+ ' <BaseURL>a-en.mp4</BaseURL>',
1980
+ ' <SegmentBase indexRange="100-200" />',
1981
+ ' </Representation>',
1982
+ ' </AdaptationSet>',
1983
+ ' </Period>',
1984
+ '</MPD>',
1985
+ ].join('\n');
1986
+
1987
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
1988
+
1989
+ /** @type {shaka.extern.Manifest} */
1990
+ const manifest = await parser.start('dummy://foo', playerInterface);
1991
+ expect(manifest.variants.length).toBe(1);
1992
+ const stream = manifest.variants[0].video;
1993
+ expect(stream.hdr).toBe('HLG');
1994
+ });
1995
+
1996
+ it('supports SDR signalling via EssentialProperty', async () => {
1997
+ // (DASH-IF IOP v4.3 6.2.5.1.)
1998
+ const scheme = cicpScheme('TransferCharacteristics');
1999
+ const sdrValues = [1, 6, 13, 14, 15];
2000
+ const manifestPromises = [];
2001
+ for (const value of sdrValues) {
2002
+ const manifestText = [
2003
+ '<MPD minBufferTime="PT75S">',
2004
+ ' <Period id="1" duration="PT30S">',
2005
+ ' <AdaptationSet id="2" mimeType="video/mp4">',
2006
+ ` <EssentialProperty schemeIdUri="${scheme}" value="${value}" />`,
2007
+ ' <Representation codecs="avc1.640028">',
2008
+ ' <BaseURL>v-sd.mp4</BaseURL>',
2009
+ ' <SegmentBase indexRange="100-200" />',
2010
+ ' </Representation>',
2011
+ ' </AdaptationSet>',
2012
+ ' <AdaptationSet id="3" mimeType="audio/mp4">',
2013
+ ' <Representation id="audio-en">',
2014
+ ' <BaseURL>a-en.mp4</BaseURL>',
2015
+ ' <SegmentBase indexRange="100-200" />',
2016
+ ' </Representation>',
2017
+ ' </AdaptationSet>',
2018
+ ' </Period>',
2019
+ '</MPD>',
2020
+ ].join('\n');
2021
+
2022
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
2023
+
2024
+ manifestPromises.push(parser.start('dummy://foo', playerInterface));
2025
+ }
2026
+ const manifests = await Promise.all(manifestPromises);
2027
+ for (const manifest of manifests) {
2028
+ expect(manifest.variants.length).toBe(1);
2029
+ const stream = manifest.variants[0].video;
2030
+ expect(stream.hdr).toBe('SDR');
2031
+ }
2032
+ });
2033
+
1888
2034
  it('Does not error when image adaptation sets are present', async () => {
1889
2035
  const manifestText = [
1890
2036
  '<MPD minBufferTime="PT75S">',
@@ -2108,4 +2254,77 @@ describe('DashParser Manifest', () => {
2108
2254
  expect(uri).not.toContain('/p2/');
2109
2255
  }
2110
2256
  });
2257
+
2258
+ /**
2259
+ * @param {!Array.<number>} periods Start time of multiple periods
2260
+ * @return {string}
2261
+ */
2262
+ function buildManifestWithPeriodStartTime(periods) {
2263
+ const mpdTemplate = [
2264
+ `<MPD type="dynamic"`,
2265
+ 'availabilityStartTime="1970-01-01T00:00:00Z"',
2266
+ 'timeShiftBufferDepth="PT10H">',
2267
+ ' %(periods)s',
2268
+ '</MPD>',
2269
+ ].join('\n');
2270
+ const periodTemplate = (id, period, duration) => {
2271
+ return [
2272
+ ` <Period id="${id}" start="PT${period}S">`,
2273
+ ' <AdaptationSet mimeType="video/mp4" lang="en" group="1">',
2274
+ ' <SegmentTemplate startNumber="1" media="l-$Number$.mp4">',
2275
+ ' <SegmentTimeline>',
2276
+ ` <S t="0" d="${duration}" />`,
2277
+ ' </SegmentTimeline>',
2278
+ ' </SegmentTemplate>',
2279
+ ' <Representation id="1"/>',
2280
+ ' </AdaptationSet>',
2281
+ ' </Period>',
2282
+ ].join('\n');
2283
+ };
2284
+ const periodXmls = periods.map((period, i) => {
2285
+ const duration = i+1 === periods.length ? 10 : periods[i+1] - period;
2286
+ // Period start time as ID here. If we use index then there will be
2287
+ // periods with same period ID and different start time which are invalid.
2288
+ return periodTemplate(period, period, duration);
2289
+ });
2290
+ return sprintf(mpdTemplate, {
2291
+ periods: periodXmls.join('\n'),
2292
+ });
2293
+ }
2294
+
2295
+ // Bug description: Inconsistent period start time in the manifests due
2296
+ // to failover triggered in backend servers
2297
+
2298
+ // When one of the servers is down, the manifest will be served by other
2299
+ // redundant servers. The period start time might become out of sync
2300
+ // during the switch-over/recovery.
2301
+
2302
+ it('skip periods that are earlier than max period start time', async () => {
2303
+ const sources = [
2304
+ buildManifestWithPeriodStartTime([5, 15]),
2305
+ buildManifestWithPeriodStartTime([6, 15]), // simulate out-of-sync of +1s
2306
+ buildManifestWithPeriodStartTime([4, 15]), // simulate out-of-sync of -1s
2307
+ ];
2308
+ const segments = [];
2309
+
2310
+ for (const source of sources) {
2311
+ fakeNetEngine.setResponseText('dummy://foo', source);
2312
+ /** @type {shaka.extern.Manifest} */
2313
+ // eslint-disable-next-line no-await-in-loop
2314
+ const manifest = await parser.start('dummy://foo', playerInterface);
2315
+ const video = manifest.variants[0].video;
2316
+ // eslint-disable-next-line no-await-in-loop
2317
+ await video.createSegmentIndex();
2318
+ goog.asserts.assert(video.segmentIndex, 'Null segmentIndex!');
2319
+ segments.push(Array.from(video.segmentIndex));
2320
+ }
2321
+
2322
+ // Expect identical segments
2323
+ expect(segments[0].length).toBe(2);
2324
+ expect(segments[1].length).toBe(2);
2325
+ expect(segments[0][0].startTime).toBe(5);
2326
+ expect(segments[1][0].startTime).toBe(5);
2327
+ expect(segments[0][1].startTime).toBe(15);
2328
+ expect(segments[1][1].startTime).toBe(15);
2329
+ });
2111
2330
  });