shaka-player 3.2.0 → 3.2.4

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 (162) 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 +182 -1
  5. package/CONTRIBUTORS +3 -0
  6. package/README.md +4 -4
  7. package/build/checkversion.py +1 -1
  8. package/build/generateExterns.js +1 -1
  9. package/build/misspellings.txt +1 -0
  10. package/build/shakaBuildHelpers.py +6 -5
  11. package/build/subprocessWindowsPatch.py +103 -0
  12. package/build/test.py +6 -0
  13. package/demo/common/assets.js +1 -1
  14. package/demo/index.html +1 -1
  15. package/dist/controls.css +2 -2
  16. package/dist/controls.css.map +1 -1
  17. package/dist/demo.compiled.debug.js +1 -1
  18. package/dist/demo.compiled.js +1 -1
  19. package/dist/deps.js +8 -8
  20. package/dist/locales.js +37 -37
  21. package/dist/receiver.compiled.debug.js +1 -1
  22. package/dist/receiver.compiled.js +1 -1
  23. package/dist/shaka-player.compiled.d.ts +195 -5
  24. package/dist/shaka-player.compiled.debug.d.ts +195 -5
  25. package/dist/shaka-player.compiled.debug.externs.js +226 -14
  26. package/dist/shaka-player.compiled.debug.js +424 -399
  27. package/dist/shaka-player.compiled.debug.map +4 -4
  28. package/dist/shaka-player.compiled.externs.js +226 -14
  29. package/dist/shaka-player.compiled.js +826 -812
  30. package/dist/shaka-player.compiled.map +4 -4
  31. package/dist/shaka-player.ui.d.ts +197 -16
  32. package/dist/shaka-player.ui.debug.d.ts +197 -16
  33. package/dist/shaka-player.ui.debug.externs.js +230 -27
  34. package/dist/shaka-player.ui.debug.js +466 -442
  35. package/dist/shaka-player.ui.debug.map +4 -4
  36. package/dist/shaka-player.ui.externs.js +230 -27
  37. package/dist/shaka-player.ui.js +946 -932
  38. package/dist/shaka-player.ui.map +4 -4
  39. package/docs/tutorials/fairplay.md +1 -1
  40. package/docs/tutorials/faq.md +14 -0
  41. package/docs/tutorials/ui.md +5 -5
  42. package/externs/ima.js +4 -0
  43. package/externs/shaka/ads.js +25 -0
  44. package/externs/shaka/player.js +8 -0
  45. package/externs/shaka/text.js +10 -0
  46. package/karma.conf.js +20 -15
  47. package/lib/ads/ad_manager.js +46 -26
  48. package/lib/ads/client_side_ad_manager.js +82 -88
  49. package/lib/ads/server_side_ad_manager.js +45 -13
  50. package/lib/cast/cast_proxy.js +6 -2
  51. package/lib/cast/cast_receiver.js +5 -1
  52. package/lib/cast/cast_sender.js +2 -2
  53. package/lib/cea/sei_processor.js +24 -17
  54. package/lib/dash/dash_parser.js +131 -24
  55. package/lib/dash/segment_list.js +1 -1
  56. package/lib/dash/segment_template.js +1 -1
  57. package/lib/hls/hls_parser.js +74 -17
  58. package/lib/hls/manifest_text_parser.js +1 -0
  59. package/lib/media/closed_caption_parser.js +3 -1
  60. package/lib/media/content_workarounds.js +12 -0
  61. package/lib/media/drm_engine.js +20 -1
  62. package/lib/media/gap_jumping_controller.js +8 -4
  63. package/lib/media/media_source_engine.js +1 -2
  64. package/lib/media/playhead.js +1 -1
  65. package/lib/media/region_observer.js +39 -33
  66. package/lib/media/region_timeline.js +17 -19
  67. package/lib/media/segment_index.js +2 -1
  68. package/lib/media/segment_reference.js +19 -1
  69. package/lib/media/streaming_engine.js +8 -4
  70. package/lib/net/networking_engine.js +6 -1
  71. package/lib/offline/download_manager.js +1 -2
  72. package/lib/offline/storage.js +8 -9
  73. package/lib/player.js +129 -59
  74. package/lib/polyfill/aria.js +12 -2
  75. package/lib/polyfill/encryption_scheme.js +2 -0
  76. package/lib/polyfill/fullscreen.js +2 -0
  77. package/lib/polyfill/mathround.js +2 -0
  78. package/lib/polyfill/media_capabilities.js +73 -21
  79. package/lib/polyfill/mediasource.js +8 -0
  80. package/lib/polyfill/orientation.js +3 -1
  81. package/lib/polyfill/patchedmediakeys_apple.js +6 -4
  82. package/lib/polyfill/patchedmediakeys_ms.js +6 -4
  83. package/lib/polyfill/patchedmediakeys_nop.js +2 -0
  84. package/lib/polyfill/patchedmediakeys_webkit.js +6 -4
  85. package/lib/polyfill/pip_webkit.js +2 -0
  86. package/lib/polyfill/storage_estimate.js +2 -0
  87. package/lib/polyfill/video_play_promise.js +2 -0
  88. package/lib/polyfill/videoplaybackquality.js +2 -0
  89. package/lib/polyfill/vttcue.js +2 -0
  90. package/lib/text/cue.js +6 -0
  91. package/lib/text/mp4_ttml_parser.js +3 -0
  92. package/lib/text/text_engine.js +14 -2
  93. package/lib/text/ttml_text_parser.js +135 -87
  94. package/lib/text/ui_text_displayer.js +213 -80
  95. package/lib/text/vtt_text_parser.js +4 -2
  96. package/lib/text/web_vtt_generator.js +21 -12
  97. package/lib/util/fake_event.js +53 -7
  98. package/lib/util/fake_event_target.js +21 -1
  99. package/lib/util/mp4_box_parsers.js +1 -1
  100. package/lib/util/periods.js +4 -1
  101. package/lib/util/platform.js +13 -1
  102. package/lib/util/stream_utils.js +105 -13
  103. package/lib/util/xml_utils.js +50 -12
  104. package/package.json +3 -6
  105. package/roadmap.md +24 -22
  106. package/test/cast/cast_proxy_unit.js +12 -6
  107. package/test/cast/cast_receiver_integration.js +1 -1
  108. package/test/cast/cast_utils_unit.js +1 -1
  109. package/test/cea/mp4_cea_parser_unit.js +26 -0
  110. package/test/dash/dash_parser_live_unit.js +43 -0
  111. package/test/dash/dash_parser_manifest_unit.js +222 -3
  112. package/test/hls/hls_parser_unit.js +79 -1
  113. package/test/media/closed_caption_parser_unit.js +20 -0
  114. package/test/media/drm_engine_integration.js +86 -2
  115. package/test/media/drm_engine_unit.js +57 -0
  116. package/test/media/playhead_unit.js +27 -1
  117. package/test/media/region_observer_unit.js +40 -5
  118. package/test/media/region_timeline_unit.js +40 -22
  119. package/test/media/streaming_engine_integration.js +2 -1
  120. package/test/media/streaming_engine_unit.js +145 -0
  121. package/test/player_integration.js +79 -8
  122. package/test/player_src_equals_integration.js +41 -8
  123. package/test/player_unit.js +14 -4
  124. package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
  125. package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
  126. package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
  127. package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
  128. package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
  129. package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
  130. package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
  131. package/test/test/assets/3675/dash_0.mpd +30 -0
  132. package/test/test/assets/3675/dash_1.mpd +30 -0
  133. package/test/test/assets/3675/dash_2.mpd +30 -0
  134. package/test/test/assets/3675/dash_3.mpd +30 -0
  135. package/test/test/assets/3675/dash_4.mpd +30 -0
  136. package/test/test/assets/3675/dash_5.mpd +26 -0
  137. package/test/test/assets/3675/init-stream0.m4s +0 -0
  138. package/test/test/assets/chapters.vtt +2 -2
  139. package/test/test/assets/chapters2.vtt +10 -0
  140. package/test/test/assets/empty_caption_video_init.mp4 +0 -0
  141. package/test/test/assets/empty_caption_video_segment.mp4 +0 -0
  142. package/test/test/util/fake_ad_manager.js +11 -1
  143. package/test/test/util/ttml_utils.js +81 -0
  144. package/test/test/util/util.js +10 -7
  145. package/test/text/mp4_ttml_parser_unit.js +11 -18
  146. package/test/text/ttml_text_parser_unit.js +542 -220
  147. package/test/text/ui_text_displayer_unit.js +93 -20
  148. package/test/text/vtt_text_parser_unit.js +17 -1
  149. package/test/text/web_vtt_generator_unit.js +58 -3
  150. package/test/ui/localization_unit.js +15 -14
  151. package/test/ui/text_displayer_layout_unit.js +21 -8
  152. package/test/util/mp4_box_parsers_unit.js +28 -0
  153. package/test/util/stream_utils_unit.js +60 -3
  154. package/ui/cast_button.js +2 -3
  155. package/ui/controls.js +11 -6
  156. package/ui/less/buttons.less +0 -4
  157. package/ui/less/containers.less +6 -7
  158. package/ui/locales/ru.json +6 -6
  159. package/ui/localization.js +40 -87
  160. package/ui/pip_button.js +2 -3
  161. package/ui/seek_bar.js +1 -2
  162. package/ui/text_selection.js +1 -0
@@ -15,6 +15,9 @@ describe('RegionObserver', () => {
15
15
  /** @type {!shaka.media.RegionObserver} */
16
16
  let observer;
17
17
 
18
+ /** @type {!jasmine.Spy} */
19
+ let onSeekRange;
20
+
18
21
  /** @type {!jasmine.Spy} */
19
22
  let onEnterRegion;
20
23
  /** @type {!jasmine.Spy} */
@@ -23,18 +26,26 @@ describe('RegionObserver', () => {
23
26
  let onSkipRegion;
24
27
 
25
28
  beforeEach(() => {
29
+ onSeekRange = jasmine.createSpy('onSeekRange');
30
+ onSeekRange.and.returnValue({start: 0, end: 100});
31
+
26
32
  onEnterRegion = jasmine.createSpy('onEnterRegion');
27
33
  onExitRegion = jasmine.createSpy('onExitRegion');
28
34
  onSkipRegion = jasmine.createSpy('onSkipRegion');
29
35
 
30
36
  timeline = new shaka.media.RegionTimeline(
31
- () => { return {start: 0, end: 100}; });
37
+ shaka.test.Util.spyFunc(onSeekRange));
32
38
 
33
39
  observer = new shaka.media.RegionObserver(timeline);
34
- observer.setListeners(
35
- /* onEnter= */ shaka.test.Util.spyFunc(onEnterRegion),
36
- /* onExit= */ shaka.test.Util.spyFunc(onExitRegion),
37
- /* onSkip= */ shaka.test.Util.spyFunc(onSkipRegion));
40
+ observer.addEventListener('enter', (event) => {
41
+ shaka.test.Util.spyFunc(onEnterRegion)(event['region'], event['seeking']);
42
+ });
43
+ observer.addEventListener('exit', (event) => {
44
+ shaka.test.Util.spyFunc(onExitRegion)(event['region'], event['seeking']);
45
+ });
46
+ observer.addEventListener('skip', (event) => {
47
+ shaka.test.Util.spyFunc(onSkipRegion)(event['region'], event['seeking']);
48
+ });
38
49
  });
39
50
 
40
51
  it('fires enter event when adding a region the playhead is in', () => {
@@ -313,6 +324,30 @@ describe('RegionObserver', () => {
313
324
  expect(onSkipRegion).not.toHaveBeenCalled();
314
325
  });
315
326
 
327
+ // See https://github.com/google/shaka-player/issues/3949
328
+ it('cleans up references to regions', async () => {
329
+ const region = createRegion('my-region', 0, 10);
330
+ timeline.addRegion(region);
331
+
332
+ // Poll so that RegionObserver will store regions.
333
+ poll(observer,
334
+ /* timeInSeconds= */ 5,
335
+ /* seeking= */ false);
336
+
337
+ // Hack to get a private member without the compiler complaining:
338
+ /** @type {Map} */
339
+ const regionMap = (/** @type {?} */(observer))['oldPosition_'];
340
+ expect(regionMap.size).not.toBe(0);
341
+
342
+ // Move the seek range so that the region will be removed.
343
+ onSeekRange.and.returnValue({start: 20, end: 100});
344
+ // Give the timeline time to filter regions
345
+ await shaka.test.Util.delay(
346
+ shaka.media.RegionTimeline.REGION_FILTER_INTERVAL * 2);
347
+
348
+ expect(regionMap.size).toBe(0);
349
+ });
350
+
316
351
  /**
317
352
  * @param {string} id
318
353
  * @param {number} startTimeSeconds
@@ -14,18 +14,27 @@ describe('RegionTimeline', () => {
14
14
  /** @type {!jasmine.Spy} */
15
15
  let onNewRegion;
16
16
 
17
+ /** @type {!jasmine.Spy} */
18
+ let onRemoveRegion;
19
+
17
20
  /** @type {!jasmine.Spy} */
18
21
  let onSeekRange;
19
22
 
20
23
  beforeEach(() => {
21
- onNewRegion = jasmine.createSpy('onNewRegion');
22
-
23
24
  onSeekRange = jasmine.createSpy('onSeekRange');
24
25
  onSeekRange.and.returnValue({start: 0, end: 100});
25
-
26
26
  timeline = new shaka.media.RegionTimeline(
27
27
  shaka.test.Util.spyFunc(onSeekRange));
28
- timeline.setListeners(shaka.test.Util.spyFunc(onNewRegion));
28
+
29
+ onNewRegion = jasmine.createSpy('onNewRegion');
30
+ timeline.addEventListener('regionadd', (event) => {
31
+ shaka.test.Util.spyFunc(onNewRegion)(event['region']);
32
+ });
33
+
34
+ onRemoveRegion = jasmine.createSpy('onRemoveRegion');
35
+ timeline.addEventListener('regionremove', (event) => {
36
+ shaka.test.Util.spyFunc(onRemoveRegion)(event['region']);
37
+ });
29
38
  });
30
39
 
31
40
  afterEach(() => {
@@ -85,24 +94,33 @@ describe('RegionTimeline', () => {
85
94
  ]);
86
95
  });
87
96
 
88
- it('filters out regions that end before the start of the seek range',
89
- async () => {
90
- onSeekRange.and.returnValue({start: 5, end: 100});
91
- const region1 = createRegion('urn:foo', 'my-region', 0, 3);
92
- const region2 = createRegion('urn:foo', 'my-region', 3, 10);
93
- const region3 = createRegion('urn:foo', 'my-region', 5, 10);
94
- timeline.addRegion(region1);
95
- timeline.addRegion(region2);
96
- timeline.addRegion(region3);
97
- expect(onNewRegion).toHaveBeenCalledTimes(3);
98
- let regions = Array.from(timeline.regions());
99
- expect(regions.length).toBe(3);
100
- // Give the timeline time to filter regions
101
- await shaka.test.Util.delay(
102
- shaka.media.RegionTimeline.REGION_FILTER_INTERVAL * 2);
103
- regions = Array.from(timeline.regions());
104
- expect(regions).toEqual([region2, region3]);
105
- });
97
+ it('filters regions that end before the seek range', async () => {
98
+ onSeekRange.and.returnValue({start: 5, end: 100});
99
+
100
+ const region1 = createRegion('urn:foo', 'my-region', 0, 3);
101
+ const region2 = createRegion('urn:foo', 'my-region', 3, 10);
102
+ const region3 = createRegion('urn:foo', 'my-region', 5, 10);
103
+
104
+ timeline.addRegion(region1);
105
+ timeline.addRegion(region2);
106
+ timeline.addRegion(region3);
107
+ expect(onNewRegion).toHaveBeenCalledTimes(3);
108
+ expect(onNewRegion).toHaveBeenCalledWith(region1);
109
+ expect(onNewRegion).toHaveBeenCalledWith(region2);
110
+ expect(onNewRegion).toHaveBeenCalledWith(region3);
111
+
112
+ let regions = Array.from(timeline.regions());
113
+ expect(regions.length).toBe(3);
114
+
115
+ // Give the timeline time to filter regions
116
+ await shaka.test.Util.delay(
117
+ shaka.media.RegionTimeline.REGION_FILTER_INTERVAL * 2);
118
+
119
+ regions = Array.from(timeline.regions());
120
+ expect(regions).toEqual([region2, region3]);
121
+
122
+ expect(onRemoveRegion).toHaveBeenCalledWith(region1);
123
+ });
106
124
 
107
125
  /**
108
126
  * @param {string} schemeIdUri
@@ -419,8 +419,9 @@ describe('StreamingEngine', () => {
419
419
  // 3. Playhead seeks to force us back inside the window
420
420
  // 4. (maybe) seek if there is a gap at the period boundary
421
421
  // 5. (maybe) seek to flush a pipeline stall
422
+ // 6. (maybe) on slower platforms (e.g. GitHub actions)
422
423
  expect(seekCount).toBeGreaterThan(2);
423
- expect(seekCount).toBeLessThan(6);
424
+ expect(seekCount).toBeLessThan(7);
424
425
  });
425
426
  });
426
427
 
@@ -97,6 +97,9 @@ describe('StreamingEngine', () => {
97
97
  /** @type {!shaka.media.StreamingEngine} */
98
98
  let streamingEngine;
99
99
 
100
+ /** @type {function(function(), number)} */
101
+ let realSetTimeout;
102
+
100
103
  /**
101
104
  * Runs the fake event loop.
102
105
  * @param {function()=} callback An optional callback that is executed
@@ -116,6 +119,7 @@ describe('StreamingEngine', () => {
116
119
  }
117
120
 
118
121
  beforeAll(() => {
122
+ realSetTimeout = window.setTimeout;
119
123
  jasmine.clock().install();
120
124
  jasmine.clock().mockDate();
121
125
  });
@@ -822,6 +826,53 @@ describe('StreamingEngine', () => {
822
826
  .toHaveBeenCalledWith('video', 0, lt20, gt40);
823
827
  });
824
828
 
829
+ // Regression test for https://github.com/google/shaka-player/issues/3717
830
+ it('applies fudge factors for the duration', async () => {
831
+ setupVod();
832
+
833
+ // In #3717, the duration was just barely large enough to encompass an
834
+ // additional segment, but that segment didn't exist, so playback never
835
+ // completed. Here, we set the duration to just beyond the 3rd segment, and
836
+ // we make the 4th segment fail when requested.
837
+ const duration = 30.000000005;
838
+ timeline.getDuration.and.returnValue(duration);
839
+
840
+ const targetUri = '1_video_3'; // The URI of the 4th video segment.
841
+ failRequestsForTarget(netEngine, targetUri);
842
+
843
+ mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
844
+ createStreamingEngine();
845
+
846
+ // Here we go!
847
+ streamingEngine.switchVariant(variant);
848
+ streamingEngine.switchTextStream(textStream);
849
+ await streamingEngine.start();
850
+ playing = true;
851
+ await runTest();
852
+
853
+ // The end of the stream should have been reached, and the 4th segment from
854
+ // each type should never have been requested.
855
+ expect(mediaSourceEngine.endOfStream).toHaveBeenCalled();
856
+
857
+ const segmentType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
858
+
859
+ netEngine.expectRequest('0_audio_0', segmentType);
860
+ netEngine.expectRequest('0_video_0', segmentType);
861
+ netEngine.expectRequest('0_text_0', segmentType);
862
+
863
+ netEngine.expectRequest('0_audio_1', segmentType);
864
+ netEngine.expectRequest('0_video_1', segmentType);
865
+ netEngine.expectRequest('0_text_1', segmentType);
866
+
867
+ netEngine.expectRequest('1_audio_2', segmentType);
868
+ netEngine.expectRequest('1_video_2', segmentType);
869
+ netEngine.expectRequest('1_text_2', segmentType);
870
+
871
+ netEngine.expectNoRequest('1_audio_3', segmentType);
872
+ netEngine.expectNoRequest('1_video_3', segmentType);
873
+ netEngine.expectNoRequest('1_text_3', segmentType);
874
+ });
875
+
825
876
  it('does not buffer one media type ahead of another', async () => {
826
877
  setupVod();
827
878
  mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
@@ -1389,6 +1440,66 @@ describe('StreamingEngine', () => {
1389
1440
  });
1390
1441
  });
1391
1442
 
1443
+ it('into unbuffered regions when nothing is buffered ' +
1444
+ 'and mediaState is performing an update', async () => {
1445
+ // Here we go!
1446
+ streamingEngine.switchVariant(variant);
1447
+ streamingEngine.switchTextStream(textStream);
1448
+ // ensure init source buffer promise does not resolve before seeked()
1449
+ // so mediaState remains in "performingUpdate" state
1450
+ const initSourceBufferPromise = new shaka.util.PublicPromise();
1451
+ mediaSourceEngine.setStreamProperties.and
1452
+ .returnValue(initSourceBufferPromise);
1453
+ await streamingEngine.start();
1454
+ playing = true;
1455
+
1456
+ // tick to trigger mediaState updates
1457
+ jasmine.clock().tick(1);
1458
+ // give a chance for fetchAndAppend_ to be invoked
1459
+ // by async onUpdate_ callback
1460
+ await Util.shortDelay(realSetTimeout);
1461
+
1462
+ // Nothing is buffered yet.
1463
+ expect(mediaSourceEngine.segments).toEqual({
1464
+ audio: [false, false, false, false],
1465
+ video: [false, false, false, false],
1466
+ text: [false, false, false, false],
1467
+ });
1468
+
1469
+ // Seek forward to an unbuffered region in the first Period.
1470
+ presentationTimeInSeconds = 15;
1471
+ streamingEngine.seeked();
1472
+
1473
+ // resolve initSourceBufferPromise after seeked(), waitingToClearBuffer
1474
+ // should have been set, so this will now trigger the actual flush of
1475
+ // the buffer
1476
+ initSourceBufferPromise.resolve();
1477
+ mediaSourceEngine.setStreamProperties.and
1478
+ .returnValue(Promise.resolve());
1479
+
1480
+ // allow mediaState update to resolve
1481
+ await Util.shortDelay(realSetTimeout);
1482
+
1483
+ onTick.and.callFake(() => {
1484
+ expect(mediaSourceEngine.clear).toHaveBeenCalled();
1485
+ onTick.and.stub();
1486
+ });
1487
+
1488
+ await runTest(Util.spyFunc(onTick));
1489
+
1490
+ // Verify buffers.
1491
+ expect(mediaSourceEngine.initSegments).toEqual({
1492
+ audio: [false, true],
1493
+ video: [false, true],
1494
+ text: [],
1495
+ });
1496
+ expect(mediaSourceEngine.segments).toEqual({
1497
+ audio: [false, true, true, true],
1498
+ video: [false, true, true, true],
1499
+ text: [false, true, true, true],
1500
+ });
1501
+ });
1502
+
1392
1503
  it('into unbuffered regions near segment start', async () => {
1393
1504
  // Here we go!
1394
1505
  streamingEngine.switchVariant(variant);
@@ -3388,6 +3499,40 @@ describe('StreamingEngine', () => {
3388
3499
  segmentAvailability.end++;
3389
3500
  }
3390
3501
 
3502
+ /**
3503
+ * @param {!shaka.test.FakeNetworkingEngine} netEngine A NetworkingEngine
3504
+ * look-alike.
3505
+ * @param {string} targetUri
3506
+ * @param {shaka.util.Error.Code=} errorCode
3507
+ */
3508
+ function failRequestsForTarget(
3509
+ netEngine, targetUri, errorCode=shaka.util.Error.Code.BAD_HTTP_STATUS) {
3510
+ // eslint-disable-next-line no-restricted-syntax
3511
+ const originalNetEngineRequest = netEngine.request.bind(netEngine);
3512
+
3513
+ netEngine.request = jasmine.createSpy('request').and.callFake(
3514
+ (requestType, request) => {
3515
+ if (request.uris[0] == targetUri) {
3516
+ const data = [targetUri];
3517
+
3518
+ if (errorCode == shaka.util.Error.Code.BAD_HTTP_STATUS) {
3519
+ data.push(404);
3520
+ data.push('');
3521
+ }
3522
+
3523
+ // The compiler still sees the error code parameter as potentially
3524
+ // undefined, even though we gave it a default value.
3525
+ goog.asserts.assert(errorCode != undefined, 'Undefined error code');
3526
+
3527
+ return shaka.util.AbortableOperation.failed(new shaka.util.Error(
3528
+ shaka.util.Error.Severity.CRITICAL,
3529
+ shaka.util.Error.Category.NETWORK,
3530
+ errorCode, data));
3531
+ }
3532
+ return originalNetEngineRequest(requestType, request);
3533
+ });
3534
+ }
3535
+
3391
3536
  /**
3392
3537
  * @param {!shaka.test.FakeNetworkingEngine} netEngine A NetworkingEngine
3393
3538
  * look-alike.
@@ -652,6 +652,44 @@ describe('Player', () => {
652
652
  });
653
653
  });
654
654
 
655
+ describe('seek range', () => {
656
+ // Regression test for Issue #3675.
657
+ it('has a reasonable seek range after going from live to VOD', async () => {
658
+ const netEngine = player.getNetworkingEngine();
659
+ const startTime = Date.now();
660
+ netEngine.registerRequestFilter((type, request) => {
661
+ if (type != shaka.net.NetworkingEngine.RequestType.MANIFEST) {
662
+ return;
663
+ }
664
+ // Simulate a live stream by providing different manifests over time.
665
+ const time = (Date.now() - startTime) / 1000;
666
+ const manifestNumber = Math.min(5, Math.floor(0.5 + time / 2));
667
+ request.uris = [
668
+ '/base/test/test/assets/3675/dash_' + manifestNumber + '.mpd',
669
+ ];
670
+ console.log('getting manifest', request.uris);
671
+ });
672
+
673
+ // Play the stream.
674
+ await player.load('/base/test/test/assets/3675/dash_0.mpd');
675
+ video.play();
676
+
677
+ // Wait for the stream to be over.
678
+ eventManager.listen(player, 'error', Util.spyFunc(onErrorSpy));
679
+ /** @type {shaka.test.Waiter} */
680
+ const waiter = new shaka.test.Waiter(eventManager)
681
+ .timeoutAfter(20).failOnTimeout(true);
682
+ await waiter.waitForEnd(video);
683
+
684
+ // The stream should have transitioned to VOD by now.
685
+ expect(player.isLive()).toBe(false);
686
+
687
+ // Check that the final seek range is as expected.
688
+ const seekRange = player.seekRange();
689
+ expect(seekRange.end).toBe(14);
690
+ });
691
+ });
692
+
655
693
  describe('buffering', () => {
656
694
  const startBuffering = jasmine.objectContaining({buffering: true});
657
695
  const endBuffering = jasmine.objectContaining({buffering: false});
@@ -926,9 +964,9 @@ describe('Player', () => {
926
964
  it('add external chapters in vtt format', async () => {
927
965
  await player.load('test:sintel_no_text_compiled');
928
966
  const locationUri = new goog.Uri(location.href);
929
- const partialUri = new goog.Uri('/base/test/test/assets/chapters.vtt');
930
- const absoluteUri = locationUri.resolve(partialUri);
931
- await player.addChaptersTrack(absoluteUri.toString(), 'en');
967
+ const partialUri1 = new goog.Uri('/base/test/test/assets/chapters.vtt');
968
+ const absoluteUri1 = locationUri.resolve(partialUri1);
969
+ await player.addChaptersTrack(absoluteUri1.toString(), 'en');
932
970
 
933
971
  await shaka.test.Util.delay(1.5);
934
972
 
@@ -941,11 +979,44 @@ describe('Player', () => {
941
979
  const chapter2 = chapters[1];
942
980
  expect(chapter2.title).toBe('Chapter 2');
943
981
  expect(chapter2.startTime).toBe(5);
944
- expect(chapter2.endTime).toBe(30);
982
+ expect(chapter2.endTime).toBe(10);
945
983
  const chapter3 = chapters[2];
946
984
  expect(chapter3.title).toBe('Chapter 3');
947
- expect(chapter3.startTime).toBe(30);
948
- expect(chapter3.endTime).toBe(61.349);
985
+ expect(chapter3.startTime).toBe(10);
986
+ expect(chapter3.endTime).toBe(20);
987
+
988
+ const partialUri2 = new goog.Uri('/base/test/test/assets/chapters2.vtt');
989
+ const absoluteUri2 = locationUri.resolve(partialUri2);
990
+ await player.addChaptersTrack(absoluteUri2.toString(), 'en');
991
+
992
+ await shaka.test.Util.delay(1.5);
993
+
994
+ const chaptersUpdated = player.getChapters('en');
995
+ expect(chaptersUpdated.length).toBe(6);
996
+ const chapterUpdated1 = chaptersUpdated[0];
997
+ expect(chapterUpdated1.title).toBe('Chapter 1');
998
+ expect(chapterUpdated1.startTime).toBe(0);
999
+ expect(chapterUpdated1.endTime).toBe(5);
1000
+ const chapterUpdated2 = chaptersUpdated[1];
1001
+ expect(chapterUpdated2.title).toBe('Chapter 2');
1002
+ expect(chapterUpdated2.startTime).toBe(5);
1003
+ expect(chapterUpdated2.endTime).toBe(10);
1004
+ const chapterUpdated3 = chaptersUpdated[2];
1005
+ expect(chapterUpdated3.title).toBe('Chapter 3');
1006
+ expect(chapterUpdated3.startTime).toBe(10);
1007
+ expect(chapterUpdated3.endTime).toBe(20);
1008
+ const chapterUpdated4 = chaptersUpdated[3];
1009
+ expect(chapterUpdated4.title).toBe('Chapter 4');
1010
+ expect(chapterUpdated4.startTime).toBe(20);
1011
+ expect(chapterUpdated4.endTime).toBe(30);
1012
+ const chapterUpdated5 = chaptersUpdated[4];
1013
+ expect(chapterUpdated5.title).toBe('Chapter 5');
1014
+ expect(chapterUpdated5.startTime).toBe(30);
1015
+ expect(chapterUpdated5.endTime).toBe(40);
1016
+ const chapterUpdated6 = chaptersUpdated[5];
1017
+ expect(chapterUpdated6.title).toBe('Chapter 6');
1018
+ expect(chapterUpdated6.startTime).toBe(40);
1019
+ expect(chapterUpdated6.endTime).toBe(61.349);
949
1020
  });
950
1021
 
951
1022
  it('add external chapters in srt format', async () => {
@@ -953,11 +1024,11 @@ describe('Player', () => {
953
1024
  const locationUri = new goog.Uri(location.href);
954
1025
  const partialUri = new goog.Uri('/base/test/test/assets/chapters.srt');
955
1026
  const absoluteUri = locationUri.resolve(partialUri);
956
- await player.addChaptersTrack(absoluteUri.toString(), 'en');
1027
+ await player.addChaptersTrack(absoluteUri.toString(), 'es');
957
1028
 
958
1029
  await shaka.test.Util.delay(1.5);
959
1030
 
960
- const chapters = player.getChapters('en');
1031
+ const chapters = player.getChapters('es');
961
1032
  expect(chapters.length).toBe(3);
962
1033
  const chapter1 = chapters[0];
963
1034
  expect(chapter1.title).toBe('Chapter 1');
@@ -330,9 +330,9 @@ describe('Player Src Equals', () => {
330
330
  await loadWithSrcEquals(SMALL_MP4_CONTENT_URI, /* startTime= */ null);
331
331
 
332
332
  const locationUri = new goog.Uri(location.href);
333
- const partialUri = new goog.Uri('/base/test/test/assets/chapters.vtt');
334
- const absoluteUri = locationUri.resolve(partialUri);
335
- await player.addChaptersTrack(absoluteUri.toString(), 'en');
333
+ const partialUri1 = new goog.Uri('/base/test/test/assets/chapters.vtt');
334
+ const absoluteUri1 = locationUri.resolve(partialUri1);
335
+ await player.addChaptersTrack(absoluteUri1.toString(), 'en');
336
336
 
337
337
  await shaka.test.Util.delay(1.5);
338
338
 
@@ -345,11 +345,44 @@ describe('Player Src Equals', () => {
345
345
  const chapter2 = chapters[1];
346
346
  expect(chapter2.title).toBe('Chapter 2');
347
347
  expect(chapter2.startTime).toBe(5);
348
- expect(chapter2.endTime).toBe(30);
348
+ expect(chapter2.endTime).toBe(10);
349
349
  const chapter3 = chapters[2];
350
350
  expect(chapter3.title).toBe('Chapter 3');
351
- expect(chapter3.startTime).toBe(30);
352
- expect(chapter3.endTime).toBe(61.349);
351
+ expect(chapter3.startTime).toBe(10);
352
+ expect(chapter3.endTime).toBe(20);
353
+
354
+ const partialUri2 = new goog.Uri('/base/test/test/assets/chapters2.vtt');
355
+ const absoluteUri2 = locationUri.resolve(partialUri2);
356
+ await player.addChaptersTrack(absoluteUri2.toString(), 'en');
357
+
358
+ await shaka.test.Util.delay(1.5);
359
+
360
+ const chaptersUpdated = player.getChapters('en');
361
+ expect(chaptersUpdated.length).toBe(6);
362
+ const chapterUpdated1 = chaptersUpdated[0];
363
+ expect(chapterUpdated1.title).toBe('Chapter 1');
364
+ expect(chapterUpdated1.startTime).toBe(0);
365
+ expect(chapterUpdated1.endTime).toBe(5);
366
+ const chapterUpdated2 = chaptersUpdated[1];
367
+ expect(chapterUpdated2.title).toBe('Chapter 2');
368
+ expect(chapterUpdated2.startTime).toBe(5);
369
+ expect(chapterUpdated2.endTime).toBe(10);
370
+ const chapterUpdated3 = chaptersUpdated[2];
371
+ expect(chapterUpdated3.title).toBe('Chapter 3');
372
+ expect(chapterUpdated3.startTime).toBe(10);
373
+ expect(chapterUpdated3.endTime).toBe(20);
374
+ const chapterUpdated4 = chaptersUpdated[3];
375
+ expect(chapterUpdated4.title).toBe('Chapter 4');
376
+ expect(chapterUpdated4.startTime).toBe(20);
377
+ expect(chapterUpdated4.endTime).toBe(30);
378
+ const chapterUpdated5 = chaptersUpdated[4];
379
+ expect(chapterUpdated5.title).toBe('Chapter 5');
380
+ expect(chapterUpdated5.startTime).toBe(30);
381
+ expect(chapterUpdated5.endTime).toBe(40);
382
+ const chapterUpdated6 = chaptersUpdated[5];
383
+ expect(chapterUpdated6.title).toBe('Chapter 6');
384
+ expect(chapterUpdated6.startTime).toBe(40);
385
+ expect(chapterUpdated6.endTime).toBe(61.349);
353
386
  });
354
387
 
355
388
  it('add external chapters in srt format', async () => {
@@ -358,11 +391,11 @@ describe('Player Src Equals', () => {
358
391
  const locationUri = new goog.Uri(location.href);
359
392
  const partialUri = new goog.Uri('/base/test/test/assets/chapters.srt');
360
393
  const absoluteUri = locationUri.resolve(partialUri);
361
- await player.addChaptersTrack(absoluteUri.toString(), 'en');
394
+ await player.addChaptersTrack(absoluteUri.toString(), 'es');
362
395
 
363
396
  await shaka.test.Util.delay(1.5);
364
397
 
365
- const chapters = player.getChapters('en');
398
+ const chapters = player.getChapters('es');
366
399
  expect(chapters.length).toBe(3);
367
400
  const chapter1 = chapters[0];
368
401
  expect(chapter1.title).toBe('Chapter 1');
@@ -1092,8 +1092,8 @@ describe('Player', () => {
1092
1092
  // Image tracks
1093
1093
  manifest.addImageStream(53, (stream) => {
1094
1094
  stream.originalId = 'thumbnail';
1095
- stream.width = 100;
1096
- stream.height = 200;
1095
+ stream.width = 200;
1096
+ stream.height = 400;
1097
1097
  stream.bandwidth = 10;
1098
1098
  stream.mimeType = 'image/jpeg';
1099
1099
  stream.tilesLayout = '1x1';
@@ -1503,8 +1503,8 @@ describe('Player', () => {
1503
1503
  audioBandwidth: null,
1504
1504
  videoBandwidth: null,
1505
1505
  bandwidth: 10,
1506
- width: 100,
1507
- height: 200,
1506
+ width: 200,
1507
+ height: 400,
1508
1508
  frameRate: null,
1509
1509
  pixelAspectRatio: null,
1510
1510
  hdr: null,
@@ -3537,29 +3537,39 @@ describe('Player', () => {
3537
3537
 
3538
3538
  await player.load(fakeManifestUri, 0, fakeMimeType);
3539
3539
 
3540
+ expect(player.getImageTracks()[0].width).toBe(100);
3541
+ expect(player.getImageTracks()[0].height).toBe(50);
3540
3542
  const thumbnail0 = await player.getThumbnails(5, 0);
3541
3543
  const thumbnail1 = await player.getThumbnails(5, 11);
3542
3544
  const thumbnail2 = await player.getThumbnails(5, 21);
3543
3545
  const thumbnail5 = await player.getThumbnails(5, 51);
3544
3546
  expect(thumbnail0).toEqual(jasmine.objectContaining({
3547
+ imageHeight: 150,
3548
+ imageWidth: 200,
3545
3549
  positionX: 0,
3546
3550
  positionY: 0,
3547
3551
  width: 100,
3548
3552
  height: 50,
3549
3553
  }));
3550
3554
  expect(thumbnail1).toEqual(jasmine.objectContaining({
3555
+ imageHeight: 150,
3556
+ imageWidth: 200,
3551
3557
  positionX: 100,
3552
3558
  positionY: 0,
3553
3559
  width: 100,
3554
3560
  height: 50,
3555
3561
  }));
3556
3562
  expect(thumbnail2).toEqual(jasmine.objectContaining({
3563
+ imageHeight: 150,
3564
+ imageWidth: 200,
3557
3565
  positionX: 0,
3558
3566
  positionY: 50,
3559
3567
  width: 100,
3560
3568
  height: 50,
3561
3569
  }));
3562
3570
  expect(thumbnail5).toEqual(jasmine.objectContaining({
3571
+ imageHeight: 150,
3572
+ imageWidth: 200,
3563
3573
  positionX: 100,
3564
3574
  positionY: 100,
3565
3575
  width: 100,
@@ -0,0 +1,30 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3
+ xmlns="urn:mpeg:dash:schema:mpd:2011"
4
+ xmlns:xlink="http://www.w3.org/1999/xlink"
5
+ xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd"
6
+ profiles="urn:mpeg:dash:profile:isoff-live:2011"
7
+ type="dynamic"
8
+ minimumUpdatePeriod="PT2S"
9
+ suggestedPresentationDelay="PT2S"
10
+ availabilityStartTime="2021-10-28T07:36:27.755Z"
11
+ publishTime="2021-10-28T07:36:29.744Z"
12
+ timeShiftBufferDepth="PT10.0S"
13
+ maxSegmentDuration="PT2.0S"
14
+ minBufferTime="PT4.0S">
15
+ <ProgramInformation>
16
+ </ProgramInformation>
17
+ <ServiceDescription id="0">
18
+ </ServiceDescription>
19
+ <Period id="0" start="PT0.0S">
20
+ <AdaptationSet id="0" contentType="video" startWithSAP="1" segmentAlignment="true" bitstreamSwitching="true" frameRate="30/1" maxWidth="1280" maxHeight="720" par="16:9">
21
+ <Representation id="0" mimeType="video/mp4" codecs="avc1.64001f" bandwidth="122012" width="1280" height="720" sar="1:1">
22
+ <SegmentTemplate timescale="15360" initialization="init-stream$RepresentationID$.m4s" media="chunk-stream$RepresentationID$-$Number%05d$.m4s" startNumber="1">
23
+ <SegmentTimeline>
24
+ <S t="0" d="30720" />
25
+ </SegmentTimeline>
26
+ </SegmentTemplate>
27
+ </Representation>
28
+ </AdaptationSet>
29
+ </Period>
30
+ </MPD>