shaka-player 4.3.3 → 4.3.5

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 (67) hide show
  1. package/.github/workflows/build-and-test.yaml +4 -4
  2. package/.github/workflows/demo-version-index.yaml +6 -0
  3. package/.github/workflows/selenium-lab-tests.yaml +72 -11
  4. package/.github/workflows/update-screenshots.yaml +142 -0
  5. package/CHANGELOG.md +41 -0
  6. package/demo/common/asset.js +15 -0
  7. package/demo/common/assets.js +4 -2
  8. package/demo/common/message_ids.js +1 -0
  9. package/demo/custom.js +19 -0
  10. package/demo/locales/en.json +1 -0
  11. package/demo/locales/source.json +4 -0
  12. package/demo/main.js +14 -0
  13. package/dist/demo.compiled.debug.js +30 -28
  14. package/dist/demo.compiled.debug.map +3 -3
  15. package/dist/demo.compiled.js +30 -28
  16. package/dist/demo.compiled.map +3 -3
  17. package/dist/deps.js +4 -3
  18. package/dist/locales.js +30 -30
  19. package/dist/receiver.compiled.debug.js +22 -22
  20. package/dist/receiver.compiled.debug.map +2 -2
  21. package/dist/receiver.compiled.js +22 -22
  22. package/dist/receiver.compiled.map +2 -2
  23. package/dist/shaka-player.compiled.d.ts +7 -0
  24. package/dist/shaka-player.compiled.debug.d.ts +7 -0
  25. package/dist/shaka-player.compiled.debug.js +116 -109
  26. package/dist/shaka-player.compiled.debug.map +4 -4
  27. package/dist/shaka-player.compiled.js +825 -821
  28. package/dist/shaka-player.compiled.map +4 -4
  29. package/dist/shaka-player.ui.d.ts +7 -0
  30. package/dist/shaka-player.ui.debug.d.ts +7 -0
  31. package/dist/shaka-player.ui.debug.js +128 -121
  32. package/dist/shaka-player.ui.debug.map +4 -4
  33. package/dist/shaka-player.ui.js +937 -932
  34. package/dist/shaka-player.ui.map +4 -4
  35. package/docs/tutorials/errors.md +94 -0
  36. package/docs/tutorials/fairplay.md +2 -2
  37. package/docs/tutorials/index.json +2 -0
  38. package/docs/tutorials/widevine-service-certs.md +45 -0
  39. package/externs/ima.js +9 -0
  40. package/lib/ads/client_side_ad_manager.js +6 -3
  41. package/lib/dash/segment_template.js +17 -5
  42. package/lib/hls/hls_parser.js +12 -2
  43. package/lib/media/drm_engine.js +4 -2
  44. package/lib/media/transmuxer.js +13 -7
  45. package/lib/media/video_wrapper.js +3 -3
  46. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  47. package/lib/player.js +3 -1
  48. package/lib/polyfill/media_capabilities.js +6 -9
  49. package/lib/polyfill/mediasource.js +24 -2
  50. package/lib/text/simple_text_displayer.js +2 -64
  51. package/lib/text/text_utils.js +88 -0
  52. package/lib/text/ui_text_displayer.js +1 -5
  53. package/lib/text/vtt_text_parser.js +228 -102
  54. package/lib/text/web_vtt_generator.js +2 -64
  55. package/lib/util/mime_utils.js +1 -1
  56. package/lib/util/platform.js +8 -0
  57. package/lib/util/stream_utils.js +26 -14
  58. package/package.json +1 -1
  59. package/support.html +1 -0
  60. package/test/dash/dash_parser_manifest_unit.js +0 -36
  61. package/test/polyfill/media_capabilities_unit.js +21 -22
  62. package/test/test/util/layout_tests.js +433 -0
  63. package/test/test/util/util.js +0 -120
  64. package/test/{ui → text}/text_displayer_layout_unit.js +59 -235
  65. package/test/text/ui_text_displayer_unit.js +2 -4
  66. package/test/text/vtt_text_parser_unit.js +138 -8
  67. package/test/text/web_vtt_layout_integration.js +525 -0
@@ -287,42 +287,6 @@ describe('DashParser Manifest', () => {
287
287
  }));
288
288
  });
289
289
 
290
- it('rejects periods after one without duration', async () => {
291
- const periodContents = [
292
- ' <AdaptationSet mimeType="video/mp4" lang="en" group="1">',
293
- ' <Representation bandwidth="100">',
294
- ' <SegmentTemplate startNumber="1" media="l-$Number$.mp4">',
295
- ' <SegmentTimeline>',
296
- ' <S t="0" d="10" />',
297
- ' </SegmentTimeline>',
298
- ' </SegmentTemplate>',
299
- ' </Representation>',
300
- ' </AdaptationSet>',
301
- ].join('\n');
302
- const template = [
303
- '<MPD mediaPresentationDuration="PT75S">',
304
- ' <Period id="1">',
305
- '%(periodContents)s',
306
- ' </Period>',
307
- ' <Period id="2">',
308
- '%(periodContents)s',
309
- ' </Period>',
310
- '</MPD>',
311
- ].join('\n');
312
- const source = sprintf(template, {periodContents: periodContents});
313
-
314
- fakeNetEngine.setResponseText('dummy://foo', source);
315
- /** @type {shaka.extern.Manifest} */
316
- const manifest = await parser.start('dummy://foo', playerInterface);
317
- const video = manifest.variants[0].video;
318
- await video.createSegmentIndex();
319
-
320
- // The first period has a segment from 0-10.
321
- // With the second period skipping, we should fail to find a segment at 10.
322
- expect(video.segmentIndex.find(0)).not.toBe(null);
323
- expect(video.segmentIndex.find(10)).toBe(null);
324
- });
325
-
326
290
  it('calculates Period times when missing', async () => {
327
291
  const periodContents = [
328
292
  ' <AdaptationSet mimeType="video/mp4" lang="en" group="1">',
@@ -5,7 +5,6 @@
5
5
  */
6
6
 
7
7
  describe('MediaCapabilities', () => {
8
- const Util = shaka.test.Util;
9
8
  const originalCast = window['cast'];
10
9
  const originalVendor = navigator.vendor;
11
10
  const originalUserAgent = navigator.userAgent;
@@ -163,30 +162,30 @@ describe('MediaCapabilities', () => {
163
162
  expect(result.keySystemAccess).toEqual(mockResult);
164
163
  });
165
164
 
166
- it('throws when the cast namespace is not available', async () => {
167
- // Temporarily remove window.cast to trigger error. It's restored after
168
- // every test.
169
- delete window['cast'];
165
+ it('falls back to isTypeSupported() when cast namespace is not available',
166
+ async () => {
167
+ // Temporarily remove window.cast to trigger error. It's restored
168
+ // after every test.
169
+ delete window['cast'];
170
170
 
171
- const isChromecastSpy =
172
- spyOn(shaka['util']['Platform'],
173
- 'isChromecast').and.returnValue(true);
174
- const expected = Util.jasmineError(new shaka.util.Error(
175
- shaka.util.Error.Severity.CRITICAL,
176
- shaka.util.Error.Category.CAST,
177
- shaka.util.Error.Code.CAST_API_UNAVAILABLE));
178
- const isTypeSupportedSpy =
179
- spyOn(window['MediaSource'], 'isTypeSupported').and.returnValue(true);
171
+ const isChromecastSpy =
172
+ spyOn(shaka['util']['Platform'],
173
+ 'isChromecast').and.returnValue(true);
174
+ expect(window['MediaSource']['isTypeSupported']).toBeDefined();
180
175
 
181
- shaka.polyfill.MediaCapabilities.install();
182
- await expectAsync(
183
- navigator.mediaCapabilities.decodingInfo(mockDecodingConfig))
184
- .toBeRejectedWith(expected);
176
+ shaka.polyfill.MediaCapabilities.install();
177
+ await navigator.mediaCapabilities.decodingInfo(mockDecodingConfig);
185
178
 
186
- expect(isTypeSupportedSpy).not.toHaveBeenCalled();
187
- // 1 (during install()) + 1 (for video config check).
188
- expect(isChromecastSpy).toHaveBeenCalledTimes(2);
189
- });
179
+ expect(mockCanDisplayType).not.toHaveBeenCalled();
180
+ // 1 (during install()) + 1 (for video config check).
181
+ expect(isChromecastSpy).toHaveBeenCalledTimes(2);
182
+ // 1 (fallback in canCastDisplayType()) +
183
+ // 1 (mockDecodingConfig.audio).
184
+ expect(supportMap.has(mockDecodingConfig.video.contentType))
185
+ .toBe(true);
186
+ expect(supportMap.has(mockDecodingConfig.audio.contentType))
187
+ .toBe(true);
188
+ });
190
189
 
191
190
  it('falls back to isTypeSupported() when canDisplayType() missing',
192
191
  async () => {
@@ -0,0 +1,433 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2016 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ // These helpers have many async interface methods that only need to do async
8
+ // work in some implementations. Disable the normal eslint requirement that
9
+ // async functions always use await.
10
+ /* eslint-disable require-await */
11
+
12
+
13
+ // A minimum similarity score for screenshots, between 0 and 1.
14
+ const minSimilarity = 0.95;
15
+
16
+ const originalCast = window.chrome && window.chrome.cast;
17
+
18
+ shaka.test.LayoutTests = class {
19
+ /** @param {string} prefix */
20
+ constructor(prefix) {
21
+ /** @type {string} */
22
+ this.prefix = prefix;
23
+ }
24
+
25
+ /**
26
+ * Waits for a particular font to be loaded. Useful in screenshot tests to
27
+ * make sure we have consistent results with regard to the web fonts we load
28
+ * in the UI.
29
+ *
30
+ * @param {string} name
31
+ * @return {!Promise}
32
+ */
33
+ async waitForFont(name) {
34
+ await new Promise((resolve, reject) => {
35
+ // https://github.com/zachleat/fontfaceonload
36
+ // eslint-disable-next-line new-cap
37
+ FontFaceOnload(name, {
38
+ success: resolve,
39
+ error: () => {
40
+ reject(new Error('Timeout waiting for font ' + name + ' to load'));
41
+ },
42
+ timeout: 10 * 1000, // ms
43
+ });
44
+ });
45
+
46
+ // Wait one extra tick to make sure the font rendering on the page has been
47
+ // updated. Without this, we saw some rare test flake in Firefox on Mac.
48
+ await shaka.test.Util.shortDelay();
49
+ }
50
+
51
+ /**
52
+ * Checks with Karma to see if this browser can take a screenshot.
53
+ *
54
+ * Only WebDriver-connected browsers can take a screenshot, and only Karma
55
+ * knows if the browser is connected via WebDriver. So this must be checked
56
+ * in Karma via an HTTP request.
57
+ *
58
+ * @return {!Promise.<boolean>}
59
+ */
60
+ static async supported() {
61
+ // We need our own ID for Karma to look up the WebDriver connection.
62
+ // For manually-connected browsers, this ID may not exist. In those cases,
63
+ // this method is expected to return false.
64
+ const parentUrlParams = window.parent.location.search;
65
+
66
+ const buffer = await shaka.test.Util.fetch(
67
+ '/screenshot/isSupported' + parentUrlParams);
68
+ const json = shaka.util.StringUtils.fromUTF8(buffer);
69
+ const ok = /** @type {boolean} */(JSON.parse(json));
70
+ return ok;
71
+ }
72
+
73
+ /**
74
+ * Asks Karma to take a screenshot for us via the WebDriver connection and
75
+ * compare it to the "official" screenshot for this test and platform. Sets
76
+ * an expectation that the new screenshot does not differ from the official
77
+ * screenshot more than a fixed threshold.
78
+ *
79
+ * Only works on browsers connected via WebDriver. Use supportsScreenshots()
80
+ * to filter screenshot-dependent tests.
81
+ *
82
+ * @param {HTMLElement} element The HTML element to screenshot. Must be
83
+ * within the bounds of the viewport.
84
+ * @param {string} name An identifier for the screenshot. Use alphanumeric
85
+ * plus dash and underscore only.
86
+ * @param {number} minSimilarity A minimum similarity score between 0 and 1.
87
+ * @return {!Promise}
88
+ */
89
+ static async checkScreenshot(element, name, minSimilarity=1) {
90
+ // Make sure the DOM is up-to-date and layout has settled before continuing.
91
+ // Without this delay, or with a shorter delay, we sometimes get missing
92
+ // elements in our UITextDisplayer tests on some platforms.
93
+ await shaka.test.Util.delay(0.1);
94
+
95
+ // We need our own ID for Karma to look up the WebDriver connection.
96
+ // By this point, we should have passed supportsScreenshots(), so the ID
97
+ // should definitely be there.
98
+ const parentUrlParams = window.parent.location.search;
99
+ goog.asserts.assert(parentUrlParams.includes('id='), 'No ID in URL!');
100
+
101
+ // Tests run in an iframe. So we also need the coordinates of that iframe
102
+ // within the page, so that the screenshot can be consistently cropped to
103
+ // the element we care about.
104
+ const iframe = /** @type {HTMLIFrameElement} */(
105
+ window.parent.document.getElementById('context'));
106
+ const iframeRect = iframe.getBoundingClientRect();
107
+ const elementRect = element.getBoundingClientRect();
108
+ const x = iframeRect.left + elementRect.left;
109
+ const y = iframeRect.top + elementRect.top;
110
+ const width = elementRect.width;
111
+ const height = elementRect.height;
112
+
113
+ // Furthermore, the screenshot may not be at the scale you expect. Measure
114
+ // the browser window size in JavaScript and communicate that to Karma, too,
115
+ // so it can convert coordinates before cropping. This value, as opposed to
116
+ // document.body.getBoundingClientRect(), seems to most accurately reflect
117
+ // the size of the screenshot area.
118
+ const bodyWidth = window.parent.innerWidth;
119
+ const bodyHeight = window.parent.innerHeight;
120
+
121
+ // In addition to the id param from the top-level window, pass these
122
+ // parameters to the screenshot endpoint in Karma.
123
+ const params = {x, y, width, height, bodyWidth, bodyHeight, name};
124
+
125
+ let paramsString = '';
126
+ for (const k in params) {
127
+ paramsString += '&' + k + '=' + params[k];
128
+ }
129
+
130
+ const buffer = await shaka.test.Util.fetch(
131
+ '/screenshot/diff' + parentUrlParams + paramsString);
132
+ const json = shaka.util.StringUtils.fromUTF8(buffer);
133
+ const similarity = /** @type {number} */(JSON.parse(json));
134
+
135
+ // If the minimum similarity is not met, you can review the new screenshot
136
+ // and the diff image in the screenshots folder. Look for images that end
137
+ // with "-new" and "-diff". (NOTE: The diff is a pixel-wise diff for human
138
+ // review, and is not produced with the same structural similarity
139
+ // algorithm used to detect changes in the test.) If cropping doesn't work
140
+ // right, you can view the full-page screenshot in the image that ends with
141
+ // "-full".
142
+ expect(similarity).withContext(name).not.toBeLessThan(minSimilarity);
143
+ }
144
+
145
+ /** @param {!HTMLElement} element */
146
+ positionElementForScreenshot(element) {
147
+ // The element we screenshot will be 16:9 and small.
148
+ element.style.width = '320px';
149
+ element.style.height = '180px';
150
+
151
+ // The background is green so we can better see the background color of
152
+ // the text spans within the subtitles, and so it is easier to identify
153
+ // cropping issues.
154
+ element.style.backgroundColor = 'green';
155
+
156
+ // Make sure the element is in the top-left corner of the iframe that
157
+ // contains the tests.
158
+ element.style.top = '0';
159
+ element.style.left = '0';
160
+ element.style.position = 'fixed';
161
+ element.style.margin = '0';
162
+ element.style.padding = '0';
163
+ }
164
+ };
165
+
166
+ shaka.test.TextLayoutTests = class extends shaka.test.LayoutTests {
167
+ /** @param {string} prefix */
168
+ constructor(prefix) {
169
+ super(prefix);
170
+
171
+ /** @type {HTMLElement} */
172
+ this.videoContainer = null;
173
+
174
+ /** @type {shaka.extern.TextDisplayer} */
175
+ this.textDisplayer = null;
176
+ }
177
+
178
+ /** @override */
179
+ static async supported() {
180
+ const baseSupported = await super.supported();
181
+ if (!baseSupported) {
182
+ return false;
183
+ }
184
+
185
+ // Due to a Safari implementation bug, the browser only does the correct
186
+ // thing for a timing edge case on Safari 16+. Skip the tests on earlier
187
+ // versions.
188
+ const safariVersion = shaka.util.Platform.safariVersion();
189
+ if (safariVersion && safariVersion < 16) {
190
+ return false;
191
+ }
192
+
193
+ // Due to updates in the rendering and/or default styles in Chrome, the
194
+ // screenshots for native rendering only match in Chrome 106+.
195
+ const chromeVersion = shaka.util.Platform.chromeVersion();
196
+ if (chromeVersion && chromeVersion < 106) {
197
+ return false;
198
+ }
199
+
200
+ return true;
201
+ }
202
+
203
+ /** @return {!Promise} */
204
+ async beforeAll() {}
205
+
206
+ /** @return {!Promise} */
207
+ async beforeEach() {}
208
+
209
+ recreateTextDisplayer() {}
210
+
211
+ /**
212
+ * @param {number} time
213
+ * @return {!Promise}
214
+ */
215
+ async beforeScreenshot(time) {}
216
+
217
+ /** @return {!Promise} */
218
+ async afterEach() {
219
+ await this.textDisplayer.destroy();
220
+ this.textDisplayer = null;
221
+ }
222
+
223
+ /** @return {!Promise} */
224
+ async afterAll() {}
225
+
226
+ /**
227
+ * @param {string} baseName The base name of the screenshot.
228
+ * @param {number=} time The time to seek to in the screenshot. Defaults to
229
+ * 0.1, when most of our tests will be showing cues (timed 0-1).
230
+ * @return {!Promise}
231
+ */
232
+ async checkScreenshot(baseName, time=0.1) {
233
+ await this.beforeScreenshot(time);
234
+
235
+ return shaka.test.LayoutTests.checkScreenshot(
236
+ /* element= */ this.videoContainer,
237
+ this.prefix + '-' + baseName,
238
+ minSimilarity);
239
+ }
240
+ };
241
+
242
+ shaka.test.DomTextLayoutTests = class extends shaka.test.TextLayoutTests {
243
+ /** @param {string} prefix */
244
+ constructor(prefix) {
245
+ super(prefix);
246
+
247
+ /** @type {HTMLLinkElement} */
248
+ this.cssLink = null;
249
+
250
+ /** @type {shaka.test.FakeVideo} */
251
+ this.mockVideo = null;
252
+ }
253
+
254
+ /** @override */
255
+ async beforeAll() {
256
+ // Disable cast so the UI controls don't create cast sessions.
257
+ if (window.chrome) {
258
+ window.chrome['cast'] = null;
259
+ }
260
+
261
+ // Add css file
262
+ this.cssLink = /** @type {!HTMLLinkElement} */(
263
+ document.createElement('link'));
264
+ await shaka.test.UiUtils.setupCSS(this.cssLink);
265
+
266
+ // There's no actual video inside this container, but subtitles will be
267
+ // positioned within this space.
268
+ this.videoContainer = /** @type {!HTMLElement} */(
269
+ document.createElement('div'));
270
+ document.body.appendChild(this.videoContainer);
271
+
272
+ this.positionElementForScreenshot(this.videoContainer);
273
+
274
+ // Some of the styles in our CSS are only applied within this class. Add
275
+ // this explicitly, since we don't instantiate controls in all of the
276
+ // tests.
277
+ this.videoContainer.classList.add('shaka-video-container');
278
+
279
+ await this.waitForFont('Roboto');
280
+ }
281
+
282
+ /** @override */
283
+ async beforeEach() {
284
+ this.mockVideo = new shaka.test.FakeVideo();
285
+
286
+ this.recreateTextDisplayer();
287
+ }
288
+
289
+ /** @override */
290
+ recreateTextDisplayer() {
291
+ this.textDisplayer = new shaka.text.UITextDisplayer(
292
+ /** @type {!HTMLMediaElement} */(this.mockVideo),
293
+ this.videoContainer);
294
+ this.textDisplayer.setTextVisibility(true);
295
+ }
296
+
297
+ /** @override */
298
+ async beforeScreenshot(time) {
299
+ // Set the faked time.
300
+ this.mockVideo.currentTime = time;
301
+
302
+ // Trigger the display update logic to notice the time change by
303
+ // appending an empty array.
304
+ this.textDisplayer.append([]);
305
+ }
306
+
307
+ /** @override */
308
+ async afterEach() {
309
+ await super.afterEach();
310
+ this.mockVideo = null;
311
+ }
312
+
313
+ /** @override */
314
+ async afterAll() {
315
+ document.body.removeChild(this.videoContainer);
316
+ this.videoContainer = null;
317
+
318
+ document.head.removeChild(this.cssLink);
319
+ this.cssLink = null;
320
+
321
+ if (window.chrome) {
322
+ window.chrome['cast'] = originalCast;
323
+ }
324
+ }
325
+ };
326
+
327
+ shaka.test.NativeTextLayoutTests = class extends shaka.test.TextLayoutTests {
328
+ /** @param {string} prefix */
329
+ constructor(prefix) {
330
+ super(prefix);
331
+
332
+ /** @type {HTMLVideoElement} */
333
+ this.video = null;
334
+
335
+ /** @type {shaka.util.EventManager} */
336
+ this.eventManager = null;
337
+
338
+ /** @type {shaka.test.Waiter} */
339
+ this.waiter = null;
340
+ }
341
+
342
+ /** @override */
343
+ async beforeAll() {
344
+ this.video = shaka.test.UiUtils.createVideoElement();
345
+
346
+ // On some platforms, such as Chrome on Android, we may see a "cast"
347
+ // button overlayed if this isn't set.
348
+ this.video.disableRemotePlayback = true;
349
+
350
+ document.body.appendChild(this.video);
351
+
352
+ this.positionElementForScreenshot(this.video);
353
+
354
+ this.eventManager = new shaka.util.EventManager();
355
+ this.waiter = new shaka.test.Waiter(this.eventManager);
356
+
357
+ const canPlay = this.waiter.failOnTimeout(false).timeoutAfter(10)
358
+ .waitForEvent(this.video, 'canplay');
359
+
360
+ // Video content is required to show native subtitles. This is a small
361
+ // green frame.
362
+ this.video.src = '/base/test/test/assets/green-pixel.mp4';
363
+ await canPlay;
364
+ expect(this.video.duration).toBeGreaterThan(0);
365
+ expect(this.video.videoWidth).toBeGreaterThan(0);
366
+
367
+ // There is no separate container, so assign the video to the base class
368
+ // container. This is where screenshots will be taken by the base class.
369
+ this.videoContainer = this.video;
370
+ }
371
+
372
+ /** @override */
373
+ async beforeEach() {
374
+ this.recreateTextDisplayer();
375
+ }
376
+
377
+ /** @override */
378
+ recreateTextDisplayer() {
379
+ this.textDisplayer = new shaka.text.SimpleTextDisplayer(this.video);
380
+ this.textDisplayer.setTextVisibility(true);
381
+ }
382
+
383
+ /** @override */
384
+ async beforeScreenshot(time) {
385
+ // On Firefox, Safari, and legacy Edge, the video must be played a little
386
+ // _after_ appending cues in order to consistently show subtitles
387
+ // natively on the video element.
388
+
389
+ // Seek to the beginning so that we can reasonably wait for movement
390
+ // after playing below. If somehow the playhead ends up at the end of
391
+ // the video, we should seek back before we play.
392
+ this.video.currentTime = 0;
393
+
394
+ // The this.video must be played a little now, after the cues were appended,
395
+ // but before the screenshot.
396
+ this.video.playbackRate = 1;
397
+ this.video.play();
398
+ await this.waiter.failOnTimeout(false).timeoutAfter(5)
399
+ .waitForMovement(this.video);
400
+ this.video.pause();
401
+
402
+ // Seek to a time when cues should be showing.
403
+ this.video.currentTime = time;
404
+ // Get into a playing state, but without movement.
405
+ this.video.playbackRate = 0;
406
+ this.video.play();
407
+
408
+ // Add a short delay to ensure that the system has caught up and that
409
+ // native text displayers have been updated by the browser.
410
+ await shaka.test.Util.delay(0.1);
411
+ }
412
+
413
+ /** @override */
414
+ async afterEach() {
415
+ await super.afterEach();
416
+ }
417
+
418
+ /** @override */
419
+ async afterAll() {
420
+ document.body.removeChild(this.video);
421
+ this.video = null;
422
+ // The video container and the video are the same in this class.
423
+ this.videoContainer = null;
424
+
425
+ this.eventManager.release();
426
+ this.eventManager = null;
427
+ this.waiter = null;
428
+ }
429
+ };
430
+
431
+ // If we don't re-enable this rule by the end of the file, eslint exits with an
432
+ // error code.
433
+ /* eslint-enable require-await */
@@ -326,126 +326,6 @@ shaka.test.Util = class {
326
326
  // https://github.com/shaka-project/closure-compiler/issues/1422
327
327
  return /** @type {Function} */(spy)(...varArgs);
328
328
  }
329
-
330
- /**
331
- * Waits for a particular font to be loaded. Useful in screenshot tests to
332
- * make sure we have consistent results with regard to the web fonts we load
333
- * in the UI.
334
- *
335
- * @param {string} name
336
- * @return {!Promise}
337
- */
338
- static async waitForFont(name) {
339
- await new Promise((resolve, reject) => {
340
- // https://github.com/zachleat/fontfaceonload
341
- // eslint-disable-next-line new-cap
342
- FontFaceOnload(name, {
343
- success: resolve,
344
- error: () => {
345
- reject(new Error('Timeout waiting for font ' + name + ' to load'));
346
- },
347
- timeout: 10 * 1000, // ms
348
- });
349
- });
350
-
351
- // Wait one extra tick to make sure the font rendering on the page has been
352
- // updated. Without this, we saw some rare test flake in Firefox on Mac.
353
- await this.shortDelay();
354
- }
355
-
356
- /**
357
- * Checks with Karma to see if this browser can take a screenshot.
358
- *
359
- * Only WebDriver-connected browsers can take a screenshot, and only Karma
360
- * knows if the browser is connected via WebDriver. So this must be checked
361
- * in Karma via an HTTP request.
362
- *
363
- * @return {!Promise.<boolean>}
364
- */
365
- static async supportsScreenshots() {
366
- // We need our own ID for Karma to look up the WebDriver connection.
367
- // For manually-connected browsers, this ID may not exist. In those cases,
368
- // this method is expected to return false.
369
- const parentUrlParams = window.parent.location.search;
370
-
371
- const buffer = await shaka.test.Util.fetch(
372
- '/screenshot/isSupported' + parentUrlParams);
373
- const json = shaka.util.StringUtils.fromUTF8(buffer);
374
- const ok = /** @type {boolean} */(JSON.parse(json));
375
- return ok;
376
- }
377
-
378
- /**
379
- * Asks Karma to take a screenshot for us via the WebDriver connection and
380
- * compare it to the "official" screenshot for this test and platform. Sets
381
- * an expectation that the new screenshot does not differ from the official
382
- * screenshot more than a fixed threshold.
383
- *
384
- * Only works on browsers connected via WebDriver. Use supportsScreenshots()
385
- * to filter screenshot-dependent tests.
386
- *
387
- * @param {!HTMLElement} element The HTML element to screenshot. Must be
388
- * within the bounds of the viewport.
389
- * @param {string} name An identifier for the screenshot. Use alphanumeric
390
- * plus dash and underscore only.
391
- * @param {number} minSimilarity A minimum similarity score between 0 and 1.
392
- * @return {!Promise}
393
- */
394
- static async checkScreenshot(element, name, minSimilarity=1) {
395
- // Make sure the DOM is up-to-date and layout has settled before continuing.
396
- // Without this delay, or with a shorter delay, we sometimes get missing
397
- // elements in our UITextDisplayer tests on some platforms.
398
- await this.delay(0.1);
399
-
400
- // We need our own ID for Karma to look up the WebDriver connection.
401
- // By this point, we should have passed supportsScreenshots(), so the ID
402
- // should definitely be there.
403
- const parentUrlParams = window.parent.location.search;
404
- goog.asserts.assert(parentUrlParams.includes('id='), 'No ID in URL!');
405
-
406
- // Tests run in an iframe. So we also need the coordinates of that iframe
407
- // within the page, so that the screenshot can be consistently cropped to
408
- // the element we care about.
409
- const iframe = /** @type {HTMLIFrameElement} */(
410
- window.parent.document.getElementById('context'));
411
- const iframeRect = iframe.getBoundingClientRect();
412
- const elementRect = element.getBoundingClientRect();
413
- const x = iframeRect.left + elementRect.left;
414
- const y = iframeRect.top + elementRect.top;
415
- const width = elementRect.width;
416
- const height = elementRect.height;
417
-
418
- // Furthermore, the screenshot may not be at the scale you expect. Measure
419
- // the browser window size in JavaScript and communicate that to Karma, too,
420
- // so it can convert coordinates before cropping. This value, as opposed to
421
- // document.body.getBoundingClientRect(), seems to most accurately reflect
422
- // the size of the screenshot area.
423
- const bodyWidth = window.parent.innerWidth;
424
- const bodyHeight = window.parent.innerHeight;
425
-
426
- // In addition to the id param from the top-level window, pass these
427
- // parameters to the screenshot endpoint in Karma.
428
- const params = {x, y, width, height, bodyWidth, bodyHeight, name};
429
-
430
- let paramsString = '';
431
- for (const k in params) {
432
- paramsString += '&' + k + '=' + params[k];
433
- }
434
-
435
- const buffer = await shaka.test.Util.fetch(
436
- '/screenshot/diff' + parentUrlParams + paramsString);
437
- const json = shaka.util.StringUtils.fromUTF8(buffer);
438
- const similarity = /** @type {number} */(JSON.parse(json));
439
-
440
- // If the minimum similarity is not met, you can review the new screenshot
441
- // and the diff image in the screenshots folder. Look for images that end
442
- // with "-new" and "-diff". (NOTE: The diff is a pixel-wise diff for human
443
- // review, and is not produced with the same structural similarity
444
- // algorithm used to detect changes in the test.) If cropping doesn't work
445
- // right, you can view the full-page screenshot in the image that ends with
446
- // "-full".
447
- expect(similarity).withContext(name).not.toBeLessThan(minSimilarity);
448
- }
449
329
  };
450
330
 
451
331
  /**