shaka-player 3.3.12 → 3.3.13

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 (39) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/deps.js +2 -2
  3. package/dist/locales.js +31 -31
  4. package/dist/shaka-player.compiled.d.ts +1 -1
  5. package/dist/shaka-player.compiled.debug.d.ts +1 -1
  6. package/dist/shaka-player.compiled.debug.externs.js +2 -1
  7. package/dist/shaka-player.compiled.debug.js +77 -75
  8. package/dist/shaka-player.compiled.debug.map +3 -3
  9. package/dist/shaka-player.compiled.externs.js +2 -1
  10. package/dist/shaka-player.compiled.js +582 -579
  11. package/dist/shaka-player.compiled.map +3 -3
  12. package/dist/shaka-player.ui.d.ts +1 -1
  13. package/dist/shaka-player.ui.debug.d.ts +1 -1
  14. package/dist/shaka-player.ui.debug.externs.js +2 -1
  15. package/dist/shaka-player.ui.debug.js +95 -93
  16. package/dist/shaka-player.ui.debug.map +3 -3
  17. package/dist/shaka-player.ui.externs.js +2 -1
  18. package/dist/shaka-player.ui.js +676 -673
  19. package/dist/shaka-player.ui.map +3 -3
  20. package/lib/cast/cast_proxy.js +2 -1
  21. package/lib/cast/cast_receiver.js +19 -5
  22. package/lib/cast/cast_sender.js +2 -1
  23. package/lib/cast/cast_utils.js +16 -6
  24. package/lib/hls/hls_parser.js +9 -2
  25. package/lib/media/playhead.js +23 -1
  26. package/lib/media/streaming_engine.js +3 -12
  27. package/lib/player.js +24 -11
  28. package/lib/util/manifest_parser_utils.js +4 -0
  29. package/package.json +1 -1
  30. package/test/cast/cast_receiver_integration.js +97 -16
  31. package/test/cast/cast_receiver_unit.js +17 -12
  32. package/test/cast/cast_utils_unit.js +1 -0
  33. package/test/media/playhead_unit.js +41 -0
  34. package/test/media/streaming_engine_integration.js +13 -0
  35. package/test/media/streaming_engine_unit.js +23 -8
  36. package/test/player_unit.js +87 -0
  37. package/test/test/util/simple_fakes.js +3 -0
  38. package/ui/controls.js +4 -1
  39. package/ui/settings_menu.js +11 -2
@@ -694,7 +694,8 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
694
694
  // If we are casting, but the first update has not come in yet, use local
695
695
  // getters, but not local methods.
696
696
  if (this.sender_.isCasting() && !this.sender_.hasRemoteProperties()) {
697
- if (shaka.cast.CastUtils.PlayerGetterMethods[name]) {
697
+ if (shaka.cast.CastUtils.PlayerGetterMethods[name] ||
698
+ shaka.cast.CastUtils.LargePlayerGetterMethods[name]) {
698
699
  const value = /** @type {Object} */(this.localPlayer_)[name];
699
700
  goog.asserts.assert(typeof value == 'function',
700
701
  'only methods on Player');
@@ -518,6 +518,25 @@ shaka.cast.CastReceiver = class extends shaka.util.FakeEventTarget {
518
518
  update['video']['muted'] = systemVolume.muted;
519
519
  }
520
520
 
521
+ this.sendMessage_({
522
+ 'type': 'update',
523
+ 'update': update,
524
+ }, this.shakaBus_);
525
+
526
+ // Getters with large outputs each get sent in their own update message.
527
+ for (const name in shaka.cast.CastUtils.LargePlayerGetterMethods) {
528
+ const frequency = shaka.cast.CastUtils.LargePlayerGetterMethods[name];
529
+ if (this.updateNumber_ % frequency == 0) {
530
+ const update = {'player': {}};
531
+ update['player'][name] = /** @type {Object} */ (this.player_)[name]();
532
+
533
+ this.sendMessage_({
534
+ 'type': 'update',
535
+ 'update': update,
536
+ }, this.shakaBus_);
537
+ }
538
+ }
539
+
521
540
  // Only start progressing the update number once data is loaded,
522
541
  // just in case any of the "rarely changing" properties with less frequent
523
542
  // update messages changes significantly during the loading process.
@@ -525,11 +544,6 @@ shaka.cast.CastReceiver = class extends shaka.util.FakeEventTarget {
525
544
  this.updateNumber_ += 1;
526
545
  }
527
546
 
528
- this.sendMessage_({
529
- 'type': 'update',
530
- 'update': update,
531
- }, this.shakaBus_);
532
-
533
547
  this.maybeSendMediaInfoMessage_();
534
548
  }
535
549
 
@@ -360,7 +360,8 @@ shaka.cast.CastSender = class {
360
360
  return (...args) =>
361
361
  this.remoteAsyncCall_(targetName, property, ...args);
362
362
  }
363
- if (CastUtils.PlayerGetterMethods[property]) {
363
+ if (CastUtils.PlayerGetterMethods[property] ||
364
+ CastUtils.LargePlayerGetterMethods[property]) {
364
365
  return () => this.propertyGetter_(targetName, property);
365
366
  }
366
367
  }
@@ -307,9 +307,6 @@ shaka.cast.CastUtils.PlayerGetterMethods = {
307
307
  'getAudioLanguagesAndRoles': 4,
308
308
  'getBufferFullness': 1,
309
309
  'getBufferedInfo': 2,
310
- // NOTE: The 'getSharedConfiguration' property is not proxied as it would
311
- // not be possible to share a reference.
312
- 'getConfiguration': 4,
313
310
  'getExpiration': 2,
314
311
  'getKeyStatuses': 2,
315
312
  // NOTE: The 'getManifest' property is not proxied, as it is very large.
@@ -318,9 +315,6 @@ shaka.cast.CastUtils.PlayerGetterMethods = {
318
315
  'getPlaybackRate': 2,
319
316
  'getTextLanguages': 4,
320
317
  'getTextLanguagesAndRoles': 4,
321
- 'getTextTracks': 2,
322
- 'getStats': 5,
323
- 'getVariantTracks': 2,
324
318
  'getImageTracks': 2,
325
319
  'getThumbnails': 2,
326
320
  'isAudioOnly': 10,
@@ -334,6 +328,22 @@ shaka.cast.CastUtils.PlayerGetterMethods = {
334
328
  };
335
329
 
336
330
 
331
+ /**
332
+ * Player getter methods with data large enough to be sent in their own update
333
+ * messages, to reduce the size of each message. The format of this is
334
+ * identical to PlayerGetterMethods.
335
+ * @const {!Object.<string, number>}
336
+ */
337
+ shaka.cast.CastUtils.LargePlayerGetterMethods = {
338
+ // NOTE: The 'getSharedConfiguration' property is not proxied as it would
339
+ // not be possible to share a reference.
340
+ 'getConfiguration': 4,
341
+ 'getStats': 5,
342
+ 'getTextTracks': 2,
343
+ 'getVariantTracks': 2,
344
+ };
345
+
346
+
337
347
  /**
338
348
  * Player getter methods that are proxied while casting, but only when casting
339
349
  * a livestream.
@@ -639,13 +639,19 @@ shaka.hls.HlsParser = class {
639
639
  shaka.util.ArrayUtils.remove(allCodecs, textCodecs);
640
640
  }
641
641
  if (audioGroupId) {
642
- const codecs = shaka.util.ManifestParserUtils.guessCodecs(
642
+ let codecs = shaka.util.ManifestParserUtils.guessCodecsSafe(
643
643
  ContentType.AUDIO, allCodecs);
644
+ if (!codecs) {
645
+ codecs = this.config_.hls.defaultAudioCodec;
646
+ }
644
647
  this.groupIdToCodecsMap_.set(audioGroupId, codecs);
645
648
  }
646
649
  if (videoGroupId) {
647
- const codecs = shaka.util.ManifestParserUtils.guessCodecs(
650
+ let codecs = shaka.util.ManifestParserUtils.guessCodecsSafe(
648
651
  ContentType.VIDEO, allCodecs);
652
+ if (!codecs) {
653
+ codecs = this.config_.hls.defaultVideoCodec;
654
+ }
649
655
  this.groupIdToCodecsMap_.set(videoGroupId, codecs);
650
656
  }
651
657
  }
@@ -3028,6 +3034,7 @@ shaka.hls.HlsParser.TEXT_EXTENSIONS_TO_MIME_TYPES_ = {
3028
3034
  'm4f': 'application/mp4',
3029
3035
  'cmft': 'application/mp4',
3030
3036
  'vtt': 'text/vtt',
3037
+ 'webvtt': 'txt/vtt',
3031
3038
  'ttml': 'application/ttml+xml',
3032
3039
  };
3033
3040
 
@@ -33,6 +33,14 @@ goog.requireType('shaka.media.PresentationTimeline');
33
33
  * @interface
34
34
  */
35
35
  shaka.media.Playhead = class {
36
+ /**
37
+ * Called when the Player is ready to begin playback. Anything that depends
38
+ * on setStartTime() should be done here, not in the constructor.
39
+ *
40
+ * @see https://github.com/shaka-project/shaka-player/issues/4244
41
+ */
42
+ ready() {}
43
+
36
44
  /**
37
45
  * Set the start time. If the content has already started playback, this will
38
46
  * be ignored.
@@ -76,6 +84,14 @@ shaka.media.SrcEqualsPlayhead = class {
76
84
 
77
85
  /** @private {shaka.util.EventManager} */
78
86
  this.eventManager_ = new shaka.util.EventManager();
87
+ }
88
+
89
+ /** @override */
90
+ ready() {
91
+ goog.asserts.assert(
92
+ this.mediaElement_ != null,
93
+ 'Playhead should not be released before calling ready()',
94
+ );
79
95
 
80
96
  // We listen for the loaded-data-event so that we know when we can
81
97
  // interact with |currentTime|.
@@ -88,6 +104,7 @@ shaka.media.SrcEqualsPlayhead = class {
88
104
  this.eventManager_.listenOnce(this.mediaElement_, 'seeking', () => {
89
105
  this.started_ = true;
90
106
  });
107
+
91
108
  const currentTime = this.mediaElement_.currentTime;
92
109
  // Using the currentTime allows using a negative number in Live HLS
93
110
  const newTime = Math.max(0, currentTime + this.startTime_);
@@ -211,7 +228,12 @@ shaka.media.MediaSourcePlayhead = class {
211
228
  /** @type {shaka.util.Timer} */
212
229
  this.checkWindowTimer_ = new shaka.util.Timer(() => {
213
230
  this.onPollWindow_();
214
- }).tickEvery(/* seconds= */ 0.25);
231
+ });
232
+ }
233
+
234
+ /** @override */
235
+ ready() {
236
+ this.checkWindowTimer_.tickEvery(/* seconds= */ 0.25);
215
237
  }
216
238
 
217
239
  /** @override */
@@ -1366,16 +1366,6 @@ shaka.media.StreamingEngine = class {
1366
1366
  this.mediaStates_.delete(ContentType.TEXT);
1367
1367
  } else if (error.code == shaka.util.Error.Code.QUOTA_EXCEEDED_ERROR) {
1368
1368
  this.handleQuotaExceeded_(mediaState, error);
1369
- } else if (error.code == shaka.util.Error.Code.BAD_HTTP_STATUS &&
1370
- error.data && error.data[1] == 404) {
1371
- // The segment could not be found, does not exist, or is not available.
1372
- // In any case just try again.
1373
- // The current segment is not available. Schedule another update to
1374
- // fetch the segment again.
1375
- shaka.log.v2(logPrefix, 'segment not available.');
1376
- mediaState.performingUpdate = false;
1377
- mediaState.updateTimer = null;
1378
- this.scheduleUpdate_(mediaState, 1);
1379
1369
  } else {
1380
1370
  shaka.log.error(logPrefix, 'failed fetch and append: code=' +
1381
1371
  error.code);
@@ -1390,9 +1380,10 @@ shaka.media.StreamingEngine = class {
1390
1380
 
1391
1381
  /**
1392
1382
  * Clear per-stream error states and retry any failed streams.
1383
+ * @param {number} delaySeconds
1393
1384
  * @return {boolean} False if unable to retry.
1394
1385
  */
1395
- retry() {
1386
+ retry(delaySeconds) {
1396
1387
  if (this.destroyer_.destroyed()) {
1397
1388
  shaka.log.error('Unable to retry after StreamingEngine is destroyed!');
1398
1389
  return false;
@@ -1409,7 +1400,7 @@ shaka.media.StreamingEngine = class {
1409
1400
  if (mediaState.hasError) {
1410
1401
  shaka.log.info(logPrefix, 'Retrying after failure...');
1411
1402
  mediaState.hasError = false;
1412
- this.scheduleUpdate_(mediaState, 0.1);
1403
+ this.scheduleUpdate_(mediaState, delaySeconds);
1413
1404
  }
1414
1405
  }
1415
1406
 
package/lib/player.js CHANGED
@@ -2040,6 +2040,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2040
2040
  shaka.util.StreamUtils.variantToTrack(initialVariant));
2041
2041
  }
2042
2042
 
2043
+ this.playhead_.ready();
2044
+
2043
2045
  // Decide if text should be shown automatically.
2044
2046
  // similar to video/audio track, we would skip switch initial text track
2045
2047
  // if user already pick text track (via selectTextTrack api)
@@ -2374,6 +2376,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2374
2376
  HTMLMediaElement.HAVE_METADATA,
2375
2377
  this.loadEventManager_,
2376
2378
  () => {
2379
+ this.playhead_.ready();
2377
2380
  fullyLoaded.resolve();
2378
2381
  });
2379
2382
 
@@ -4958,12 +4961,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4958
4961
  * If the player has loaded content, and streaming seen an error, but the
4959
4962
  * could not resume streaming, this will return <code>false</code>.
4960
4963
  *
4964
+ * @param {number=} retryDelaySeconds
4961
4965
  * @return {boolean}
4962
4966
  * @export
4963
4967
  */
4964
- retryStreaming() {
4968
+ retryStreaming(retryDelaySeconds = 0.1) {
4965
4969
  return this.loadMode_ == shaka.Player.LoadMode.MEDIA_SOURCE ?
4966
- this.streamingEngine_.retry() :
4970
+ this.streamingEngine_.retry(retryDelaySeconds) :
4967
4971
  false;
4968
4972
  }
4969
4973
 
@@ -5059,17 +5063,26 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5059
5063
  * @private
5060
5064
  */
5061
5065
  defaultStreamingFailureCallback_(error) {
5062
- const retryErrorCodes = [
5063
- shaka.util.Error.Code.BAD_HTTP_STATUS,
5064
- shaka.util.Error.Code.HTTP_ERROR,
5065
- shaka.util.Error.Code.TIMEOUT,
5066
- ];
5066
+ // For live streams, we retry streaming automatically for certain errors.
5067
+ // For VOD streams, all streaming failures are fatal.
5068
+ if (!this.isLive()) {
5069
+ return;
5070
+ }
5067
5071
 
5068
- if (this.isLive() && retryErrorCodes.includes(error.code)) {
5069
- error.severity = shaka.util.Error.Severity.RECOVERABLE;
5072
+ let retryDelaySeconds = null;
5073
+ if (error.code == shaka.util.Error.Code.BAD_HTTP_STATUS ||
5074
+ error.code == shaka.util.Error.Code.HTTP_ERROR) {
5075
+ // These errors can be near-instant, so delay a bit before retrying.
5076
+ retryDelaySeconds = 1;
5077
+ } else if (error.code == shaka.util.Error.Code.TIMEOUT) {
5078
+ // We already waited for a timeout, so retry quickly.
5079
+ retryDelaySeconds = 0.1;
5080
+ }
5070
5081
 
5082
+ if (retryDelaySeconds != null) {
5083
+ error.severity = shaka.util.Error.Severity.RECOVERABLE;
5071
5084
  shaka.log.warning('Live streaming error. Retrying automatically...');
5072
- this.retryStreaming();
5085
+ this.retryStreaming(retryDelaySeconds);
5073
5086
  }
5074
5087
  }
5075
5088
 
@@ -6635,7 +6648,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6635
6648
  * @define {string} A version number taken from git at compile time.
6636
6649
  * @export
6637
6650
  */
6638
- shaka.Player.version = 'v3.3.12-uncompiled';
6651
+ shaka.Player.version = 'v3.3.13-uncompiled';
6639
6652
 
6640
6653
  // Initialize the deprecation system using the version string we just set
6641
6654
  // on the player.
@@ -168,6 +168,7 @@ shaka.util.ManifestParserUtils.VIDEO_CODEC_REGEXPS_ = [
168
168
  /^hvc/,
169
169
  /^vp0?[89]/,
170
170
  /^av01/,
171
+ /^dvhe/,
171
172
  ];
172
173
 
173
174
 
@@ -183,6 +184,9 @@ shaka.util.ManifestParserUtils.AUDIO_CODEC_REGEXPS_ = [
183
184
  /^flac$/,
184
185
  /^mp4a/,
185
186
  /^[ae]c-3$/,
187
+ /^ac-4$/,
188
+ /^dts[cx]$/, // DTS Surround Sound
189
+ /^[du]dts$/, // DTS Surround Sound
186
190
  ];
187
191
 
188
192
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shaka-player",
3
3
  "description": "DASH/EME video player library",
4
- "version": "3.3.12",
4
+ "version": "3.3.13",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -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);