shaka-player 4.0.2 → 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 (79) hide show
  1. package/CHANGELOG.md +25 -18
  2. package/CONTRIBUTORS +1 -0
  3. package/demo/common/message_ids.js +1 -0
  4. package/demo/config.js +3 -1
  5. package/demo/locales/en.json +1 -0
  6. package/demo/locales/source.json +4 -0
  7. package/dist/demo.compiled.debug.js +4 -4
  8. package/dist/demo.compiled.debug.map +2 -2
  9. package/dist/demo.compiled.js +4 -4
  10. package/dist/demo.compiled.map +2 -2
  11. package/dist/deps.js +2 -2
  12. package/dist/locales.js +31 -31
  13. package/dist/receiver.compiled.debug.js +2 -2
  14. package/dist/receiver.compiled.debug.map +2 -2
  15. package/dist/receiver.compiled.js +2 -2
  16. package/dist/receiver.compiled.map +2 -2
  17. package/dist/shaka-player.compiled.d.ts +14 -8
  18. package/dist/shaka-player.compiled.debug.d.ts +14 -8
  19. package/dist/shaka-player.compiled.debug.externs.js +10 -2
  20. package/dist/shaka-player.compiled.debug.js +165 -157
  21. package/dist/shaka-player.compiled.debug.map +3 -3
  22. package/dist/shaka-player.compiled.externs.js +10 -2
  23. package/dist/shaka-player.compiled.js +758 -754
  24. package/dist/shaka-player.compiled.map +3 -3
  25. package/dist/shaka-player.ui.d.ts +15 -9
  26. package/dist/shaka-player.ui.debug.d.ts +15 -9
  27. package/dist/shaka-player.ui.debug.externs.js +10 -2
  28. package/dist/shaka-player.ui.debug.js +198 -186
  29. package/dist/shaka-player.ui.debug.map +3 -3
  30. package/dist/shaka-player.ui.externs.js +10 -2
  31. package/dist/shaka-player.ui.js +931 -928
  32. package/dist/shaka-player.ui.map +3 -3
  33. package/docs/design/chromecast.md +3 -1
  34. package/docs/tutorials/drm-config.md +16 -0
  35. package/docs/tutorials/ui.md +26 -0
  36. package/externs/chromecast.js +9 -2
  37. package/externs/shaka/manifest.js +7 -0
  38. package/externs/shaka/player.js +21 -2
  39. package/externs/shaka/text.js +7 -0
  40. package/lib/cast/cast_proxy.js +19 -8
  41. package/lib/cast/cast_receiver.js +3 -3
  42. package/lib/cast/cast_sender.js +12 -2
  43. package/lib/hls/hls_parser.js +12 -1
  44. package/lib/media/drm_engine.js +17 -8
  45. package/lib/media/gap_jumping_controller.js +23 -1
  46. package/lib/media/playhead.js +47 -4
  47. package/lib/media/stall_detector.js +19 -2
  48. package/lib/media/streaming_engine.js +8 -1
  49. package/lib/offline/manifest_converter.js +1 -0
  50. package/lib/player.js +175 -69
  51. package/lib/text/cue.js +6 -0
  52. package/lib/text/ui_text_displayer.js +1 -0
  53. package/lib/text/vtt_text_parser.js +4 -0
  54. package/lib/util/error.js +6 -0
  55. package/lib/util/fake_event.js +39 -0
  56. package/lib/util/player_configuration.js +2 -0
  57. package/lib/util/stats.js +28 -1
  58. package/lib/util/stream_utils.js +7 -0
  59. package/package.json +1 -1
  60. package/test/cast/cast_proxy_unit.js +3 -1
  61. package/test/cast/cast_sender_unit.js +21 -6
  62. package/test/demo/demo_unit.js +2 -1
  63. package/test/media/adaptation_set_unit.js +1 -0
  64. package/test/media/playhead_unit.js +114 -25
  65. package/test/media/stall_detector_unit.js +20 -2
  66. package/test/media/streaming_engine_integration.js +2 -1
  67. package/test/media/streaming_engine_unit.js +1 -0
  68. package/test/player_integration.js +3 -0
  69. package/test/player_unit.js +142 -1
  70. package/test/test/util/manifest_generator.js +2 -0
  71. package/test/test/util/simple_fakes.js +14 -0
  72. package/test/test/util/streaming_engine_util.js +1 -0
  73. package/test/text/vtt_text_parser_unit.js +4 -0
  74. package/test/ui/ui_unit.js +18 -5
  75. package/test/util/stream_utils_unit.js +108 -0
  76. package/ui/controls.js +12 -8
  77. package/ui/externs/ui.js +10 -1
  78. package/ui/statistics_button.js +10 -0
  79. package/ui/ui.js +12 -1
@@ -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
  }
@@ -5,6 +5,8 @@
5
5
  */
6
6
 
7
7
  describe('StallDetector', () => {
8
+ const Util = shaka.test.Util;
9
+
8
10
  /**
9
11
  * @implements {shaka.media.StallDetector.Implementation}
10
12
  * @final
@@ -38,16 +40,21 @@ describe('StallDetector', () => {
38
40
  /** @type {!jasmine.Spy} */
39
41
  let onStall;
40
42
 
43
+ /** @type {!jasmine.Spy} */
44
+ let onEvent;
45
+
41
46
  beforeEach(() => {
42
47
  onStall = jasmine.createSpy('onStall');
48
+ onEvent = jasmine.createSpy('onEvent');
43
49
 
44
50
  implementation = new TestImplementation();
45
51
 
46
52
  detector = new shaka.media.StallDetector(
47
53
  implementation,
48
- /* stallThresholdSeconds= */ 1);
54
+ /* stallThresholdSeconds= */ 1,
55
+ Util.spyFunc(onEvent));
49
56
 
50
- detector.onStall(shaka.test.Util.spyFunc(onStall));
57
+ detector.onStall(Util.spyFunc(onStall));
51
58
  });
52
59
 
53
60
  it('does not call onStall when values changes', () => {
@@ -63,6 +70,7 @@ describe('StallDetector', () => {
63
70
 
64
71
  detector.poll();
65
72
 
73
+ expect(onEvent).not.toHaveBeenCalled();
66
74
  expect(onStall).not.toHaveBeenCalled();
67
75
  });
68
76
 
@@ -74,6 +82,7 @@ describe('StallDetector', () => {
74
82
  implementation.wallSeconds = time;
75
83
 
76
84
  detector.poll();
85
+ expect(onEvent).not.toHaveBeenCalled();
77
86
  expect(onStall).not.toHaveBeenCalled();
78
87
  }
79
88
 
@@ -81,6 +90,7 @@ describe('StallDetector', () => {
81
90
  implementation.wallSeconds = 1;
82
91
 
83
92
  detector.poll();
93
+ expect(onEvent).toHaveBeenCalled();
84
94
  expect(onStall).toHaveBeenCalledOnceMoreWith([
85
95
  /* stalledWith= */ 5,
86
96
  /* stallDurationSeconds= */ 1,
@@ -96,6 +106,7 @@ describe('StallDetector', () => {
96
106
  implementation.wallSeconds = 10;
97
107
  detector.poll();
98
108
 
109
+ expect(onEvent).toHaveBeenCalled();
99
110
  expect(onStall).toHaveBeenCalledOnceMoreWith([
100
111
  /* stalledWith= */ 5,
101
112
  /* stallDurationms= */ 10,
@@ -117,6 +128,7 @@ describe('StallDetector', () => {
117
128
 
118
129
  detector.poll();
119
130
 
131
+ expect(onEvent).not.toHaveBeenCalled();
120
132
  expect(onStall).not.toHaveBeenCalled();
121
133
  });
122
134
 
@@ -135,6 +147,7 @@ describe('StallDetector', () => {
135
147
  implementation.wallSeconds = 10;
136
148
  detector.poll();
137
149
 
150
+ expect(onEvent).not.toHaveBeenCalled();
138
151
  expect(onStall).not.toHaveBeenCalled();
139
152
  });
140
153
 
@@ -143,16 +156,20 @@ describe('StallDetector', () => {
143
156
  implementation.presentationSeconds = 0;
144
157
  implementation.wallSeconds = 0;
145
158
  detector.poll();
159
+ expect(onEvent).not.toHaveBeenCalled();
146
160
  expect(onStall).not.toHaveBeenCalled();
147
161
 
148
162
  implementation.wallSeconds = 10;
149
163
  detector.poll();
164
+ expect(onEvent).toHaveBeenCalled();
150
165
  expect(onStall).toHaveBeenCalled();
166
+ onEvent.calls.reset();
151
167
  onStall.calls.reset();
152
168
 
153
169
  // This is the same stall, should not be called again.
154
170
  implementation.wallSeconds = 20;
155
171
  detector.poll();
172
+ expect(onEvent).not.toHaveBeenCalled();
156
173
  expect(onStall).not.toHaveBeenCalled();
157
174
 
158
175
  // Now that we changed time, we should get another call.
@@ -161,6 +178,7 @@ describe('StallDetector', () => {
161
178
  detector.poll();
162
179
  implementation.wallSeconds = 40;
163
180
  detector.poll();
181
+ expect(onEvent).toHaveBeenCalled();
164
182
  expect(onStall).toHaveBeenCalled();
165
183
  });
166
184
  });
@@ -214,7 +214,8 @@ describe('StreamingEngine', () => {
214
214
  manifest,
215
215
  config,
216
216
  /* startTime= */ null,
217
- onSeek);
217
+ onSeek,
218
+ shaka.test.Util.spyFunc(onEvent));
218
219
  }
219
220
 
220
221
  function setupManifest(
@@ -396,6 +396,7 @@ describe('StreamingEngine', () => {
396
396
  video: /** @type {shaka.extern.Stream} */ (alternateVideoStream),
397
397
  id: 0,
398
398
  language: 'und',
399
+ disabledUntilTime: 0,
399
400
  primary: false,
400
401
  bandwidth: 0,
401
402
  allowedByApplication: true,
@@ -126,6 +126,9 @@ describe('Player', () => {
126
126
  corruptedFrames: jasmine.any(Number),
127
127
  estimatedBandwidth: jasmine.any(Number),
128
128
 
129
+ gapsJumped: jasmine.any(Number),
130
+ stallsDetected: jasmine.any(Number),
131
+
129
132
  completionPercent: jasmine.any(Number),
130
133
  loadLatency: jasmine.any(Number),
131
134
  manifestTimeSeconds: jasmine.any(Number),