shaka-player 3.2.1 → 3.3.1

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 (213) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +1 -1
  3. package/.github/ISSUE_TEMPLATE/question.md +1 -1
  4. package/AUTHORS +9 -5
  5. package/CHANGELOG.md +358 -0
  6. package/CONTRIBUTORS +7 -2
  7. package/build/conformance.textproto +183 -169
  8. package/build/generateExterns.js +1 -1
  9. package/build/types/core +3 -0
  10. package/build/types/offline +1 -0
  11. package/build/types/polyfill +1 -0
  12. package/build/types/ui +2 -0
  13. package/demo/common/assets.js +19 -1
  14. package/demo/common/message_ids.js +13 -1
  15. package/demo/config.js +31 -1
  16. package/demo/index.html +1 -1
  17. package/demo/locales/en.json +12 -0
  18. package/demo/locales/source.json +48 -0
  19. package/dist/controls.css +26 -2
  20. package/dist/controls.css.map +1 -1
  21. package/dist/demo.compiled.debug.js +20 -17
  22. package/dist/demo.compiled.debug.map +3 -3
  23. package/dist/demo.compiled.js +20 -17
  24. package/dist/demo.compiled.map +3 -3
  25. package/dist/deps.js +19 -12
  26. package/dist/locales.js +39 -37
  27. package/dist/receiver.compiled.debug.js +12 -10
  28. package/dist/receiver.compiled.debug.map +3 -3
  29. package/dist/receiver.compiled.js +12 -10
  30. package/dist/receiver.compiled.map +3 -3
  31. package/dist/shaka-player.compiled.d.ts +231 -18
  32. package/dist/shaka-player.compiled.debug.d.ts +231 -18
  33. package/dist/shaka-player.compiled.debug.externs.js +279 -8
  34. package/dist/shaka-player.compiled.debug.js +512 -449
  35. package/dist/shaka-player.compiled.debug.map +4 -4
  36. package/dist/shaka-player.compiled.externs.js +279 -8
  37. package/dist/shaka-player.compiled.js +854 -821
  38. package/dist/shaka-player.compiled.map +4 -4
  39. package/dist/shaka-player.ui.d.ts +247 -19
  40. package/dist/shaka-player.ui.debug.d.ts +247 -19
  41. package/dist/shaka-player.ui.debug.externs.js +307 -9
  42. package/dist/shaka-player.ui.debug.js +662 -580
  43. package/dist/shaka-player.ui.debug.map +4 -4
  44. package/dist/shaka-player.ui.externs.js +307 -9
  45. package/dist/shaka-player.ui.js +1047 -1000
  46. package/dist/shaka-player.ui.map +4 -4
  47. package/docs/design/bg-fetch-after.gv +29 -0
  48. package/docs/design/bg-fetch-after.gv.png +0 -0
  49. package/docs/design/bg-fetch-before.gv +18 -0
  50. package/docs/design/bg-fetch-before.gv.png +0 -0
  51. package/docs/design/bg-fetch.md +134 -0
  52. package/docs/tutorials/blob-url.md +15 -0
  53. package/docs/tutorials/fairplay.md +1 -1
  54. package/docs/tutorials/index.json +1 -0
  55. package/docs/tutorials/ui-customization.md +66 -0
  56. package/docs/tutorials/ui.md +5 -5
  57. package/externs/cmcd.js +186 -0
  58. package/externs/ima.js +3 -0
  59. package/externs/shaka/ads.js +5 -0
  60. package/externs/shaka/manifest.js +12 -1
  61. package/externs/shaka/manifest_parser.js +10 -1
  62. package/externs/shaka/net.js +20 -4
  63. package/externs/shaka/offline.js +31 -2
  64. package/externs/shaka/player.js +138 -5
  65. package/externs/shaka/text.js +24 -0
  66. package/externs/webcrypto.js +18 -0
  67. package/karma.conf.js +1 -0
  68. package/lib/abr/ewma.js +14 -0
  69. package/lib/abr/ewma_bandwidth_estimator.js +16 -2
  70. package/lib/abr/simple_abr_manager.js +6 -0
  71. package/lib/ads/client_side_ad.js +8 -0
  72. package/lib/ads/client_side_ad_manager.js +54 -90
  73. package/lib/ads/server_side_ad.js +7 -0
  74. package/lib/ads/server_side_ad_manager.js +7 -10
  75. package/lib/cast/cast_proxy.js +3 -2
  76. package/lib/cast/cast_receiver.js +2 -1
  77. package/lib/cast/cast_sender.js +2 -2
  78. package/lib/cast/cast_utils.js +1 -0
  79. package/lib/dash/dash_parser.js +141 -16
  80. package/lib/dash/segment_base.js +25 -1
  81. package/lib/dash/segment_list.js +19 -10
  82. package/lib/dash/segment_template.js +24 -11
  83. package/lib/hls/hls_parser.js +146 -40
  84. package/lib/media/drm_engine.js +7 -0
  85. package/lib/media/gap_jumping_controller.js +4 -2
  86. package/lib/media/media_source_engine.js +1 -2
  87. package/lib/media/quality_observer.js +291 -0
  88. package/lib/media/segment_index.js +8 -1
  89. package/lib/media/segment_reference.js +37 -4
  90. package/lib/media/streaming_engine.js +49 -7
  91. package/lib/net/http_fetch_plugin.js +31 -11
  92. package/lib/net/http_plugin_utils.js +1 -0
  93. package/lib/net/http_xhr_plugin.js +40 -14
  94. package/lib/net/networking_engine.js +83 -19
  95. package/lib/offline/download_info.js +68 -0
  96. package/lib/offline/download_manager.js +13 -5
  97. package/lib/offline/indexeddb/base_storage_cell.js +35 -0
  98. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  99. package/lib/offline/indexeddb/v5_storage_cell.js +5 -0
  100. package/lib/offline/storage.js +393 -284
  101. package/lib/offline/stored_content_utils.js +2 -0
  102. package/lib/player.js +249 -43
  103. package/lib/polyfill/media_capabilities.js +36 -15
  104. package/lib/polyfill/mediasource.js +6 -0
  105. package/lib/polyfill/orientation.js +1 -1
  106. package/lib/polyfill/patchedmediakeys_apple.js +4 -4
  107. package/lib/polyfill/patchedmediakeys_ms.js +4 -4
  108. package/lib/polyfill/patchedmediakeys_webkit.js +4 -4
  109. package/lib/polyfill/random_uuid.js +52 -0
  110. package/lib/text/cue.js +18 -0
  111. package/lib/text/mp4_ttml_parser.js +3 -0
  112. package/lib/text/text_engine.js +14 -2
  113. package/lib/text/ttml_text_parser.js +114 -60
  114. package/lib/text/ui_text_displayer.js +95 -41
  115. package/lib/util/cmcd_manager.js +672 -0
  116. package/lib/util/error.js +6 -0
  117. package/lib/util/fake_event.js +53 -7
  118. package/lib/util/language_utils.js +12 -0
  119. package/lib/util/mutex.js +52 -0
  120. package/lib/util/periods.js +102 -65
  121. package/lib/util/platform.js +13 -1
  122. package/lib/util/player_configuration.js +18 -0
  123. package/lib/util/stream_utils.js +99 -10
  124. package/lib/util/xml_utils.js +19 -0
  125. package/package.json +2 -2
  126. package/shaka-player.uncompiled.js +3 -0
  127. package/test/cast/cast_proxy_unit.js +12 -6
  128. package/test/cast/cast_utils_unit.js +1 -1
  129. package/test/dash/dash_parser_content_protection_unit.js +2 -0
  130. package/test/dash/dash_parser_live_unit.js +101 -1
  131. package/test/dash/dash_parser_manifest_unit.js +222 -2
  132. package/test/dash/dash_parser_segment_base_unit.js +2 -0
  133. package/test/dash/dash_parser_segment_list_unit.js +2 -1
  134. package/test/dash/dash_parser_segment_template_unit.js +2 -1
  135. package/test/hls/hls_live_unit.js +2 -0
  136. package/test/hls/hls_parser_unit.js +7 -1
  137. package/test/media/closed_caption_parser_unit.js +6 -1
  138. package/test/media/drm_engine_unit.js +47 -3
  139. package/test/media/quality_observer_unit.js +139 -0
  140. package/test/media/segment_reference_unit.js +16 -1
  141. package/test/media/streaming_engine_integration.js +4 -2
  142. package/test/media/streaming_engine_unit.js +147 -0
  143. package/test/net/http_plugin_unit.js +20 -15
  144. package/test/offline/storage_integration.js +108 -4
  145. package/test/player_integration.js +178 -8
  146. package/test/player_src_equals_integration.js +41 -8
  147. package/test/player_unit.js +19 -9
  148. package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
  149. package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
  150. package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
  151. package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
  152. package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
  153. package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
  154. package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
  155. package/test/test/assets/3675/dash_0.mpd +30 -0
  156. package/test/test/assets/3675/dash_1.mpd +30 -0
  157. package/test/test/assets/3675/dash_2.mpd +30 -0
  158. package/test/test/assets/3675/dash_3.mpd +30 -0
  159. package/test/test/assets/3675/dash_4.mpd +30 -0
  160. package/test/test/assets/3675/dash_5.mpd +26 -0
  161. package/test/test/assets/3675/init-stream0.m4s +0 -0
  162. package/test/test/assets/chapters.vtt +2 -2
  163. package/test/test/assets/chapters2.vtt +10 -0
  164. package/test/test/util/dash_parser_util.js +4 -0
  165. package/test/test/util/fake_ad.js +7 -0
  166. package/test/test/util/fake_ad_manager.js +1 -1
  167. package/test/test/util/fake_drm_engine.js +9 -2
  168. package/test/test/util/manifest_generator.js +3 -2
  169. package/test/test/util/test_scheme.js +14 -1
  170. package/test/test/util/ttml_utils.js +81 -0
  171. package/test/text/mp4_ttml_parser_unit.js +11 -18
  172. package/test/text/ttml_text_parser_unit.js +552 -227
  173. package/test/text/ui_text_displayer_unit.js +29 -17
  174. package/test/ui/localization_unit.js +15 -14
  175. package/test/ui/text_displayer_layout_unit.js +13 -1
  176. package/test/ui/ui_unit.js +250 -0
  177. package/test/util/cmcd_manager_unit.js +291 -0
  178. package/test/util/stream_utils_unit.js +45 -4
  179. package/third_party/closure-uri/uri.js +21 -0
  180. package/ui/airplay_button.js +1 -0
  181. package/ui/audio_language_selection.js +7 -0
  182. package/ui/cast_button.js +3 -3
  183. package/ui/context_menu.js +109 -0
  184. package/ui/controls.js +45 -13
  185. package/ui/controls.less +1 -0
  186. package/ui/enums.js +4 -0
  187. package/ui/externs/ui.js +28 -1
  188. package/ui/fast_forward_button.js +14 -5
  189. package/ui/fullscreen_button.js +1 -0
  190. package/ui/less/buttons.less +0 -4
  191. package/ui/less/containers.less +77 -7
  192. package/ui/less/overflow_menu.less +0 -10
  193. package/ui/less/tooltip.less +97 -0
  194. package/ui/locales/en.json +1 -0
  195. package/ui/locales/ru.json +6 -6
  196. package/ui/locales/source.json +4 -0
  197. package/ui/localization.js +28 -51
  198. package/ui/loop_button.js +5 -0
  199. package/ui/mute_button.js +2 -1
  200. package/ui/overflow_menu.js +5 -25
  201. package/ui/pip_button.js +7 -3
  202. package/ui/play_button.js +2 -2
  203. package/ui/playback_rate_selection.js +8 -9
  204. package/ui/resolution_selection.js +7 -0
  205. package/ui/rewind_button.js +15 -6
  206. package/ui/seek_bar.js +4 -2
  207. package/ui/settings_menu.js +36 -4
  208. package/ui/small_play_button.js +1 -0
  209. package/ui/statistics_button.js +253 -0
  210. package/ui/text_selection.js +12 -4
  211. package/ui/ui.js +30 -0
  212. package/ui/volume_bar.js +1 -1
  213. package/externs/media_capabilities.js +0 -16
@@ -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
  });
@@ -451,6 +455,7 @@ describe('StreamingEngine', () => {
451
455
  presentationTimeInSeconds != undefined,
452
456
  'All tests should have defined an initial presentation time by now!');
453
457
  const playerInterface = {
458
+ modifySegmentRequest: (request, segmentInfo) => {},
454
459
  getPresentationTime: () => presentationTimeInSeconds,
455
460
  getBandwidthEstimate: Util.spyFunc(getBandwidthEstimate),
456
461
  mediaSourceEngine: mediaSourceEngine,
@@ -459,6 +464,7 @@ describe('StreamingEngine', () => {
459
464
  onEvent: Util.spyFunc(onEvent),
460
465
  onManifestUpdate: Util.spyFunc(onManifestUpdate),
461
466
  onSegmentAppended: Util.spyFunc(onSegmentAppended),
467
+ onInitSegmentAppended: () => {},
462
468
  };
463
469
  streamingEngine = new shaka.media.StreamingEngine(
464
470
  /** @type {shaka.extern.Manifest} */(manifest), playerInterface);
@@ -822,6 +828,53 @@ describe('StreamingEngine', () => {
822
828
  .toHaveBeenCalledWith('video', 0, lt20, gt40);
823
829
  });
824
830
 
831
+ // Regression test for https://github.com/google/shaka-player/issues/3717
832
+ it('applies fudge factors for the duration', async () => {
833
+ setupVod();
834
+
835
+ // In #3717, the duration was just barely large enough to encompass an
836
+ // additional segment, but that segment didn't exist, so playback never
837
+ // completed. Here, we set the duration to just beyond the 3rd segment, and
838
+ // we make the 4th segment fail when requested.
839
+ const duration = 30.000000005;
840
+ timeline.getDuration.and.returnValue(duration);
841
+
842
+ const targetUri = '1_video_3'; // The URI of the 4th video segment.
843
+ failRequestsForTarget(netEngine, targetUri);
844
+
845
+ mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
846
+ createStreamingEngine();
847
+
848
+ // Here we go!
849
+ streamingEngine.switchVariant(variant);
850
+ streamingEngine.switchTextStream(textStream);
851
+ await streamingEngine.start();
852
+ playing = true;
853
+ await runTest();
854
+
855
+ // The end of the stream should have been reached, and the 4th segment from
856
+ // each type should never have been requested.
857
+ expect(mediaSourceEngine.endOfStream).toHaveBeenCalled();
858
+
859
+ const segmentType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
860
+
861
+ netEngine.expectRequest('0_audio_0', segmentType);
862
+ netEngine.expectRequest('0_video_0', segmentType);
863
+ netEngine.expectRequest('0_text_0', segmentType);
864
+
865
+ netEngine.expectRequest('0_audio_1', segmentType);
866
+ netEngine.expectRequest('0_video_1', segmentType);
867
+ netEngine.expectRequest('0_text_1', segmentType);
868
+
869
+ netEngine.expectRequest('1_audio_2', segmentType);
870
+ netEngine.expectRequest('1_video_2', segmentType);
871
+ netEngine.expectRequest('1_text_2', segmentType);
872
+
873
+ netEngine.expectNoRequest('1_audio_3', segmentType);
874
+ netEngine.expectNoRequest('1_video_3', segmentType);
875
+ netEngine.expectNoRequest('1_text_3', segmentType);
876
+ });
877
+
825
878
  it('does not buffer one media type ahead of another', async () => {
826
879
  setupVod();
827
880
  mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
@@ -1389,6 +1442,66 @@ describe('StreamingEngine', () => {
1389
1442
  });
1390
1443
  });
1391
1444
 
1445
+ it('into unbuffered regions when nothing is buffered ' +
1446
+ 'and mediaState is performing an update', async () => {
1447
+ // Here we go!
1448
+ streamingEngine.switchVariant(variant);
1449
+ streamingEngine.switchTextStream(textStream);
1450
+ // ensure init source buffer promise does not resolve before seeked()
1451
+ // so mediaState remains in "performingUpdate" state
1452
+ const initSourceBufferPromise = new shaka.util.PublicPromise();
1453
+ mediaSourceEngine.setStreamProperties.and
1454
+ .returnValue(initSourceBufferPromise);
1455
+ await streamingEngine.start();
1456
+ playing = true;
1457
+
1458
+ // tick to trigger mediaState updates
1459
+ jasmine.clock().tick(1);
1460
+ // give a chance for fetchAndAppend_ to be invoked
1461
+ // by async onUpdate_ callback
1462
+ await Util.shortDelay(realSetTimeout);
1463
+
1464
+ // Nothing is buffered yet.
1465
+ expect(mediaSourceEngine.segments).toEqual({
1466
+ audio: [false, false, false, false],
1467
+ video: [false, false, false, false],
1468
+ text: [false, false, false, false],
1469
+ });
1470
+
1471
+ // Seek forward to an unbuffered region in the first Period.
1472
+ presentationTimeInSeconds = 15;
1473
+ streamingEngine.seeked();
1474
+
1475
+ // resolve initSourceBufferPromise after seeked(), waitingToClearBuffer
1476
+ // should have been set, so this will now trigger the actual flush of
1477
+ // the buffer
1478
+ initSourceBufferPromise.resolve();
1479
+ mediaSourceEngine.setStreamProperties.and
1480
+ .returnValue(Promise.resolve());
1481
+
1482
+ // allow mediaState update to resolve
1483
+ await Util.shortDelay(realSetTimeout);
1484
+
1485
+ onTick.and.callFake(() => {
1486
+ expect(mediaSourceEngine.clear).toHaveBeenCalled();
1487
+ onTick.and.stub();
1488
+ });
1489
+
1490
+ await runTest(Util.spyFunc(onTick));
1491
+
1492
+ // Verify buffers.
1493
+ expect(mediaSourceEngine.initSegments).toEqual({
1494
+ audio: [false, true],
1495
+ video: [false, true],
1496
+ text: [],
1497
+ });
1498
+ expect(mediaSourceEngine.segments).toEqual({
1499
+ audio: [false, true, true, true],
1500
+ video: [false, true, true, true],
1501
+ text: [false, true, true, true],
1502
+ });
1503
+ });
1504
+
1392
1505
  it('into unbuffered regions near segment start', async () => {
1393
1506
  // Here we go!
1394
1507
  streamingEngine.switchVariant(variant);
@@ -3388,6 +3501,40 @@ describe('StreamingEngine', () => {
3388
3501
  segmentAvailability.end++;
3389
3502
  }
3390
3503
 
3504
+ /**
3505
+ * @param {!shaka.test.FakeNetworkingEngine} netEngine A NetworkingEngine
3506
+ * look-alike.
3507
+ * @param {string} targetUri
3508
+ * @param {shaka.util.Error.Code=} errorCode
3509
+ */
3510
+ function failRequestsForTarget(
3511
+ netEngine, targetUri, errorCode=shaka.util.Error.Code.BAD_HTTP_STATUS) {
3512
+ // eslint-disable-next-line no-restricted-syntax
3513
+ const originalNetEngineRequest = netEngine.request.bind(netEngine);
3514
+
3515
+ netEngine.request = jasmine.createSpy('request').and.callFake(
3516
+ (requestType, request) => {
3517
+ if (request.uris[0] == targetUri) {
3518
+ const data = [targetUri];
3519
+
3520
+ if (errorCode == shaka.util.Error.Code.BAD_HTTP_STATUS) {
3521
+ data.push(404);
3522
+ data.push('');
3523
+ }
3524
+
3525
+ // The compiler still sees the error code parameter as potentially
3526
+ // undefined, even though we gave it a default value.
3527
+ goog.asserts.assert(errorCode != undefined, 'Undefined error code');
3528
+
3529
+ return shaka.util.AbortableOperation.failed(new shaka.util.Error(
3530
+ shaka.util.Error.Severity.CRITICAL,
3531
+ shaka.util.Error.Category.NETWORK,
3532
+ errorCode, data));
3533
+ }
3534
+ return originalNetEngineRequest(requestType, request);
3535
+ });
3536
+ }
3537
+
3391
3538
  /**
3392
3539
  * @param {!shaka.test.FakeNetworkingEngine} netEngine A NetworkingEngine
3393
3540
  * look-alike.
@@ -26,6 +26,9 @@ function httpPluginTests(usingFetch) {
26
26
  // A dummy progress callback.
27
27
  const progressUpdated = (elapsedMs, bytes, bytesRemaining) => {};
28
28
 
29
+ // A dummy headers callback.
30
+ const headersReceived = (headers) => {};
31
+
29
32
  /** @type {shaka.extern.RetryParameters} */
30
33
  let retryParameters;
31
34
 
@@ -168,7 +171,8 @@ function httpPluginTests(usingFetch) {
168
171
  request.method = 'POST';
169
172
  request.headers['BAZ'] = '123';
170
173
 
171
- await plugin(request.uris[0], request, requestType, progressUpdated)
174
+ await plugin(
175
+ request.uris[0], request, requestType, progressUpdated, headersReceived)
172
176
  .promise;
173
177
 
174
178
  const actual = mostRecentRequest();
@@ -189,8 +193,8 @@ function httpPluginTests(usingFetch) {
189
193
  request.body = null;
190
194
  request.method = 'GET';
191
195
 
192
- await plugin(request.uris[0], request, requestType, progressUpdated)
193
- .promise;
196
+ await plugin(request.uris[0], request, requestType, progressUpdated,
197
+ headersReceived).promise;
194
198
 
195
199
  const actual = jasmine.Fetch.requests.mostRecent();
196
200
  expect(actual).toBeTruthy();
@@ -205,8 +209,8 @@ function httpPluginTests(usingFetch) {
205
209
 
206
210
  const request = shaka.net.NetworkingEngine.makeRequest(
207
211
  [uri], retryParameters, Util.spyFunc(streamDataCallback));
208
- const response =
209
- await plugin(uri, request, requestType, progressUpdated).promise;
212
+ const response = await plugin(
213
+ uri, request, requestType, progressUpdated, headersReceived).promise;
210
214
 
211
215
  expect(mostRecentRequest().url).toBe(uri);
212
216
  expect(response).toBeTruthy();
@@ -297,9 +301,9 @@ function httpPluginTests(usingFetch) {
297
301
  const request = shaka.net.NetworkingEngine.makeRequest(
298
302
  ['https://foo.bar/cache'], retryParameters);
299
303
 
300
- const response =
301
- await plugin(request.uris[0], request, requestType, progressUpdated)
302
- .promise;
304
+ const response = await plugin(
305
+ request.uris[0], request, requestType, progressUpdated, headersReceived)
306
+ .promise;
303
307
  expect(response).toBeTruthy();
304
308
  expect(response.fromCache).toBe(true);
305
309
  });
@@ -312,8 +316,8 @@ function httpPluginTests(usingFetch) {
312
316
  uri = 'https://foo.bar/timeout';
313
317
  const request = shaka.net.NetworkingEngine.makeRequest(
314
318
  [uri], retryParameters);
315
- const operation = plugin(
316
- request.uris[0], request, requestType, progressUpdated);
319
+ const operation = plugin(request.uris[0], request, requestType,
320
+ progressUpdated, headersReceived);
317
321
 
318
322
  /** @type {jasmine.Fetch.RequestStub} */
319
323
  const actual = jasmine.Fetch.requests.mostRecent();
@@ -354,8 +358,8 @@ function httpPluginTests(usingFetch) {
354
358
  uri = 'https://foo.bar/';
355
359
  const request = shaka.net.NetworkingEngine.makeRequest(
356
360
  [uri], retryParameters);
357
- operation = plugin(
358
- request.uris[0], request, requestType, progressUpdated);
361
+ operation = plugin(request.uris[0], request, requestType, progressUpdated,
362
+ headersReceived);
359
363
  requestPromise = operation.promise;
360
364
  }
361
365
 
@@ -388,8 +392,8 @@ function httpPluginTests(usingFetch) {
388
392
  async function testSucceeds(uri, overrideUri) {
389
393
  const request = shaka.net.NetworkingEngine.makeRequest(
390
394
  [uri], retryParameters);
391
- const response =
392
- await plugin(uri, request, requestType, progressUpdated).promise;
395
+ const response = await plugin(
396
+ uri, request, requestType, progressUpdated, headersReceived).promise;
393
397
 
394
398
  expect(mostRecentRequest().url).toBe(uri);
395
399
  expect(response).toBeTruthy();
@@ -410,7 +414,8 @@ function httpPluginTests(usingFetch) {
410
414
  const request = shaka.net.NetworkingEngine.makeRequest(
411
415
  [uri], retryParameters);
412
416
 
413
- const p = plugin(uri, request, requestType, progressUpdated).promise;
417
+ const p = plugin(
418
+ uri, request, requestType, progressUpdated, headersReceived).promise;
414
419
  if (expected.code == shaka.util.Error.Code.TIMEOUT) {
415
420
  jasmine.clock().tick(5000);
416
421
  }
@@ -22,10 +22,12 @@ goog.require('shaka.test.ManifestGenerator');
22
22
  goog.require('shaka.test.TestScheme');
23
23
  goog.require('shaka.test.Util');
24
24
  goog.require('shaka.util.AbortableOperation');
25
+ goog.require('shaka.util.BufferUtils');
25
26
  goog.require('shaka.util.Error');
26
27
  goog.require('shaka.util.EventManager');
27
28
  goog.require('shaka.util.PlayerConfiguration');
28
29
  goog.require('shaka.util.PublicPromise');
30
+ goog.require('shaka.util.Uint8ArrayUtils');
29
31
  goog.requireType('shaka.media.SegmentReference');
30
32
 
31
33
  /** @return {boolean} */
@@ -58,12 +60,16 @@ filterDescribe('Storage', storageSupport, () => {
58
60
  const manifestWithNonZeroStartUri = 'fake:manifest-with-non-zero-start';
59
61
  const manifestWithLiveTimelineUri = 'fake:manifest-with-live-timeline';
60
62
  const manifestWithAlternateSegmentsUri = 'fake:manifest-with-alt-segments';
63
+ const manifestWithVideoInitSegmentsUri =
64
+ 'fake:manifest-with-video-init-segments';
61
65
 
66
+ const initSegmentUri = 'fake:init-segment';
62
67
  const segment1Uri = 'fake:segment-1';
63
68
  const segment2Uri = 'fake:segment-2';
64
69
  const segment3Uri = 'fake:segment-3';
65
70
  const segment4Uri = 'fake:segment-4';
66
71
 
72
+ const alternateInitSegmentUri = 'fake:alt-init-segment';
67
73
  const alternateSegment1Uri = 'fake:alt-segment-1';
68
74
  const alternateSegment2Uri = 'fake:alt-segment-2';
69
75
  const alternateSegment3Uri = 'fake:alt-segment-3';
@@ -945,6 +951,46 @@ filterDescribe('Storage', storageSupport, () => {
945
951
  }
946
952
  });
947
953
 
954
+ it('can extract DRM info from segments', async () => {
955
+ const pssh1 =
956
+ '00000028' + // atom size
957
+ '70737368' + // atom type='pssh'
958
+ '00000000' + // v0, flags=0
959
+ 'edef8ba979d64acea3c827dcd51d21ed' + // system id (Widevine)
960
+ '00000008' + // data size
961
+ '0102030405060708'; // data
962
+ const psshData1 = shaka.util.Uint8ArrayUtils.fromHex(pssh1);
963
+ const pssh2 =
964
+ '00000028' + // atom size
965
+ '70737368' + // atom type='pssh'
966
+ '00000000' + // v0, flags=0
967
+ 'edef8ba979d64acea3c827dcd51d21ed' + // system id (Widevine)
968
+ '00000008' + // data size
969
+ '1337420123456789'; // data
970
+ const psshData2 = shaka.util.Uint8ArrayUtils.fromHex(pssh2);
971
+ netEngine.setResponseValue(initSegmentUri,
972
+ shaka.util.BufferUtils.toArrayBuffer(psshData1));
973
+ netEngine.setResponseValue(alternateInitSegmentUri,
974
+ shaka.util.BufferUtils.toArrayBuffer(psshData2));
975
+
976
+ const drm = new shaka.test.FakeDrmEngine();
977
+ const drmInfo = makeDrmInfo();
978
+ drmInfo.keySystem = 'com.widevine.alpha';
979
+ drm.setDrmInfo(drmInfo);
980
+ overrideDrmAndManifest(
981
+ storage,
982
+ drm,
983
+ makeManifestWithVideoInitSegments());
984
+
985
+ const stored = await storage.store(
986
+ manifestWithVideoInitSegmentsUri, noMetadata, fakeMimeType).promise;
987
+ goog.asserts.assert(stored.offlineUri != null, 'URI should not be null!');
988
+
989
+ // The manifest chooses the alternate stream, so expect only the alt init
990
+ // segment.
991
+ expect(drm.newInitData).toHaveBeenCalledWith('cenc', psshData2);
992
+ });
993
+
948
994
  it('can store multiple assets at once', async () => {
949
995
  // Block the network so that we won't finish the first store command.
950
996
  /** @type {!shaka.util.PublicPromise} */
@@ -1234,11 +1280,11 @@ filterDescribe('Storage', storageSupport, () => {
1234
1280
  goog.asserts.assert(
1235
1281
  content.offlineUri != null, 'URI should not be null!');
1236
1282
 
1237
- /**
1238
- * @type {!Array.<number>}
1239
- */
1283
+ // We expect 5 progress events because there are 4 unique segments, plus
1284
+ // the manifest.
1285
+ /** @type {!Array.<number>}*/
1240
1286
  const progressSteps = [
1241
- 0.111, 0.222, 0.333, 0.444, 0.555, 0.666, 0.777, 0.888, 1.0,
1287
+ 0.2, 0.4, 0.6, 0.8, 1,
1242
1288
  ];
1243
1289
 
1244
1290
  const progressCallback = (content, progress) => {
@@ -1399,6 +1445,62 @@ filterDescribe('Storage', storageSupport, () => {
1399
1445
  };
1400
1446
  }
1401
1447
 
1448
+ /** @return {shaka.extern.Manifest} */
1449
+ function makeManifestWithVideoInitSegments() {
1450
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
1451
+ manifest.presentationTimeline.setDuration(20);
1452
+
1453
+ manifest.addVariant(0, (variant) => {
1454
+ variant.bandwidth = kbps(13);
1455
+ variant.addVideo(1, (stream) => {
1456
+ stream.bandwidth = kbps(13);
1457
+ stream.size(100, 200);
1458
+ });
1459
+ });
1460
+ manifest.addVariant(2, (variant) => {
1461
+ variant.bandwidth = kbps(20);
1462
+ variant.addVideo(3, (stream) => {
1463
+ stream.bandwidth = kbps(20);
1464
+ stream.size(200, 400);
1465
+ });
1466
+ });
1467
+ });
1468
+
1469
+ const stream = manifest.variants[0].video;
1470
+ goog.asserts.assert(stream, 'The first stream should exist');
1471
+ stream.encrypted = true;
1472
+ const init = new shaka.media.InitSegmentReference(
1473
+ () => [initSegmentUri], 0, null);
1474
+ const refs = [
1475
+ makeReference(segment1Uri, 0, 1),
1476
+ makeReference(segment2Uri, 1, 2),
1477
+ makeReference(segment3Uri, 2, 3),
1478
+ makeReference(segment4Uri, 3, 4),
1479
+ ];
1480
+ for (const ref of refs) {
1481
+ ref.initSegmentReference = init;
1482
+ }
1483
+ overrideSegmentIndex(stream, refs);
1484
+
1485
+ const streamAlt = manifest.variants[1].video;
1486
+ goog.asserts.assert(streamAlt, 'The second stream should exist');
1487
+ streamAlt.encrypted = true;
1488
+ const initAlt = new shaka.media.InitSegmentReference(
1489
+ () => [alternateInitSegmentUri], 0, null);
1490
+ const refsAlt = [
1491
+ makeReference(alternateSegment1Uri, 0, 1),
1492
+ makeReference(alternateSegment2Uri, 1, 2),
1493
+ makeReference(alternateSegment3Uri, 2, 3),
1494
+ makeReference(alternateSegment4Uri, 3, 4),
1495
+ ];
1496
+ for (const ref of refsAlt) {
1497
+ ref.initSegmentReference = initAlt;
1498
+ }
1499
+ overrideSegmentIndex(streamAlt, refsAlt);
1500
+
1501
+ return manifest;
1502
+ }
1503
+
1402
1504
  /** @return {shaka.extern.Manifest} */
1403
1505
  function makeManifestWithPerStreamBandwidth() {
1404
1506
  const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
@@ -1612,6 +1714,8 @@ filterDescribe('Storage', storageSupport, () => {
1612
1714
  makeManifestWithLiveTimeline();
1613
1715
  this.map_[manifestWithAlternateSegmentsUri] =
1614
1716
  makeManifestWithAlternateSegments();
1717
+ this.map_[manifestWithVideoInitSegmentsUri] =
1718
+ makeManifestWithVideoInitSegments();
1615
1719
  }
1616
1720
 
1617
1721
  /** @override */
@@ -87,6 +87,41 @@ describe('Player', () => {
87
87
  });
88
88
  }); // describe('attach')
89
89
 
90
+ describe('updateStartTime() in manifestparsed event handler', () => {
91
+ it('does not get segments prior to startTime', async () => {
92
+ player.addEventListener('manifestparsed', () => {
93
+ player.updateStartTime(24);
94
+ });
95
+ const results = {
96
+ requestedVideoSegment0: false,
97
+ requestedVideoSegment1: false,
98
+ requestedVideoSegment2: false,
99
+ };
100
+ const expected = {
101
+ requestedVideoSegment0: false,
102
+ requestedVideoSegment1: false,
103
+ requestedVideoSegment2: true,
104
+ };
105
+ player.getNetworkingEngine().registerRequestFilter(
106
+ (type, request) => {
107
+ if (request.uris[0] == 'test:sintel/video/0') {
108
+ results.requestedVideoSegment0 = true;
109
+ }
110
+ if (request.uris[0] == 'test:sintel/video/1') {
111
+ results.requestedVideoSegment1 = true;
112
+ }
113
+ if (request.uris[0] == 'test:sintel/video/2') {
114
+ results.requestedVideoSegment2 = true;
115
+ }
116
+ });
117
+ await player.load('test:sintel_compiled');
118
+ video.play();
119
+ await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 25, 30);
120
+ expect(results).toEqual(expected);
121
+ });
122
+ });
123
+
124
+
90
125
  describe('getStats', () => {
91
126
  it('gives stats about current stream', async () => {
92
127
  // This is tested more in player_unit.js. This is here to test the public
@@ -652,6 +687,108 @@ describe('Player', () => {
652
687
  });
653
688
  });
654
689
 
690
+ describe('mediaQualityChanges', () => {
691
+ /** @type {!jasmine.Spy} */
692
+ let onQualityChange;
693
+ /** @type {!shaka.test.Waiter} */
694
+ let waiter;
695
+
696
+ const qualityChange1 = jasmine.objectContaining({
697
+ mediaQuality: jasmine.objectContaining({
698
+ bandwidth: 1,
699
+ codecs: 'mp4a.40.2',
700
+ contentType: 'audio',
701
+ mimeType: 'audio/mp4',
702
+ }),
703
+ type: 'mediaqualitychanged',
704
+ position: jasmine.any(Number),
705
+ });
706
+ const qualityChange2 = jasmine.objectContaining({
707
+ mediaQuality: jasmine.objectContaining({
708
+ bandwidth: 1,
709
+ codecs: 'avc1.42c01e',
710
+ contentType: 'video',
711
+ mimeType: 'video/mp4',
712
+ }),
713
+ type: 'mediaqualitychanged',
714
+ position: jasmine.any(Number),
715
+ });
716
+
717
+ beforeEach(() => {
718
+ onQualityChange = jasmine.createSpy('onQualityChange');
719
+ // const spyFunc = Util.spyFunc(onQualityChange);
720
+ player.addEventListener('mediaqualitychanged',
721
+ Util.spyFunc(onQualityChange));
722
+ waiter = new shaka.test.Waiter(eventManager)
723
+ .timeoutAfter(10)
724
+ .failOnTimeout(true);
725
+ });
726
+
727
+ it('emits audio/video quality changes at start when enabled', async () => {
728
+ player.configure('streaming.observeQualityChanges', true);
729
+
730
+ await player.load('test:sintel_compiled');
731
+ video.play();
732
+
733
+ // Wait for the video to start playback. If it takes longer than 10
734
+ // seconds, fail the test.
735
+ await waiter.waitForMovementOrFailOnTimeout(video, 10);
736
+ expect(onQualityChange).toHaveBeenCalledTimes(2);
737
+ expect(onQualityChange).toHaveBeenCalledWith(qualityChange1);
738
+ expect(onQualityChange).toHaveBeenCalledWith(qualityChange2);
739
+ });
740
+
741
+ it('does not emit quality changes at start when disabled', async () => {
742
+ player.configure('streaming.observeQualityChanges', false);
743
+
744
+ await player.load('test:sintel_compiled');
745
+ video.play();
746
+
747
+ // Wait for the video to start playback. If it takes longer than 10
748
+ // seconds, fail the test.
749
+ await waiter.waitForMovementOrFailOnTimeout(video, 10);
750
+ expect(onQualityChange).not.toHaveBeenCalled();
751
+ });
752
+ });
753
+
754
+ describe('seek range', () => {
755
+ // Regression test for Issue #3675.
756
+ it('has a reasonable seek range after going from live to VOD', async () => {
757
+ const netEngine = player.getNetworkingEngine();
758
+ const startTime = Date.now();
759
+ netEngine.registerRequestFilter((type, request) => {
760
+ if (type != shaka.net.NetworkingEngine.RequestType.MANIFEST) {
761
+ return;
762
+ }
763
+ // Simulate a live stream by providing different manifests over time.
764
+ const time = (Date.now() - startTime) / 1000;
765
+ const manifestNumber = Math.min(5, Math.floor(0.5 + time / 2));
766
+ request.uris = [
767
+ '/base/test/test/assets/3675/dash_' + manifestNumber + '.mpd',
768
+ ];
769
+ console.log('getting manifest', request.uris);
770
+ });
771
+
772
+ // Play the stream.
773
+ await player.load('/base/test/test/assets/3675/dash_0.mpd');
774
+ video.play();
775
+
776
+ // Wait for the stream to be over.
777
+ eventManager.listen(player, 'error', Util.spyFunc(onErrorSpy));
778
+ /** @type {shaka.test.Waiter} */
779
+ const waiter = new shaka.test.Waiter(eventManager)
780
+ .timeoutAfter(20).failOnTimeout(true);
781
+ await waiter.waitForEnd(video);
782
+
783
+ // The stream should have transitioned to VOD by now.
784
+ expect(player.isLive()).toBe(false);
785
+
786
+ // Check that the final seek range is as expected.
787
+ const seekRange = player.seekRange();
788
+ expect(seekRange.end).toBe(14);
789
+ });
790
+ });
791
+
655
792
  describe('buffering', () => {
656
793
  const startBuffering = jasmine.objectContaining({buffering: true});
657
794
  const endBuffering = jasmine.objectContaining({buffering: false});
@@ -926,9 +1063,9 @@ describe('Player', () => {
926
1063
  it('add external chapters in vtt format', async () => {
927
1064
  await player.load('test:sintel_no_text_compiled');
928
1065
  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');
1066
+ const partialUri1 = new goog.Uri('/base/test/test/assets/chapters.vtt');
1067
+ const absoluteUri1 = locationUri.resolve(partialUri1);
1068
+ await player.addChaptersTrack(absoluteUri1.toString(), 'en');
932
1069
 
933
1070
  await shaka.test.Util.delay(1.5);
934
1071
 
@@ -941,11 +1078,44 @@ describe('Player', () => {
941
1078
  const chapter2 = chapters[1];
942
1079
  expect(chapter2.title).toBe('Chapter 2');
943
1080
  expect(chapter2.startTime).toBe(5);
944
- expect(chapter2.endTime).toBe(30);
1081
+ expect(chapter2.endTime).toBe(10);
945
1082
  const chapter3 = chapters[2];
946
1083
  expect(chapter3.title).toBe('Chapter 3');
947
- expect(chapter3.startTime).toBe(30);
948
- expect(chapter3.endTime).toBe(61.349);
1084
+ expect(chapter3.startTime).toBe(10);
1085
+ expect(chapter3.endTime).toBe(20);
1086
+
1087
+ const partialUri2 = new goog.Uri('/base/test/test/assets/chapters2.vtt');
1088
+ const absoluteUri2 = locationUri.resolve(partialUri2);
1089
+ await player.addChaptersTrack(absoluteUri2.toString(), 'en');
1090
+
1091
+ await shaka.test.Util.delay(1.5);
1092
+
1093
+ const chaptersUpdated = player.getChapters('en');
1094
+ expect(chaptersUpdated.length).toBe(6);
1095
+ const chapterUpdated1 = chaptersUpdated[0];
1096
+ expect(chapterUpdated1.title).toBe('Chapter 1');
1097
+ expect(chapterUpdated1.startTime).toBe(0);
1098
+ expect(chapterUpdated1.endTime).toBe(5);
1099
+ const chapterUpdated2 = chaptersUpdated[1];
1100
+ expect(chapterUpdated2.title).toBe('Chapter 2');
1101
+ expect(chapterUpdated2.startTime).toBe(5);
1102
+ expect(chapterUpdated2.endTime).toBe(10);
1103
+ const chapterUpdated3 = chaptersUpdated[2];
1104
+ expect(chapterUpdated3.title).toBe('Chapter 3');
1105
+ expect(chapterUpdated3.startTime).toBe(10);
1106
+ expect(chapterUpdated3.endTime).toBe(20);
1107
+ const chapterUpdated4 = chaptersUpdated[3];
1108
+ expect(chapterUpdated4.title).toBe('Chapter 4');
1109
+ expect(chapterUpdated4.startTime).toBe(20);
1110
+ expect(chapterUpdated4.endTime).toBe(30);
1111
+ const chapterUpdated5 = chaptersUpdated[4];
1112
+ expect(chapterUpdated5.title).toBe('Chapter 5');
1113
+ expect(chapterUpdated5.startTime).toBe(30);
1114
+ expect(chapterUpdated5.endTime).toBe(40);
1115
+ const chapterUpdated6 = chaptersUpdated[5];
1116
+ expect(chapterUpdated6.title).toBe('Chapter 6');
1117
+ expect(chapterUpdated6.startTime).toBe(40);
1118
+ expect(chapterUpdated6.endTime).toBe(61.349);
949
1119
  });
950
1120
 
951
1121
  it('add external chapters in srt format', async () => {
@@ -953,11 +1123,11 @@ describe('Player', () => {
953
1123
  const locationUri = new goog.Uri(location.href);
954
1124
  const partialUri = new goog.Uri('/base/test/test/assets/chapters.srt');
955
1125
  const absoluteUri = locationUri.resolve(partialUri);
956
- await player.addChaptersTrack(absoluteUri.toString(), 'en');
1126
+ await player.addChaptersTrack(absoluteUri.toString(), 'es');
957
1127
 
958
1128
  await shaka.test.Util.delay(1.5);
959
1129
 
960
- const chapters = player.getChapters('en');
1130
+ const chapters = player.getChapters('es');
961
1131
  expect(chapters.length).toBe(3);
962
1132
  const chapter1 = chapters[0];
963
1133
  expect(chapter1.title).toBe('Chapter 1');