shaka-player 4.2.11 → 4.2.13

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 (73) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +11 -0
  2. package/.github/workflows/appspot.yaml +39 -0
  3. package/.github/workflows/build-and-test.yaml +15 -0
  4. package/.github/workflows/selenium-lab-tests.yaml +3 -2
  5. package/.github/workflows/update-issues.yaml +1 -0
  6. package/CHANGELOG.md +42 -0
  7. package/CONTRIBUTORS +1 -0
  8. package/demo/custom.js +1 -1
  9. package/dist/demo.compiled.debug.js +2 -2
  10. package/dist/demo.compiled.debug.map +2 -2
  11. package/dist/demo.compiled.js +2 -2
  12. package/dist/demo.compiled.map +2 -2
  13. package/dist/deps.js +3 -3
  14. package/dist/locales.js +33 -33
  15. package/dist/shaka-player.compiled.d.ts +22 -0
  16. package/dist/shaka-player.compiled.debug.d.ts +22 -0
  17. package/dist/shaka-player.compiled.debug.js +233 -232
  18. package/dist/shaka-player.compiled.debug.map +3 -3
  19. package/dist/shaka-player.compiled.js +738 -738
  20. package/dist/shaka-player.compiled.map +3 -3
  21. package/dist/shaka-player.ui.d.ts +22 -0
  22. package/dist/shaka-player.ui.debug.d.ts +22 -0
  23. package/dist/shaka-player.ui.debug.js +259 -258
  24. package/dist/shaka-player.ui.debug.map +3 -3
  25. package/dist/shaka-player.ui.js +917 -916
  26. package/dist/shaka-player.ui.map +4 -4
  27. package/docs/tutorials/drm-config.md +9 -6
  28. package/docs/tutorials/selenium-grid-config.md +60 -8
  29. package/externs/ima.js +34 -0
  30. package/karma.conf.js +50 -7
  31. package/lib/abr/simple_abr_manager.js +6 -6
  32. package/lib/cea/mp4_cea_parser.js +30 -14
  33. package/lib/dash/dash_parser.js +18 -3
  34. package/lib/hls/hls_parser.js +13 -2
  35. package/lib/media/drm_engine.js +14 -8
  36. package/lib/media/gap_jumping_controller.js +24 -2
  37. package/lib/media/media_source_engine.js +5 -3
  38. package/lib/media/playhead.js +11 -0
  39. package/lib/media/region_observer.js +19 -1
  40. package/lib/media/streaming_engine.js +7 -3
  41. package/lib/media/video_wrapper.js +6 -1
  42. package/lib/player.js +9 -8
  43. package/lib/polyfill/abort_controller.js +7 -0
  44. package/lib/polyfill/orientation.js +34 -7
  45. package/lib/text/vtt_text_parser.js +3 -1
  46. package/lib/util/manifest_parser_utils.js +5 -3
  47. package/lib/util/mp4_box_parsers.js +8 -3
  48. package/lib/util/platform.js +14 -0
  49. package/lib/util/stream_utils.js +70 -26
  50. package/package.json +2 -3
  51. package/test/cast/cast_utils_unit.js +4 -1
  52. package/test/cea/mp4_cea_parser_unit.js +26 -3
  53. package/test/dash/dash_parser_manifest_unit.js +120 -0
  54. package/test/hls/hls_parser_unit.js +222 -0
  55. package/test/media/drm_engine_integration.js +2 -2
  56. package/test/media/region_observer_unit.js +2 -1
  57. package/test/media/segment_index_unit.js +3 -3
  58. package/test/media/streaming_engine_integration.js +14 -11
  59. package/test/offline/offline_integration.js +6 -3
  60. package/test/player_external.js +19 -16
  61. package/test/player_integration.js +35 -48
  62. package/test/player_src_equals_integration.js +9 -5
  63. package/test/test/assets/multiple-trun-init.mp4 +0 -0
  64. package/test/test/assets/multiple-trun-segment.mp4 +0 -0
  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/ui_utils.js +9 -2
  68. package/test/test/util/util.js +6 -27
  69. package/test/test/util/waiter.js +73 -3
  70. package/test/ui/ad_ui_unit.js +9 -9
  71. package/test/util/stream_utils_unit.js +46 -0
  72. package/ui/range_element.js +5 -0
  73. package/ui/resolution_selection.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,14 +495,12 @@ 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
506
  video.currentTime = 20;
@@ -514,9 +519,8 @@ describe('Player', () => {
514
519
  // even if one is not specified in load().
515
520
  it('immediately after construction with MIME type', async () => {
516
521
  const testSchemeMimeType = 'application/x-test-manifest';
517
- player = new compiledShaka.Player(video);
518
522
  await player.load('test:sintel_compiled', 0, testSchemeMimeType);
519
- video.play();
523
+ await video.play();
520
524
  await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 1, 10);
521
525
  });
522
526
 
@@ -555,7 +559,7 @@ describe('Player', () => {
555
559
  // Regression test for https://github.com/shaka-project/shaka-player/issues/1187
556
560
  it('does not throw on destroy', async () => {
557
561
  await player.load('test:sintel_compiled');
558
- video.play();
562
+ await video.play();
559
563
  await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 1, 10);
560
564
  await player.unload();
561
565
  // Before we fixed #1187, the call to destroy() on textDisplayer was
@@ -801,7 +805,7 @@ describe('Player', () => {
801
805
  player.configure('streaming.observeQualityChanges', true);
802
806
 
803
807
  await player.load('test:sintel_compiled');
804
- video.play();
808
+ await video.play();
805
809
 
806
810
  // Wait for the video to start playback. If it takes longer than 10
807
811
  // seconds, fail the test.
@@ -815,7 +819,7 @@ describe('Player', () => {
815
819
  player.configure('streaming.observeQualityChanges', false);
816
820
 
817
821
  await player.load('test:sintel_compiled');
818
- video.play();
822
+ await video.play();
819
823
 
820
824
  // Wait for the video to start playback. If it takes longer than 10
821
825
  // seconds, fail the test.
@@ -844,7 +848,7 @@ describe('Player', () => {
844
848
 
845
849
  // Play the stream.
846
850
  await player.load('/base/test/test/assets/3675/dash_0.mpd');
847
- video.play();
851
+ await video.play();
848
852
 
849
853
  // Wait for the stream to be over.
850
854
  eventManager.listen(player, 'error', Util.spyFunc(onErrorSpy));
@@ -932,22 +936,22 @@ describe('Player', () => {
932
936
 
933
937
  await player.load('test:sintel_long_compiled');
934
938
  video.pause();
935
- await waitUntilBuffered(10);
939
+ await waiter.waitUntilBuffered(video, 10);
936
940
 
937
941
  player.configure('streaming.bufferingGoal', 30);
938
- await waitUntilBuffered(30);
942
+ await waiter.waitUntilBuffered(video, 30);
939
943
 
940
944
  player.configure('streaming.bufferingGoal', 60);
941
- await waitUntilBuffered(60);
942
- await Util.delay(0.2);
945
+ await waiter.waitUntilBuffered(video, 60);
946
+ await Util.delay(1);
943
947
  expect(getBufferedAhead()).toBeLessThan(70); // 60 + segment_size
944
948
 
945
949
  // We don't remove buffered content ahead of the playhead, so seek to
946
950
  // clear the buffer.
947
951
  player.configure('streaming.bufferingGoal', 10);
948
952
  video.currentTime = 120;
949
- await waitUntilBuffered(10);
950
- await Util.delay(0.2);
953
+ await waiter.waitUntilBuffered(video, 10);
954
+ await Util.delay(1);
951
955
  expect(getBufferedAhead()).toBeLessThan(20); // 10 + segment_size
952
956
  });
953
957
 
@@ -957,20 +961,22 @@ describe('Player', () => {
957
961
 
958
962
  await player.load('test:sintel_long_compiled');
959
963
  video.pause();
960
- await waitUntilBuffered(30);
964
+ await waiter.waitUntilBuffered(video, 30);
961
965
  video.currentTime = 20;
962
- await waitUntilBuffered(30);
966
+ await waiter.waitUntilBuffered(video, 30);
963
967
 
964
968
  expect(getBufferedBehind()).toBe(20); // Buffered to start still.
965
969
  video.currentTime = 50;
966
- await waitUntilBuffered(30);
970
+ await waiter.waitUntilBuffered(video, 30);
971
+ await Util.delay(1);
967
972
  expect(getBufferedBehind()).toBeLessThan(40); // 30 + segment_size
968
973
 
969
974
  player.configure('streaming.bufferBehind', 10);
970
975
  // We only evict content when we append a segment, so increase the
971
976
  // buffering goal so we append another segment.
972
977
  player.configure('streaming.bufferingGoal', 40);
973
- await waitUntilBuffered(40);
978
+ await waiter.waitUntilBuffered(video, 40);
979
+ await Util.delay(1);
974
980
  expect(getBufferedBehind()).toBeLessThanOrEqual(10);
975
981
  });
976
982
 
@@ -989,25 +995,6 @@ describe('Player', () => {
989
995
  }
990
996
  return video.currentTime - start;
991
997
  }
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
998
  }); // describe('buffering')
1012
999
 
1013
1000
  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.
@@ -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
 
@@ -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.');
@@ -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 */
@@ -8,6 +8,7 @@
8
8
  goog.provide('shaka.ui.ResolutionSelection');
9
9
 
10
10
  goog.require('goog.asserts');
11
+ goog.require('shaka.Player');
11
12
  goog.require('shaka.ui.Controls');
12
13
  goog.require('shaka.ui.Enums');
13
14
  goog.require('shaka.ui.Locales');
@@ -67,7 +68,12 @@ shaka.ui.ResolutionSelection = class extends shaka.ui.SettingsMenu {
67
68
  /** @private */
68
69
  updateResolutionSelection_() {
69
70
  /** @type {!Array.<shaka.extern.Track>} */
70
- let tracks = this.player.getVariantTracks();
71
+ let tracks = [];
72
+ // When played with src=, the variant tracks available from
73
+ // player.getVariantTracks() represent languages, not resolutions.
74
+ if (this.player.getLoadMode() != shaka.Player.LoadMode.SRC_EQUALS) {
75
+ tracks = this.player.getVariantTracks();
76
+ }
71
77
 
72
78
  // If there is a selected variant track, then we filter out any tracks in
73
79
  // a different language. Then we use those remaining tracks to display the