shaka-player 4.2.12 → 4.2.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 (77) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +11 -0
  2. package/.github/workflows/build-and-test.yaml +22 -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 +28 -0
  7. package/demo/common/assets.js +12 -12
  8. package/demo/main.js +5 -0
  9. package/dist/demo.compiled.debug.js +13 -13
  10. package/dist/demo.compiled.debug.map +2 -2
  11. package/dist/demo.compiled.js +13 -13
  12. package/dist/demo.compiled.map +2 -2
  13. package/dist/deps.js +1 -1
  14. package/dist/locales.js +31 -31
  15. package/dist/receiver.compiled.debug.js +8 -8
  16. package/dist/receiver.compiled.debug.map +1 -1
  17. package/dist/receiver.compiled.js +8 -8
  18. package/dist/receiver.compiled.map +1 -1
  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 +51 -49
  22. package/dist/shaka-player.compiled.debug.map +3 -3
  23. package/dist/shaka-player.compiled.js +403 -404
  24. package/dist/shaka-player.compiled.map +3 -3
  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 +60 -59
  28. package/dist/shaka-player.ui.debug.map +3 -3
  29. package/dist/shaka-player.ui.js +411 -411
  30. package/dist/shaka-player.ui.map +2 -2
  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 +4 -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
@@ -331,7 +338,7 @@ describe('Player', () => {
331
338
  await player.load('test:sintel_realistic_compiled');
332
339
 
333
340
  // Play until a time at which the external cues would be on screen.
334
- video.play();
341
+ await video.play();
335
342
  await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 4, 20);
336
343
 
337
344
  expect(player.isTextTrackVisible()).toBe(true);
@@ -363,7 +370,7 @@ describe('Player', () => {
363
370
  await waiter.waitForEvent(player, 'texttrackvisibility');
364
371
 
365
372
  // Play until a time at which the external cues would be on screen.
366
- video.play();
373
+ await video.play();
367
374
  await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 4, 20);
368
375
 
369
376
  expect(player.isTextTrackVisible()).toBe(true);
@@ -434,7 +441,7 @@ describe('Player', () => {
434
441
  player.setTextTrackVisibility(true);
435
442
  player.selectTextLanguage('fr');
436
443
  video.currentTime = 5;
437
- video.play();
444
+ await video.play();
438
445
  await waiter.waitForMovementOrFailOnTimeout(video, 10);
439
446
 
440
447
  expect(video.textTracks[0].activeCues.length).toBe(1);
@@ -472,7 +479,7 @@ describe('Player', () => {
472
479
 
473
480
  it('at higher playback rates', async () => {
474
481
  await player.load('test:sintel_compiled');
475
- video.play();
482
+ await video.play();
476
483
  await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 1, 10);
477
484
 
478
485
  // Enabling trick play should change our playback rate to the same rate.
@@ -488,17 +495,18 @@ describe('Player', () => {
488
495
  });
489
496
 
490
497
  it('in sequence mode', async () => {
491
- const testSchemeMimeType = 'application/x-test-manifest';
492
- player = new compiledShaka.Player(video);
493
- await player.load('test:sintel_sequence_compiled', 0, testSchemeMimeType);
498
+ await player.load('test:sintel_sequence_compiled');
494
499
  expect(player.getManifest().sequenceMode).toBe(true);
495
500
 
496
501
  // Ensure the video plays.
497
- video.play();
498
- await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 5, 10);
502
+ await video.play();
503
+ await waiter.timeoutAfter(20).waitUntilPlayheadReaches(video, 5);
499
504
 
500
505
  // Seek the video, and see if it can continue playing from that point.
501
- video.currentTime = 20;
506
+ // If this is a multiple of 10, it tends to flake on Tizen with stall
507
+ // detection disabled. So use 25.
508
+ video.currentTime = 25;
509
+
502
510
  // Expect that we can then reach the end of the video.
503
511
  await waiter.timeoutAfter(40).waitForEnd(video);
504
512
  });
@@ -514,9 +522,8 @@ describe('Player', () => {
514
522
  // even if one is not specified in load().
515
523
  it('immediately after construction with MIME type', async () => {
516
524
  const testSchemeMimeType = 'application/x-test-manifest';
517
- player = new compiledShaka.Player(video);
518
525
  await player.load('test:sintel_compiled', 0, testSchemeMimeType);
519
- video.play();
526
+ await video.play();
520
527
  await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 1, 10);
521
528
  });
522
529
 
@@ -555,7 +562,7 @@ describe('Player', () => {
555
562
  // Regression test for https://github.com/shaka-project/shaka-player/issues/1187
556
563
  it('does not throw on destroy', async () => {
557
564
  await player.load('test:sintel_compiled');
558
- video.play();
565
+ await video.play();
559
566
  await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 1, 10);
560
567
  await player.unload();
561
568
  // Before we fixed #1187, the call to destroy() on textDisplayer was
@@ -801,7 +808,7 @@ describe('Player', () => {
801
808
  player.configure('streaming.observeQualityChanges', true);
802
809
 
803
810
  await player.load('test:sintel_compiled');
804
- video.play();
811
+ await video.play();
805
812
 
806
813
  // Wait for the video to start playback. If it takes longer than 10
807
814
  // seconds, fail the test.
@@ -815,7 +822,7 @@ describe('Player', () => {
815
822
  player.configure('streaming.observeQualityChanges', false);
816
823
 
817
824
  await player.load('test:sintel_compiled');
818
- video.play();
825
+ await video.play();
819
826
 
820
827
  // Wait for the video to start playback. If it takes longer than 10
821
828
  // seconds, fail the test.
@@ -844,7 +851,7 @@ describe('Player', () => {
844
851
 
845
852
  // Play the stream.
846
853
  await player.load('/base/test/test/assets/3675/dash_0.mpd');
847
- video.play();
854
+ await video.play();
848
855
 
849
856
  // Wait for the stream to be over.
850
857
  eventManager.listen(player, 'error', Util.spyFunc(onErrorSpy));
@@ -932,22 +939,22 @@ describe('Player', () => {
932
939
 
933
940
  await player.load('test:sintel_long_compiled');
934
941
  video.pause();
935
- await waitUntilBuffered(10);
942
+ await waiter.waitUntilBuffered(video, 10);
936
943
 
937
944
  player.configure('streaming.bufferingGoal', 30);
938
- await waitUntilBuffered(30);
945
+ await waiter.waitUntilBuffered(video, 30);
939
946
 
940
947
  player.configure('streaming.bufferingGoal', 60);
941
- await waitUntilBuffered(60);
942
- await Util.delay(0.2);
948
+ await waiter.waitUntilBuffered(video, 60);
949
+ await Util.delay(1);
943
950
  expect(getBufferedAhead()).toBeLessThan(70); // 60 + segment_size
944
951
 
945
952
  // We don't remove buffered content ahead of the playhead, so seek to
946
953
  // clear the buffer.
947
954
  player.configure('streaming.bufferingGoal', 10);
948
955
  video.currentTime = 120;
949
- await waitUntilBuffered(10);
950
- await Util.delay(0.2);
956
+ await waiter.waitUntilBuffered(video, 10);
957
+ await Util.delay(1);
951
958
  expect(getBufferedAhead()).toBeLessThan(20); // 10 + segment_size
952
959
  });
953
960
 
@@ -957,20 +964,22 @@ describe('Player', () => {
957
964
 
958
965
  await player.load('test:sintel_long_compiled');
959
966
  video.pause();
960
- await waitUntilBuffered(30);
967
+ await waiter.waitUntilBuffered(video, 30);
961
968
  video.currentTime = 20;
962
- await waitUntilBuffered(30);
969
+ await waiter.waitUntilBuffered(video, 30);
963
970
 
964
971
  expect(getBufferedBehind()).toBe(20); // Buffered to start still.
965
972
  video.currentTime = 50;
966
- await waitUntilBuffered(30);
973
+ await waiter.waitUntilBuffered(video, 30);
974
+ await Util.delay(1);
967
975
  expect(getBufferedBehind()).toBeLessThan(40); // 30 + segment_size
968
976
 
969
977
  player.configure('streaming.bufferBehind', 10);
970
978
  // We only evict content when we append a segment, so increase the
971
979
  // buffering goal so we append another segment.
972
980
  player.configure('streaming.bufferingGoal', 40);
973
- await waitUntilBuffered(40);
981
+ await waiter.waitUntilBuffered(video, 40);
982
+ await Util.delay(1);
974
983
  expect(getBufferedBehind()).toBeLessThanOrEqual(10);
975
984
  });
976
985
 
@@ -989,25 +998,6 @@ describe('Player', () => {
989
998
  }
990
999
  return video.currentTime - start;
991
1000
  }
992
-
993
- async function waitUntilBuffered(amount) {
994
- for (let i = 0; i < 50; i++) {
995
- // We buffer from an internal segment, so this shouldn't take long to
996
- // buffer.
997
- await Util.delay(0.1); // eslint-disable-line no-await-in-loop
998
- if (getBufferedAhead() >= amount) {
999
- return;
1000
- }
1001
- }
1002
-
1003
- const ranges =
1004
- shaka.media.TimeRangesUtils.getBufferedInfo(video.buffered);
1005
- const currentTime = video.currentTime;
1006
- const target = currentTime + amount;
1007
-
1008
- throw new Error('Timeout waiting to buffer! ' +
1009
- JSON.stringify({ranges, currentTime, target}));
1010
- }
1011
1001
  }); // describe('buffering')
1012
1002
 
1013
1003
  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.
@@ -544,6 +544,8 @@ shaka.test.ManifestGenerator.Stream = class {
544
544
  this.hdr = undefined;
545
545
  /** @type {(string|undefined)} */
546
546
  this.tilesLayout = undefined;
547
+ /** @type {boolean} */
548
+ this.external = false;
547
549
  }
548
550
 
549
551
  /** @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