shaka-player 4.0.0 → 4.1.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 (114) hide show
  1. package/.github/config.yml +1 -0
  2. package/.github/workflows/release-please.yaml +6 -11
  3. package/CHANGELOG.md +33 -0
  4. package/CONTRIBUTORS +1 -0
  5. package/README.md +4 -4
  6. package/build/misspellings.txt +1 -0
  7. package/demo/common/message_ids.js +1 -0
  8. package/demo/config.js +3 -1
  9. package/demo/demo.less +0 -1
  10. package/demo/locales/en.json +1 -0
  11. package/demo/locales/source.json +4 -0
  12. package/dist/controls.css +3 -3
  13. package/dist/controls.css.map +1 -1
  14. package/dist/demo.compiled.debug.js +4 -4
  15. package/dist/demo.compiled.debug.map +2 -2
  16. package/dist/demo.compiled.js +4 -4
  17. package/dist/demo.compiled.map +2 -2
  18. package/dist/demo.css +1 -1
  19. package/dist/demo.css.map +1 -1
  20. package/dist/deps.js +3 -3
  21. package/dist/locales.js +32 -32
  22. package/dist/receiver.compiled.debug.js +2 -2
  23. package/dist/receiver.compiled.debug.map +2 -2
  24. package/dist/receiver.compiled.js +2 -2
  25. package/dist/receiver.compiled.map +2 -2
  26. package/dist/shaka-player.compiled.d.ts +15 -8
  27. package/dist/shaka-player.compiled.debug.d.ts +15 -8
  28. package/dist/shaka-player.compiled.debug.externs.js +11 -2
  29. package/dist/shaka-player.compiled.debug.js +266 -253
  30. package/dist/shaka-player.compiled.debug.map +3 -3
  31. package/dist/shaka-player.compiled.externs.js +11 -2
  32. package/dist/shaka-player.compiled.js +792 -784
  33. package/dist/shaka-player.compiled.map +3 -3
  34. package/dist/shaka-player.ui.d.ts +16 -9
  35. package/dist/shaka-player.ui.debug.d.ts +16 -9
  36. package/dist/shaka-player.ui.debug.externs.js +11 -2
  37. package/dist/shaka-player.ui.debug.js +299 -282
  38. package/dist/shaka-player.ui.debug.map +3 -3
  39. package/dist/shaka-player.ui.externs.js +11 -2
  40. package/dist/shaka-player.ui.js +948 -941
  41. package/dist/shaka-player.ui.map +3 -3
  42. package/docs/design/chromecast.md +3 -1
  43. package/docs/tutorials/drm-config.md +16 -0
  44. package/docs/tutorials/ui.md +26 -0
  45. package/docs/tutorials/upgrade.md +4 -2
  46. package/externs/chromecast.js +9 -2
  47. package/externs/shaka/manifest.js +7 -0
  48. package/externs/shaka/player.js +32 -2
  49. package/externs/shaka/text.js +7 -0
  50. package/karma.conf.js +4 -0
  51. package/lib/abr/simple_abr_manager.js +2 -1
  52. package/lib/cast/cast_proxy.js +19 -8
  53. package/lib/cast/cast_receiver.js +3 -3
  54. package/lib/cast/cast_sender.js +12 -2
  55. package/lib/dash/dash_parser.js +2 -2
  56. package/lib/hls/hls_parser.js +46 -15
  57. package/lib/media/drm_engine.js +36 -8
  58. package/lib/media/gap_jumping_controller.js +23 -1
  59. package/lib/media/media_source_engine.js +91 -37
  60. package/lib/media/playhead.js +47 -4
  61. package/lib/media/stall_detector.js +19 -2
  62. package/lib/media/streaming_engine.js +9 -3
  63. package/lib/offline/indexeddb/storage_mechanism.js +4 -2
  64. package/lib/offline/manifest_converter.js +1 -0
  65. package/lib/player.js +339 -175
  66. package/lib/text/cue.js +6 -0
  67. package/lib/text/ui_text_displayer.js +1 -0
  68. package/lib/text/vtt_text_parser.js +11 -7
  69. package/lib/util/config_utils.js +25 -0
  70. package/lib/util/error.js +13 -0
  71. package/lib/util/fake_event.js +39 -0
  72. package/lib/util/manifest_parser_utils.js +1 -1
  73. package/lib/util/mime_utils.js +62 -6
  74. package/lib/util/periods.js +5 -4
  75. package/lib/util/player_configuration.js +16 -14
  76. package/lib/util/stats.js +28 -1
  77. package/lib/util/stream_utils.js +11 -2
  78. package/package.json +5 -2
  79. package/test/cast/cast_proxy_unit.js +3 -1
  80. package/test/cast/cast_sender_unit.js +21 -6
  81. package/test/demo/demo_unit.js +2 -1
  82. package/test/hls/hls_live_unit.js +79 -7
  83. package/test/hls/manifest_text_parser_unit.js +19 -0
  84. package/test/media/adaptation_set_unit.js +1 -0
  85. package/test/media/playhead_unit.js +114 -25
  86. package/test/media/stall_detector_unit.js +20 -2
  87. package/test/media/streaming_engine_integration.js +2 -1
  88. package/test/media/streaming_engine_unit.js +1 -0
  89. package/test/offline/indexeddb_storage_unit.js +1 -1
  90. package/test/offline/storage_compatibility_unit.js +1 -1
  91. package/test/player_external.js +7 -1
  92. package/test/player_integration.js +9 -10
  93. package/test/player_src_equals_integration.js +81 -83
  94. package/test/player_unit.js +240 -2
  95. package/test/test/boot.js +9 -0
  96. package/test/test/externs/filters.js +4 -0
  97. package/test/test/util/manifest_generator.js +2 -0
  98. package/test/test/util/simple_fakes.js +45 -2
  99. package/test/test/util/streaming_engine_util.js +1 -0
  100. package/test/text/vtt_text_parser_unit.js +68 -5
  101. package/test/ui/ui_unit.js +18 -5
  102. package/test/util/mime_utils_unit.js +54 -0
  103. package/test/util/periods_unit.js +79 -0
  104. package/test/util/stream_utils_unit.js +108 -0
  105. package/ui/controls.js +12 -8
  106. package/ui/externs/ui.js +10 -1
  107. package/ui/less/containers.less +1 -1
  108. package/ui/less/general.less +1 -1
  109. package/ui/less/range_elements.less +1 -0
  110. package/ui/resolution_selection.js +1 -1
  111. package/ui/statistics_button.js +10 -0
  112. package/ui/ui.js +12 -1
  113. package/.github/pull_request_template.md +0 -10
  114. package/docs/tutorials/gsoc-ideas-list.md +0 -3
@@ -539,11 +539,13 @@ describe('HlsParser live', () => {
539
539
  ManifestParser.verifySegmentIndex(video, [ref1, ref2]);
540
540
  });
541
541
 
542
+ // Test for https://github.com/shaka-project/shaka-player/issues/4223
542
543
  it('parses streams with partial and preload hinted segments', async () => {
543
544
  playerInterface.isLowLatencyMode = () => true;
544
545
  const mediaWithPartialSegments = [
545
546
  '#EXTM3U\n',
546
547
  '#EXT-X-TARGETDURATION:5\n',
548
+ '#EXT-X-PART-INF:PART-TARGET=1.5\n',
547
549
  '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
548
550
  '#EXT-X-MEDIA-SEQUENCE:0\n',
549
551
  // ref includes partialRef, partialRef2
@@ -576,26 +578,73 @@ describe('HlsParser live', () => {
576
578
  'test:/partial2.mp4', 2, 4, /* syncTime= */ null,
577
579
  /* baseUri= */ '', /* startByte= */ 200, /* endByte= */ 429);
578
580
 
581
+ const ref = makeReference(
582
+ 'test:/main.mp4', 0, 4, /* syncTime= */ null,
583
+ /* baseUri= */ '', /* startByte= */ 0, /* endByte= */ 429,
584
+ /* timestampOffset= */ 0, [partialRef, partialRef2]);
585
+
579
586
  const partialRef3 = makeReference(
580
587
  'test:/partial.mp4', 4, 6, /* syncTime= */ null,
581
588
  /* baseUri= */ '', /* startByte= */ 0, /* endByte= */ 209);
582
589
 
583
- // A preload hinted partial segment doesn't have duration information,
584
- // so its startTime and endTime are the same.
585
590
  const preloadRef = makeReference(
586
- 'test:/partial.mp4', 6, 6, /* syncTime= */ null,
591
+ 'test:/partial.mp4', 6, 7.5, /* syncTime= */ null,
587
592
  /* baseUri= */ '', /* startByte= */ 210, /* endByte= */ null);
588
593
 
594
+ // ref2 is not fully published yet, so it doesn't have a segment uri.
595
+ const ref2 = makeReference(
596
+ '', 4, 7.5, /* syncTime= */ null,
597
+ /* baseUri= */ '', /* startByte= */ 0, /* endByte= */ null,
598
+ /* timestampOffset= */ 0, [partialRef3, preloadRef]);
599
+
600
+ const manifest = await parser.start('test:/master', playerInterface);
601
+ const video = manifest.variants[0].video;
602
+ await video.createSegmentIndex();
603
+ ManifestParser.verifySegmentIndex(video, [ref, ref2]);
604
+ });
605
+
606
+ // Test for https://github.com/shaka-project/shaka-player/issues/4223
607
+ it('ignores preload hinted segments without target duration', async () => {
608
+ playerInterface.isLowLatencyMode = () => true;
609
+
610
+ // Missing PART-TARGET, so preload hints are skipped.
611
+ const mediaWithPartialSegments = [
612
+ '#EXTM3U\n',
613
+ '#EXT-X-TARGETDURATION:5\n',
614
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
615
+ '#EXT-X-MEDIA-SEQUENCE:0\n',
616
+ '#EXTINF:4,\n',
617
+ // ref1
618
+ 'main.mp4\n',
619
+ // ref2 includes partialRef, but not preloadRef
620
+ // partialRef
621
+ '#EXT-X-PART:DURATION=2,URI="partial.mp4",BYTERANGE=210@0\n',
622
+ // preloadRef
623
+ '#EXT-X-PRELOAD-HINT:TYPE=PART,URI="partial.mp4",BYTERANGE-START=210\n',
624
+ ].join('');
625
+
626
+ fakeNetEngine
627
+ .setResponseText('test:/master', master)
628
+ .setResponseText('test:/video', mediaWithPartialSegments)
629
+ .setResponseValue('test:/init.mp4', initSegmentData)
630
+ .setResponseValue('test:/main.mp4', segmentData)
631
+ .setResponseValue('test:/partial.mp4', segmentData)
632
+ .setResponseValue('test:/partial2.mp4', segmentData);
633
+
589
634
  const ref = makeReference(
590
635
  'test:/main.mp4', 0, 4, /* syncTime= */ null,
591
- /* baseUri= */ '', /* startByte= */ 0, /* endByte= */ 429,
592
- /* timestampOffset= */ 0, [partialRef, partialRef2]);
636
+ /* baseUri= */ '', /* startByte= */ 0, /* endByte= */ null,
637
+ /* timestampOffset= */ 0, []);
638
+
639
+ const partialRef = makeReference(
640
+ 'test:/partial.mp4', 4, 6, /* syncTime= */ null,
641
+ /* baseUri= */ '', /* startByte= */ 0, /* endByte= */ 209);
593
642
 
594
643
  // ref2 is not fully published yet, so it doesn't have a segment uri.
595
644
  const ref2 = makeReference(
596
645
  '', 4, 6, /* syncTime= */ null,
597
- /* baseUri= */ '', /* startByte= */ 0, /* endByte= */ null,
598
- /* timestampOffset= */ 0, [partialRef3, preloadRef]);
646
+ /* baseUri= */ '', /* startByte= */ 0, /* endByte= */ 209,
647
+ /* timestampOffset= */ 0, [partialRef]);
599
648
 
600
649
  const manifest = await parser.start('test:/master', playerInterface);
601
650
  const video = manifest.variants[0].video;
@@ -603,6 +652,29 @@ describe('HlsParser live', () => {
603
652
  ManifestParser.verifySegmentIndex(video, [ref, ref2]);
604
653
  });
605
654
 
655
+ // Test for https://github.com/shaka-project/shaka-player/issues/4185
656
+ it('does not fail on preload hints with LL mode off', async () => {
657
+ // LL mode must be off for this test!
658
+ playerInterface.isLowLatencyMode = () => false;
659
+
660
+ const mediaWithPartialSegments = [
661
+ '#EXTM3U\n',
662
+ '#EXT-X-TARGETDURATION:5\n',
663
+ '#EXT-X-PART-INF:PART-TARGET=1.5\n',
664
+ '#EXTINF:4,\n',
665
+ 'main.mp4\n',
666
+ '#EXT-X-PART:DURATION=2,URI="partial.mp4",BYTERANGE=210@0\n',
667
+ '#EXT-X-PRELOAD-HINT:TYPE=PART,URI="partial.mp4",BYTERANGE-START=210\n',
668
+ ].join('');
669
+
670
+ fakeNetEngine
671
+ .setResponseText('test:/master', master)
672
+ .setResponseText('test:/video', mediaWithPartialSegments);
673
+
674
+ // If this throws, the test fails. Otherwise, it passes.
675
+ await parser.start('test:/master', playerInterface);
676
+ });
677
+
606
678
  describe('update', () => {
607
679
  it('adds new segments when they appear', async () => {
608
680
  const ref1 = makeReference(
@@ -238,6 +238,25 @@ describe('ManifestTextParser', () => {
238
238
  '#EXTM3U\n' +
239
239
  '#EXT-X-MEDIA:CODECS="avc1.64002a,mp4a.40.2",AUDIO="a1,a2"',
240
240
 
241
+ // manifest URI:
242
+ 'https://test/manifest.m3u8');
243
+
244
+ verifyPlaylist(
245
+ {
246
+ type: shaka.hls.PlaylistType.MASTER,
247
+ tags: [
248
+ new shaka.hls.Tag(/* id= */ 3, 'EXT-X-MEDIA',
249
+ [
250
+ new shaka.hls.Attribute('CODECS',
251
+ 'av01.0.08M.08,mp4a.40.2'),
252
+ ]),
253
+ ],
254
+ },
255
+
256
+ // playlist text:
257
+ '#EXTM3U\n' +
258
+ '#EXT-X-MEDIA:CODECS="av01.0.08M.08,mp4a.40.2"',
259
+
241
260
  // manifest URI:
242
261
  'https://test/manifest.m3u8');
243
262
  });
@@ -179,6 +179,7 @@ describe('AdaptationSet', () => {
179
179
  audio: audio,
180
180
  bandwidth: 1024,
181
181
  id: id,
182
+ disabledUntilTime: 0,
182
183
  language: '',
183
184
  primary: false,
184
185
  video: video,
@@ -21,12 +21,15 @@ let TimeRange;
21
21
  * start: number,
22
22
  * waitingAt: number,
23
23
  * expectedEndTime: number,
24
+ * expectEvent: boolean,
24
25
  * }}
25
26
  *
26
27
  * @description
27
28
  * Parameters for a test where we start playing inside a buffered range and play
28
29
  * until the end of the buffer. Then, if we expect it, Playhead should jump
29
- * to the expected time.
30
+ * to the expected time. We should get a 'stalldetected' event when the Playhead
31
+ * detects a stall through the StallDetector, and a 'gapjumped' event when the
32
+ * Playhead jumps over a gap in the buffered range(s).
30
33
  *
31
34
  * @property {!Array.<TimeRange>} buffered
32
35
  * The buffered ranges for the test.
@@ -36,6 +39,9 @@ let TimeRange;
36
39
  * The time to pause at and fire a 'waiting' event.
37
40
  * @property {number} expectedEndTime
38
41
  * The expected time at the end of the test.
42
+ * @property {boolean} expectEvent
43
+ * If true, expect either the 'stalldetected' or 'gapjumped' event to be
44
+ * fired.
39
45
  */
40
46
  let PlayingTestInfo;
41
47
 
@@ -47,6 +53,7 @@ let PlayingTestInfo;
47
53
  * start: number,
48
54
  * seekTo: number,
49
55
  * expectedEndTime: number,
56
+ * expectEvent: boolean,
50
57
  * }}
51
58
  *
52
59
  * @description
@@ -65,6 +72,9 @@ let PlayingTestInfo;
65
72
  * The time to seek to.
66
73
  * @property {number} expectedEndTime
67
74
  * The expected time at the end of the test.
75
+ * @property {boolean} expectEvent
76
+ * If true, expect either the 'stalldetected' or 'gapjumped' event to be
77
+ * fired.
68
78
  */
69
79
  let SeekTestInfo;
70
80
 
@@ -87,6 +97,10 @@ describe('Playhead', () => {
87
97
  /** @type {!jasmine.Spy} */
88
98
  let onSeek;
89
99
 
100
+ // Callback to us from Playhead when an event should be sent to the app.
101
+ /** @type {!jasmine.Spy} */
102
+ let onEvent;
103
+
90
104
  beforeAll(() => {
91
105
  jasmine.clock().install();
92
106
  });
@@ -100,6 +114,7 @@ describe('Playhead', () => {
100
114
  timeline = new shaka.test.FakePresentationTimeline();
101
115
 
102
116
  onSeek = jasmine.createSpy('onSeek');
117
+ onEvent = jasmine.createSpy('onEvent');
103
118
 
104
119
  timeline.isLive.and.returnValue(false);
105
120
  timeline.getSeekRangeStart.and.returnValue(5);
@@ -143,7 +158,8 @@ describe('Playhead', () => {
143
158
  manifest,
144
159
  config,
145
160
  /* startTime= */ 5,
146
- Util.spyFunc(onSeek));
161
+ Util.spyFunc(onSeek),
162
+ Util.spyFunc(onEvent));
147
163
 
148
164
  expect(video.currentTime).toBe(5);
149
165
  expect(playhead.getTime()).toBe(5);
@@ -163,7 +179,8 @@ describe('Playhead', () => {
163
179
  manifest,
164
180
  config,
165
181
  /* startTime= */ 5,
166
- Util.spyFunc(onSeek));
182
+ Util.spyFunc(onSeek),
183
+ Util.spyFunc(onEvent));
167
184
 
168
185
  expect(video.addEventListener).toHaveBeenCalledWith(
169
186
  'loadedmetadata', jasmine.any(Function), jasmine.anything());
@@ -203,7 +220,8 @@ describe('Playhead', () => {
203
220
  manifest,
204
221
  config,
205
222
  /* startTime= */ 5,
206
- Util.spyFunc(onSeek));
223
+ Util.spyFunc(onSeek),
224
+ Util.spyFunc(onEvent));
207
225
 
208
226
  video.on['seeking']();
209
227
  expect(playhead.getTime()).toBe(5);
@@ -221,7 +239,12 @@ describe('Playhead', () => {
221
239
  timeline.getSeekRangeEnd.and.returnValue(60);
222
240
 
223
241
  playhead = new shaka.media.MediaSourcePlayhead(
224
- video, manifest, config, /* startTime= */ 0, Util.spyFunc(onSeek));
242
+ video,
243
+ manifest,
244
+ config,
245
+ /* startTime= */ 0,
246
+ Util.spyFunc(onSeek),
247
+ Util.spyFunc(onEvent));
225
248
 
226
249
  expect(playhead.getTime()).toBe(0);
227
250
  });
@@ -234,7 +257,12 @@ describe('Playhead', () => {
234
257
  timeline.getDuration.and.returnValue(60);
235
258
 
236
259
  playhead = new shaka.media.MediaSourcePlayhead(
237
- video, manifest, config, /* startTime= */ 60, Util.spyFunc(onSeek));
260
+ video,
261
+ manifest,
262
+ config,
263
+ /* startTime= */ 60,
264
+ Util.spyFunc(onSeek),
265
+ Util.spyFunc(onEvent));
238
266
 
239
267
  expect(playhead.getTime()).toBe(59); // duration - durationBackoff
240
268
  expect(video.currentTime).toBe(59); // duration - durationBackoff
@@ -248,7 +276,12 @@ describe('Playhead', () => {
248
276
  timeline.getSeekRangeEnd.and.returnValue(60);
249
277
 
250
278
  playhead = new shaka.media.MediaSourcePlayhead(
251
- video, manifest, config, /* startTime= */ -15, Util.spyFunc(onSeek));
279
+ video,
280
+ manifest,
281
+ config,
282
+ /* startTime= */ -15,
283
+ Util.spyFunc(onSeek),
284
+ Util.spyFunc(onEvent));
252
285
 
253
286
  expect(playhead.getTime()).toBe(45);
254
287
  });
@@ -262,7 +295,12 @@ describe('Playhead', () => {
262
295
  // If the live stream's playback offset time is not available, start
263
296
  // playing from the seek range start time.
264
297
  playhead = new shaka.media.MediaSourcePlayhead(
265
- video, manifest, config, /* startTime= */ -40, Util.spyFunc(onSeek));
298
+ video,
299
+ manifest,
300
+ config,
301
+ /* startTime= */ -40,
302
+ Util.spyFunc(onSeek),
303
+ Util.spyFunc(onEvent));
266
304
 
267
305
  expect(playhead.getTime()).toBe(30);
268
306
  });
@@ -273,7 +311,8 @@ describe('Playhead', () => {
273
311
  manifest,
274
312
  config,
275
313
  /* startTime= */ 5,
276
- Util.spyFunc(onSeek));
314
+ Util.spyFunc(onSeek),
315
+ Util.spyFunc(onEvent));
277
316
 
278
317
  expect(video.addEventListener).toHaveBeenCalledWith(
279
318
  'loadedmetadata', jasmine.any(Function), jasmine.anything());
@@ -305,7 +344,8 @@ describe('Playhead', () => {
305
344
  manifest,
306
345
  config,
307
346
  /* startTime= */ null,
308
- Util.spyFunc(onSeek));
347
+ Util.spyFunc(onSeek),
348
+ Util.spyFunc(onEvent));
309
349
 
310
350
  expect(video.addEventListener).toHaveBeenCalledWith(
311
351
  'loadedmetadata', jasmine.any(Function), jasmine.anything());
@@ -337,7 +377,8 @@ describe('Playhead', () => {
337
377
  manifest,
338
378
  config,
339
379
  /* startTime= */ 5,
340
- Util.spyFunc(onSeek));
380
+ Util.spyFunc(onSeek),
381
+ Util.spyFunc(onEvent));
341
382
 
342
383
  // This has to periodically increment the mock date to allow the onSeeking_
343
384
  // handler to seek, if appropriate.
@@ -496,7 +537,8 @@ describe('Playhead', () => {
496
537
  manifest,
497
538
  config,
498
539
  /* startTime= */ 5,
499
- Util.spyFunc(onSeek));
540
+ Util.spyFunc(onSeek),
541
+ Util.spyFunc(onEvent));
500
542
 
501
543
  setMockDate(0);
502
544
  video.on['seeking']();
@@ -544,7 +586,8 @@ describe('Playhead', () => {
544
586
  manifest,
545
587
  config,
546
588
  /* startTime= */ 5,
547
- Util.spyFunc(onSeek));
589
+ Util.spyFunc(onSeek),
590
+ Util.spyFunc(onEvent));
548
591
 
549
592
  // First, seek to start time.
550
593
  video.currentTime = 0;
@@ -604,7 +647,8 @@ describe('Playhead', () => {
604
647
  manifest,
605
648
  config,
606
649
  /* startTime= */ 5,
607
- Util.spyFunc(onSeek));
650
+ Util.spyFunc(onSeek),
651
+ Util.spyFunc(onEvent));
608
652
  expect(currentTime).toBe(1000);
609
653
  seekCount = 0;
610
654
 
@@ -649,7 +693,8 @@ describe('Playhead', () => {
649
693
  manifest,
650
694
  config,
651
695
  /* startTime= */ 5,
652
- Util.spyFunc(onSeek));
696
+ Util.spyFunc(onSeek),
697
+ Util.spyFunc(onEvent));
653
698
 
654
699
  video.on['seeking']();
655
700
  expect(video.currentTime).toBe(5);
@@ -680,7 +725,8 @@ describe('Playhead', () => {
680
725
  manifest,
681
726
  config,
682
727
  /* startTime= */ 5,
683
- Util.spyFunc(onSeek));
728
+ Util.spyFunc(onSeek),
729
+ Util.spyFunc(onEvent));
684
730
 
685
731
  video.on['seeking']();
686
732
  expect(video.currentTime).toBe(5);
@@ -714,7 +760,8 @@ describe('Playhead', () => {
714
760
  manifest,
715
761
  config,
716
762
  /* startTime= */ 30,
717
- Util.spyFunc(onSeek));
763
+ Util.spyFunc(onSeek),
764
+ Util.spyFunc(onEvent));
718
765
 
719
766
  video.currentTime = 0;
720
767
  video.seeking = true;
@@ -759,7 +806,8 @@ describe('Playhead', () => {
759
806
  manifest,
760
807
  config,
761
808
  /* startTime= */ 30,
762
- Util.spyFunc(onSeek));
809
+ Util.spyFunc(onSeek),
810
+ Util.spyFunc(onEvent));
763
811
 
764
812
  /**
765
813
  * Prevent retries on the initial start time seek. This will ensure that
@@ -798,6 +846,7 @@ describe('Playhead', () => {
798
846
  buffered: [{start: 0, end: 10}],
799
847
  start: 3,
800
848
  waitingAt: 10,
849
+ expectEvent: false,
801
850
  expectedEndTime: 10,
802
851
  });
803
852
 
@@ -805,6 +854,7 @@ describe('Playhead', () => {
805
854
  buffered: [{start: 0, end: 10}, {start: 20, end: 30}],
806
855
  start: 24,
807
856
  waitingAt: 30,
857
+ expectEvent: false,
808
858
  expectedEndTime: 30,
809
859
  });
810
860
 
@@ -812,6 +862,7 @@ describe('Playhead', () => {
812
862
  buffered: [{start: 0, end: 10}, {start: 11, end: 20}],
813
863
  start: 5,
814
864
  waitingAt: 10,
865
+ expectEvent: true,
815
866
  expectedEndTime: 11,
816
867
  });
817
868
 
@@ -820,6 +871,7 @@ describe('Playhead', () => {
820
871
  [{start: 0, end: 10}, {start: 11, end: 20}, {start: 21, end: 30}],
821
872
  start: 5,
822
873
  waitingAt: 10,
874
+ expectEvent: true,
823
875
  expectedEndTime: 11,
824
876
  });
825
877
 
@@ -828,6 +880,7 @@ describe('Playhead', () => {
828
880
  [{start: 0, end: 10}, {start: 11, end: 20}, {start: 21, end: 30}],
829
881
  start: 15,
830
882
  waitingAt: 20,
883
+ expectEvent: true,
831
884
  expectedEndTime: 21,
832
885
  });
833
886
  }); // with small gaps
@@ -837,6 +890,7 @@ describe('Playhead', () => {
837
890
  buffered: [{start: 0, end: 10}, {start: 30, end: 40}],
838
891
  start: 5,
839
892
  waitingAt: 10,
893
+ expectEvent: true,
840
894
  expectedEndTime: 30,
841
895
  });
842
896
 
@@ -845,6 +899,7 @@ describe('Playhead', () => {
845
899
  [{start: 0, end: 10}, {start: 30, end: 40}, {start: 50, end: 60}],
846
900
  start: 5,
847
901
  waitingAt: 10,
902
+ expectEvent: true,
848
903
  expectedEndTime: 30,
849
904
  });
850
905
 
@@ -853,6 +908,7 @@ describe('Playhead', () => {
853
908
  [{start: 0, end: 10}, {start: 20, end: 30}, {start: 50, end: 60}],
854
909
  start: 24,
855
910
  waitingAt: 30,
911
+ expectEvent: true,
856
912
  expectedEndTime: 50,
857
913
  });
858
914
  }); // with large gaps
@@ -867,12 +923,15 @@ describe('Playhead', () => {
867
923
  video.currentTime = data.start;
868
924
  video.readyState = HTMLMediaElement.HAVE_ENOUGH_DATA;
869
925
 
926
+ onEvent.and.callFake((event) => {});
927
+
870
928
  playhead = new shaka.media.MediaSourcePlayhead(
871
929
  video,
872
930
  manifest,
873
931
  config,
874
932
  /* startTime= */ data.start,
875
- Util.spyFunc(onSeek));
933
+ Util.spyFunc(onSeek),
934
+ Util.spyFunc(onEvent));
876
935
 
877
936
  jasmine.clock().tick(500);
878
937
  for (let time = data.start; time < data.waitingAt; time++) {
@@ -893,11 +952,14 @@ describe('Playhead', () => {
893
952
  expect(video.currentTime).toBe(time + 0.4);
894
953
  }
895
954
 
955
+ expect(onEvent).not.toHaveBeenCalled();
956
+
896
957
  video.currentTime = data.waitingAt;
897
958
  video.readyState = HTMLMediaElement.HAVE_CURRENT_DATA;
898
959
  video.on['waiting']();
899
960
  jasmine.clock().tick(500);
900
961
 
962
+ expect(onEvent).toHaveBeenCalledTimes(data.expectEvent ? 1 : 0);
901
963
  expect(video.currentTime).toBe(data.expectedEndTime);
902
964
  });
903
965
  }
@@ -909,6 +971,7 @@ describe('Playhead', () => {
909
971
  buffered: [{start: 0, end: 10}],
910
972
  start: 4,
911
973
  seekTo: 14,
974
+ expectEvent: false,
912
975
  expectedEndTime: 14,
913
976
  });
914
977
 
@@ -916,6 +979,7 @@ describe('Playhead', () => {
916
979
  buffered: [{start: 0, end: 10}, {start: 11, end: 20}],
917
980
  start: 3,
918
981
  seekTo: 10.4,
982
+ expectEvent: true,
919
983
  expectedEndTime: 11,
920
984
  });
921
985
 
@@ -924,6 +988,7 @@ describe('Playhead', () => {
924
988
  [{start: 0, end: 10}, {start: 11, end: 20}, {start: 21, end: 30}],
925
989
  start: 3,
926
990
  seekTo: 10.4,
991
+ expectEvent: true,
927
992
  expectedEndTime: 11,
928
993
  });
929
994
 
@@ -932,6 +997,7 @@ describe('Playhead', () => {
932
997
  [{start: 0, end: 10}, {start: 11, end: 20}, {start: 21, end: 30}],
933
998
  start: 3,
934
999
  seekTo: 20.5,
1000
+ expectEvent: true,
935
1001
  expectedEndTime: 21,
936
1002
  });
937
1003
 
@@ -939,6 +1005,7 @@ describe('Playhead', () => {
939
1005
  buffered: [{start: 0, end: 10}, {start: 30, end: 40}],
940
1006
  start: 3,
941
1007
  seekTo: 29.2,
1008
+ expectEvent: true,
942
1009
  expectedEndTime: 30,
943
1010
  });
944
1011
  }); // with small gaps
@@ -948,6 +1015,7 @@ describe('Playhead', () => {
948
1015
  buffered: [{start: 0, end: 10}, {start: 30, end: 40}],
949
1016
  start: 5,
950
1017
  seekTo: 12,
1018
+ expectEvent: true,
951
1019
  expectedEndTime: 30,
952
1020
  });
953
1021
  }); // with large gaps
@@ -961,6 +1029,7 @@ describe('Playhead', () => {
961
1029
  newBuffered: [{start: 20, end: 30}, {start: 31, end: 40}],
962
1030
  start: 3,
963
1031
  seekTo: 22,
1032
+ expectEvent: false,
964
1033
  expectedEndTime: 22,
965
1034
  });
966
1035
 
@@ -971,6 +1040,7 @@ describe('Playhead', () => {
971
1040
  newBuffered: [{start: 0.2, end: 10}],
972
1041
  start: 4,
973
1042
  seekTo: 0,
1043
+ expectEvent: true,
974
1044
  expectedEndTime: 0.2,
975
1045
  });
976
1046
 
@@ -980,6 +1050,7 @@ describe('Playhead', () => {
980
1050
  newBuffered: [{start: 20, end: 30}, {start: 31, end: 40}],
981
1051
  start: 3,
982
1052
  seekTo: 30.2,
1053
+ expectEvent: true,
983
1054
  expectedEndTime: 31,
984
1055
  });
985
1056
 
@@ -989,6 +1060,7 @@ describe('Playhead', () => {
989
1060
  newBuffered: [{start: 20, end: 30}, {start: 31, end: 40}],
990
1061
  start: 3,
991
1062
  seekTo: 30,
1063
+ expectEvent: true,
992
1064
  expectedEndTime: 31,
993
1065
  });
994
1066
 
@@ -998,6 +1070,7 @@ describe('Playhead', () => {
998
1070
  newBuffered: [{start: 20, end: 30}],
999
1071
  start: 3,
1000
1072
  seekTo: 34,
1073
+ expectEvent: false,
1001
1074
  expectedEndTime: 34,
1002
1075
  });
1003
1076
 
@@ -1007,6 +1080,7 @@ describe('Playhead', () => {
1007
1080
  newBuffered: [{start: 0, end: 10}],
1008
1081
  start: 24,
1009
1082
  seekTo: 4,
1083
+ expectEvent: false,
1010
1084
  expectedEndTime: 4,
1011
1085
  });
1012
1086
 
@@ -1017,6 +1091,7 @@ describe('Playhead', () => {
1017
1091
  // should still be waiting.
1018
1092
  start: 24,
1019
1093
  seekTo: 4,
1094
+ expectEvent: false,
1020
1095
  expectedEndTime: 4,
1021
1096
  });
1022
1097
 
@@ -1026,6 +1101,7 @@ describe('Playhead', () => {
1026
1101
  newBuffered: [{start: 2, end: 10}],
1027
1102
  start: 24,
1028
1103
  seekTo: 1.6,
1104
+ expectEvent: true,
1029
1105
  expectedEndTime: 2,
1030
1106
  });
1031
1107
  }); // with small gaps
@@ -1036,6 +1112,7 @@ describe('Playhead', () => {
1036
1112
  newBuffered: [{start: 20, end: 30}],
1037
1113
  start: 25,
1038
1114
  seekTo: 0,
1115
+ expectEvent: true,
1039
1116
  expectedEndTime: 20,
1040
1117
  });
1041
1118
 
@@ -1045,6 +1122,7 @@ describe('Playhead', () => {
1045
1122
  newBuffered: [{start: 20, end: 30}, {start: 40, end: 50}],
1046
1123
  start: 3,
1047
1124
  seekTo: 32,
1125
+ expectEvent: true,
1048
1126
  expectedEndTime: 40,
1049
1127
  });
1050
1128
  }); // with large gaps
@@ -1061,9 +1139,11 @@ describe('Playhead', () => {
1061
1139
  manifest,
1062
1140
  config,
1063
1141
  /* startTime= */ 12,
1064
- Util.spyFunc(onSeek));
1142
+ Util.spyFunc(onSeek),
1143
+ Util.spyFunc(onEvent));
1065
1144
 
1066
1145
  jasmine.clock().tick(500);
1146
+ expect(onEvent).not.toHaveBeenCalled();
1067
1147
 
1068
1148
  // Append a segment before seeking.
1069
1149
  playhead.notifyOfBufferingChange();
@@ -1107,7 +1187,8 @@ describe('Playhead', () => {
1107
1187
  manifest,
1108
1188
  config,
1109
1189
  /* startTime= */ 0,
1110
- Util.spyFunc(onSeek));
1190
+ Util.spyFunc(onSeek),
1191
+ Util.spyFunc(onEvent));
1111
1192
 
1112
1193
  playhead.notifyOfBufferingChange();
1113
1194
  jasmine.clock().tick(500);
@@ -1128,7 +1209,8 @@ describe('Playhead', () => {
1128
1209
  manifest,
1129
1210
  config,
1130
1211
  /* startTime= */ 5,
1131
- Util.spyFunc(onSeek));
1212
+ Util.spyFunc(onSeek),
1213
+ Util.spyFunc(onEvent));
1132
1214
 
1133
1215
  playhead.notifyOfBufferingChange();
1134
1216
  jasmine.clock().tick(500);
@@ -1151,7 +1233,8 @@ describe('Playhead', () => {
1151
1233
  manifest,
1152
1234
  config,
1153
1235
  /* startTime= */ 0,
1154
- Util.spyFunc(onSeek));
1236
+ Util.spyFunc(onSeek),
1237
+ Util.spyFunc(onEvent));
1155
1238
 
1156
1239
  playhead.notifyOfBufferingChange();
1157
1240
  jasmine.clock().tick(500);
@@ -1174,7 +1257,8 @@ describe('Playhead', () => {
1174
1257
  manifest,
1175
1258
  config,
1176
1259
  /* startTime= */ 0,
1177
- Util.spyFunc(onSeek));
1260
+ Util.spyFunc(onSeek),
1261
+ Util.spyFunc(onEvent));
1178
1262
 
1179
1263
  playhead.notifyOfBufferingChange();
1180
1264
  jasmine.clock().tick(500);
@@ -1193,14 +1277,18 @@ describe('Playhead', () => {
1193
1277
  video.currentTime = data.start;
1194
1278
  video.readyState = HTMLMediaElement.HAVE_ENOUGH_DATA;
1195
1279
 
1280
+ onEvent.and.callFake((event) => {});
1281
+
1196
1282
  playhead = new shaka.media.MediaSourcePlayhead(
1197
1283
  video,
1198
1284
  manifest,
1199
1285
  config,
1200
1286
  /* startTime= */ data.start,
1201
- Util.spyFunc(onSeek));
1287
+ Util.spyFunc(onSeek),
1288
+ Util.spyFunc(onEvent));
1202
1289
 
1203
1290
  jasmine.clock().tick(500);
1291
+ expect(onEvent).not.toHaveBeenCalled();
1204
1292
 
1205
1293
  // Seek to the given position and update ready state.
1206
1294
  video.currentTime = data.seekTo;
@@ -1232,6 +1320,7 @@ describe('Playhead', () => {
1232
1320
  jasmine.clock().tick(250);
1233
1321
  }
1234
1322
 
1323
+ expect(onEvent).toHaveBeenCalledTimes(data.expectEvent ? 1 : 0);
1235
1324
  expect(video.currentTime).toBe(data.expectedEndTime);
1236
1325
  });
1237
1326
  }