shaka-player 4.3.7 → 4.3.9

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 (86) 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 +8 -0
  4. package/.github/workflows/selenium-lab-tests.yaml +3 -2
  5. package/.github/workflows/update-issues.yaml +1 -0
  6. package/CHANGELOG.md +44 -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 +2 -2
  14. package/dist/locales.js +31 -31
  15. package/dist/shaka-player.compiled.d.ts +24 -2
  16. package/dist/shaka-player.compiled.debug.d.ts +24 -2
  17. package/dist/shaka-player.compiled.debug.js +251 -247
  18. package/dist/shaka-player.compiled.debug.map +3 -3
  19. package/dist/shaka-player.compiled.js +796 -793
  20. package/dist/shaka-player.compiled.map +4 -4
  21. package/dist/shaka-player.ui.d.ts +24 -2
  22. package/dist/shaka-player.ui.debug.d.ts +24 -2
  23. package/dist/shaka-player.ui.debug.js +289 -286
  24. package/dist/shaka-player.ui.debug.map +3 -3
  25. package/dist/shaka-player.ui.js +831 -827
  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/externs/shaka/manifest.js +5 -1
  31. package/externs/shaka/offline.js +5 -1
  32. package/karma.conf.js +50 -7
  33. package/lib/abr/simple_abr_manager.js +6 -6
  34. package/lib/cea/mp4_cea_parser.js +24 -18
  35. package/lib/dash/dash_parser.js +19 -3
  36. package/lib/hls/hls_parser.js +24 -4
  37. package/lib/media/drm_engine.js +14 -8
  38. package/lib/media/gap_jumping_controller.js +24 -2
  39. package/lib/media/media_source_engine.js +11 -8
  40. package/lib/media/playhead.js +11 -0
  41. package/lib/media/region_observer.js +19 -1
  42. package/lib/media/streaming_engine.js +41 -21
  43. package/lib/media/video_wrapper.js +6 -1
  44. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  45. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  46. package/lib/offline/manifest_converter.js +1 -0
  47. package/lib/offline/storage.js +1 -0
  48. package/lib/player.js +20 -9
  49. package/lib/polyfill/abort_controller.js +7 -0
  50. package/lib/polyfill/orientation.js +34 -7
  51. package/lib/text/vtt_text_parser.js +3 -1
  52. package/lib/util/manifest_parser_utils.js +5 -3
  53. package/lib/util/periods.js +2 -0
  54. package/lib/util/platform.js +14 -0
  55. package/lib/util/stream_utils.js +70 -26
  56. package/package.json +2 -2
  57. package/test/cast/cast_utils_unit.js +4 -1
  58. package/test/cea/mp4_cea_parser_unit.js +28 -5
  59. package/test/dash/dash_parser_manifest_unit.js +120 -0
  60. package/test/hls/hls_parser_unit.js +216 -0
  61. package/test/media/adaptation_set_unit.js +1 -0
  62. package/test/media/drm_engine_integration.js +2 -2
  63. package/test/media/media_source_engine_unit.js +1 -1
  64. package/test/media/region_observer_unit.js +2 -1
  65. package/test/media/segment_index_unit.js +3 -3
  66. package/test/media/streaming_engine_integration.js +14 -11
  67. package/test/media/streaming_engine_unit.js +26 -2
  68. package/test/offline/manifest_convert_unit.js +5 -0
  69. package/test/offline/offline_integration.js +6 -3
  70. package/test/player_external.js +19 -16
  71. package/test/player_integration.js +35 -48
  72. package/test/player_src_equals_integration.js +9 -5
  73. package/test/test/assets/multiple-trun-init.mp4 +0 -0
  74. package/test/test/assets/multiple-trun-segment.mp4 +0 -0
  75. package/test/test/util/fake_demo_main.js +7 -3
  76. package/test/test/util/layout_tests.js +2 -2
  77. package/test/test/util/manifest_generator.js +2 -0
  78. package/test/test/util/offline_utils.js +1 -0
  79. package/test/test/util/streaming_engine_util.js +3 -0
  80. package/test/test/util/ui_utils.js +9 -2
  81. package/test/test/util/util.js +6 -27
  82. package/test/test/util/waiter.js +73 -3
  83. package/test/ui/ad_ui_unit.js +9 -9
  84. package/test/util/stream_utils_unit.js +46 -0
  85. package/ui/range_element.js +5 -0
  86. package/ui/resolution_selection.js +7 -1
@@ -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
 
@@ -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