shaka-player 3.3.12 → 3.3.14

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 (60) hide show
  1. package/AUTHORS +1 -0
  2. package/CHANGELOG.md +33 -0
  3. package/CONTRIBUTORS +1 -0
  4. package/dist/deps.js +3 -3
  5. package/dist/locales.js +31 -31
  6. package/dist/shaka-player.compiled.d.ts +1 -1
  7. package/dist/shaka-player.compiled.debug.d.ts +1 -1
  8. package/dist/shaka-player.compiled.debug.externs.js +2 -1
  9. package/dist/shaka-player.compiled.debug.js +135 -126
  10. package/dist/shaka-player.compiled.debug.map +3 -3
  11. package/dist/shaka-player.compiled.externs.js +2 -1
  12. package/dist/shaka-player.compiled.js +744 -739
  13. package/dist/shaka-player.compiled.map +3 -3
  14. package/dist/shaka-player.ui.d.ts +1 -1
  15. package/dist/shaka-player.ui.debug.d.ts +1 -1
  16. package/dist/shaka-player.ui.debug.externs.js +2 -1
  17. package/dist/shaka-player.ui.debug.js +228 -219
  18. package/dist/shaka-player.ui.debug.map +3 -3
  19. package/dist/shaka-player.ui.externs.js +2 -1
  20. package/dist/shaka-player.ui.js +849 -845
  21. package/dist/shaka-player.ui.map +3 -3
  22. package/docs/tutorials/welcome.md +2 -2
  23. package/externs/shaka/player.js +2 -1
  24. package/lib/cast/cast_proxy.js +2 -1
  25. package/lib/cast/cast_receiver.js +19 -5
  26. package/lib/cast/cast_sender.js +2 -1
  27. package/lib/cast/cast_utils.js +16 -6
  28. package/lib/cea/cea708_window.js +2 -2
  29. package/lib/dash/segment_template.js +1 -1
  30. package/lib/hls/hls_parser.js +17 -3
  31. package/lib/media/content_workarounds.js +12 -0
  32. package/lib/media/playhead.js +23 -1
  33. package/lib/media/streaming_engine.js +3 -12
  34. package/lib/player.js +24 -11
  35. package/lib/polyfill/media_capabilities.js +59 -9
  36. package/lib/text/ttml_text_parser.js +32 -7
  37. package/lib/text/ui_text_displayer.js +22 -5
  38. package/lib/util/manifest_parser_utils.js +4 -0
  39. package/lib/util/platform.js +21 -2
  40. package/lib/util/stream_utils.js +30 -10
  41. package/package.json +1 -1
  42. package/test/cast/cast_receiver_integration.js +97 -16
  43. package/test/cast/cast_receiver_unit.js +17 -12
  44. package/test/cast/cast_utils_unit.js +1 -0
  45. package/test/dash/dash_parser_manifest_unit.js +1 -0
  46. package/test/hls/hls_parser_unit.js +1 -0
  47. package/test/media/playhead_unit.js +41 -0
  48. package/test/media/streaming_engine_integration.js +13 -0
  49. package/test/media/streaming_engine_unit.js +23 -8
  50. package/test/player_unit.js +87 -0
  51. package/test/polyfill/media_capabilities_unit.js +261 -0
  52. package/test/test/util/simple_fakes.js +3 -0
  53. package/test/text/mp4_ttml_parser_unit.js +4 -4
  54. package/test/text/ttml_text_parser_unit.js +119 -86
  55. package/test/text/ui_text_displayer_unit.js +76 -0
  56. package/test/ui/text_displayer_layout_unit.js +14 -0
  57. package/test/util/stream_utils_unit.js +18 -0
  58. package/test/util/xml_utils_unit.js +6 -1
  59. package/ui/controls.js +5 -2
  60. package/ui/settings_menu.js +11 -2
@@ -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
 
@@ -184,7 +184,7 @@ shaka.util.Platform = class {
184
184
  * @return {?number} A major version number or null if not Chromium-based.
185
185
  */
186
186
  static chromeVersion() {
187
- if (!shaka.util.Platform.userAgentContains_('Chrome')) {
187
+ if (!shaka.util.Platform.isChrome()) {
188
188
  return null;
189
189
  }
190
190
 
@@ -223,7 +223,9 @@ shaka.util.Platform = class {
223
223
  return !!navigator.vendor && navigator.vendor.includes('Apple') &&
224
224
  !shaka.util.Platform.isTizen() &&
225
225
  !shaka.util.Platform.isEOS() &&
226
- !shaka.util.Platform.isPS4();
226
+ !shaka.util.Platform.isVirginMedia() &&
227
+ !shaka.util.Platform.isPS4() &&
228
+ !shaka.util.Platform.isAmazonFireTV();
227
229
  }
228
230
 
229
231
  /**
@@ -246,6 +248,23 @@ shaka.util.Platform = class {
246
248
  return shaka.util.Platform.userAgentContains_('PlayStation 4');
247
249
  }
248
250
 
251
+ /**
252
+ * Check if the current platform is Virgin Media device.
253
+ */
254
+ static isVirginMedia() {
255
+ return shaka.util.Platform.userAgentContains_('VirginMedia');
256
+ }
257
+
258
+ /**
259
+ * Check if the current platform is Amazon Fire TV.
260
+ * https://developer.amazon.com/docs/fire-tv/identify-amazon-fire-tv-devices.html
261
+ *
262
+ * @return {boolean}
263
+ */
264
+ static isAmazonFireTV() {
265
+ return shaka.util.Platform.userAgentContains_('AFT');
266
+ }
267
+
249
268
  /**
250
269
  * Returns a major version number for Safari, or Safari-based iOS browsers.
251
270
  *
@@ -431,15 +431,20 @@ shaka.util.StreamUtils = class {
431
431
  const video = variant.video;
432
432
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
433
433
  if (video) {
434
- let videoCodecs = shaka.util.StreamUtils.patchVp9(video.codecs);
434
+ let videoCodecs =
435
+ shaka.util.StreamUtils.getCorrectVideoCodecs_(video.codecs);
435
436
  // For multiplexed streams. Here we must check the audio of the
436
437
  // stream to see if it is compatible.
437
438
  if (video.codecs.includes(',')) {
438
439
  const allCodecs = video.codecs.split(',');
439
440
  videoCodecs = shaka.util.ManifestParserUtils.guessCodecs(
440
441
  ContentType.VIDEO, allCodecs);
441
- const audioCodecs = shaka.util.ManifestParserUtils.guessCodecs(
442
+ videoCodecs =
443
+ shaka.util.StreamUtils.getCorrectVideoCodecs_(videoCodecs);
444
+ let audioCodecs = shaka.util.ManifestParserUtils.guessCodecs(
442
445
  ContentType.AUDIO, allCodecs);
446
+ audioCodecs =
447
+ shaka.util.StreamUtils.getCorrectAudioCodecs_(audioCodecs);
443
448
  const audioFullType = shaka.util.MimeUtils.getFullOrConvertedType(
444
449
  video.mimeType, audioCodecs, ContentType.AUDIO);
445
450
  if (!MediaSource.isTypeSupported(audioFullType)) {
@@ -559,7 +564,8 @@ shaka.util.StreamUtils = class {
559
564
  const allCodecs = video.codecs.split(',');
560
565
  videoCodecs = shaka.util.ManifestParserUtils.guessCodecs(
561
566
  ContentType.VIDEO, allCodecs);
562
- videoCodecs = shaka.util.StreamUtils.patchVp9(videoCodecs);
567
+ videoCodecs =
568
+ shaka.util.StreamUtils.getCorrectVideoCodecs_(videoCodecs);
563
569
  const audioCodecs = shaka.util.ManifestParserUtils.guessCodecs(
564
570
  ContentType.AUDIO, allCodecs);
565
571
 
@@ -573,7 +579,7 @@ shaka.util.StreamUtils = class {
573
579
  spatialRendering: false,
574
580
  };
575
581
  }
576
- videoCodecs = shaka.util.StreamUtils.patchVp9(videoCodecs);
582
+ videoCodecs = shaka.util.StreamUtils.getCorrectVideoCodecs_(videoCodecs);
577
583
  const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
578
584
  video.mimeType, videoCodecs, ContentType.VIDEO);
579
585
  // VideoConfiguration
@@ -742,15 +748,29 @@ shaka.util.StreamUtils = class {
742
748
 
743
749
 
744
750
  /**
745
- * MediaCapabilities supports 'vp09...' codecs, but not 'vp9'. Translate vp9
746
- * codec strings into 'vp09...', to allow such content to play with
747
- * mediaCapabilities enabled.
748
- *
751
+ * Generates the correct video codec for MediaDecodingConfiguration and
752
+ * for MediaSource.isTypeSupported.
749
753
  * @param {string} codec
750
754
  * @return {string}
755
+ * @private
751
756
  */
752
- static patchVp9(codec) {
753
- if (codec == 'vp9') {
757
+ static getCorrectVideoCodecs_(codec) {
758
+ if (codec.includes('avc1')) {
759
+ // Convert avc1 codec string from RFC-4281 to RFC-6381 for
760
+ // MediaSource.isTypeSupported
761
+ // Example, convert avc1.66.30 to avc1.42001e (0x42 == 66 and 0x1e == 30)
762
+ const avcdata = codec.split('.');
763
+ if (avcdata.length == 3) {
764
+ let result = avcdata.shift() + '.';
765
+ result += parseInt(avcdata.shift(), 10).toString(16);
766
+ result +=
767
+ ('000' + parseInt(avcdata.shift(), 10).toString(16)).slice(-4);
768
+ return result;
769
+ }
770
+ } else if (codec == 'vp9') {
771
+ // MediaCapabilities supports 'vp09...' codecs, but not 'vp9'. Translate
772
+ // vp9 codec strings into 'vp09...', to allow such content to play with
773
+ // mediaCapabilities enabled.
754
774
  // This means profile 0, level 4.1, 8-bit color. This supports 1080p @
755
775
  // 60Hz. See https://en.wikipedia.org/wiki/VP9#Levels
756
776
  //
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.14",
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);
@@ -2105,6 +2105,7 @@ describe('DashParser Manifest', () => {
2105
2105
  /** @type {shaka.extern.Manifest} */
2106
2106
  const manifest = await parser.start('dummy://foo', playerInterface);
2107
2107
  expect(manifest.imageStreams.length).toBe(1);
2108
+ expect(manifest.presentationTimeline.getMaxSegmentDuration()).toBe(1);
2108
2109
  const imageStream = manifest.imageStreams[0];
2109
2110
  expect(imageStream.width).toBe(1024);
2110
2111
  expect(imageStream.height).toBe(1152);
@@ -37,6 +37,7 @@ describe('HlsParser', () => {
37
37
 
38
38
  afterEach(() => {
39
39
  shaka.log.alwaysWarn = originalAlwaysWarn;
40
+ parser.stop();
40
41
  });
41
42
 
42
43
  beforeEach(() => {
@@ -171,6 +171,8 @@ describe('Playhead', () => {
171
171
  Util.spyFunc(onSeek),
172
172
  Util.spyFunc(onEvent));
173
173
 
174
+ playhead.ready();
175
+
174
176
  expect(video.currentTime).toBe(5);
175
177
  expect(playhead.getTime()).toBe(5);
176
178
 
@@ -192,6 +194,8 @@ describe('Playhead', () => {
192
194
  Util.spyFunc(onSeek),
193
195
  Util.spyFunc(onEvent));
194
196
 
197
+ playhead.ready();
198
+
195
199
  expect(video.addEventListener).toHaveBeenCalledWith(
196
200
  'loadedmetadata', jasmine.any(Function), jasmine.anything());
197
201
  expect(video.addEventListener).not.toHaveBeenCalledWith(
@@ -233,6 +237,8 @@ describe('Playhead', () => {
233
237
  Util.spyFunc(onSeek),
234
238
  Util.spyFunc(onEvent));
235
239
 
240
+ playhead.ready();
241
+
236
242
  video.on['seeking']();
237
243
  expect(playhead.getTime()).toBe(5);
238
244
  expect(video.currentTime).toBe(5);
@@ -266,6 +272,8 @@ describe('Playhead', () => {
266
272
  video, manifest, config, /* startTime= */ 60, Util.spyFunc(onSeek),
267
273
  Util.spyFunc(onEvent));
268
274
 
275
+ playhead.ready();
276
+
269
277
  expect(playhead.getTime()).toBe(59); // duration - durationBackoff
270
278
  expect(video.currentTime).toBe(59); // duration - durationBackoff
271
279
  });
@@ -296,6 +304,8 @@ describe('Playhead', () => {
296
304
  video, manifest, config, /* startTime= */ -40, Util.spyFunc(onSeek),
297
305
  Util.spyFunc(onEvent));
298
306
 
307
+ playhead.ready();
308
+
299
309
  expect(playhead.getTime()).toBe(30);
300
310
  });
301
311
 
@@ -308,6 +318,8 @@ describe('Playhead', () => {
308
318
  Util.spyFunc(onSeek),
309
319
  Util.spyFunc(onEvent));
310
320
 
321
+ playhead.ready();
322
+
311
323
  expect(video.addEventListener).toHaveBeenCalledWith(
312
324
  'loadedmetadata', jasmine.any(Function), jasmine.anything());
313
325
 
@@ -341,6 +353,8 @@ describe('Playhead', () => {
341
353
  Util.spyFunc(onSeek),
342
354
  Util.spyFunc(onEvent));
343
355
 
356
+ playhead.ready();
357
+
344
358
  expect(video.addEventListener).toHaveBeenCalledWith(
345
359
  'loadedmetadata', jasmine.any(Function), jasmine.anything());
346
360
 
@@ -374,6 +388,8 @@ describe('Playhead', () => {
374
388
  Util.spyFunc(onSeek),
375
389
  Util.spyFunc(onEvent));
376
390
 
391
+ playhead.ready();
392
+
377
393
  // This has to periodically increment the mock date to allow the onSeeking_
378
394
  // handler to seek, if appropriate.
379
395
 
@@ -646,6 +662,8 @@ describe('Playhead', () => {
646
662
  expect(currentTime).toBe(1000);
647
663
  seekCount = 0;
648
664
 
665
+ playhead.ready();
666
+
649
667
  // The availability window slips ahead.
650
668
  timeline.getSeekRangeStart.and.returnValue(1030);
651
669
  timeline.getSeekRangeEnd.and.returnValue(1030);
@@ -690,6 +708,8 @@ describe('Playhead', () => {
690
708
  Util.spyFunc(onSeek),
691
709
  Util.spyFunc(onEvent));
692
710
 
711
+ playhead.ready();
712
+
693
713
  video.on['seeking']();
694
714
  expect(video.currentTime).toBe(5);
695
715
  expect(playhead.getTime()).toBe(5);
@@ -722,6 +742,8 @@ describe('Playhead', () => {
722
742
  Util.spyFunc(onSeek),
723
743
  Util.spyFunc(onEvent));
724
744
 
745
+ playhead.ready();
746
+
725
747
  video.on['seeking']();
726
748
  expect(video.currentTime).toBe(5);
727
749
  expect(playhead.getTime()).toBe(5);
@@ -759,6 +781,9 @@ describe('Playhead', () => {
759
781
 
760
782
  video.currentTime = 0;
761
783
  video.seeking = true;
784
+
785
+ playhead.ready();
786
+
762
787
  // "video.seeking" stays true until the buffered range intersects with
763
788
  // "video.currentTime". Playhead should correct anyway.
764
789
  video.on['seeking']();
@@ -803,6 +828,8 @@ describe('Playhead', () => {
803
828
  Util.spyFunc(onSeek),
804
829
  Util.spyFunc(onEvent));
805
830
 
831
+ playhead.ready();
832
+
806
833
  /**
807
834
  * Prevent retries on the initial start time seek. This will ensure that
808
835
  * only one call is made for the initial seek, and that any additional calls
@@ -955,6 +982,8 @@ describe('Playhead', () => {
955
982
  Util.spyFunc(onSeek),
956
983
  Util.spyFunc(onEvent));
957
984
 
985
+ playhead.ready();
986
+
958
987
  jasmine.clock().tick(500);
959
988
  for (let time = data.start; time < data.waitingAt; time++) {
960
989
  // We don't want to run tick() for 1 second because it will trigger
@@ -1208,6 +1237,8 @@ describe('Playhead', () => {
1208
1237
  Util.spyFunc(onSeek),
1209
1238
  Util.spyFunc(onEvent));
1210
1239
 
1240
+ playhead.ready();
1241
+
1211
1242
  jasmine.clock().tick(500);
1212
1243
  expect(onEvent).not.toHaveBeenCalled();
1213
1244
 
@@ -1257,6 +1288,8 @@ describe('Playhead', () => {
1257
1288
  Util.spyFunc(onSeek),
1258
1289
  Util.spyFunc(onEvent));
1259
1290
 
1291
+ playhead.ready();
1292
+
1260
1293
  playhead.notifyOfBufferingChange();
1261
1294
  jasmine.clock().tick(500);
1262
1295
 
@@ -1280,6 +1313,8 @@ describe('Playhead', () => {
1280
1313
  Util.spyFunc(onSeek),
1281
1314
  Util.spyFunc(onEvent));
1282
1315
 
1316
+ playhead.ready();
1317
+
1283
1318
  playhead.notifyOfBufferingChange();
1284
1319
  jasmine.clock().tick(500);
1285
1320
 
@@ -1305,6 +1340,8 @@ describe('Playhead', () => {
1305
1340
  Util.spyFunc(onSeek),
1306
1341
  Util.spyFunc(onEvent));
1307
1342
 
1343
+ playhead.ready();
1344
+
1308
1345
  playhead.notifyOfBufferingChange();
1309
1346
  jasmine.clock().tick(500);
1310
1347
 
@@ -1330,6 +1367,8 @@ describe('Playhead', () => {
1330
1367
  Util.spyFunc(onSeek),
1331
1368
  Util.spyFunc(onEvent));
1332
1369
 
1370
+ playhead.ready();
1371
+
1333
1372
  playhead.notifyOfBufferingChange();
1334
1373
  jasmine.clock().tick(500);
1335
1374
 
@@ -1362,6 +1401,8 @@ describe('Playhead', () => {
1362
1401
  Util.spyFunc(onSeek),
1363
1402
  Util.spyFunc(onEvent));
1364
1403
 
1404
+ playhead.ready();
1405
+
1365
1406
  jasmine.clock().tick(500);
1366
1407
  expect(onEvent).not.toHaveBeenCalled();
1367
1408
 
@@ -154,6 +154,19 @@ describe('StreamingEngine', () => {
154
154
  /* presentationDuration= */ Infinity);
155
155
  setupPlayhead();
156
156
 
157
+ // Retry on failure for live streams.
158
+ config.failureCallback = () => streamingEngine.retry(0.1);
159
+
160
+ // Ignore 404 errors in live stream tests.
161
+ onError.and.callFake((error) => {
162
+ if (error.code == shaka.util.Error.Code.BAD_HTTP_STATUS &&
163
+ error.data[1] == 404) {
164
+ // 404 error
165
+ } else {
166
+ fail(error);
167
+ }
168
+ });
169
+
157
170
  createStreamingEngine();
158
171
  }
159
172