shaka-player 4.3.13 → 4.3.14

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 (124) 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/AUTHORS +5 -0
  5. package/CHANGELOG.md +87 -0
  6. package/CODE_OF_CONDUCT.md +1 -1
  7. package/CONTRIBUTORS +6 -0
  8. package/README.md +2 -0
  9. package/demo/common/asset.js +0 -1
  10. package/demo/common/assets.js +0 -1
  11. package/demo/custom.js +2 -0
  12. package/dist/demo.compiled.debug.js +1 -1
  13. package/dist/demo.compiled.debug.map +1 -1
  14. package/dist/demo.compiled.js +1 -1
  15. package/dist/demo.compiled.map +1 -1
  16. package/dist/deps.js +6 -6
  17. package/dist/locales.js +29 -29
  18. package/dist/receiver.compiled.debug.map +1 -1
  19. package/dist/receiver.compiled.map +1 -1
  20. package/dist/shaka-player.compiled.d.ts +25 -5
  21. package/dist/shaka-player.compiled.debug.d.ts +25 -5
  22. package/dist/shaka-player.compiled.debug.externs.js +73 -45
  23. package/dist/shaka-player.compiled.debug.js +414 -396
  24. package/dist/shaka-player.compiled.debug.map +4 -4
  25. package/dist/shaka-player.compiled.externs.js +73 -45
  26. package/dist/shaka-player.compiled.js +912 -903
  27. package/dist/shaka-player.compiled.map +4 -4
  28. package/dist/shaka-player.ui.d.ts +37 -6
  29. package/dist/shaka-player.ui.debug.d.ts +37 -6
  30. package/dist/shaka-player.ui.debug.externs.js +74 -45
  31. package/dist/shaka-player.ui.debug.js +438 -418
  32. package/dist/shaka-player.ui.debug.map +4 -4
  33. package/dist/shaka-player.ui.externs.js +74 -45
  34. package/dist/shaka-player.ui.js +1060 -1051
  35. package/dist/shaka-player.ui.map +4 -4
  36. package/docs/tutorials/ad_monetization.md +54 -0
  37. package/docs/tutorials/fairplay.md +2 -2
  38. package/docs/tutorials/index.json +1 -0
  39. package/docs/tutorials/plugins.md +4 -0
  40. package/docs/tutorials/text-displayer.md +15 -0
  41. package/docs/tutorials/ui-customization.md +29 -0
  42. package/docs/tutorials/ui.md +4 -1
  43. package/externs/lcevc.js +6 -0
  44. package/externs/shaka/manifest_parser.js +8 -0
  45. package/externs/shaka/player.js +1 -1
  46. package/externs/shaka/text.js +3 -1
  47. package/lib/abr/simple_abr_manager.js +1 -1
  48. package/lib/ads/client_side_ad.js +9 -0
  49. package/lib/ads/client_side_ad_manager.js +7 -2
  50. package/lib/ads/server_side_ad_manager.js +4 -0
  51. package/lib/cast/cast_proxy.js +18 -0
  52. package/lib/cea/cea608_memory.js +1 -1
  53. package/lib/cea/cea_decoder.js +11 -1
  54. package/lib/dash/dash_parser.js +75 -41
  55. package/lib/dash/mpd_utils.js +39 -27
  56. package/lib/hls/hls_parser.js +90 -27
  57. package/lib/hls/hls_utils.js +5 -5
  58. package/lib/media/content_workarounds.js +30 -0
  59. package/lib/media/drm_engine.js +16 -12
  60. package/lib/media/media_source_engine.js +4 -2
  61. package/lib/media/play_rate_controller.js +1 -0
  62. package/lib/media/presentation_timeline.js +11 -0
  63. package/lib/media/quality_observer.js +1 -1
  64. package/lib/media/segment_reference.js +5 -2
  65. package/lib/media/streaming_engine.js +50 -30
  66. package/lib/media/transmuxer.js +1 -1
  67. package/lib/media/video_wrapper.js +4 -3
  68. package/lib/offline/offline_manifest_parser.js +5 -0
  69. package/lib/offline/session_deleter.js +0 -8
  70. package/lib/offline/storage.js +4 -1
  71. package/lib/player.js +79 -18
  72. package/lib/polyfill/media_capabilities.js +23 -3
  73. package/lib/text/mp4_ttml_parser.js +2 -2
  74. package/lib/text/simple_text_displayer.js +7 -6
  75. package/lib/text/srt_text_parser.js +2 -2
  76. package/lib/text/text_engine.js +3 -2
  77. package/lib/text/ttml_text_parser.js +33 -5
  78. package/lib/text/ui_text_displayer.js +81 -15
  79. package/lib/text/vtt_text_parser.js +15 -2
  80. package/lib/util/buffer_utils.js +26 -4
  81. package/lib/util/cmcd_manager.js +10 -0
  82. package/lib/util/fairplay_utils.js +1 -1
  83. package/lib/util/manifest_parser_utils.js +14 -8
  84. package/lib/util/object_utils.js +1 -1
  85. package/lib/util/periods.js +91 -36
  86. package/lib/util/platform.js +39 -2
  87. package/lib/util/player_configuration.js +5 -3
  88. package/lib/util/stream_utils.js +36 -28
  89. package/lib/util/xml_utils.js +5 -0
  90. package/package.json +2 -2
  91. package/test/cea/cea_decoder_unit.js +40 -0
  92. package/test/dash/dash_parser_live_unit.js +5 -8
  93. package/test/hls/hls_parser_unit.js +50 -3
  94. package/test/media/buffering_observer_unit.js +13 -0
  95. package/test/media/content_workarounds_unit.js +1 -1
  96. package/test/media/drm_engine_unit.js +30 -3
  97. package/test/media/media_source_engine_unit.js +1 -1
  98. package/test/media/transmuxer_integration.js +2 -2
  99. package/test/offline/storage_integration.js +3 -0
  100. package/test/player_integration.js +40 -40
  101. package/test/player_unit.js +31 -0
  102. package/test/polyfill/media_capabilities_unit.js +12 -6
  103. package/test/test/util/fake_drm_engine.js +7 -0
  104. package/test/test/util/layout_tests.js +2 -1
  105. package/test/test/util/simple_fakes.js +2 -2
  106. package/test/test/util/test_scheme.js +3 -0
  107. package/test/test/util/waiter.js +5 -0
  108. package/test/text/mp4_ttml_parser_unit.js +4 -4
  109. package/test/text/simple_text_displayer_unit.js +1 -1
  110. package/test/text/srt_text_parser_unit.js +1 -1
  111. package/test/text/text_engine_unit.js +6 -0
  112. package/test/text/ttml_text_parser_unit.js +49 -18
  113. package/test/text/ui_text_displayer_unit.js +9 -0
  114. package/test/util/buffer_utils_unit.js +1 -1
  115. package/test/util/periods_unit.js +113 -0
  116. package/third_party/closure-uri/uri.js +1 -1
  117. package/ui/externs/ui.js +24 -0
  118. package/ui/language_utils.js +11 -5
  119. package/ui/play_button.js +20 -0
  120. package/ui/playback_rate_selection.js +4 -0
  121. package/ui/presentation_time.js +5 -5
  122. package/ui/small_play_button.js +2 -2
  123. package/ui/volume_bar.js +10 -6
  124. /package/.github/{config.yml → ISSUE_TEMPLATE/config.yml} +0 -0
@@ -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 () => {
@@ -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});
@@ -3291,6 +3291,37 @@ describe('Player', () => {
3291
3291
  expect(tracks[0].id).toBe(2);
3292
3292
  });
3293
3293
 
3294
+ it('removes based on frameRate', async () => {
3295
+ manifest = shaka.test.ManifestGenerator.generate((manifest) => {
3296
+ manifest.addVariant(0, (variant) => {
3297
+ variant.addVideo(1, (stream) => {
3298
+ stream.frameRate = 15;
3299
+ });
3300
+ });
3301
+
3302
+ manifest.addVariant(1, (variant) => {
3303
+ variant.addVideo(2, (stream) => {
3304
+ stream.frameRate = 30;
3305
+ });
3306
+ });
3307
+
3308
+ manifest.addVariant(2, (variant) => {
3309
+ variant.addVideo(3, (stream) => {
3310
+ stream.frameRate = 60;
3311
+ });
3312
+ });
3313
+ });
3314
+
3315
+ await player.load(fakeManifestUri, 0, fakeMimeType);
3316
+ expect(player.getVariantTracks().length).toBe(3);
3317
+
3318
+ player.configure({restrictions: {minFrameRate: 20, maxFrameRate: 40}});
3319
+
3320
+ const tracks = player.getVariantTracks();
3321
+ expect(tracks.length).toBe(1);
3322
+ expect(tracks[0].id).toBe(1);
3323
+ });
3324
+
3294
3325
  it('removes the whole variant if one stream is restricted', async () => {
3295
3326
  manifest = shaka.test.ManifestGenerator.generate((manifest) => {
3296
3327
  manifest.addVariant(0, (variant) => {
@@ -178,8 +178,10 @@ describe('MediaCapabilities', () => {
178
178
  await navigator.mediaCapabilities.decodingInfo(mockDecodingConfig);
179
179
 
180
180
  expect(mockCanDisplayType).not.toHaveBeenCalled();
181
- // 1 (during install()) + 1 (for video config check).
182
- expect(isChromecastSpy).toHaveBeenCalledTimes(2);
181
+ // 1 (during install()) +
182
+ // 1 (for video config check) +
183
+ // 1 (for audio config check).
184
+ expect(isChromecastSpy).toHaveBeenCalledTimes(3);
183
185
  // 1 (fallback in canCastDisplayType()) +
184
186
  // 1 (mockDecodingConfig.audio).
185
187
  expect(supportMap.has(mockDecodingConfig.video.contentType))
@@ -201,8 +203,10 @@ describe('MediaCapabilities', () => {
201
203
  await navigator.mediaCapabilities.decodingInfo(mockDecodingConfig);
202
204
 
203
205
  expect(mockCanDisplayType).not.toHaveBeenCalled();
204
- // 1 (during install()) + 1 (for video config check).
205
- expect(isChromecastSpy).toHaveBeenCalledTimes(2);
206
+ // 1 (during install()) +
207
+ // 1 (for video config check) +
208
+ // 1 (for audio config check).
209
+ expect(isChromecastSpy).toHaveBeenCalledTimes(3);
206
210
  // 1 (fallback in canCastDisplayType()) +
207
211
  // 1 (mockDecodingConfig.audio).
208
212
  expect(supportMap.has(mockDecodingConfig.video.contentType))
@@ -248,8 +252,10 @@ describe('MediaCapabilities', () => {
248
252
  shaka.polyfill.MediaCapabilities.install();
249
253
  await navigator.mediaCapabilities.decodingInfo(mockDecodingConfig);
250
254
 
251
- // 1 (during install()) + 1 (for video config check).
252
- expect(isChromecastSpy).toHaveBeenCalledTimes(2);
255
+ // 1 (during install()) +
256
+ // 1 (for video config check) +
257
+ // 1 (for audio config check).
258
+ expect(isChromecastSpy).toHaveBeenCalledTimes(3);
253
259
  // 1 (mockDecodingConfig.audio).
254
260
  expect(supportMap.has(chromecastType)).toBe(true);
255
261
  // Called once in canCastDisplayType.
@@ -100,4 +100,11 @@ shaka.test.FakeDrmEngine = class {
100
100
  // Copy the values to break the reference to the input value.
101
101
  this.offlineSessions_ = sessions.map((s) => s);
102
102
  }
103
+
104
+ /**
105
+ * @override
106
+ */
107
+ hasManifestInitData() {
108
+ return true;
109
+ }
103
110
  };
@@ -377,7 +377,8 @@ shaka.test.NativeTextLayoutTests = class extends shaka.test.TextLayoutTests {
377
377
 
378
378
  /** @override */
379
379
  recreateTextDisplayer() {
380
- this.textDisplayer = new shaka.text.SimpleTextDisplayer(this.video);
380
+ this.textDisplayer = new shaka.text.SimpleTextDisplayer(
381
+ this.video, shaka.Player.TextTrackLabel);
381
382
  this.textDisplayer.setTextVisibility(true);
382
383
  }
383
384
 
@@ -520,7 +520,7 @@ shaka.test.FakeTransmuxer = class {
520
520
  jasmine.createSpy('transmux').and.returnValue(Promise.resolve(output));
521
521
 
522
522
  /** @type {!jasmine.Spy} */
523
- this.getOrginalMimeType =
524
- jasmine.createSpy('getOrginalMimeType').and.returnValue('mimeType');
523
+ this.getOriginalMimeType =
524
+ jasmine.createSpy('getOriginalMimeType').and.returnValue('mimeType');
525
525
  }
526
526
  };
@@ -763,6 +763,9 @@ shaka.test.TestScheme.ManifestParser = class {
763
763
 
764
764
  /** @override */
765
765
  onExpirationUpdated() {}
766
+
767
+ /** @override */
768
+ onInitialVariantChosen() {}
766
769
  };
767
770
 
768
771
 
@@ -184,6 +184,7 @@ shaka.test.Waiter = class {
184
184
  }
185
185
  this.eventManager_.unlisten(mediaElement, 'timeupdate');
186
186
  this.eventManager_.unlisten(mediaElement, 'ended');
187
+ this.eventManager_.unlisten(mediaElement, 'durationchange');
187
188
  };
188
189
 
189
190
  const p = new Promise((resolve) => {
@@ -202,6 +203,10 @@ shaka.test.Waiter = class {
202
203
  timer.tickEvery(/* seconds= */ 1);
203
204
  this.eventManager_.listen(mediaElement, 'timeupdate', check);
204
205
  this.eventManager_.listen(mediaElement, 'ended', check);
206
+ // Some tests on Safari expose a condition where the ended flag can be
207
+ // found in the desired state in a brief window during a durationchange
208
+ // event.
209
+ this.eventManager_.listen(mediaElement, 'durationchange', check);
205
210
  });
206
211
 
207
212
  return this.waitUntilGeneric_(goalName, p, cleanup, mediaElement);
@@ -43,7 +43,7 @@ describe('Mp4TtmlParser', () => {
43
43
  parser.parseInit(ttmlInitSegment);
44
44
  const time =
45
45
  {periodStart: 0, segmentStart: 0, segmentEnd: 60, vttOffset: 0};
46
- const ret = parser.parseMedia(ttmlSegmentMultipleMDAT, time);
46
+ const ret = parser.parseMedia(ttmlSegmentMultipleMDAT, time, null);
47
47
  // Bodies.
48
48
  expect(ret.length).toBe(2);
49
49
  // Divs.
@@ -63,10 +63,10 @@ describe('Mp4TtmlParser', () => {
63
63
  const parser = new shaka.text.Mp4TtmlParser();
64
64
  parser.parseInit(ttmlInitSegment);
65
65
 
66
- const ret1 = parser.parseMedia(ttmlSegment, time1);
66
+ const ret1 = parser.parseMedia(ttmlSegment, time1, null);
67
67
  expect(ret1.length).toBeGreaterThan(0);
68
68
 
69
- const ret2 = parser.parseMedia(ttmlSegment, time2);
69
+ const ret2 = parser.parseMedia(ttmlSegment, time2, null);
70
70
  expect(ret2.length).toBeGreaterThan(0);
71
71
 
72
72
  expect(ret2[0].startTime).toBe(ret1[0].startTime + 7);
@@ -164,7 +164,7 @@ describe('Mp4TtmlParser', () => {
164
164
  parser.parseInit(ttmlInitSegment);
165
165
  const time =
166
166
  {periodStart: 0, segmentStart: 0, segmentEnd: 60, vttOffset: 0};
167
- const result = parser.parseMedia(ttmlSegment, time);
167
+ const result = parser.parseMedia(ttmlSegment, time, null);
168
168
  shaka.test.TtmlUtils.verifyHelper(
169
169
  cues, result, {startTime: 23, endTime: 53.5});
170
170
  });
@@ -18,7 +18,7 @@ describe('SimpleTextDisplayer', () => {
18
18
 
19
19
  beforeEach(() => {
20
20
  video = new shaka.test.FakeVideo();
21
- displayer = new SimpleTextDisplayer(video);
21
+ displayer = new SimpleTextDisplayer(video, shaka.Player.TextTrackLabel);
22
22
 
23
23
  expect(video.textTracks.length).toBe(1);
24
24
  mockTrack = /** @type {!shaka.test.FakeTextTrack} */ (video.textTracks[0]);
@@ -71,7 +71,7 @@ describe('SrtTextParser', () => {
71
71
  const data = BufferUtils.toUint8(StringUtils.toUTF8(text));
72
72
 
73
73
  const parser = new shaka.text.SrtTextParser();
74
- const result = parser.parseMedia(data, time);
74
+ const result = parser.parseMedia(data, time, null);
75
75
 
76
76
  const expected = cues.map((cue) => {
77
77
  if (cue.nestedCues) {
@@ -95,6 +95,7 @@ describe('TextEngine', () => {
95
95
  expect(mockParseMedia).toHaveBeenCalledOnceMoreWith([
96
96
  dummyData,
97
97
  {periodStart: 0, segmentStart: 0, segmentEnd: 3, vttOffset: 0},
98
+ undefined,
98
99
  ]);
99
100
 
100
101
  expect(mockDisplayer.appendSpy).toHaveBeenCalledOnceMoreWith([
@@ -110,6 +111,7 @@ describe('TextEngine', () => {
110
111
  expect(mockParseMedia).toHaveBeenCalledOnceMoreWith([
111
112
  dummyData,
112
113
  {periodStart: 0, segmentStart: 3, segmentEnd: 5, vttOffset: 0},
114
+ undefined,
113
115
  ]);
114
116
 
115
117
  expect(mockDisplayer.appendSpy).toHaveBeenCalledOnceMoreWith([
@@ -262,6 +264,7 @@ describe('TextEngine', () => {
262
264
  expect(mockParseMedia).toHaveBeenCalledOnceMoreWith([
263
265
  dummyData,
264
266
  {periodStart: 0, segmentStart: 0, segmentEnd: 3, vttOffset: 0},
267
+ undefined,
265
268
  ]);
266
269
  expect(mockDisplayer.appendSpy).toHaveBeenCalledOnceMoreWith([
267
270
  [
@@ -276,6 +279,7 @@ describe('TextEngine', () => {
276
279
  expect(mockParseMedia).toHaveBeenCalledOnceMoreWith([
277
280
  dummyData,
278
281
  {periodStart: 4, segmentStart: 4, segmentEnd: 7, vttOffset: 4},
282
+ undefined,
279
283
  ]);
280
284
  expect(mockDisplayer.appendSpy).toHaveBeenCalledOnceMoreWith([
281
285
  [
@@ -305,6 +309,7 @@ describe('TextEngine', () => {
305
309
  expect(mockParseMedia).toHaveBeenCalledOnceMoreWith([
306
310
  dummyData,
307
311
  {periodStart: 0, segmentStart: 0, segmentEnd: 3, vttOffset: 0},
312
+ undefined,
308
313
  ]);
309
314
 
310
315
  textEngine.setTimestampOffset(8);
@@ -314,6 +319,7 @@ describe('TextEngine', () => {
314
319
  expect(mockParseMedia).toHaveBeenCalledOnceMoreWith([
315
320
  dummyData,
316
321
  {periodStart: 8, segmentStart: 4, segmentEnd: 7, vttOffset: 4},
322
+ undefined,
317
323
  ]);
318
324
  });
319
325
  });