shaka-player 4.2.3 → 4.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 (147) hide show
  1. package/.github/workflows/sync-labels.yaml +1 -1
  2. package/.github/workflows/update-issues.yaml +1 -1
  3. package/AUTHORS +5 -0
  4. package/CHANGELOG.md +85 -39
  5. package/CONTRIBUTORS +5 -0
  6. package/README.md +14 -0
  7. package/demo/common/assets.js +58 -0
  8. package/demo/common/message_ids.js +22 -1
  9. package/demo/config.js +89 -5
  10. package/demo/demo.less +7 -1
  11. package/demo/index.html +8 -1
  12. package/demo/locales/en.json +21 -1
  13. package/demo/locales/source.json +82 -2
  14. package/demo/main.js +57 -1
  15. package/demo/search.js +2 -0
  16. package/demo/visualizer.js +337 -0
  17. package/demo/visualizer_button.js +103 -0
  18. package/dist/controls.css +1 -1
  19. package/dist/controls.css.map +1 -1
  20. package/dist/demo.compiled.debug.js +83 -58
  21. package/dist/demo.compiled.debug.map +4 -4
  22. package/dist/demo.compiled.js +83 -58
  23. package/dist/demo.compiled.map +4 -4
  24. package/dist/demo.css +1 -1
  25. package/dist/demo.css.map +1 -1
  26. package/dist/deps.js +18 -11
  27. package/dist/locales.js +32 -32
  28. package/dist/receiver.compiled.debug.js +23 -19
  29. package/dist/receiver.compiled.debug.map +3 -3
  30. package/dist/receiver.compiled.js +23 -19
  31. package/dist/receiver.compiled.map +3 -3
  32. package/dist/shaka-player.compiled.d.ts +178 -27
  33. package/dist/shaka-player.compiled.debug.d.ts +178 -27
  34. package/dist/shaka-player.compiled.debug.externs.js +163 -32
  35. package/dist/shaka-player.compiled.debug.js +488 -422
  36. package/dist/shaka-player.compiled.debug.map +4 -4
  37. package/dist/shaka-player.compiled.externs.js +163 -32
  38. package/dist/shaka-player.compiled.js +895 -857
  39. package/dist/shaka-player.compiled.map +4 -4
  40. package/dist/shaka-player.ui.d.ts +178 -27
  41. package/dist/shaka-player.ui.debug.d.ts +178 -27
  42. package/dist/shaka-player.ui.debug.externs.js +163 -32
  43. package/dist/shaka-player.ui.debug.js +519 -450
  44. package/dist/shaka-player.ui.debug.map +4 -4
  45. package/dist/shaka-player.ui.externs.js +163 -32
  46. package/dist/shaka-player.ui.js +1053 -1015
  47. package/dist/shaka-player.ui.map +4 -4
  48. package/docs/design/lcevc-architecture.png +0 -0
  49. package/docs/design/lcevc-demo.png +0 -0
  50. package/docs/design/lcevc-integration.md +72 -0
  51. package/docs/tutorials/fairplay.md +7 -0
  52. package/docs/tutorials/faq.md +1 -1
  53. package/docs/tutorials/ui.md +7 -7
  54. package/docs/tutorials/upgrade.md +6 -0
  55. package/externs/ima.js +13 -1
  56. package/externs/lcevc.js +81 -0
  57. package/externs/mux.js +4 -42
  58. package/externs/shaka/ads.js +11 -1
  59. package/externs/shaka/manifest_parser.js +5 -1
  60. package/externs/shaka/player.js +178 -15
  61. package/lib/abr/simple_abr_manager.js +7 -0
  62. package/lib/ads/ad_manager.js +0 -8
  63. package/lib/ads/client_side_ad.js +15 -0
  64. package/lib/ads/client_side_ad_manager.js +21 -8
  65. package/lib/ads/server_side_ad.js +15 -0
  66. package/lib/ads/server_side_ad_manager.js +3 -3
  67. package/lib/cast/cast_proxy.js +2 -1
  68. package/lib/cast/cast_receiver.js +19 -5
  69. package/lib/cast/cast_sender.js +2 -1
  70. package/lib/cast/cast_utils.js +18 -6
  71. package/lib/cea/dummy_cea_parser.js +28 -0
  72. package/lib/cea/i_cea_parser.js +14 -2
  73. package/lib/cea/mp4_cea_parser.js +87 -5
  74. package/lib/config/auto_show_text.js +33 -0
  75. package/lib/dash/dash_parser.js +1 -1
  76. package/lib/hls/hls_parser.js +799 -376
  77. package/lib/lcevc/lcevc_dil.js +152 -0
  78. package/lib/media/closed_caption_parser.js +9 -6
  79. package/lib/media/drm_engine.js +14 -6
  80. package/lib/media/media_source_engine.js +112 -26
  81. package/lib/media/playhead.js +23 -1
  82. package/lib/media/presentation_timeline.js +26 -11
  83. package/lib/media/segment_reference.js +49 -1
  84. package/lib/media/streaming_engine.js +145 -24
  85. package/lib/media/transmuxer.js +79 -18
  86. package/lib/offline/storage.js +1 -0
  87. package/lib/player.js +477 -85
  88. package/lib/polyfill/patchedmediakeys_apple.js +55 -1
  89. package/lib/text/ui_text_displayer.js +5 -5
  90. package/lib/text/vtt_text_parser.js +35 -1
  91. package/lib/util/dom_utils.js +0 -1
  92. package/lib/util/error.js +17 -0
  93. package/lib/util/fake_event.js +2 -0
  94. package/lib/util/id3_utils.js +300 -0
  95. package/lib/util/manifest_parser_utils.js +4 -0
  96. package/lib/util/mime_utils.js +3 -6
  97. package/lib/util/mp4_box_parsers.js +15 -5
  98. package/lib/util/platform.js +9 -1
  99. package/lib/util/player_configuration.js +23 -1
  100. package/lib/util/ts_parser.js +499 -0
  101. package/maintained-branches.md +2 -2
  102. package/package.json +4 -1
  103. package/roadmap.md +27 -7
  104. package/test/ads/ad_manager_unit.js +180 -29
  105. package/test/cast/cast_receiver_integration.js +97 -16
  106. package/test/cast/cast_receiver_unit.js +17 -12
  107. package/test/cast/cast_utils_unit.js +4 -1
  108. package/test/dash/dash_parser_content_protection_unit.js +1 -0
  109. package/test/dash/dash_parser_live_unit.js +1 -0
  110. package/test/dash/dash_parser_manifest_unit.js +93 -1
  111. package/test/dash/dash_parser_segment_base_unit.js +1 -0
  112. package/test/dash/dash_parser_segment_list_unit.js +1 -0
  113. package/test/dash/dash_parser_segment_template_unit.js +1 -0
  114. package/test/demo/demo_unit.js +2 -1
  115. package/test/hls/hls_live_unit.js +253 -19
  116. package/test/hls/hls_parser_unit.js +500 -12
  117. package/test/media/closed_caption_parser_unit.js +2 -1
  118. package/test/media/drm_engine_integration.js +3 -1
  119. package/test/media/media_source_engine_integration.js +102 -6
  120. package/test/media/media_source_engine_unit.js +10 -5
  121. package/test/media/playhead_unit.js +41 -0
  122. package/test/media/streaming_engine_integration.js +17 -1
  123. package/test/media/streaming_engine_unit.js +180 -24
  124. package/test/media/transmuxer_integration.js +21 -10
  125. package/test/player_integration.js +224 -82
  126. package/test/player_unit.js +92 -2
  127. package/test/polyfill/patchedmediakeys_apple_unit.js +124 -0
  128. package/test/test/assets/id3-metadata.aac +0 -0
  129. package/test/test/assets/id3-metadata.ts +0 -0
  130. package/test/test/assets/thumbnails-sprites.vtt +10 -0
  131. package/test/test/assets/thumbnails.vtt +10 -0
  132. package/test/test/util/dash_parser_util.js +2 -0
  133. package/test/test/util/fake_ad.js +21 -0
  134. package/test/test/util/fake_media_source_engine.js +7 -0
  135. package/test/test/util/id3_generator.js +100 -0
  136. package/test/test/util/manifest_generator.js +3 -6
  137. package/test/test/util/simple_fakes.js +7 -0
  138. package/test/test/util/stream_generator.js +38 -0
  139. package/test/test/util/test_scheme.js +43 -14
  140. package/test/text/vtt_text_parser_unit.js +11 -0
  141. package/test/ui/text_displayer_layout_unit.js +14 -0
  142. package/test/util/id3_utils_unit.js +116 -0
  143. package/test/util/ts_parser_unit.js +77 -0
  144. package/ui/controls.js +4 -1
  145. package/ui/less/containers.less +7 -0
  146. package/ui/settings_menu.js +11 -2
  147. package/ui/ui.js +43 -4
@@ -25,45 +25,156 @@ describe('Ad manager', () => {
25
25
  /** @type {!HTMLElement} */ (document.createElement('div'));
26
26
  });
27
27
 
28
- it('doesn\'t init CS if CS IMA is missing', () => {
29
- const error = createError(
30
- shaka.util.Error.Severity.CRITICAL,
31
- shaka.util.Error.Code.CS_IMA_SDK_MISSING);
28
+ describe('client side', () => {
29
+ it('doesn\'t init if CS IMA is missing', () => {
30
+ const error = createError(
31
+ shaka.util.Error.Severity.CRITICAL,
32
+ shaka.util.Error.Code.CS_IMA_SDK_MISSING);
32
33
 
33
- expect(() => adManager.initClientSide(
34
- adContainer, mockVideo)).toThrow(error);
35
- });
34
+ expect(() => adManager.initClientSide(
35
+ adContainer, mockVideo)).toThrow(error);
36
+ });
36
37
 
37
- it('doesn\'t init SS if SS IMA is missing', () => {
38
- const error = createError(
39
- shaka.util.Error.Severity.CRITICAL,
40
- shaka.util.Error.Code.SS_IMA_SDK_MISSING);
38
+ it('doesn\'t request ads until CS is initialized', () => {
39
+ setupFakeIMA();
40
+ const error = createError(
41
+ shaka.util.Error.Severity.RECOVERABLE,
42
+ shaka.util.Error.Code.CS_AD_MANAGER_NOT_INITIALIZED);
41
43
 
42
- expect(() => adManager.initServerSide(
43
- adContainer, mockVideo)).toThrow(error);
44
- });
44
+ const request = new google.ima.AdsRequest();
45
+ request.adTagUrl = 'fakeTag';
46
+
47
+ expect(() => adManager.requestClientSideAds(request)).toThrow(error);
48
+ });
49
+
50
+ it('doesn\'t request ads if CS events return no ad', () => {
51
+ setupFakeIMA();
52
+
53
+ const request = new google.ima.AdsRequest();
54
+ request.adTagUrl = 'fakeTag';
55
+
56
+ /** @type {google.ima.AdsLoader} */
57
+ let mockAdsLoaderInstance;
58
+ let numAdsRequested = 0;
59
+ /** @type {google.ima.AdsManager} */
60
+ let mockAdsManagerInstance;
61
+ /** @type {Event} */
62
+ let loadEvent;
63
+ /** @type {Event} */
64
+ let startedEventA;
65
+ /** @type {Event} */
66
+ let startedEventB;
67
+
68
+ /** @suppress {invalidCasts} */
69
+ function makeMocks() {
70
+ const mockAdsLoader = class extends shaka.util.FakeEventTarget {
71
+ constructor(container) {
72
+ super();
73
+ mockAdsLoaderInstance = /** @type {!google.ima.AdsLoader} */ (this);
74
+ }
75
+
76
+ getSettings() {
77
+ return {
78
+ setPlayerType: (type) => {},
79
+ setPlayerVersion: (version) => {},
80
+ };
81
+ }
45
82
 
46
- it('doesn\'t request CS ads until CS is initialized', () => {
47
- setupFakeIMA();
48
- const error = createError(
49
- shaka.util.Error.Severity.RECOVERABLE,
50
- shaka.util.Error.Code.CS_AD_MANAGER_NOT_INITIALIZED);
83
+ requestAds(imaRequest) {
84
+ numAdsRequested += 1;
85
+ }
86
+ };
87
+ window['google'].ima.AdsLoader = mockAdsLoader;
51
88
 
52
- const request = new google.ima.AdsRequest();
53
- request.adTagUrl = 'fakeTag';
89
+ loadEvent = /** @type {!google.ima.AdsManagerLoadedEvent} */ (
90
+ new shaka.util.FakeEvent(
91
+ google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED));
92
+ loadEvent.getAdsManager = () => {
93
+ const MockAdManager = class extends shaka.util.FakeEventTarget {
94
+ constructor() {
95
+ super();
96
+ mockAdsManagerInstance =
97
+ /** @type {!google.ima.AdsManager} */ (this);
98
+ }
54
99
 
55
- expect(() => adManager.requestClientSideAds(request)).toThrow(error);
100
+ getCuePoints() {
101
+ return [];
102
+ }
103
+
104
+ getVolume() {
105
+ return 0;
106
+ }
107
+ };
108
+ return new MockAdManager();
109
+ };
110
+ startedEventA = /** @type {!google.ima.AdEvent} */ (
111
+ new shaka.util.FakeEvent(google.ima.AdEvent.Type.STARTED));
112
+ startedEventA.getAd = () => {
113
+ return null;
114
+ };
115
+ startedEventB = /** @type {!google.ima.AdEvent} */ (
116
+ new shaka.util.FakeEvent(google.ima.AdEvent.Type.STARTED));
117
+ startedEventB.getAd = () => {
118
+ return {};
119
+ };
120
+ }
121
+ makeMocks();
122
+
123
+ // Set up event listeners.
124
+ const eventManager = new shaka.util.EventManager();
125
+ let loaded = false;
126
+ let numAdStarted = 0;
127
+ const AdManager = shaka.ads.AdManager;
128
+ eventManager.listen(adManager, AdManager.IMA_AD_MANAGER_LOADED, () => {
129
+ loaded = true;
130
+ });
131
+ eventManager.listen(adManager, AdManager.AD_STARTED, () => {
132
+ numAdStarted += 1;
133
+ });
134
+
135
+ // Set up the ad manager.
136
+ adManager.initClientSide(adContainer, mockVideo);
137
+ goog.asserts.assert(loadEvent != null, 'loadEvent exists');
138
+ mockAdsLoaderInstance.dispatchEvent(/** @type {!Event} */ (loadEvent));
139
+ expect(loaded).toBe(true);
140
+
141
+ // Request an ad, but create an event with no ad.
142
+ adManager.requestClientSideAds(request);
143
+ expect(numAdsRequested).toBe(1);
144
+ mockAdsManagerInstance.dispatchEvent(/** @type {!Event} */ (
145
+ startedEventA));
146
+ expect(numAdStarted).toBe(0);
147
+
148
+ // Request another ad. This time, the IMA event has an ad, so the ad
149
+ // manager should fire a started event.
150
+ adManager.requestClientSideAds(request);
151
+ expect(numAdsRequested).toBe(2);
152
+ mockAdsManagerInstance.dispatchEvent(/** @type {!Event} */ (
153
+ startedEventB));
154
+ expect(numAdStarted).toBe(1);
155
+ });
56
156
  });
57
157
 
58
- it('doesn\'t request SS streams until SS is initialized', () => {
59
- setupFakeIMA();
60
- const error = createError(
61
- shaka.util.Error.Severity.RECOVERABLE,
62
- shaka.util.Error.Code.SS_AD_MANAGER_NOT_INITIALIZED);
158
+ describe('server side', () => {
159
+ it('doesn\'t init if SS IMA is missing', () => {
160
+ const error = createError(
161
+ shaka.util.Error.Severity.CRITICAL,
162
+ shaka.util.Error.Code.SS_IMA_SDK_MISSING);
163
+
164
+ expect(() => adManager.initServerSide(
165
+ adContainer, mockVideo)).toThrow(error);
166
+ });
167
+
168
+ it('doesn\'t request streams until SS is initialized', () => {
169
+ setupFakeIMA();
170
+ const error = createError(
171
+ shaka.util.Error.Severity.RECOVERABLE,
172
+ shaka.util.Error.Code.SS_AD_MANAGER_NOT_INITIALIZED);
63
173
 
64
- const request = new google.ima.dai.api.StreamRequest();
174
+ const request = new google.ima.dai.api.StreamRequest();
65
175
 
66
- expect(() => adManager.requestServerSideStream(request)).toThrow(error);
176
+ expect(() => adManager.requestServerSideStream(request)).toThrow(error);
177
+ });
67
178
  });
68
179
 
69
180
  /**
@@ -86,5 +197,45 @@ describe('Ad manager', () => {
86
197
  window['google'].ima.AdsRequest = class {};
87
198
  window['google'].ima.dai.api = {};
88
199
  window['google'].ima.dai.api.StreamRequest = class {};
200
+ window['google'].ima.settings = {};
201
+ window['google'].ima.settings.setLocale = (locale) => {};
202
+ window['google'].ima.AdsManagerLoadedEvent = {};
203
+ window['google'].ima.AdsManagerLoadedEvent.Type = {
204
+ ADS_MANAGER_LOADED: 'ADS_MANAGER_LOADED',
205
+ };
206
+ window['google'].ima.AdErrorEvent = {};
207
+ window['google'].ima.AdErrorEvent.Type = {
208
+ AD_ERROR: 'AD_ERROR',
209
+ };
210
+ window['google'].ima.AdEvent = {};
211
+ window['google'].ima.AdEvent.Type = {
212
+ CONTENT_PAUSE_REQUESTED: 'CONTENT_PAUSE_REQUESTED',
213
+ STARTED: 'STARTED',
214
+ FIRST_QUARTILE: 'FIRST_QUARTILE',
215
+ MIDPOINT: 'MIDPOINT',
216
+ THIRD_QUARTILE: 'THIRD_QUARTILE',
217
+ COMPLETE: 'COMPLETE',
218
+ CONTENT_RESUME_REQUESTED: 'CONTENT_RESUME_REQUESTED',
219
+ ALL_ADS_COMPLETED: 'ALL_ADS_COMPLETED',
220
+ SKIPPED: 'SKIPPED',
221
+ VOLUME_CHANGED: 'VOLUME_CHANGED',
222
+ VOLUME_MUTED: 'VOLUME_MUTED',
223
+ PAUSED: 'PAUSED',
224
+ RESUMED: 'RESUMED',
225
+ SKIPPABLE_STATE_CHANGED: 'SKIPPABLE_STATE_CHANGED',
226
+ CLICK: 'CLICK',
227
+ AD_PROGRESS: 'AD_PROGRESS',
228
+ AD_BUFFERING: 'AD_BUFFERING',
229
+ IMPRESSION: 'IMPRESSION',
230
+ DURATION_CHANGE: 'DURATION_CHANGE',
231
+ USER_CLOSE: 'USER_CLOSE',
232
+ LOADED: 'LOADED',
233
+ LINEAR_CHANGED: 'LINEAR_CHANGED',
234
+ AD_METADATA: 'AD_METADATA',
235
+ LOG: 'LOG',
236
+ AD_BREAK_READY: 'AD_BREAK_READY',
237
+ INTERACTION: 'INTERACTION',
238
+ };
239
+ window['google'].ima.AdDisplayContainer = class { initialize() {} };
89
240
  }
90
241
  });
@@ -34,6 +34,8 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
34
34
 
35
35
  /** @type {shaka.util.PublicPromise} */
36
36
  let messageWaitPromise;
37
+ /** @type {Array.<string>} */
38
+ let pendingMessages = null;
37
39
 
38
40
  /** @type {!Array.<function()>} */
39
41
  let toRestore;
@@ -91,6 +93,9 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
91
93
  toRestore = [];
92
94
  pendingWaitWrapperCalls = 0;
93
95
 
96
+ messageWaitPromise = null;
97
+ pendingMessages = null;
98
+
94
99
  fakeInitState = {
95
100
  player: {
96
101
  configure: {},
@@ -118,6 +123,12 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
118
123
  player = null;
119
124
  video = null;
120
125
  receiver = null;
126
+
127
+ if (messageWaitPromise) {
128
+ messageWaitPromise.resolve([]);
129
+ }
130
+ messageWaitPromise = null;
131
+ pendingMessages = null;
121
132
  });
122
133
 
123
134
  afterAll(() => {
@@ -128,6 +139,66 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
128
139
  }
129
140
  });
130
141
 
142
+ describe('without drm', () => {
143
+ it('sends reasonably-sized updates', async () => {
144
+ // Use an unencrypted asset.
145
+ fakeInitState.manifest = 'test:sintel';
146
+
147
+ const p = waitForLoadedData();
148
+
149
+ // Start the process of loading by sending a fake init message.
150
+ fakeConnectedSenders(1);
151
+ fakeIncomingMessage({
152
+ type: 'init',
153
+ initState: fakeInitState,
154
+ appData: {},
155
+ }, mockShakaMessageBus);
156
+
157
+ await p;
158
+ // Wait for an update message.
159
+ const messages = await waitForUpdateMessages();
160
+ for (const message of messages) {
161
+ // Check that the update message is of a reasonable size. From previous
162
+ // testing we found that the socket would silently reject data that got
163
+ // too big. 6KB is safely below the limit.
164
+ expect(message.length).toBeLessThan(6000);
165
+ }
166
+ });
167
+
168
+ it('has reasonable average message size', async () => {
169
+ // Use an unencrypted asset.
170
+ fakeInitState.manifest = 'test:sintel';
171
+
172
+ const p = waitForLoadedData();
173
+
174
+ // Start the process of loading by sending a fake init message.
175
+ fakeConnectedSenders(1);
176
+ fakeIncomingMessage({
177
+ type: 'init',
178
+ initState: fakeInitState,
179
+ appData: {},
180
+ }, mockShakaMessageBus);
181
+
182
+ await p;
183
+
184
+ // Collect messages over 50 update cycles, and average their length.
185
+ // Not all properties are passed along on every update message, so
186
+ // the average length is expected to be lower than the length of the first
187
+ // update message.
188
+ let totalLength = 0;
189
+ let totalMessages = 0;
190
+ for (let i = 0; i < 50; i++) {
191
+ // eslint-disable-next-line no-await-in-loop
192
+ const messages = await waitForUpdateMessages();
193
+ for (const message of messages) {
194
+ totalLength += message.length;
195
+ totalMessages += 1;
196
+ }
197
+ }
198
+ expect(totalLength / totalMessages).toBeLessThan(2000);
199
+ });
200
+ });
201
+
131
202
  filterDescribe('with drm', () => support['com.widevine.alpha'], () => {
132
203
  drmIt('sends reasonably-sized updates', async () => {
133
204
  // Use an encrypted asset, to make sure DRM info doesn't balloon the size.
@@ -150,11 +221,13 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
150
221
 
151
222
  await p;
152
223
  // Wait for an update message.
153
- const message = await waitForUpdateMessage();
154
- // Check that the update message is of a reasonable size. From previous
155
- // testing we found that the socket would silently reject data that got
156
- // too big. 6KB is safely below the limit.
157
- expect(message.length).toBeLessThan(7 * 1024);
224
+ const messages = await waitForUpdateMessages();
225
+ for (const message of messages) {
226
+ // Check that the update message is of a reasonable size. From previous
227
+ // testing we found that the socket would silently reject data that got
228
+ // too big. 6KB is safely below the limit.
229
+ expect(message.length).toBeLessThan(6000);
230
+ }
158
231
  });
159
232
 
160
233
  drmIt('has reasonable average message size', async () => {
@@ -177,17 +250,22 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
177
250
  }, mockShakaMessageBus);
178
251
 
179
252
  await p;
180
- // Collect 50 update messages, and average their length.
253
+
254
+ // Collect messages over 50 update cycles, and average their length.
181
255
  // Not all properties are passed along on every update message, so
182
256
  // the average length is expected to be lower than the length of the first
183
257
  // update message.
184
258
  let totalLength = 0;
259
+ let totalMessages = 0;
185
260
  for (let i = 0; i < 50; i++) {
186
261
  // eslint-disable-next-line no-await-in-loop
187
- const message = await waitForUpdateMessage();
188
- totalLength += message.length;
262
+ const messages = await waitForUpdateMessages();
263
+ for (const message of messages) {
264
+ totalLength += message.length;
265
+ totalMessages += 1;
266
+ }
189
267
  }
190
- expect(totalLength / 50).toBeLessThan(3000);
268
+ expect(totalLength / totalMessages).toBeLessThan(2000);
191
269
  });
192
270
  });
193
271
 
@@ -227,12 +305,12 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
227
305
  expect(pendingWaitWrapperCalls).toBe(0);
228
306
 
229
307
  // Wait for a final update message before proceeding.
230
- await waitForUpdateMessage();
308
+ await waitForUpdateMessages();
231
309
  });
232
310
 
233
311
  /**
234
312
  * Creates a wrapper around a method on a given prototype, which makes it
235
- * wait on waitForUpdateMessage before returning, and registers that wrapper
313
+ * wait on waitForUpdateMessages before returning, and registers that wrapper
236
314
  * to be uninstalled afterwards.
237
315
  * The replaced method is expected to be a method that returns a promise.
238
316
  * @param {!Object} prototype
@@ -249,7 +327,7 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
249
327
  'Waiting for update message before calling ' +
250
328
  name + '.' + methodName + '...');
251
329
  const originalArguments = Array.from(arguments);
252
- await waitForUpdateMessage();
330
+ await waitForUpdateMessages();
253
331
  // eslint-disable-next-line no-restricted-syntax
254
332
  return original.apply(this, originalArguments);
255
333
  };
@@ -265,7 +343,8 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
265
343
  });
266
344
  }
267
345
 
268
- function waitForUpdateMessage() {
346
+ function waitForUpdateMessages() {
347
+ pendingMessages = [];
269
348
  messageWaitPromise = new shaka.util.PublicPromise();
270
349
  return messageWaitPromise;
271
350
  }
@@ -316,10 +395,12 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
316
395
  bus.messages.push(CastUtils.deserialize(message));
317
396
  // Check to see if it's an update message.
318
397
  const parsed = CastUtils.deserialize(message);
319
- if (parsed.type == 'update' && messageWaitPromise) {
398
+ if (parsed.type == 'update' && pendingMessages) {
320
399
  shaka.log.debug('Received update message. Proceeding...');
321
- messageWaitPromise.resolve(message);
322
- messageWaitPromise = null;
400
+ // The code waiting on this Promise will get an array of all of the
401
+ // messages processed within this tick.
402
+ pendingMessages.push(message);
403
+ messageWaitPromise.resolve(pendingMessages);
323
404
  }
324
405
  });
325
406
  const channel = {
@@ -245,18 +245,20 @@ filterDescribe('CastReceiver', castReceiverSupport, () => {
245
245
  const fakeEvent = {type: 'timeupdate'};
246
246
  mockVideo.on['timeupdate'](fakeEvent);
247
247
 
248
- // There are now "update" and "event" messages, in that order.
249
- expect(mockShakaMessageBus.messages).toEqual([
250
- {
251
- type: 'update',
252
- update: jasmine.any(Object),
253
- },
254
- {
255
- type: 'event',
256
- targetName: 'video',
257
- event: jasmine.objectContaining(fakeEvent),
258
- },
259
- ]);
248
+ // There are now some number of "update" and "event" messages, in that
249
+ // order.
250
+ expect(mockShakaMessageBus.messages).toContain({
251
+ type: 'update',
252
+ update: jasmine.any(Object),
253
+ });
254
+ expect(mockShakaMessageBus.messages).toContain({
255
+ type: 'event',
256
+ targetName: 'video',
257
+ event: jasmine.objectContaining(fakeEvent),
258
+ });
259
+ const eventIndex = mockShakaMessageBus.messages.findIndex(
260
+ (message) => message.type == 'event');
261
+ expect(eventIndex).toBe(mockShakaMessageBus.messages.length - 1);
260
262
  });
261
263
  });
262
264
 
@@ -1105,6 +1107,9 @@ filterDescribe('CastReceiver', castReceiverSupport, () => {
1105
1107
  for (const name in CastUtils.PlayerGetterMethods) {
1106
1108
  player[name] = jasmine.createSpy(name);
1107
1109
  }
1110
+ for (const name in CastUtils.LargePlayerGetterMethods) {
1111
+ player[name] = jasmine.createSpy(name);
1112
+ }
1108
1113
  for (const name in CastUtils.PlayerGetterMethodsThatRequireLive) {
1109
1114
  player[name] = jasmine.createSpy(name);
1110
1115
  }
@@ -34,6 +34,7 @@ describe('CastUtils', () => {
34
34
  const castMembers = CastUtils.PlayerVoidMethods
35
35
  .concat(CastUtils.PlayerPromiseMethods)
36
36
  .concat(Object.keys(CastUtils.PlayerGetterMethods))
37
+ .concat(Object.keys(CastUtils.LargePlayerGetterMethods))
37
38
  .concat(Object.keys(CastUtils.PlayerGetterMethodsThatRequireLive));
38
39
  // eslint-disable-next-line no-restricted-syntax
39
40
  const allPlayerMembers = Object.getOwnPropertyNames(shaka.Player.prototype);
@@ -205,8 +206,10 @@ describe('CastUtils', () => {
205
206
 
206
207
  mediaSourceEngine = new shaka.media.MediaSourceEngine(
207
208
  video,
208
- new shaka.test.FakeClosedCaptionParser(),
209
209
  new shaka.test.FakeTextDisplayer());
210
+ const config =
211
+ shaka.util.PlayerConfiguration.createDefault().mediaSource;
212
+ mediaSourceEngine.configure(config);
210
213
 
211
214
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
212
215
  const initObject = new Map();
@@ -44,6 +44,7 @@ describe('DashParser ContentProtection', () => {
44
44
  isAutoLowLatencyMode: () => false,
45
45
  enableLowLatencyMode: () => {},
46
46
  updateDuration: () => {},
47
+ newDrmInfo: (stream) => {},
47
48
  };
48
49
 
49
50
  const actual = await dashParser.start(
@@ -37,6 +37,7 @@ describe('DashParser Live', () => {
37
37
  isAutoLowLatencyMode: () => false,
38
38
  enableLowLatencyMode: () => {},
39
39
  updateDuration: () => {},
40
+ newDrmInfo: (stream) => {},
40
41
  };
41
42
  });
42
43
 
@@ -53,6 +53,7 @@ describe('DashParser Manifest', () => {
53
53
  isAutoLowLatencyMode: () => false,
54
54
  enableLowLatencyMode: () => {},
55
55
  updateDuration: () => {},
56
+ newDrmInfo: (stream) => {},
56
57
  };
57
58
  });
58
59
 
@@ -119,7 +120,97 @@ describe('DashParser Manifest', () => {
119
120
  });
120
121
  }
121
122
 
122
- describe('parses and inherits attributes', () => {
123
+ describe('parses and inherits attributes with sequenceMode', () => {
124
+ beforeEach(() => {
125
+ const config = shaka.util.PlayerConfiguration.createDefault().manifest;
126
+ config.dash.sequenceMode = true;
127
+ parser.configure(config);
128
+ });
129
+
130
+ makeTestsForEach(
131
+ [
132
+ '<MPD minBufferTime="PT75S">',
133
+ ' <Period id="1" duration="PT30S">',
134
+ ' <BaseURL>http://example.com</BaseURL>',
135
+ ],
136
+ [
137
+ ' <AdaptationSet contentType="video" mimeType="video/mp4"',
138
+ ' codecs="avc1.4d401f" frameRate="1000000/42000">',
139
+ ' <Representation bandwidth="100" width="768" height="576" />',
140
+ ' <Representation bandwidth="50" width="576" height="432" />',
141
+ ' </AdaptationSet>',
142
+ ' <AdaptationSet mimeType="text/vtt"',
143
+ ' lang="es" label="spanish">',
144
+ ' <Role value="caption" />',
145
+ ' <Role value="main" />',
146
+ ' <Representation bandwidth="100" />',
147
+ ' </AdaptationSet>',
148
+ ' <AdaptationSet mimeType="audio/mp4" lang="en" ',
149
+ ' codecs="mp4a.40.29">',
150
+ ' <Role value="main" />',
151
+ ' <Representation bandwidth="100" />',
152
+ ' </AdaptationSet>',
153
+ ' </Period>',
154
+ '</MPD>',
155
+ ],
156
+ shaka.test.ManifestGenerator.generate((manifest) => {
157
+ manifest.sequenceMode = true;
158
+ manifest.anyTimeline();
159
+ manifest.minBufferTime = 75;
160
+ manifest.addPartialVariant((variant) => {
161
+ variant.language = 'en';
162
+ variant.bandwidth = 200;
163
+ variant.primary = true;
164
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
165
+ stream.bandwidth = 100;
166
+ stream.frameRate = 1000000 / 42000;
167
+ stream.size(768, 576);
168
+ stream.mime('video/mp4', 'avc1.4d401f');
169
+ });
170
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
171
+ stream.bandwidth = 100;
172
+ stream.primary = true;
173
+ stream.roles = ['main'];
174
+ stream.mime('audio/mp4', 'mp4a.40.29');
175
+ });
176
+ });
177
+ manifest.addPartialVariant((variant) => {
178
+ variant.language = 'en';
179
+ variant.bandwidth = 150;
180
+ variant.primary = true;
181
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
182
+ stream.bandwidth = 50;
183
+ stream.frameRate = 1000000 / 42000;
184
+ stream.size(576, 432);
185
+ stream.mime('video/mp4', 'avc1.4d401f');
186
+ });
187
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
188
+ stream.bandwidth = 100;
189
+ stream.primary = true;
190
+ stream.roles = ['main'];
191
+ stream.mime('audio/mp4', 'mp4a.40.29');
192
+ });
193
+ });
194
+ manifest.addPartialTextStream((stream) => {
195
+ stream.language = 'es';
196
+ stream.label = 'spanish';
197
+ stream.primary = true;
198
+ stream.mimeType = 'text/vtt';
199
+ stream.bandwidth = 100;
200
+ stream.kind = 'caption';
201
+ stream.roles = ['caption', 'main'];
202
+ });
203
+ }));
204
+ });
205
+
206
+ describe('parses and inherits attributes without sequenceMode', () => {
207
+ beforeEach(() => {
208
+ const config = shaka.util.PlayerConfiguration.createDefault().manifest;
209
+ config.dash.sequenceMode = false;
210
+ parser.configure(config);
211
+ });
212
+
213
+
123
214
  makeTestsForEach(
124
215
  [
125
216
  '<MPD minBufferTime="PT75S">',
@@ -147,6 +238,7 @@ describe('DashParser Manifest', () => {
147
238
  '</MPD>',
148
239
  ],
149
240
  shaka.test.ManifestGenerator.generate((manifest) => {
241
+ manifest.sequenceMode = false;
150
242
  manifest.anyTimeline();
151
243
  manifest.minBufferTime = 75;
152
244
  manifest.addPartialVariant((variant) => {
@@ -39,6 +39,7 @@ describe('DashParser SegmentBase', () => {
39
39
  isAutoLowLatencyMode: () => false,
40
40
  enableLowLatencyMode: () => {},
41
41
  updateDuration: () => {},
42
+ newDrmInfo: (stream) => {},
42
43
  };
43
44
  });
44
45
 
@@ -349,6 +349,7 @@ describe('DashParser SegmentList', () => {
349
349
  isAutoLowLatencyMode: () => false,
350
350
  enableLowLatencyMode: () => {},
351
351
  updateDuration: () => {},
352
+ newDrmInfo: (stream) => {},
352
353
  };
353
354
  const manifest = await dashParser.start('dummy://foo', playerInterface);
354
355
  const stream = manifest.variants[0].video;
@@ -48,6 +48,7 @@ describe('DashParser SegmentTemplate', () => {
48
48
  isAutoLowLatencyMode: () => false,
49
49
  enableLowLatencyMode: () => {},
50
50
  updateDuration: () => {},
51
+ newDrmInfo: (stream) => {},
51
52
  };
52
53
  });
53
54
 
@@ -95,7 +95,8 @@ describe('Demo', () => {
95
95
  .add('playRangeEnd')
96
96
  .add('manifest.dash.keySystemsByURI')
97
97
  .add('manifest.hls.mediaPlaylistFullMimeType')
98
- .add('drm.keySystemsMapping');
98
+ .add('drm.keySystemsMapping')
99
+ .add('streaming.parsePrftBox');
99
100
 
100
101
  /**
101
102
  * @param {!Object} section