shaka-player 4.3.13 → 4.3.15

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 (137) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/.github/ISSUE_TEMPLATE/question.md +7 -0
  3. package/.github/workflows/release-please.yaml +2 -27
  4. package/.github/workflows/shaka-bot-commands/lib.sh +37 -12
  5. package/.github/workflows/shaka-bot-commands/main.sh +4 -2
  6. package/.github/workflows/talk-to-shaka-bot.yaml +1 -0
  7. package/AUTHORS +5 -0
  8. package/CHANGELOG.md +96 -0
  9. package/CODE_OF_CONDUCT.md +1 -1
  10. package/CONTRIBUTORS +6 -0
  11. package/README.md +2 -0
  12. package/demo/common/asset.js +0 -1
  13. package/demo/common/assets.js +0 -1
  14. package/demo/custom.js +2 -0
  15. package/dist/controls.css +1 -1
  16. package/dist/controls.css.map +1 -1
  17. package/dist/demo.compiled.debug.js +1 -1
  18. package/dist/demo.compiled.debug.map +1 -1
  19. package/dist/demo.compiled.js +1 -1
  20. package/dist/demo.compiled.map +1 -1
  21. package/dist/deps.js +6 -6
  22. package/dist/locales.js +30 -30
  23. package/dist/receiver.compiled.debug.map +1 -1
  24. package/dist/receiver.compiled.map +1 -1
  25. package/dist/shaka-player.compiled.d.ts +25 -5
  26. package/dist/shaka-player.compiled.debug.d.ts +25 -5
  27. package/dist/shaka-player.compiled.debug.externs.js +73 -45
  28. package/dist/shaka-player.compiled.debug.js +426 -406
  29. package/dist/shaka-player.compiled.debug.map +4 -4
  30. package/dist/shaka-player.compiled.externs.js +73 -45
  31. package/dist/shaka-player.compiled.js +912 -903
  32. package/dist/shaka-player.compiled.map +4 -4
  33. package/dist/shaka-player.ui.d.ts +37 -6
  34. package/dist/shaka-player.ui.debug.d.ts +37 -6
  35. package/dist/shaka-player.ui.debug.externs.js +74 -45
  36. package/dist/shaka-player.ui.debug.js +449 -428
  37. package/dist/shaka-player.ui.debug.map +4 -4
  38. package/dist/shaka-player.ui.externs.js +74 -45
  39. package/dist/shaka-player.ui.js +1060 -1051
  40. package/dist/shaka-player.ui.map +4 -4
  41. package/docs/tutorials/ad_monetization.md +54 -0
  42. package/docs/tutorials/fairplay.md +2 -2
  43. package/docs/tutorials/index.json +1 -0
  44. package/docs/tutorials/plugins.md +4 -0
  45. package/docs/tutorials/text-displayer.md +15 -0
  46. package/docs/tutorials/ui-customization.md +29 -0
  47. package/docs/tutorials/ui.md +4 -1
  48. package/externs/lcevc.js +6 -0
  49. package/externs/shaka/manifest_parser.js +8 -0
  50. package/externs/shaka/player.js +1 -1
  51. package/externs/shaka/text.js +3 -1
  52. package/lib/abr/simple_abr_manager.js +1 -1
  53. package/lib/ads/client_side_ad.js +9 -0
  54. package/lib/ads/client_side_ad_manager.js +7 -2
  55. package/lib/ads/server_side_ad_manager.js +4 -0
  56. package/lib/cast/cast_proxy.js +18 -0
  57. package/lib/cea/cea608_memory.js +1 -1
  58. package/lib/cea/cea_decoder.js +11 -1
  59. package/lib/dash/dash_parser.js +75 -41
  60. package/lib/dash/mpd_utils.js +39 -27
  61. package/lib/deprecate/deprecate.js +1 -1
  62. package/lib/hls/hls_parser.js +106 -28
  63. package/lib/hls/hls_utils.js +5 -5
  64. package/lib/media/content_workarounds.js +30 -0
  65. package/lib/media/drm_engine.js +16 -12
  66. package/lib/media/manifest_parser.js +2 -1
  67. package/lib/media/media_source_engine.js +4 -2
  68. package/lib/media/play_rate_controller.js +1 -0
  69. package/lib/media/presentation_timeline.js +11 -0
  70. package/lib/media/quality_observer.js +1 -1
  71. package/lib/media/segment_reference.js +5 -2
  72. package/lib/media/streaming_engine.js +50 -30
  73. package/lib/media/transmuxer.js +1 -1
  74. package/lib/media/video_wrapper.js +4 -3
  75. package/lib/offline/offline_manifest_parser.js +5 -0
  76. package/lib/offline/session_deleter.js +0 -8
  77. package/lib/offline/storage.js +4 -1
  78. package/lib/player.js +80 -19
  79. package/lib/polyfill/media_capabilities.js +23 -3
  80. package/lib/polyfill/mediasource.js +5 -36
  81. package/lib/text/mp4_ttml_parser.js +2 -2
  82. package/lib/text/simple_text_displayer.js +7 -6
  83. package/lib/text/srt_text_parser.js +2 -2
  84. package/lib/text/text_engine.js +3 -2
  85. package/lib/text/ttml_text_parser.js +33 -5
  86. package/lib/text/ui_text_displayer.js +81 -15
  87. package/lib/text/vtt_text_parser.js +17 -3
  88. package/lib/util/buffer_utils.js +26 -4
  89. package/lib/util/cmcd_manager.js +10 -0
  90. package/lib/util/error.js +1 -0
  91. package/lib/util/fairplay_utils.js +1 -1
  92. package/lib/util/manifest_parser_utils.js +14 -8
  93. package/lib/util/object_utils.js +1 -1
  94. package/lib/util/periods.js +91 -36
  95. package/lib/util/platform.js +39 -2
  96. package/lib/util/player_configuration.js +5 -3
  97. package/lib/util/stream_utils.js +36 -28
  98. package/lib/util/xml_utils.js +5 -0
  99. package/package.json +3 -3
  100. package/test/cea/cea_decoder_unit.js +40 -0
  101. package/test/dash/dash_parser_live_unit.js +5 -8
  102. package/test/hls/hls_parser_unit.js +78 -3
  103. package/test/media/buffering_observer_unit.js +13 -0
  104. package/test/media/content_workarounds_unit.js +1 -1
  105. package/test/media/drm_engine_unit.js +30 -3
  106. package/test/media/media_source_engine_unit.js +1 -1
  107. package/test/media/transmuxer_integration.js +2 -2
  108. package/test/offline/storage_integration.js +3 -0
  109. package/test/player_integration.js +49 -41
  110. package/test/player_load_graph_integration.js +13 -7
  111. package/test/player_src_equals_integration.js +2 -2
  112. package/test/player_unit.js +40 -3
  113. package/test/polyfill/media_capabilities_unit.js +12 -6
  114. package/test/test/util/fake_drm_engine.js +7 -0
  115. package/test/test/util/layout_tests.js +2 -1
  116. package/test/test/util/simple_fakes.js +2 -2
  117. package/test/test/util/test_scheme.js +3 -0
  118. package/test/test/util/waiter.js +5 -0
  119. package/test/text/mp4_ttml_parser_unit.js +4 -4
  120. package/test/text/simple_text_displayer_unit.js +1 -1
  121. package/test/text/srt_text_parser_unit.js +1 -1
  122. package/test/text/text_engine_unit.js +6 -0
  123. package/test/text/ttml_text_parser_unit.js +49 -18
  124. package/test/text/ui_text_displayer_unit.js +9 -0
  125. package/test/text/vtt_text_parser_unit.js +31 -0
  126. package/test/util/buffer_utils_unit.js +1 -1
  127. package/test/util/periods_unit.js +113 -0
  128. package/third_party/closure-uri/uri.js +1 -1
  129. package/ui/externs/ui.js +24 -0
  130. package/ui/language_utils.js +11 -5
  131. package/ui/less/containers.less +1 -0
  132. package/ui/play_button.js +20 -0
  133. package/ui/playback_rate_selection.js +4 -0
  134. package/ui/presentation_time.js +5 -5
  135. package/ui/small_play_button.js +2 -2
  136. package/ui/volume_bar.js +10 -6
  137. /package/.github/{config.yml → ISSUE_TEMPLATE/config.yml} +0 -0
@@ -434,19 +434,36 @@ shaka.util.StreamUtils = class {
434
434
 
435
435
  await shaka.util.StreamUtils.getDecodingInfosForVariants(
436
436
  manifest.variants, usePersistentLicenses, /* srcEquals= */ false);
437
+
438
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
439
+ const Capabilities = shaka.media.Capabilities;
440
+ const ManifestParserUtils = shaka.util.ManifestParserUtils;
441
+ const MimeUtils = shaka.util.MimeUtils;
442
+ const StreamUtils = shaka.util.StreamUtils;
443
+ const isXboxOne = shaka.util.Platform.isXboxOne();
444
+
445
+ const isFirefoxAndroid = shaka.util.Platform.isFirefox() &&
446
+ shaka.util.Platform.isAndroid();
447
+
437
448
  manifest.variants = manifest.variants.filter((variant) => {
438
449
  // See: https://github.com/shaka-project/shaka-player/issues/3860
439
450
  const video = variant.video;
451
+ const videoWidth = (video && video.width) || 0;
452
+ const videoHeight = (video && video.height) || 0;
440
453
 
441
- const ContentType = shaka.util.ManifestParserUtils.ContentType;
442
- const Capabilities = shaka.media.Capabilities;
443
- const ManifestParserUtils = shaka.util.ManifestParserUtils;
444
- const MimeUtils = shaka.util.MimeUtils;
445
- const StreamUtils = shaka.util.StreamUtils;
454
+ // See: https://github.com/shaka-project/shaka-player/issues/3380
455
+ // Note: it makes sense to drop early
456
+ if (isXboxOne && video &&
457
+ (videoWidth > 1920 || videoHeight > 1080) &&
458
+ (video.codecs.includes('avc1.') || video.codecs.includes('avc3.'))
459
+ ) {
460
+ shaka.log.debug('Dropping variant - not compatible with platform',
461
+ StreamUtils.getVariantSummaryString_(variant));
462
+ return false;
463
+ }
446
464
 
447
465
  if (video) {
448
466
  let videoCodecs = StreamUtils.getCorrectVideoCodecs_(video.codecs);
449
-
450
467
  // For multiplexed streams. Here we must check the audio of the
451
468
  // stream to see if it is compatible.
452
469
  if (video.codecs.includes(',')) {
@@ -455,7 +472,6 @@ shaka.util.StreamUtils = class {
455
472
  videoCodecs = ManifestParserUtils.guessCodecs(
456
473
  ContentType.VIDEO, allCodecs);
457
474
  videoCodecs = StreamUtils.getCorrectVideoCodecs_(videoCodecs);
458
-
459
475
  let audioCodecs = ManifestParserUtils.guessCodecs(
460
476
  ContentType.AUDIO, allCodecs);
461
477
  audioCodecs = StreamUtils.getCorrectAudioCodecs_(audioCodecs);
@@ -484,6 +500,17 @@ shaka.util.StreamUtils = class {
484
500
 
485
501
  const audio = variant.audio;
486
502
 
503
+ // See: https://github.com/shaka-project/shaka-player/issues/6111
504
+ // It seems that Firefox Android reports that it supports
505
+ // Opus + Widevine, but it is not actually supported.
506
+ // It makes sense to drop early.
507
+ if (isFirefoxAndroid && audio && audio.encrypted &&
508
+ audio.codecs.toLowerCase().includes('opus')) {
509
+ shaka.log.debug('Dropping variant - not compatible with platform',
510
+ StreamUtils.getVariantSummaryString_(variant));
511
+ return false;
512
+ }
513
+
487
514
  if (audio) {
488
515
  const codecs = StreamUtils.getCorrectAudioCodecs_(audio.codecs);
489
516
  const fullType = MimeUtils.getFullOrConvertedType(
@@ -497,17 +524,6 @@ shaka.util.StreamUtils = class {
497
524
  audio.codecs = codecs;
498
525
  }
499
526
 
500
- // See: https://github.com/shaka-project/shaka-player/issues/3380
501
- if (shaka.util.Platform.isXboxOne() && video &&
502
- ((video.width && video.width > 1920) ||
503
- (video.height && video.height > 1080)) &&
504
- (video.codecs.includes('avc1.') ||
505
- video.codecs.includes('avc3.'))) {
506
- shaka.log.debug('Dropping variant - not compatible with platform',
507
- StreamUtils.getVariantSummaryString_(variant));
508
- return false;
509
- }
510
-
511
527
  const supported = variant.decodingInfos.some((decodingInfo) => {
512
528
  return decodingInfo.supported;
513
529
  });
@@ -790,7 +806,7 @@ shaka.util.StreamUtils = class {
790
806
  // currently don't support 'fLaC', while 'flac' is supported by most
791
807
  // major browsers.
792
808
  // See https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
793
- if (codecs === 'fLaC') {
809
+ if (codecs === 'fLaC' && !shaka.util.Platform.isSafari()) {
794
810
  return 'flac';
795
811
  }
796
812
 
@@ -799,15 +815,7 @@ shaka.util.StreamUtils = class {
799
815
  return 'opus';
800
816
  }
801
817
 
802
- // Some Tizen devices seem to misreport AC-3 support, but correctly
803
- // report EC-3 support. So query EC-3 as a fallback for AC-3.
804
- // See https://github.com/shaka-project/shaka-player/issues/2989 for
805
- // details.
806
- if (shaka.util.Platform.isTizen()) {
807
- return codecs.toLowerCase() == 'ac-3' ? 'ec-3' : codecs;
808
- } else {
809
- return codecs;
810
- }
818
+ return codecs;
811
819
  }
812
820
 
813
821
 
@@ -384,6 +384,11 @@ shaka.util.XmlUtils = class {
384
384
  return null;
385
385
  }
386
386
 
387
+ // Cobalt browser doesn't support document.createNodeIterator.
388
+ if (!('createNodeIterator' in document)) {
389
+ return rootElem;
390
+ }
391
+
387
392
  // SECURITY: Verify that the document does not contain elements from the
388
393
  // HTML or SVG namespaces, which could trigger script execution and XSS.
389
394
  const iterator = document.createNodeIterator(
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.3.13",
4
+ "version": "4.3.15",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -42,13 +42,13 @@
42
42
  "karma-coverage": "^2.2.0",
43
43
  "karma-jasmine": "^4.0.1",
44
44
  "karma-jasmine-ajax": "^0.1.13",
45
- "karma-local-wd-launcher": "^1.6.6",
45
+ "karma-local-wd-launcher": "^1.6.7",
46
46
  "karma-opera-launcher": "^1.0.0",
47
47
  "karma-sourcemap-loader": "^0.3.8",
48
48
  "karma-spec-reporter": "^0.0.34",
49
49
  "karma-webdriver-launcher": "^1.0.8",
50
50
  "lcevc_dil.js": "^1.1.20",
51
- "less": "https://gitpkg.now.sh/joeyparrish/less.js/packages/less?28c63a43",
51
+ "less": "^4.2.0",
52
52
  "less-plugin-clean-css": "github:austingardner/less-plugin-clean-css#4e9e77bf",
53
53
  "material-design-lite": "^1.3.0",
54
54
  "mux.js": "^6.2.0",
@@ -44,6 +44,39 @@ describe('CeaDecoder', () => {
44
44
  decoder.clear();
45
45
  });
46
46
 
47
+ it('painton captions on CC4', () => {
48
+ const controlCount = 0x03;
49
+ const captionData = 0xc0 | controlCount;
50
+ const paintonCaptionCC4Packet = new Uint8Array([
51
+ ...atscCaptionInitBytes, captionData, /* padding= */ 0xff,
52
+ 0xfd, 0x9d, 0x29, // Paint-on mode (RDC control code).
53
+ 0xfd, 0xf4, 0xe5, // t, e
54
+ 0xfd, 0x73, 0xf4, // s, t
55
+ ]);
56
+
57
+ const startTimeCaption1 = 1;
58
+ const startTimeCaption2 = 2;
59
+ const expectedText = 'test';
60
+
61
+ const topLevelCue = new shaka.text.Cue(startTimeCaption1,
62
+ startTimeCaption2, '');
63
+ topLevelCue.nestedCues = [
64
+ CeaUtils.createDefaultCue(
65
+ startTimeCaption1, startTimeCaption2, expectedText),
66
+ ];
67
+
68
+ const expectedCaptions = [{
69
+ stream: 'CC4',
70
+ cue: topLevelCue,
71
+ }];
72
+
73
+ decoder.extract(paintonCaptionCC4Packet, startTimeCaption1);
74
+ decoder.extract(eraseDisplayedMemory, startTimeCaption2);
75
+ const captions = decoder.decode();
76
+
77
+ expect(captions).toEqual(expectedCaptions);
78
+ });
79
+
47
80
  it('green and underlined popon caption data on CC3', () => {
48
81
  const controlCount = 0x08;
49
82
  const captionData = 0xc0 | controlCount;
@@ -486,6 +519,13 @@ describe('CeaDecoder', () => {
486
519
 
487
520
  expect(decoder.reset).toHaveBeenCalledTimes(1);
488
521
  });
522
+
523
+ it('does not attempt to extract SEI packet that is too short', () => {
524
+ const badData = new Uint8Array([0xb5]);
525
+ decoder.extract(badData, 0);
526
+ const captions = decoder.decode();
527
+ expect(captions.length).toBe(0);
528
+ });
489
529
  });
490
530
 
491
531
  describe('decodes CEA-708', () => {
@@ -607,12 +607,12 @@ describe('DashParser Live', () => {
607
607
  expect(onError).toHaveBeenCalledTimes(1);
608
608
  });
609
609
 
610
- it('uses @minimumUpdatePeriod', async () => {
610
+ it('still updates when @minimumUpdatePeriod is zero', async () => {
611
611
  const lines = [
612
612
  '<SegmentTemplate startNumber="1" media="s$Number$.mp4" duration="2" />',
613
613
  ];
614
614
  // updateTime parameter sets @minimumUpdatePeriod in the manifest.
615
- const manifestText = makeSimpleLiveManifestText(lines, updateTime);
615
+ const manifestText = makeSimpleLiveManifestText(lines, /* updateTime= */ 0);
616
616
 
617
617
  /** @type {!jasmine.Spy} */
618
618
  const tickAfter = updateTickSpy();
@@ -622,16 +622,14 @@ describe('DashParser Live', () => {
622
622
  await parser.start('dummy://foo', playerInterface);
623
623
 
624
624
  expect(tickAfter).toHaveBeenCalledTimes(1);
625
- const delay = tickAfter.calls.mostRecent().args[0];
626
- expect(delay).toBe(updateTime);
627
625
  });
628
626
 
629
- it('still updates when @minimumUpdatePeriod is zero', async () => {
627
+ it('uses @minimumUpdatePeriod', async () => {
630
628
  const lines = [
631
629
  '<SegmentTemplate startNumber="1" media="s$Number$.mp4" duration="2" />',
632
630
  ];
633
631
  // updateTime parameter sets @minimumUpdatePeriod in the manifest.
634
- const manifestText = makeSimpleLiveManifestText(lines, /* updateTime= */ 0);
632
+ const manifestText = makeSimpleLiveManifestText(lines, updateTime);
635
633
 
636
634
  /** @type {!jasmine.Spy} */
637
635
  const tickAfter = updateTickSpy();
@@ -640,10 +638,9 @@ describe('DashParser Live', () => {
640
638
  fakeNetEngine.setResponseText('dummy://foo', manifestText);
641
639
  await parser.start('dummy://foo', playerInterface);
642
640
 
643
- const waitTime = shaka.dash.DashParser['MIN_UPDATE_PERIOD_'];
644
641
  expect(tickAfter).toHaveBeenCalledTimes(1);
645
642
  const delay = tickAfter.calls.mostRecent().args[0];
646
- expect(delay).toBe(waitTime);
643
+ expect(delay).toBe(updateTime);
647
644
  });
648
645
 
649
646
  it('does not update when @minimumUpdatePeriod is missing', async () => {
@@ -739,6 +739,45 @@ describe('HlsParser', () => {
739
739
  await testHlsParser(master, media, manifest);
740
740
  });
741
741
 
742
+ it('parses audio+video variant with global closed captions', async () => {
743
+ const master = [
744
+ '#EXTM3U\n',
745
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",CHANNELS="2",',
746
+ 'URI="audio"\n',
747
+ '#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,GROUP-ID="cap1",LANGUAGE="eng",',
748
+ 'INSTREAM-ID="CC1"\n',
749
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
750
+ 'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
751
+ 'video\n',
752
+ ].join('');
753
+
754
+ const media = [
755
+ '#EXTM3U\n',
756
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
757
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
758
+ '#EXTINF:5,\n',
759
+ '#EXT-X-BYTERANGE:121090@616\n',
760
+ 'main.mp4',
761
+ ].join('');
762
+
763
+ const closedCaptions = new Map([['CC1', 'en']]);
764
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
765
+ manifest.anyTimeline();
766
+ manifest.addPartialVariant((variant) => {
767
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
768
+ stream.closedCaptions = closedCaptions;
769
+ stream.mime('video/mp4', 'avc1');
770
+ });
771
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
772
+ stream.mime('audio/mp4', 'mp4a');
773
+ });
774
+ });
775
+ manifest.sequenceMode = true;
776
+ });
777
+
778
+ await testHlsParser(master, media, manifest);
779
+ });
780
+
742
781
  it('parses audio+video variant with no closed captions', async () => {
743
782
  const master = [
744
783
  '#EXTM3U\n',
@@ -2926,7 +2965,8 @@ describe('HlsParser', () => {
2926
2965
  const media = [
2927
2966
  '#EXTM3U\n',
2928
2967
  '#EXT-X-PLAYLIST-TYPE:VOD\n',
2929
- '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
2968
+ '#EXT-X-BYTERANGE:616@0\n',
2969
+ '#EXT-X-MAP:URI="init.mp4"\n',
2930
2970
  '#EXTINF:5,\n',
2931
2971
  'main.mp4\n',
2932
2972
  '#EXT-X-MAP:URI="init2.mp4",BYTERANGE="616@0"\n',
@@ -2954,8 +2994,15 @@ describe('HlsParser', () => {
2954
2994
  // uri.
2955
2995
  const initSegments = Array.from(actualVideo.segmentIndex).map(
2956
2996
  (seg) => seg.initSegmentReference);
2957
- expect(initSegments[0].getUris()[0]).toBe('test:/init.mp4');
2958
- expect(initSegments[1].getUris()[0]).toBe('test:/init2.mp4');
2997
+ expect(initSegments.length).toBe(2);
2998
+ const firstInitSegment = initSegments[0];
2999
+ expect(firstInitSegment.getUris()[0]).toBe('test:/init.mp4');
3000
+ expect(firstInitSegment.startByte).toBe(0);
3001
+ expect(firstInitSegment.endByte).toBe(615);
3002
+ const secondInitSegment = initSegments[1];
3003
+ expect(secondInitSegment.getUris()[0]).toBe('test:/init2.mp4');
3004
+ expect(secondInitSegment.startByte).toBe(0);
3005
+ expect(secondInitSegment.endByte).toBe(615);
2959
3006
  });
2960
3007
 
2961
3008
  it('parses variants encrypted with AES-128', async () => {
@@ -3696,6 +3743,34 @@ describe('HlsParser', () => {
3696
3743
  await verifyError(master, media, error, /* onCreateSegmentIndex= */ true);
3697
3744
  });
3698
3745
 
3746
+ it('if SAMPLE-AES encryption with MSE and mp2t content', async () => {
3747
+ const master = [
3748
+ '#EXTM3U\n',
3749
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
3750
+ 'RESOLUTION=960x540,FRAME-RATE=60\n',
3751
+ 'video\n',
3752
+ ].join('');
3753
+
3754
+ const media = [
3755
+ '#EXTM3U\n',
3756
+ '#EXT-X-TARGETDURATION:6\n',
3757
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
3758
+ '#EXT-X-KEY:METHOD=SAMPLE-AES,',
3759
+ 'URI="fake",\n',
3760
+ '#EXTINF:5,\n',
3761
+ '#EXT-X-BYTERANGE:121090@616\n',
3762
+ 'main.ts',
3763
+ ].join('');
3764
+
3765
+ const error = new shaka.util.Error(
3766
+ shaka.util.Error.Severity.CRITICAL,
3767
+ shaka.util.Error.Category.MANIFEST,
3768
+ Code.HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED);
3769
+
3770
+ await verifyError(master, media, error, /* onCreateSegmentIndex= */ true);
3771
+ });
3772
+
3773
+
3699
3774
  describe('if required tags are missing', () => {
3700
3775
  /**
3701
3776
  * @param {string} master
@@ -11,15 +11,28 @@ describe('BufferingObserver', () => {
11
11
  const thresholdAfterStarving = 5;
12
12
  const thresholdAfterSatisfied = 2;
13
13
 
14
+ const originalDateNow = Date.now;
15
+
16
+ /** @type {!Date} */
17
+ let baseTime;
18
+
14
19
  /** @type {!shaka.media.BufferingObserver} */
15
20
  let controller;
16
21
 
17
22
  beforeEach(() => {
23
+ // Any test using Date needs to have Date mocked to avoid flake.
24
+ baseTime = new Date(2015, 11, 30);
25
+ Date.now = () => baseTime.getTime();
26
+
18
27
  controller = new BufferingObserver(
19
28
  thresholdAfterStarving,
20
29
  thresholdAfterSatisfied);
21
30
  });
22
31
 
32
+ afterEach(() => {
33
+ Date.now = originalDateNow;
34
+ });
35
+
23
36
  describe('when satisfied', () => {
24
37
  beforeEach(() => {
25
38
  controller.setState(State.SATISFIED);
@@ -7,7 +7,7 @@
7
7
  describe('ContentWorkarounds', () => {
8
8
  const encryptionBoxes = {
9
9
  'encv': ['hev1', 'hvc1', 'avc1', 'avc3'],
10
- 'enca': ['ac-3', 'ec-3', 'mp4a'],
10
+ 'enca': ['ac-3', 'ec-3', 'ac-4', 'mp4a'],
11
11
  };
12
12
  for (const encryptionBox of Object.keys(encryptionBoxes)) {
13
13
  for (const box of encryptionBoxes[encryptionBox]) {
@@ -2100,7 +2100,7 @@ describe('DrmEngine', () => {
2100
2100
  videoRobustness: 'really_really_ridiculously_good',
2101
2101
  serverCertificate: serverCert,
2102
2102
  serverCertificateUri: '',
2103
- initData: ['blah'],
2103
+ initData: [{keyId: 'a'}],
2104
2104
  keyIds: new Set(['deadbeefdeadbeefdeadbeefdeadbeef']),
2105
2105
  };
2106
2106
  const drmInfoAudio = {
@@ -2111,7 +2111,7 @@ describe('DrmEngine', () => {
2111
2111
  audioRobustness: 'good',
2112
2112
  serverCertificate: undefined,
2113
2113
  serverCertificateUri: '',
2114
- initData: ['init data'],
2114
+ initData: [{keyId: 'b'}],
2115
2115
  keyIds: new Set(['eadbeefdeadbeefdeadbeefdeadbeefd']),
2116
2116
  };
2117
2117
  const drmInfoDesired = {
@@ -2123,7 +2123,7 @@ describe('DrmEngine', () => {
2123
2123
  videoRobustness: 'really_really_ridiculously_good',
2124
2124
  serverCertificate: serverCert,
2125
2125
  serverCertificateUri: '',
2126
- initData: ['blah', 'init data'],
2126
+ initData: [{keyId: 'a'}, {keyId: 'b'}],
2127
2127
  keyIds: new Set([
2128
2128
  'deadbeefdeadbeefdeadbeefdeadbeef',
2129
2129
  'eadbeefdeadbeefdeadbeefdeadbeefd',
@@ -2177,6 +2177,33 @@ describe('DrmEngine', () => {
2177
2177
  [drmInfoAudio]);
2178
2178
  expect(returned).toEqual([drmInfoDesired]);
2179
2179
  });
2180
+
2181
+ it('does not match incompatible drmInfos', () => {
2182
+ // Different key systems do not match.
2183
+ const drmInfo1 = {
2184
+ keySystem: 'drm.abc',
2185
+ licenseServerUri: undefined,
2186
+ distinctiveIdentifierRequired: false,
2187
+ persistentStateRequired: false,
2188
+ serverCertificate: undefined,
2189
+ serverCertificateUri: '',
2190
+ initData: [],
2191
+ keyIds: new Set(),
2192
+ };
2193
+ const drmInfo2 = {
2194
+ keySystem: 'drm.foobar',
2195
+ licenseServerUri: undefined,
2196
+ distinctiveIdentifierRequired: false,
2197
+ persistentStateRequired: false,
2198
+ serverCertificate: undefined,
2199
+ serverCertificateUri: '',
2200
+ initData: [],
2201
+ keyIds: new Set(),
2202
+ };
2203
+ const returned1 = shaka.media.DrmEngine.getCommonDrmInfos(
2204
+ [drmInfo1], [drmInfo2]);
2205
+ expect(returned1).toEqual([]);
2206
+ });
2180
2207
  }); // describe('getCommonDrmInfos')
2181
2208
 
2182
2209
  describe('configure', () => {
@@ -560,7 +560,7 @@ describe('MediaSourceEngine', () => {
560
560
  await mediaSourceEngine.appendBuffer(
561
561
  ContentType.TEXT, data, reference, /* hasClosedCaptions= */ false);
562
562
  expect(mockTextEngine.appendBuffer).toHaveBeenCalledWith(
563
- data, 0, 10);
563
+ data, 0, 10, 'foo://bar');
564
564
  });
565
565
 
566
566
  it('appends transmuxed data and captions', async () => {
@@ -102,8 +102,8 @@ describe('Transmuxer', () => {
102
102
  });
103
103
  });
104
104
 
105
- it('getOrginalMimeType returns the correct mimeType', () => {
106
- expect(transmuxer.getOrginalMimeType()).toBe(transmuxerMimeType);
105
+ it('getOriginalMimeType returns the correct mimeType', () => {
106
+ expect(transmuxer.getOriginalMimeType()).toBe(transmuxerMimeType);
107
107
  });
108
108
 
109
109
  describe('transmuxing', () => {
@@ -1741,6 +1741,9 @@ filterDescribe('Storage', storageSupport, () => {
1741
1741
 
1742
1742
  /** @override */
1743
1743
  onExpirationUpdated(session, number) {}
1744
+
1745
+ /** @override */
1746
+ onInitialVariantChosen(variant) {}
1744
1747
  };
1745
1748
 
1746
1749
  /**
@@ -59,6 +59,46 @@ describe('Player', () => {
59
59
  document.body.removeChild(video);
60
60
  });
61
61
 
62
+ describe('seek range', () => {
63
+ // Regression test for Issue #3675.
64
+ it('has a reasonable seek range after going from live to VOD', async () => {
65
+ const netEngine = player.getNetworkingEngine();
66
+ const startTime = Date.now();
67
+ netEngine.registerRequestFilter((type, request) => {
68
+ if (type != shaka.net.NetworkingEngine.RequestType.MANIFEST) {
69
+ return;
70
+ }
71
+ // Simulate a live stream by providing different manifests over time.
72
+ const time = (Date.now() - startTime) / 1000;
73
+ const manifestNumber = Math.min(5, Math.floor(0.5 + time / 2));
74
+ request.uris = [
75
+ '/base/test/test/assets/3675/dash_' + manifestNumber + '.mpd',
76
+ ];
77
+ console.log('getting manifest', request.uris);
78
+ });
79
+
80
+ // Play the stream.
81
+ await player.load('/base/test/test/assets/3675/dash_0.mpd');
82
+ await video.play();
83
+
84
+ // Wait for the stream to be over.
85
+ eventManager.listen(player, 'error', Util.spyFunc(onErrorSpy));
86
+ /** @type {shaka.test.Waiter} */
87
+ const waiter = new shaka.test.Waiter(eventManager)
88
+ .setPlayer(player)
89
+ .timeoutAfter(40)
90
+ .failOnTimeout(true);
91
+ await waiter.waitForEnd(video);
92
+
93
+ // The stream should have transitioned to VOD by now.
94
+ expect(player.isLive()).toBe(false);
95
+
96
+ // Check that the final seek range is as expected.
97
+ const seekRange = player.seekRange();
98
+ expect(seekRange.end).toBeCloseTo(14);
99
+ });
100
+ });
101
+
62
102
  describe('attach', () => {
63
103
  beforeEach(async () => {
64
104
  // To test attach, we want to construct a player without a video element
@@ -903,46 +943,6 @@ describe('Player', () => {
903
943
  });
904
944
  });
905
945
 
906
- describe('seek range', () => {
907
- // Regression test for Issue #3675.
908
- it('has a reasonable seek range after going from live to VOD', async () => {
909
- const netEngine = player.getNetworkingEngine();
910
- const startTime = Date.now();
911
- netEngine.registerRequestFilter((type, request) => {
912
- if (type != shaka.net.NetworkingEngine.RequestType.MANIFEST) {
913
- return;
914
- }
915
- // Simulate a live stream by providing different manifests over time.
916
- const time = (Date.now() - startTime) / 1000;
917
- const manifestNumber = Math.min(5, Math.floor(0.5 + time / 2));
918
- request.uris = [
919
- '/base/test/test/assets/3675/dash_' + manifestNumber + '.mpd',
920
- ];
921
- console.log('getting manifest', request.uris);
922
- });
923
-
924
- // Play the stream.
925
- await player.load('/base/test/test/assets/3675/dash_0.mpd');
926
- await video.play();
927
-
928
- // Wait for the stream to be over.
929
- eventManager.listen(player, 'error', Util.spyFunc(onErrorSpy));
930
- /** @type {shaka.test.Waiter} */
931
- const waiter = new shaka.test.Waiter(eventManager)
932
- .setPlayer(player)
933
- .timeoutAfter(40)
934
- .failOnTimeout(true);
935
- await waiter.waitForEnd(video);
936
-
937
- // The stream should have transitioned to VOD by now.
938
- expect(player.isLive()).toBe(false);
939
-
940
- // Check that the final seek range is as expected.
941
- const seekRange = player.seekRange();
942
- expect(seekRange.end).toBeCloseTo(14);
943
- });
944
- });
945
-
946
946
  describe('buffering', () => {
947
947
  const startBuffering = jasmine.objectContaining({buffering: true});
948
948
  const endBuffering = jasmine.objectContaining({buffering: false});
@@ -1197,13 +1197,21 @@ describe('Player', () => {
1197
1197
  'com.widevine.alpha': bogusUrl,
1198
1198
  'com.microsoft.playready': bogusUrl,
1199
1199
  });
1200
- await player.load('test:sintel-enc_compiled');
1200
+
1201
+ // This load may be interrupted, so ignore errors and don't wait.
1202
+ const loadPromise =
1203
+ player.load('test:sintel-enc_compiled').catch(() => {});
1201
1204
 
1202
1205
  await errorPromise;
1203
1206
  expect(unloadPromise).not.toBeNull();
1207
+
1204
1208
  if (unloadPromise) {
1205
1209
  await unloadPromise;
1206
1210
  }
1211
+
1212
+ // This should be done, and errors ignored. But don't leave any Promise
1213
+ // unresolved.
1214
+ await loadPromise;
1207
1215
  });
1208
1216
  }); // describe('unloading')
1209
1217
 
@@ -464,11 +464,11 @@ describe('Player Load Graph', () => {
464
464
 
465
465
  // Make the two requests one-after-another so that we don't have any idle
466
466
  // time between them.
467
- const attachRequest = player.attach(video);
468
- const loadRequest = player.load('test:sintel');
467
+ const attach = player.attach(video);
468
+ const load = player.load('test:sintel');
469
469
 
470
- await attachRequest;
471
- await expectAsync(loadRequest).toBeRejected();
470
+ await attach;
471
+ await expectAsync(load).toBeRejected();
472
472
 
473
473
  // Wait a couple interrupter cycles to allow the player to enter idle
474
474
  // state.
@@ -506,10 +506,12 @@ describe('Player Load Graph', () => {
506
506
  // Normally the player would initialize media source after attaching to
507
507
  // the media element, however since we don't support media source, it
508
508
  // should stop at the attach state.
509
- player.attach(video, /* initMediaSource= */ true);
509
+ const attach = player.attach(video, /* initMediaSource= */ true);
510
510
 
511
511
  const event = await spyIsCalled(stateIdleSpy);
512
512
  expect(event.state).toBe('attach');
513
+
514
+ await attach;
513
515
  });
514
516
 
515
517
  it('loading ignores media source path', async () => {
@@ -518,10 +520,12 @@ describe('Player Load Graph', () => {
518
520
  // Normally the player would load content like this with the media source
519
521
  // path, but since we don't have media source support, it should use the
520
522
  // src= path.
521
- player.load(SMALL_MP4_CONTENT_URI);
523
+ const load = player.load(SMALL_MP4_CONTENT_URI);
522
524
 
523
525
  const event = await spyIsCalled(stateIdleSpy);
524
526
  expect(event.state).toBe('src-equals');
527
+
528
+ await load;
525
529
  });
526
530
 
527
531
  it('unloading ignores init media source flag', async () => {
@@ -531,10 +535,12 @@ describe('Player Load Graph', () => {
531
535
  // Normally the player would try to go to the media source state because
532
536
  // we are saying to initialize media source after unloading, but since we
533
537
  // don't have media source, it should stop at the attach state.
534
- player.unload(/* initMediaSource= */ true);
538
+ const unload = player.unload(/* initMediaSource= */ true);
535
539
 
536
540
  const event = await spyIsCalled(stateIdleSpy);
537
541
  expect(event.state).toBe('attach');
542
+
543
+ await unload;
538
544
  });
539
545
  });
540
546