shaka-player 3.2.2 → 3.3.0

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 (156) 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 +6 -5
  5. package/CHANGELOG.md +247 -0
  6. package/CONTRIBUTORS +4 -2
  7. package/build/types/core +3 -0
  8. package/build/types/offline +1 -0
  9. package/build/types/polyfill +1 -0
  10. package/build/types/ui +2 -0
  11. package/demo/common/assets.js +18 -0
  12. package/demo/common/message_ids.js +13 -1
  13. package/demo/config.js +31 -1
  14. package/demo/index.html +1 -1
  15. package/demo/locales/en.json +12 -0
  16. package/demo/locales/source.json +48 -0
  17. package/dist/controls.css +26 -2
  18. package/dist/controls.css.map +1 -1
  19. package/dist/demo.compiled.debug.js +19 -16
  20. package/dist/demo.compiled.debug.map +3 -3
  21. package/dist/demo.compiled.js +19 -16
  22. package/dist/demo.compiled.map +3 -3
  23. package/dist/deps.js +18 -11
  24. package/dist/locales.js +28 -26
  25. package/dist/receiver.compiled.debug.js +11 -9
  26. package/dist/receiver.compiled.debug.map +3 -3
  27. package/dist/receiver.compiled.js +11 -9
  28. package/dist/receiver.compiled.map +3 -3
  29. package/dist/shaka-player.compiled.d.ts +205 -15
  30. package/dist/shaka-player.compiled.debug.d.ts +205 -15
  31. package/dist/shaka-player.compiled.debug.externs.js +256 -6
  32. package/dist/shaka-player.compiled.debug.js +406 -354
  33. package/dist/shaka-player.compiled.debug.map +4 -4
  34. package/dist/shaka-player.compiled.externs.js +256 -6
  35. package/dist/shaka-player.compiled.js +826 -800
  36. package/dist/shaka-player.compiled.map +4 -4
  37. package/dist/shaka-player.ui.d.ts +221 -16
  38. package/dist/shaka-player.ui.debug.d.ts +221 -16
  39. package/dist/shaka-player.ui.debug.externs.js +283 -6
  40. package/dist/shaka-player.ui.debug.js +547 -476
  41. package/dist/shaka-player.ui.debug.map +4 -4
  42. package/dist/shaka-player.ui.externs.js +283 -6
  43. package/dist/shaka-player.ui.js +1019 -980
  44. package/dist/shaka-player.ui.map +4 -4
  45. package/docs/design/bg-fetch-after.gv +29 -0
  46. package/docs/design/bg-fetch-after.gv.png +0 -0
  47. package/docs/design/bg-fetch-before.gv +18 -0
  48. package/docs/design/bg-fetch-before.gv.png +0 -0
  49. package/docs/design/bg-fetch.md +134 -0
  50. package/docs/tutorials/blob-url.md +15 -0
  51. package/docs/tutorials/index.json +1 -0
  52. package/docs/tutorials/ui-customization.md +66 -0
  53. package/docs/tutorials/ui.md +5 -5
  54. package/externs/ima.js +3 -0
  55. package/externs/shaka/ads.js +5 -0
  56. package/externs/shaka/manifest.js +12 -1
  57. package/externs/shaka/manifest_parser.js +10 -1
  58. package/externs/shaka/net.js +20 -4
  59. package/externs/shaka/offline.js +31 -2
  60. package/externs/shaka/player.js +130 -5
  61. package/externs/shaka/text.js +14 -0
  62. package/externs/webcrypto.js +18 -0
  63. package/lib/abr/ewma.js +14 -0
  64. package/lib/abr/ewma_bandwidth_estimator.js +16 -2
  65. package/lib/abr/simple_abr_manager.js +6 -0
  66. package/lib/ads/client_side_ad.js +8 -0
  67. package/lib/ads/client_side_ad_manager.js +8 -4
  68. package/lib/ads/server_side_ad.js +7 -0
  69. package/lib/cast/cast_utils.js +1 -0
  70. package/lib/dash/dash_parser.js +48 -14
  71. package/lib/dash/segment_base.js +25 -1
  72. package/lib/dash/segment_list.js +18 -9
  73. package/lib/dash/segment_template.js +23 -10
  74. package/lib/hls/hls_parser.js +86 -23
  75. package/lib/media/quality_observer.js +291 -0
  76. package/lib/media/segment_index.js +6 -0
  77. package/lib/media/segment_reference.js +18 -3
  78. package/lib/media/streaming_engine.js +42 -4
  79. package/lib/net/http_fetch_plugin.js +31 -11
  80. package/lib/net/http_plugin_utils.js +1 -0
  81. package/lib/net/http_xhr_plugin.js +40 -14
  82. package/lib/net/networking_engine.js +81 -18
  83. package/lib/offline/download_info.js +68 -0
  84. package/lib/offline/download_manager.js +13 -5
  85. package/lib/offline/indexeddb/base_storage_cell.js +35 -0
  86. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  87. package/lib/offline/indexeddb/v5_storage_cell.js +5 -0
  88. package/lib/offline/storage.js +393 -284
  89. package/lib/offline/stored_content_utils.js +2 -0
  90. package/lib/player.js +206 -7
  91. package/lib/polyfill/random_uuid.js +52 -0
  92. package/lib/text/cue.js +12 -0
  93. package/lib/text/ttml_text_parser.js +25 -2
  94. package/lib/text/ui_text_displayer.js +2 -2
  95. package/lib/util/cmcd_manager.js +826 -0
  96. package/lib/util/error.js +6 -0
  97. package/lib/util/language_utils.js +12 -0
  98. package/lib/util/mutex.js +52 -0
  99. package/lib/util/periods.js +98 -64
  100. package/lib/util/player_configuration.js +18 -0
  101. package/package.json +1 -1
  102. package/shaka-player.uncompiled.js +3 -0
  103. package/test/dash/dash_parser_content_protection_unit.js +2 -0
  104. package/test/dash/dash_parser_live_unit.js +101 -1
  105. package/test/dash/dash_parser_manifest_unit.js +35 -1
  106. package/test/dash/dash_parser_segment_base_unit.js +2 -0
  107. package/test/dash/dash_parser_segment_list_unit.js +2 -1
  108. package/test/dash/dash_parser_segment_template_unit.js +2 -1
  109. package/test/hls/hls_live_unit.js +2 -0
  110. package/test/hls/hls_parser_unit.js +2 -0
  111. package/test/media/quality_observer_unit.js +139 -0
  112. package/test/media/segment_reference_unit.js +16 -1
  113. package/test/media/streaming_engine_integration.js +2 -1
  114. package/test/media/streaming_engine_unit.js +2 -0
  115. package/test/net/http_plugin_unit.js +20 -15
  116. package/test/offline/storage_integration.js +108 -4
  117. package/test/player_integration.js +99 -0
  118. package/test/test/util/dash_parser_util.js +4 -0
  119. package/test/test/util/fake_ad.js +7 -0
  120. package/test/test/util/fake_drm_engine.js +9 -2
  121. package/test/test/util/manifest_generator.js +3 -2
  122. package/test/test/util/test_scheme.js +14 -1
  123. package/test/text/ttml_text_parser_unit.js +47 -0
  124. package/test/ui/ui_unit.js +250 -0
  125. package/test/util/cmcd_manager_unit.js +277 -0
  126. package/ui/airplay_button.js +1 -0
  127. package/ui/audio_language_selection.js +7 -0
  128. package/ui/cast_button.js +1 -0
  129. package/ui/context_menu.js +109 -0
  130. package/ui/controls.js +41 -7
  131. package/ui/controls.less +1 -0
  132. package/ui/enums.js +4 -0
  133. package/ui/externs/ui.js +28 -1
  134. package/ui/fast_forward_button.js +14 -5
  135. package/ui/fullscreen_button.js +1 -0
  136. package/ui/less/buttons.less +0 -4
  137. package/ui/less/containers.less +74 -0
  138. package/ui/less/overflow_menu.less +0 -10
  139. package/ui/less/tooltip.less +97 -0
  140. package/ui/locales/en.json +1 -0
  141. package/ui/locales/source.json +4 -0
  142. package/ui/loop_button.js +5 -0
  143. package/ui/mute_button.js +2 -1
  144. package/ui/overflow_menu.js +5 -25
  145. package/ui/pip_button.js +5 -0
  146. package/ui/play_button.js +2 -2
  147. package/ui/playback_rate_selection.js +8 -9
  148. package/ui/resolution_selection.js +7 -0
  149. package/ui/rewind_button.js +15 -6
  150. package/ui/seek_bar.js +4 -2
  151. package/ui/settings_menu.js +36 -4
  152. package/ui/small_play_button.js +1 -0
  153. package/ui/statistics_button.js +253 -0
  154. package/ui/text_selection.js +11 -4
  155. package/ui/ui.js +30 -0
  156. package/ui/volume_bar.js +1 -1
@@ -57,6 +57,8 @@ describe('DashParser Manifest', () => {
57
57
  onEventSpy = jasmine.createSpy('onEvent');
58
58
  playerInterface = {
59
59
  networkingEngine: fakeNetEngine,
60
+ modifyManifestRequest: (request, manifestInfo) => {},
61
+ modifySegmentRequest: (request, segmentInfo) => {},
60
62
  filter: (manifest) => Promise.resolve(),
61
63
  makeTextStreamsForClosedCaptions: (manifest) => {},
62
64
  onTimelineRegionAdded: fail, // Should not have any EventStream elements.
@@ -1872,7 +1874,7 @@ describe('DashParser Manifest', () => {
1872
1874
  expect(textStream.kind).toBe('caption');
1873
1875
  });
1874
1876
 
1875
- it('converts Roles element to "forced"', async () => {
1877
+ it('converts Roles element to "forced" (old role)', async () => {
1876
1878
  const manifestText = [
1877
1879
  '<MPD minBufferTime="PT75S">',
1878
1880
  ' <Period id="1" duration="PT30S">',
@@ -1904,6 +1906,38 @@ describe('DashParser Manifest', () => {
1904
1906
  expect(textStream.forced).toBe(true);
1905
1907
  });
1906
1908
 
1909
+ it('converts Roles element to "forced"', async () => {
1910
+ const manifestText = [
1911
+ '<MPD minBufferTime="PT75S">',
1912
+ ' <Period id="1" duration="PT30S">',
1913
+ ' <AdaptationSet id="1" contentType="text">',
1914
+ ' <Accessibility schemeIdUri="urn:mpeg:dash:role:2011" ',
1915
+ ' value="captions" />',
1916
+ ' <Accessibility schemeIdUri="urn:mpeg:dash:role:2011" ',
1917
+ ' value="forced-subtitle" />',
1918
+ ' <Accessibility schemeIdUri="foobar" value="bar" />',
1919
+ ' <Representation id="text-en" mimeType="text/webvtt">',
1920
+ ' <BaseURL>t-en.vtt</BaseURL>',
1921
+ ' </Representation>',
1922
+ ' </AdaptationSet>',
1923
+ ' <AdaptationSet id="1" mimeType="video/mp4">',
1924
+ ' <Representation id="video-sd" width="640" height="480">',
1925
+ ' <BaseURL>v-sd.mp4</BaseURL>',
1926
+ ' <SegmentBase indexRange="100-200" />',
1927
+ ' </Representation>',
1928
+ ' </AdaptationSet>',
1929
+ ' </Period>',
1930
+ '</MPD>',
1931
+ ].join('\n');
1932
+
1933
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
1934
+ /** @type {shaka.extern.Manifest} */
1935
+ const manifest = await parser.start('dummy://foo', playerInterface);
1936
+ const textStream = manifest.textStreams[0];
1937
+ expect(textStream.roles).toEqual(['captions', 'forced-subtitle']);
1938
+ expect(textStream.forced).toBe(true);
1939
+ });
1940
+
1907
1941
  it('supports HDR signaling via profiles', async () => {
1908
1942
  // (DASH-IF IOP v4.3 10.3.3.)
1909
1943
  const hdrProfile =
@@ -35,6 +35,8 @@ describe('DashParser SegmentBase', () => {
35
35
 
36
36
  playerInterface = {
37
37
  networkingEngine: fakeNetEngine,
38
+ modifyManifestRequest: (request, manifestInfo) => {},
39
+ modifySegmentRequest: (request, segmentInfo) => {},
38
40
  filter: (manifest) => Promise.resolve(),
39
41
  makeTextStreamsForClosedCaptions: (manifest) => {},
40
42
  onTimelineRegionAdded: fail, // Should not have any EventStream elements.
@@ -344,6 +344,8 @@ describe('DashParser SegmentList', () => {
344
344
 
345
345
  const playerInterface = {
346
346
  networkingEngine: networkingEngine,
347
+ modifyManifestRequest: (request, manifestInfo) => {},
348
+ modifySegmentRequest: (request, segmentInfo) => {},
347
349
  filter: () => {},
348
350
  makeTextStreamsForClosedCaptions: (manifest) => {},
349
351
  onTimelineRegionAdded: fail, // Should not have any EventStream elements.
@@ -374,4 +376,3 @@ describe('DashParser SegmentList', () => {
374
376
  expect(actual).toEqual(expected);
375
377
  });
376
378
  });
377
-
@@ -46,6 +46,8 @@ describe('DashParser SegmentTemplate', () => {
46
46
 
47
47
  playerInterface = {
48
48
  networkingEngine: fakeNetEngine,
49
+ modifyManifestRequest: (request, manifestInfo) => {},
50
+ modifySegmentRequest: (request, segmentInfo) => {},
49
51
  filter: (manifest) => Promise.resolve(),
50
52
  makeTextStreamsForClosedCaptions: (manifest) => {},
51
53
  onTimelineRegionAdded: fail, // Should not have any EventStream elements.
@@ -585,4 +587,3 @@ describe('DashParser SegmentTemplate', () => {
585
587
  });
586
588
  });
587
589
  });
588
-
@@ -116,6 +116,8 @@ describe('HlsParser live', () => {
116
116
 
117
117
  config = shaka.util.PlayerConfiguration.createDefault().manifest;
118
118
  playerInterface = {
119
+ modifyManifestRequest: (request, manifestInfo) => {},
120
+ modifySegmentRequest: (request, segmentInfo) => {},
119
121
  filter: () => Promise.resolve(),
120
122
  makeTextStreamsForClosedCaptions: (manifest) => {},
121
123
  networkingEngine: fakeNetEngine,
@@ -94,6 +94,8 @@ describe('HlsParser', () => {
94
94
  config = shaka.util.PlayerConfiguration.createDefault().manifest;
95
95
  onEventSpy = jasmine.createSpy('onEvent');
96
96
  playerInterface = {
97
+ modifyManifestRequest: (request, manifestInfo) => {},
98
+ modifySegmentRequest: (request, segmentInfo) => {},
97
99
  filter: () => Promise.resolve(),
98
100
  makeTextStreamsForClosedCaptions: (manifest) => {},
99
101
  networkingEngine: fakeNetEngine,
@@ -0,0 +1,139 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2016 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ goog.require('shaka.media.QualityObserver');
8
+ goog.require('shaka.test.Util');
9
+
10
+
11
+ describe('QualityObserver', () => {
12
+ /** @type {!shaka.media.QualityObserver} */
13
+ let observer;
14
+
15
+ /** @type {!jasmine.Spy} */
16
+ let onQualityChange;
17
+
18
+ const createQualityInfo = (contentType, bandwidth) => {
19
+ return {
20
+ bandwidth: bandwidth,
21
+ audioSamplingRate: 444000,
22
+ codecs: 'my codec',
23
+ contentType: contentType,
24
+ frameRate: 30,
25
+ height: 720,
26
+ mimeType: 'mime type',
27
+ channelsCount: 2,
28
+ pixelAspectRatio: '1:1',
29
+ width: 1280,
30
+ };
31
+ };
32
+ let emptyBuffer = true;
33
+ let bufferStart = 0;
34
+ let bufferEnd = 0;
35
+
36
+ const quality1 = createQualityInfo('video', 1);
37
+ const quality2 = createQualityInfo('video', 2);
38
+
39
+ const getBufferedInfo = () => {
40
+ if (emptyBuffer) {
41
+ return {
42
+ video: [],
43
+ };
44
+ }
45
+ return {
46
+ video: [{start: bufferStart, end: bufferEnd}],
47
+ };
48
+ };
49
+
50
+ beforeEach(() => {
51
+ onQualityChange = jasmine.createSpy('onQualityChange');
52
+ observer = new shaka.media.QualityObserver(getBufferedInfo);
53
+ observer.setListeners(shaka.test.Util.spyFunc(onQualityChange));
54
+ emptyBuffer = true;
55
+ bufferStart = 0;
56
+ bufferEnd = 0;
57
+ });
58
+
59
+ it('does not call onQualityChange when there are no quality changes', () => {
60
+ observer.poll(10, false);
61
+ expect(onQualityChange).not.toHaveBeenCalled();
62
+ });
63
+
64
+ it('calls onQualityChange when position is after 1st quality change', () => {
65
+ observer.addMediaQualityChange(quality1, 10);
66
+ emptyBuffer = false;
67
+ bufferStart = 10;
68
+ bufferEnd = 20;
69
+ observer.poll(10, false);
70
+ expect(onQualityChange).toHaveBeenCalledWith(quality1, 10);
71
+ });
72
+
73
+ it('does not call onQualityChange when pos advances with no change', () => {
74
+ observer.addMediaQualityChange(quality1, 10);
75
+ emptyBuffer = false;
76
+ bufferStart = 10;
77
+ bufferEnd = 20;
78
+ observer.poll(10, false);
79
+ expect(onQualityChange).toHaveBeenCalledWith(quality1, 10);
80
+ observer.poll(11, false);
81
+ expect(onQualityChange).toHaveBeenCalledTimes(1);
82
+ });
83
+
84
+ it('does not call onQualityChange on seek to unbuffered position', () => {
85
+ observer.addMediaQualityChange(quality1, 10);
86
+ emptyBuffer = false;
87
+ bufferStart = 10;
88
+ bufferEnd = 20;
89
+ observer.poll(15, false);
90
+ expect(onQualityChange).toHaveBeenCalledOnceMoreWith([quality1, 15]);
91
+ observer.addMediaQualityChange(quality2, 20);
92
+ observer.poll(25, true);
93
+ expect(onQualityChange).not.toHaveBeenCalledOnceMore();
94
+ bufferEnd = 30;
95
+ observer.poll(26, false);
96
+ expect(onQualityChange).toHaveBeenCalledOnceMoreWith([quality2, 26]);
97
+ });
98
+
99
+ it('calls onQualityChange when position advances over 2nd quality change',
100
+ () => {
101
+ observer.addMediaQualityChange(quality1, 10);
102
+ emptyBuffer = false;
103
+ bufferStart = 10;
104
+ bufferEnd = 20;
105
+ observer.poll(10, false);
106
+ expect(onQualityChange).toHaveBeenCalledOnceMoreWith([quality1, 10]);
107
+ observer.addMediaQualityChange(quality2, 20);
108
+ bufferStart = 10;
109
+ bufferEnd = 30;
110
+ observer.poll(20, false);
111
+ expect(onQualityChange).toHaveBeenCalledOnceMoreWith([quality2, 20]);
112
+ });
113
+
114
+ it('calls onQualityChange when position moves back over a quality chanage',
115
+ () => {
116
+ observer.addMediaQualityChange(quality1, 10);
117
+ emptyBuffer = false;
118
+ bufferStart = 10;
119
+ bufferEnd = 20;
120
+ observer.addMediaQualityChange(quality2, 20);
121
+ bufferStart = 10;
122
+ bufferEnd = 30;
123
+ observer.poll(25, false);
124
+ expect(onQualityChange).toHaveBeenCalledOnceMoreWith([quality2, 25]);
125
+ observer.poll(15, false);
126
+ expect(onQualityChange).toHaveBeenCalledOnceMoreWith([quality1, 15]);
127
+ });
128
+
129
+ it('uses last applied quality when there are two at the same position',
130
+ () => {
131
+ observer.addMediaQualityChange(quality1, 10);
132
+ observer.addMediaQualityChange(quality2, 10);
133
+ emptyBuffer = false;
134
+ bufferStart = 10;
135
+ bufferEnd = 20;
136
+ observer.poll(15, false);
137
+ expect(onQualityChange).toHaveBeenCalledWith(quality2, 15);
138
+ });
139
+ });
@@ -38,14 +38,29 @@ describe('SegmentReference', () => {
38
38
  });
39
39
 
40
40
  describe('InitSegmentReference', () => {
41
+ const mediaQuality = {
42
+ bandwidth: 1,
43
+ audioSamplingRate: 444000,
44
+ codecs: 'my codec',
45
+ contentType: 'video',
46
+ frameRate: 30,
47
+ height: 720,
48
+ mimeType: 'mime type',
49
+ channelsCount: 2,
50
+ pixelAspectRatio: '1:1',
51
+ width: 1280,
52
+ };
53
+
41
54
  it('returns in getters values from constructor parameters', () => {
42
55
  const reference = new shaka.media.InitSegmentReference(
43
56
  /* getUris= */ () => ['x', 'y'],
44
57
  /* startByte= */ 4,
45
- /* endByte= */ 5);
58
+ /* endByte= */ 5,
59
+ mediaQuality);
46
60
 
47
61
  expect(reference.getUris()).toEqual(['x', 'y']);
48
62
  expect(reference.getStartByte()).toBe(4);
49
63
  expect(reference.getEndByte()).toBe(5);
64
+ expect(reference.getMediaQuality()).toBe(mediaQuality);
50
65
  });
51
66
  });
@@ -60,7 +60,6 @@ describe('StreamingEngine', () => {
60
60
  /** @type {!shaka.media.StreamingEngine} */
61
61
  let streamingEngine;
62
62
 
63
-
64
63
  /** @type {shaka.extern.Variant} */
65
64
  let variant;
66
65
 
@@ -262,6 +261,7 @@ describe('StreamingEngine', () => {
262
261
 
263
262
  function createStreamingEngine() {
264
263
  const playerInterface = {
264
+ modifySegmentRequest: (request, segmentInfo) => {},
265
265
  getPresentationTime: () => playhead.getTime(),
266
266
  getBandwidthEstimate: () => 1e6,
267
267
  mediaSourceEngine: mediaSourceEngine,
@@ -270,6 +270,7 @@ describe('StreamingEngine', () => {
270
270
  onEvent: Util.spyFunc(onEvent),
271
271
  onManifestUpdate: () => {},
272
272
  onSegmentAppended: () => playhead.notifyOfBufferingChange(),
273
+ onInitSegmentAppended: () => {},
273
274
  };
274
275
  streamingEngine = new shaka.media.StreamingEngine(
275
276
  /** @type {shaka.extern.Manifest} */(manifest), playerInterface);
@@ -451,6 +451,7 @@ describe('StreamingEngine', () => {
451
451
  presentationTimeInSeconds != undefined,
452
452
  'All tests should have defined an initial presentation time by now!');
453
453
  const playerInterface = {
454
+ modifySegmentRequest: (request, segmentInfo) => {},
454
455
  getPresentationTime: () => presentationTimeInSeconds,
455
456
  getBandwidthEstimate: Util.spyFunc(getBandwidthEstimate),
456
457
  mediaSourceEngine: mediaSourceEngine,
@@ -459,6 +460,7 @@ describe('StreamingEngine', () => {
459
460
  onEvent: Util.spyFunc(onEvent),
460
461
  onManifestUpdate: Util.spyFunc(onManifestUpdate),
461
462
  onSegmentAppended: Util.spyFunc(onSegmentAppended),
463
+ onInitSegmentAppended: () => {},
462
464
  };
463
465
  streamingEngine = new shaka.media.StreamingEngine(
464
466
  /** @type {shaka.extern.Manifest} */(manifest), playerInterface);
@@ -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 */