shaka-player 4.3.8 → 4.3.10

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 (77) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +11 -0
  2. package/.github/workflows/build-and-test.yaml +15 -14
  3. package/.github/workflows/compute-incremental-coverage.py +3 -0
  4. package/.github/workflows/selenium-lab-tests.yaml +48 -12
  5. package/.github/workflows/update-issues.yaml +1 -0
  6. package/CHANGELOG.md +29 -0
  7. package/demo/common/assets.js +59 -25
  8. package/demo/main.js +5 -0
  9. package/dist/demo.compiled.debug.js +15 -13
  10. package/dist/demo.compiled.debug.map +3 -3
  11. package/dist/demo.compiled.js +15 -13
  12. package/dist/demo.compiled.map +3 -3
  13. package/dist/deps.js +1 -1
  14. package/dist/locales.js +33 -33
  15. package/dist/receiver.compiled.debug.js +13 -11
  16. package/dist/receiver.compiled.debug.map +3 -3
  17. package/dist/receiver.compiled.js +13 -11
  18. package/dist/receiver.compiled.map +3 -3
  19. package/dist/shaka-player.compiled.d.ts +2 -2
  20. package/dist/shaka-player.compiled.debug.d.ts +2 -2
  21. package/dist/shaka-player.compiled.debug.js +56 -53
  22. package/dist/shaka-player.compiled.debug.map +3 -3
  23. package/dist/shaka-player.compiled.js +418 -418
  24. package/dist/shaka-player.compiled.map +2 -2
  25. package/dist/shaka-player.ui.d.ts +2 -2
  26. package/dist/shaka-player.ui.debug.d.ts +2 -2
  27. package/dist/shaka-player.ui.debug.js +66 -62
  28. package/dist/shaka-player.ui.debug.map +3 -3
  29. package/dist/shaka-player.ui.js +442 -441
  30. package/dist/shaka-player.ui.map +3 -3
  31. package/docs/tutorials/selenium-grid-config.md +60 -8
  32. package/externs/shaka/manifest.js +5 -1
  33. package/externs/shaka/offline.js +5 -1
  34. package/karma.conf.js +52 -7
  35. package/lib/abr/simple_abr_manager.js +5 -5
  36. package/lib/ads/client_side_ad_manager.js +14 -2
  37. package/lib/dash/dash_parser.js +1 -0
  38. package/lib/hls/hls_parser.js +1 -0
  39. package/lib/media/media_source_engine.js +6 -5
  40. package/lib/media/streaming_engine.js +2 -2
  41. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  42. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  43. package/lib/offline/manifest_converter.js +1 -0
  44. package/lib/offline/storage.js +1 -0
  45. package/lib/player.js +9 -5
  46. package/lib/polyfill/orientation.js +34 -7
  47. package/lib/text/vtt_text_parser.js +1 -0
  48. package/lib/util/manifest_parser_utils.js +4 -2
  49. package/lib/util/periods.js +2 -0
  50. package/lib/util/platform.js +22 -0
  51. package/lib/util/stream_utils.js +69 -26
  52. package/package.json +3 -3
  53. package/test/cast/cast_utils_unit.js +4 -1
  54. package/test/hls/hls_parser_unit.js +72 -0
  55. package/test/media/adaptation_set_unit.js +1 -0
  56. package/test/media/drm_engine_integration.js +2 -2
  57. package/test/media/media_source_engine_unit.js +1 -1
  58. package/test/media/segment_index_unit.js +3 -3
  59. package/test/media/streaming_engine_integration.js +14 -11
  60. package/test/offline/manifest_convert_unit.js +5 -0
  61. package/test/offline/offline_integration.js +6 -3
  62. package/test/player_external.js +19 -16
  63. package/test/player_integration.js +39 -49
  64. package/test/player_src_equals_integration.js +9 -5
  65. package/test/test/util/fake_demo_main.js +7 -3
  66. package/test/test/util/layout_tests.js +2 -2
  67. package/test/test/util/manifest_generator.js +2 -0
  68. package/test/test/util/offline_utils.js +1 -0
  69. package/test/test/util/streaming_engine_util.js +3 -0
  70. package/test/test/util/ui_utils.js +14 -3
  71. package/test/test/util/util.js +6 -27
  72. package/test/test/util/waiter.js +73 -3
  73. package/test/ui/ad_ui_unit.js +9 -9
  74. package/test/util/stream_utils_unit.js +46 -0
  75. package/ui/externs/ui.js +2 -2
  76. package/ui/range_element.js +5 -0
  77. package/ui/seek_bar.js +7 -1
@@ -34,6 +34,9 @@ describe('Player', () => {
34
34
  await shaka.test.TestScheme.createManifests(compiledShaka, '_compiled');
35
35
  player = new compiledShaka.Player(video);
36
36
 
37
+ // Disable stall detection, which can interfere with playback tests.
38
+ player.configure('streaming.stallEnabled', false);
39
+
37
40
  // Grab event manager from the uncompiled library:
38
41
  eventManager = new shaka.util.EventManager();
39
42
  waiter = new shaka.test.Waiter(eventManager);
@@ -81,11 +84,15 @@ describe('Player', () => {
81
84
  // Unlike the other tests in this file, this uses an uncompiled build of
82
85
  // Shaka, so that we don't need to expose shaka.util.Timer.activeTimers.
83
86
  player = new shaka.Player(video);
87
+
88
+ // Disable stall detection, which can interfere with playback tests.
89
+ player.configure('streaming.stallEnabled', false);
90
+
84
91
  waiter.setPlayer(player);
85
92
 
86
93
  // Play the video for a little while.
87
94
  await player.load('test:sintel');
88
- video.play();
95
+ await video.play();
89
96
  await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 1, 10);
90
97
 
91
98
  // Destroy the player.
@@ -127,7 +134,7 @@ describe('Player', () => {
127
134
  }
128
135
  });
129
136
  await player.load('test:sintel_compiled');
130
- video.play();
137
+ await video.play();
131
138
  await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 25, 30);
132
139
  expect(results).toEqual(expected);
133
140
  });
@@ -139,7 +146,7 @@ describe('Player', () => {
139
146
  // This is tested more in player_unit.js. This is here to test the public
140
147
  // API and to check for renaming.
141
148
  await player.load('test:sintel_compiled');
142
- video.play();
149
+ await video.play();
143
150
  await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 1, 10);
144
151
 
145
152
  const stats = player.getStats();
@@ -210,7 +217,7 @@ describe('Player', () => {
210
217
 
211
218
  it('does not cause cues to be null', async () => {
212
219
  await player.load('test:sintel_compiled');
213
- video.play();
220
+ await video.play();
214
221
  await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 1, 10);
215
222
 
216
223
  // This TextTrack was created as part of load() when we set up the
@@ -249,7 +256,7 @@ describe('Player', () => {
249
256
  await player.load('test:sintel_realistic_compiled');
250
257
 
251
258
  // Play until a time at which the external cues would be on screen.
252
- video.play();
259
+ await video.play();
253
260
  await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 4, 20);
254
261
 
255
262
  expect(player.isTextTrackVisible()).toBe(true);
@@ -281,7 +288,7 @@ describe('Player', () => {
281
288
  await waiter.waitForEvent(player, 'texttrackvisibility');
282
289
 
283
290
  // Play until a time at which the external cues would be on screen.
284
- video.play();
291
+ await video.play();
285
292
  await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 4, 20);
286
293
 
287
294
  expect(player.isTextTrackVisible()).toBe(true);
@@ -352,7 +359,7 @@ describe('Player', () => {
352
359
  player.setTextTrackVisibility(true);
353
360
  player.selectTextLanguage('fr');
354
361
  video.currentTime = 5;
355
- video.play();
362
+ await video.play();
356
363
  await waiter.waitForMovementOrFailOnTimeout(video, 10);
357
364
 
358
365
  expect(video.textTracks[0].activeCues.length).toBe(1);
@@ -544,7 +551,7 @@ describe('Player', () => {
544
551
 
545
552
  it('at higher playback rates', async () => {
546
553
  await player.load('test:sintel_compiled');
547
- video.play();
554
+ await video.play();
548
555
  await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 1, 10);
549
556
 
550
557
  // Enabling trick play should change our playback rate to the same rate.
@@ -560,17 +567,18 @@ describe('Player', () => {
560
567
  });
561
568
 
562
569
  it('in sequence mode', async () => {
563
- const testSchemeMimeType = 'application/x-test-manifest';
564
- player = new compiledShaka.Player(video);
565
- await player.load('test:sintel_sequence_compiled', 0, testSchemeMimeType);
570
+ await player.load('test:sintel_sequence_compiled');
566
571
  expect(player.getManifest().sequenceMode).toBe(true);
567
572
 
568
573
  // Ensure the video plays.
569
- video.play();
570
- await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 5, 10);
574
+ await video.play();
575
+ await waiter.timeoutAfter(20).waitUntilPlayheadReaches(video, 5);
571
576
 
572
577
  // Seek the video, and see if it can continue playing from that point.
573
- video.currentTime = 20;
578
+ // If this is a multiple of 10, it tends to flake on Tizen with stall
579
+ // detection disabled. So use 25.
580
+ video.currentTime = 25;
581
+
574
582
  // Expect that we can then reach the end of the video.
575
583
  await waiter.timeoutAfter(40).waitForEnd(video);
576
584
  });
@@ -586,9 +594,8 @@ describe('Player', () => {
586
594
  // even if one is not specified in load().
587
595
  it('immediately after construction with MIME type', async () => {
588
596
  const testSchemeMimeType = 'application/x-test-manifest';
589
- player = new compiledShaka.Player(video);
590
597
  await player.load('test:sintel_compiled', 0, testSchemeMimeType);
591
- video.play();
598
+ await video.play();
592
599
  await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 1, 10);
593
600
  });
594
601
 
@@ -627,7 +634,7 @@ describe('Player', () => {
627
634
  // Regression test for https://github.com/shaka-project/shaka-player/issues/1187
628
635
  it('does not throw on destroy', async () => {
629
636
  await player.load('test:sintel_compiled');
630
- video.play();
637
+ await video.play();
631
638
  await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 1, 10);
632
639
  await player.unload();
633
640
  // Before we fixed #1187, the call to destroy() on textDisplayer was
@@ -873,7 +880,7 @@ describe('Player', () => {
873
880
  player.configure('streaming.observeQualityChanges', true);
874
881
 
875
882
  await player.load('test:sintel_compiled');
876
- video.play();
883
+ await video.play();
877
884
 
878
885
  // Wait for the video to start playback. If it takes longer than 10
879
886
  // seconds, fail the test.
@@ -887,7 +894,7 @@ describe('Player', () => {
887
894
  player.configure('streaming.observeQualityChanges', false);
888
895
 
889
896
  await player.load('test:sintel_compiled');
890
- video.play();
897
+ await video.play();
891
898
 
892
899
  // Wait for the video to start playback. If it takes longer than 10
893
900
  // seconds, fail the test.
@@ -916,7 +923,7 @@ describe('Player', () => {
916
923
 
917
924
  // Play the stream.
918
925
  await player.load('/base/test/test/assets/3675/dash_0.mpd');
919
- video.play();
926
+ await video.play();
920
927
 
921
928
  // Wait for the stream to be over.
922
929
  eventManager.listen(player, 'error', Util.spyFunc(onErrorSpy));
@@ -1004,22 +1011,22 @@ describe('Player', () => {
1004
1011
 
1005
1012
  await player.load('test:sintel_long_compiled');
1006
1013
  video.pause();
1007
- await waitUntilBuffered(10);
1014
+ await waiter.waitUntilBuffered(video, 10);
1008
1015
 
1009
1016
  player.configure('streaming.bufferingGoal', 30);
1010
- await waitUntilBuffered(30);
1017
+ await waiter.waitUntilBuffered(video, 30);
1011
1018
 
1012
1019
  player.configure('streaming.bufferingGoal', 60);
1013
- await waitUntilBuffered(60);
1014
- await Util.delay(0.2);
1020
+ await waiter.waitUntilBuffered(video, 60);
1021
+ await Util.delay(1);
1015
1022
  expect(getBufferedAhead()).toBeLessThan(70); // 60 + segment_size
1016
1023
 
1017
1024
  // We don't remove buffered content ahead of the playhead, so seek to
1018
1025
  // clear the buffer.
1019
1026
  player.configure('streaming.bufferingGoal', 10);
1020
1027
  video.currentTime = 120;
1021
- await waitUntilBuffered(10);
1022
- await Util.delay(0.2);
1028
+ await waiter.waitUntilBuffered(video, 10);
1029
+ await Util.delay(1);
1023
1030
  expect(getBufferedAhead()).toBeLessThan(20); // 10 + segment_size
1024
1031
  });
1025
1032
 
@@ -1029,20 +1036,22 @@ describe('Player', () => {
1029
1036
 
1030
1037
  await player.load('test:sintel_long_compiled');
1031
1038
  video.pause();
1032
- await waitUntilBuffered(30);
1039
+ await waiter.waitUntilBuffered(video, 30);
1033
1040
  video.currentTime = 20;
1034
- await waitUntilBuffered(30);
1041
+ await waiter.waitUntilBuffered(video, 30);
1035
1042
 
1036
1043
  expect(getBufferedBehind()).toBe(20); // Buffered to start still.
1037
1044
  video.currentTime = 50;
1038
- await waitUntilBuffered(30);
1045
+ await waiter.waitUntilBuffered(video, 30);
1046
+ await Util.delay(1);
1039
1047
  expect(getBufferedBehind()).toBeLessThan(40); // 30 + segment_size
1040
1048
 
1041
1049
  player.configure('streaming.bufferBehind', 10);
1042
1050
  // We only evict content when we append a segment, so increase the
1043
1051
  // buffering goal so we append another segment.
1044
1052
  player.configure('streaming.bufferingGoal', 40);
1045
- await waitUntilBuffered(40);
1053
+ await waiter.waitUntilBuffered(video, 40);
1054
+ await Util.delay(1);
1046
1055
  expect(getBufferedBehind()).toBeLessThanOrEqual(10);
1047
1056
  });
1048
1057
 
@@ -1061,25 +1070,6 @@ describe('Player', () => {
1061
1070
  }
1062
1071
  return video.currentTime - start;
1063
1072
  }
1064
-
1065
- async function waitUntilBuffered(amount) {
1066
- for (let i = 0; i < 50; i++) {
1067
- // We buffer from an internal segment, so this shouldn't take long to
1068
- // buffer.
1069
- await Util.delay(0.1); // eslint-disable-line no-await-in-loop
1070
- if (getBufferedAhead() >= amount) {
1071
- return;
1072
- }
1073
- }
1074
-
1075
- const ranges =
1076
- shaka.media.TimeRangesUtils.getBufferedInfo(video.buffered);
1077
- const currentTime = video.currentTime;
1078
- const target = currentTime + amount;
1079
-
1080
- throw new Error('Timeout waiting to buffer! ' +
1081
- JSON.stringify({ranges, currentTime, target}));
1082
- }
1083
1073
  }); // describe('buffering')
1084
1074
 
1085
1075
  describe('configuration', () => {
@@ -27,6 +27,10 @@ describe('Player Src Equals', () => {
27
27
  beforeEach(() => {
28
28
  player = new shaka.Player();
29
29
  player.addEventListener('error', fail);
30
+
31
+ // Disable stall detection, which can interfere with playback tests.
32
+ player.configure('streaming.stallEnabled', false);
33
+
30
34
  eventManager = new shaka.util.EventManager();
31
35
  waiter = new shaka.test.Waiter(eventManager);
32
36
  });
@@ -101,7 +105,7 @@ describe('Player Src Equals', () => {
101
105
  expect(video.duration).not.toBeCloseTo(0);
102
106
 
103
107
  // Start playback and wait for the playhead to move.
104
- video.play();
108
+ await video.play();
105
109
  await waiter.waitForMovementOrFailOnTimeout(video, /* timeout= */10);
106
110
 
107
111
  // Make sure the playhead is roughly where we expect it to be before
@@ -138,7 +142,7 @@ describe('Player Src Equals', () => {
138
142
  await loadWithSrcEquals(SMALL_MP4_CONTENT_URI, /* startTime= */ null);
139
143
 
140
144
  // For playback to begin so that we have some content buffered.
141
- video.play();
145
+ await video.play();
142
146
  await waiter.waitForMovementOrFailOnTimeout(video, /* timeout= */10);
143
147
 
144
148
  const buffered = player.getBufferedInfo();
@@ -162,7 +166,7 @@ describe('Player Src Equals', () => {
162
166
  await loadWithSrcEquals(SMALL_MP4_CONTENT_URI, /* startTime= */ null);
163
167
 
164
168
  // Let playback run for a little.
165
- video.play();
169
+ await video.play();
166
170
  await waiter.waitForMovementOrFailOnTimeout(video, /* timeout= */10);
167
171
 
168
172
  let videoRateChange = false;
@@ -279,7 +283,7 @@ describe('Player Src Equals', () => {
279
283
  expect(video.currentTime).toBeCloseTo(0);
280
284
 
281
285
  // Start playback and wait. We should see the playhead move.
282
- video.play();
286
+ await video.play();
283
287
  await waiter.waitForMovementOrFailOnTimeout(video, /* timeout= */10);
284
288
  await shaka.test.Util.delay(1.5);
285
289
 
@@ -296,7 +300,7 @@ describe('Player Src Equals', () => {
296
300
 
297
301
  // Wait some time for playback to start so that we will have a load latency
298
302
  // value.
299
- video.play();
303
+ await video.play();
300
304
  await waiter.waitForMovementOrFailOnTimeout(video, /* timeout= */10);
301
305
 
302
306
  // Get the stats and check that some stats have been filled in.
@@ -12,9 +12,13 @@
12
12
  */
13
13
  shaka.test.FakeDemoMain = class {
14
14
  constructor() {
15
- this.video = /** @type {!HTMLVideoElement} */ (
16
- document.createElement('video'));
17
- this.player = new shaka.Player(this.video);
15
+ // Using the player's constructor argument to attach a video element seems
16
+ // to cause flaky timeouts on teardown. If a video element is needed in
17
+ // the future, please explicitly call attach(video) and await the result
18
+ // during the test setup.
19
+ /** @type {!shaka.Player} */
20
+ this.player = new shaka.Player();
21
+
18
22
  this.config_ = this.player.getConfiguration();
19
23
  this.selectedAsset = null;
20
24
 
@@ -395,7 +395,7 @@ shaka.test.NativeTextLayoutTests = class extends shaka.test.TextLayoutTests {
395
395
  // The this.video must be played a little now, after the cues were appended,
396
396
  // but before the screenshot.
397
397
  this.video.playbackRate = 1;
398
- this.video.play();
398
+ await this.video.play();
399
399
  await this.waiter.failOnTimeout(false).timeoutAfter(5)
400
400
  .waitForMovement(this.video);
401
401
  this.video.pause();
@@ -404,7 +404,7 @@ shaka.test.NativeTextLayoutTests = class extends shaka.test.TextLayoutTests {
404
404
  this.video.currentTime = time;
405
405
  // Get into a playing state, but without movement.
406
406
  this.video.playbackRate = 0;
407
- this.video.play();
407
+ await this.video.play();
408
408
 
409
409
  // Add a short delay to ensure that the system has caught up and that
410
410
  // native text displayers have been updated by the browser.
@@ -541,6 +541,8 @@ shaka.test.ManifestGenerator.Stream = class {
541
541
  this.hdr = undefined;
542
542
  /** @type {(string|undefined)} */
543
543
  this.tilesLayout = undefined;
544
+ /** @type {boolean} */
545
+ this.external = false;
544
546
  }
545
547
 
546
548
  /** @type {shaka.extern.Stream} */
@@ -56,6 +56,7 @@ shaka.test.OfflineUtils = class {
56
56
  audioSamplingRate: null,
57
57
  spatialAudio: false,
58
58
  closedCaptions: null,
59
+ external: false,
59
60
  };
60
61
  }
61
62
 
@@ -410,6 +410,7 @@ shaka.test.StreamingEngineUtil = class {
410
410
  roles: [],
411
411
  forced: false,
412
412
  spatialAudio: false,
413
+ external: false,
413
414
  };
414
415
  }
415
416
 
@@ -447,6 +448,7 @@ shaka.test.StreamingEngineUtil = class {
447
448
  roles: [],
448
449
  forced: false,
449
450
  spatialAudio: false,
451
+ external: false,
450
452
  };
451
453
  }
452
454
 
@@ -482,6 +484,7 @@ shaka.test.StreamingEngineUtil = class {
482
484
  roles: [],
483
485
  forced: false,
484
486
  spatialAudio: false,
487
+ external: false,
485
488
  };
486
489
  }
487
490
  };
@@ -115,11 +115,18 @@ shaka.test.UiUtils = class {
115
115
  // Destroying the UI destroys the controls and player inside.
116
116
  destroys.push(ui.destroy());
117
117
  }
118
- await Promise.all(destroys);
118
+
119
+ const allDestroyed = Promise.all(destroys);
120
+ // 10 seconds should be more than enough to tear down the UI.
121
+ // Adding this silent timeout fixes several tests that inconsistently hang
122
+ // during teardown and cause failures in afterEach() clauses.
123
+ await Promise.race([allDestroyed, shaka.test.Util.delay(10)]);
119
124
 
120
125
  // Now remove all the containers from the DOM.
121
126
  for (const container of containers) {
122
- container.parentElement.removeChild(container);
127
+ if (container.parentElement) {
128
+ container.parentElement.removeChild(container);
129
+ }
123
130
  }
124
131
  }
125
132
 
@@ -162,7 +169,11 @@ shaka.test.UiUtils = class {
162
169
  const video = /** @type {!HTMLVideoElement} */(document.createElement(
163
170
  'video'));
164
171
 
165
- video.muted = true;
172
+ // Tizen has issues with audio-only playbacks on muted video elements.
173
+ // Don't mute Tizen.
174
+ if (!shaka.util.Platform.isTizen()) {
175
+ video.muted = true;
176
+ }
166
177
  video.width = 600;
167
178
  video.height = 400;
168
179
 
@@ -255,33 +255,12 @@ shaka.test.Util = class {
255
255
  * @param {string} uri
256
256
  * @return {!Promise.<!ArrayBuffer>}
257
257
  */
258
- static fetch(uri) {
259
- return new Promise(((resolve, reject) => {
260
- const xhr = new XMLHttpRequest();
261
- xhr.open('GET', uri, /* asynchronous= */ true);
262
- xhr.responseType = 'arraybuffer';
263
-
264
- xhr.onload = (event) => {
265
- if (xhr.status >= 200 &&
266
- xhr.status <= 299 &&
267
- !!xhr.response) {
268
- resolve(/** @type {!ArrayBuffer} */(xhr.response));
269
- } else {
270
- let message = '';
271
- if (xhr.response) {
272
- message = ': ' + shaka.util.StringUtils.fromUTF8(
273
- /** @type {!ArrayBuffer} */(xhr.response));
274
- }
275
- reject(xhr.status + message);
276
- }
277
- };
278
-
279
- xhr.onerror = (event) => {
280
- reject('shaka.test.Util.fetch failed: ' + uri);
281
- };
282
-
283
- xhr.send(/* body= */ null);
284
- }));
258
+ static async fetch(uri) {
259
+ const response = await fetch(uri);
260
+ if (!response.ok) {
261
+ throw new Error('shaka.test.Util.fetch failed: ' + uri);
262
+ }
263
+ return response.arrayBuffer();
285
264
  }
286
265
 
287
266
  /**
@@ -109,6 +109,8 @@ shaka.test.Waiter = class {
109
109
  * @return {!Promise}
110
110
  */
111
111
  waitUntilPlayheadReaches(mediaElement, timeGoal) {
112
+ this.macPlaybackWorkaround_(mediaElement);
113
+
112
114
  // The name of what we're waiting for
113
115
  const goalName = 'movement from ' + mediaElement.currentTime +
114
116
  ' to ' + timeGoal;
@@ -153,6 +155,8 @@ shaka.test.Waiter = class {
153
155
  * @return {!Promise}
154
156
  */
155
157
  waitForEnd(mediaElement) {
158
+ this.macPlaybackWorkaround_(mediaElement);
159
+
156
160
  // The name of what we're waiting for.
157
161
  const goalName = 'end of media';
158
162
 
@@ -207,13 +211,64 @@ shaka.test.Waiter = class {
207
211
  * Wait for the video to end or for |timeout| seconds to pass, whichever
208
212
  * occurs first. The Promise is resolved when either of these happens.
209
213
  *
210
- * @param {!HTMLMediaElement} target
214
+ * @param {!HTMLMediaElement} mediaElement
211
215
  * @param {number} timeout in seconds, after which the Promise succeeds
212
216
  * @return {!Promise}
213
217
  */
214
- waitForEndOrTimeout(target, timeout) {
218
+ waitForEndOrTimeout(mediaElement, timeout) {
215
219
  this.failOnTimeout(false).timeoutAfter(timeout);
216
- return this.waitForEnd(target);
220
+ return this.waitForEnd(mediaElement);
221
+ }
222
+
223
+ /**
224
+ * Wait until a certain amount of content is buffered.
225
+ *
226
+ * @param {!HTMLMediaElement} mediaElement
227
+ * @param {number} bufferingGoal in seconds, after which the Promise succeeds
228
+ * @return {!Promise}
229
+ */
230
+ waitUntilBuffered(mediaElement, bufferingGoal) {
231
+ // The name of what we're waiting for.
232
+ const goalName = `${bufferingGoal} seconds buffered`;
233
+
234
+ // The conditions for success.
235
+ const bufferedEnough = () => {
236
+ const end = shaka.media.TimeRangesUtils.bufferEnd(mediaElement.buffered);
237
+ if (end == null) {
238
+ return false;
239
+ }
240
+ return end - mediaElement.currentTime >= bufferingGoal;
241
+ };
242
+
243
+ if (bufferedEnough()) {
244
+ return Promise.resolve();
245
+ }
246
+
247
+ // Cleanup on timeout.
248
+ let timer = null;
249
+ const cleanup = () => {
250
+ if (timer) {
251
+ timer.stop();
252
+ }
253
+ this.eventManager_.unlisten(mediaElement, 'timeupdate');
254
+ this.eventManager_.unlisten(mediaElement, 'ended');
255
+ };
256
+
257
+ const p = new Promise((resolve) => {
258
+ const check = () => {
259
+ if (bufferedEnough()) {
260
+ cleanup();
261
+ resolve();
262
+ }
263
+ };
264
+
265
+ timer = new shaka.util.Timer(check);
266
+ timer.tickEvery(/* seconds= */ 1);
267
+ this.eventManager_.listen(mediaElement, 'timeupdate', check);
268
+ this.eventManager_.listen(mediaElement, 'ended', check);
269
+ });
270
+
271
+ return this.waitUntilGeneric_(goalName, p, cleanup, mediaElement);
217
272
  }
218
273
 
219
274
  /**
@@ -333,4 +388,19 @@ shaka.test.Waiter = class {
333
388
  `buffered: ${JSON.stringify(buffered)}\n`;
334
389
  shaka.log.error(error.message);
335
390
  }
391
+
392
+ /**
393
+ * @param {!HTMLMediaElement} mediaElement
394
+ * @private
395
+ */
396
+ macPlaybackWorkaround_(mediaElement) {
397
+ if (shaka.util.Platform.isMac()) {
398
+ // Work around bizarre playback slowdowns that only seem to occur with
399
+ // WebDriver and only on Mac. Increasing the playback rate allows tests
400
+ // to complete without timing out.
401
+ if (mediaElement.playbackRate == 1) {
402
+ mediaElement.playbackRate = 2;
403
+ }
404
+ }
405
+ }
336
406
  };
@@ -25,15 +25,6 @@ describe('Ad UI', () => {
25
25
  shaka.Player.setAdManagerFactory(() => new shaka.test.FakeAdManager());
26
26
  });
27
27
 
28
- afterEach(async () => {
29
- await UiUtils.cleanupUI();
30
- });
31
-
32
- afterAll(() => {
33
- document.head.removeChild(cssLink);
34
- shaka.Player.setAdManagerFactory(() => new shaka.ads.AdManager());
35
- });
36
-
37
28
  beforeEach(() => {
38
29
  container =
39
30
  /** @type {!HTMLElement} */ (document.createElement('div'));
@@ -45,6 +36,15 @@ describe('Ad UI', () => {
45
36
  adManager = video['ui'].getControls().getPlayer().getAdManager();
46
37
  });
47
38
 
39
+ afterEach(async () => {
40
+ await UiUtils.cleanupUI();
41
+ });
42
+
43
+ afterAll(() => {
44
+ document.head.removeChild(cssLink);
45
+ shaka.Player.setAdManagerFactory(() => new shaka.ads.AdManager());
46
+ });
47
+
48
48
  it('is invisible if no ad is playing', () => {
49
49
  const adControlsContainer =
50
50
  UiUtils.getElementByClassName(container, 'shaka-ad-controls');
@@ -718,6 +718,52 @@ describe('StreamUtils', () => {
718
718
  expect(manifest.variants.length).toBe(1);
719
719
  });
720
720
 
721
+ it('supports fLaC codec', async () => {
722
+ if (!MediaSource.isTypeSupported('audio/mp4; codecs="flac"')) {
723
+ pending('Codec fLaC is not supported by the platform.');
724
+ }
725
+ manifest = shaka.test.ManifestGenerator.generate((manifest) => {
726
+ manifest.addVariant(0, (variant) => {
727
+ variant.addAudio(1, (stream) => {
728
+ stream.mime('audio/mp4', 'fLaC');
729
+ });
730
+ });
731
+ manifest.addVariant(2, (variant) => {
732
+ variant.addAudio(3, (stream) => {
733
+ stream.mime('audio/mp4', 'flac');
734
+ });
735
+ });
736
+ });
737
+
738
+ await shaka.util.StreamUtils.filterManifest(
739
+ fakeDrmEngine, /* currentVariant= */ null, manifest);
740
+
741
+ expect(manifest.variants.length).toBe(2);
742
+ });
743
+
744
+ it('supports Opus codec', async () => {
745
+ if (!MediaSource.isTypeSupported('audio/mp4; codecs="opus"')) {
746
+ pending('Codec Opus is not supported by the platform.');
747
+ }
748
+ manifest = shaka.test.ManifestGenerator.generate((manifest) => {
749
+ manifest.addVariant(0, (variant) => {
750
+ variant.addAudio(1, (stream) => {
751
+ stream.mime('audio/mp4', 'Opus');
752
+ });
753
+ });
754
+ manifest.addVariant(2, (variant) => {
755
+ variant.addAudio(3, (stream) => {
756
+ stream.mime('audio/mp4', 'opus');
757
+ });
758
+ });
759
+ });
760
+
761
+ await shaka.util.StreamUtils.filterManifest(
762
+ fakeDrmEngine, /* currentVariant= */ null, manifest);
763
+
764
+ expect(manifest.variants.length).toBe(2);
765
+ });
766
+
721
767
  it('supports legacy AVC1 codec', async () => {
722
768
  if (!MediaSource.isTypeSupported('video/mp4; codecs="avc1.42001e"')) {
723
769
  pending('Codec avc1.42001e is not supported by the platform.');
package/ui/externs/ui.js CHANGED
@@ -147,8 +147,8 @@ shaka.extern.UIVolumeBarColors;
147
147
  * ROLE means that only the role of the item is shown.
148
148
  * LANGUAGE_ROLE means both language and role are shown, or just language if
149
149
  * there is no role.
150
- * LABEL means the non-standard DASH "label" attribute or the HLS "NAME"
151
- * attribute are shown.
150
+ * LABEL means the non-standard DASH "label" attribute or the standard DASH
151
+ * "Label" element or the HLS "NAME" attribute are shown.
152
152
  * Defaults to LANGUAGE.
153
153
  * @property {number} fadeDelay
154
154
  * The delay (in seconds) before fading out the controls once the user stops
@@ -110,6 +110,11 @@ shaka.ui.RangeElement = class extends shaka.ui.Element {
110
110
  this.onChangeEnd();
111
111
  }
112
112
  });
113
+
114
+ this.eventManager.listen(this.bar, 'contextmenu', (e) => {
115
+ e.preventDefault();
116
+ e.stopPropagation();
117
+ });
113
118
  }
114
119
 
115
120
  /** @override */
package/ui/seek_bar.js CHANGED
@@ -59,7 +59,13 @@ shaka.ui.SeekBar = class extends shaka.ui.RangeElement {
59
59
  * @private {shaka.util.Timer}
60
60
  */
61
61
  this.seekTimer_ = new shaka.util.Timer(() => {
62
- this.video.currentTime = this.getValue();
62
+ let newCurrentTime = this.getValue();
63
+ if (!this.player.isLive()) {
64
+ if (newCurrentTime == this.video.duration) {
65
+ newCurrentTime -= 0.001;
66
+ }
67
+ }
68
+ this.video.currentTime = newCurrentTime;
63
69
  });
64
70
 
65
71