shaka-player 3.2.17 → 3.2.19

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 (41) hide show
  1. package/.github/workflows/selenium-lab-tests.yaml +22 -0
  2. package/CHANGELOG.md +27 -0
  3. package/dist/locales.js +28 -28
  4. package/dist/shaka-player.compiled.d.ts +5 -0
  5. package/dist/shaka-player.compiled.debug.d.ts +5 -0
  6. package/dist/shaka-player.compiled.debug.externs.js +4 -0
  7. package/dist/shaka-player.compiled.debug.js +93 -87
  8. package/dist/shaka-player.compiled.debug.map +3 -3
  9. package/dist/shaka-player.compiled.externs.js +4 -0
  10. package/dist/shaka-player.compiled.js +258 -255
  11. package/dist/shaka-player.compiled.map +3 -3
  12. package/dist/shaka-player.ui.d.ts +5 -0
  13. package/dist/shaka-player.ui.debug.d.ts +5 -0
  14. package/dist/shaka-player.ui.debug.externs.js +4 -0
  15. package/dist/shaka-player.ui.debug.js +107 -101
  16. package/dist/shaka-player.ui.debug.map +3 -3
  17. package/dist/shaka-player.ui.externs.js +4 -0
  18. package/dist/shaka-player.ui.js +271 -267
  19. package/dist/shaka-player.ui.map +3 -3
  20. package/externs/shaka/text.js +7 -0
  21. package/lib/dash/segment_template.js +17 -5
  22. package/lib/hls/hls_parser.js +1 -1
  23. package/lib/player.js +7 -1
  24. package/lib/polyfill/media_capabilities.js +1 -0
  25. package/lib/text/cue.js +6 -0
  26. package/lib/text/ui_text_displayer.js +25 -9
  27. package/lib/text/vtt_text_parser.js +256 -91
  28. package/lib/text/web_vtt_generator.js +21 -9
  29. package/lib/util/dom_utils.js +3 -1
  30. package/lib/util/platform.js +8 -0
  31. package/lib/util/stream_utils.js +6 -0
  32. package/package.json +1 -1
  33. package/support.html +1 -0
  34. package/test/dash/dash_parser_manifest_unit.js +0 -36
  35. package/test/player_unit.js +4 -2
  36. package/test/test/util/layout_tests.js +433 -0
  37. package/test/test/util/util.js +0 -120
  38. package/test/{ui → text}/text_displayer_layout_unit.js +59 -235
  39. package/test/text/ui_text_displayer_unit.js +72 -4
  40. package/test/text/vtt_text_parser_unit.js +127 -6
  41. package/test/text/web_vtt_layout_integration.js +528 -0
@@ -248,6 +248,14 @@ shaka.util.Platform = class {
248
248
  return shaka.util.Platform.userAgentContains_('PlayStation 4');
249
249
  }
250
250
 
251
+ /**
252
+ * Check if the current platform is Hisense.
253
+ */
254
+ static isHisense() {
255
+ return shaka.util.Platform.userAgentContains_('Hisense') ||
256
+ shaka.util.Platform.userAgentContains_('VIDAA');
257
+ }
258
+
251
259
  /**
252
260
  * Check if the current platform is Virgin Media device.
253
261
  */
@@ -450,12 +450,16 @@ shaka.util.StreamUtils = class {
450
450
  if (!MediaSource.isTypeSupported(audioFullType)) {
451
451
  return false;
452
452
  }
453
+ // Update the codec string with the (possibly) converted codecs.
454
+ videoCodecs = [videoCodecs, audioCodecs].join(',');
453
455
  }
454
456
  const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
455
457
  video.mimeType, videoCodecs, ContentType.VIDEO);
456
458
  if (!MediaSource.isTypeSupported(fullType)) {
457
459
  return false;
458
460
  }
461
+ // Update the codec string with the (possibly) converted codecs.
462
+ video.codecs = videoCodecs;
459
463
  }
460
464
  const audio = variant.audio;
461
465
  if (audio) {
@@ -466,6 +470,8 @@ shaka.util.StreamUtils = class {
466
470
  if (!MediaSource.isTypeSupported(fullType)) {
467
471
  return false;
468
472
  }
473
+ // Update the codec string with the (possibly) converted codecs.
474
+ audio.codecs = codecs;
469
475
  }
470
476
 
471
477
  // See: https://github.com/shaka-project/shaka-player/issues/3380
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shaka-player",
3
3
  "description": "DASH/EME video player library",
4
- "version": "3.2.17",
4
+ "version": "3.2.19",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
package/support.html CHANGED
@@ -38,6 +38,7 @@
38
38
  }
39
39
 
40
40
  </style>
41
+ <script defer src="node_modules/mux.js/dist/mux.min.js"></script>
41
42
  <script src="dist/shaka-player.compiled.js"></script>
42
43
  <script>
43
44
  function whenLoaded(fn) {
@@ -192,42 +192,6 @@ describe('DashParser Manifest', () => {
192
192
  }));
193
193
  });
194
194
 
195
- it('rejects periods after one without duration', async () => {
196
- const periodContents = [
197
- ' <AdaptationSet mimeType="video/mp4" lang="en" group="1">',
198
- ' <Representation bandwidth="100">',
199
- ' <SegmentTemplate startNumber="1" media="l-$Number$.mp4">',
200
- ' <SegmentTimeline>',
201
- ' <S t="0" d="10" />',
202
- ' </SegmentTimeline>',
203
- ' </SegmentTemplate>',
204
- ' </Representation>',
205
- ' </AdaptationSet>',
206
- ].join('\n');
207
- const template = [
208
- '<MPD mediaPresentationDuration="PT75S">',
209
- ' <Period id="1">',
210
- '%(periodContents)s',
211
- ' </Period>',
212
- ' <Period id="2">',
213
- '%(periodContents)s',
214
- ' </Period>',
215
- '</MPD>',
216
- ].join('\n');
217
- const source = sprintf(template, {periodContents: periodContents});
218
-
219
- fakeNetEngine.setResponseText('dummy://foo', source);
220
- /** @type {shaka.extern.Manifest} */
221
- const manifest = await parser.start('dummy://foo', playerInterface);
222
- const video = manifest.variants[0].video;
223
- await video.createSegmentIndex();
224
-
225
- // The first period has a segment from 0-10.
226
- // With the second period skipping, we should fail to find a segment at 10.
227
- expect(video.segmentIndex.find(0)).not.toBe(null);
228
- expect(video.segmentIndex.find(10)).toBe(null);
229
- });
230
-
231
195
  it('calculates Period times when missing', async () => {
232
196
  const periodContents = [
233
197
  ' <AdaptationSet mimeType="video/mp4" lang="en" group="1">',
@@ -3185,7 +3185,9 @@ describe('Player', () => {
3185
3185
  variant.addAudio(0, (stream) => {
3186
3186
  stream.channelsCount = 6;
3187
3187
  stream.audioSamplingRate = 48000;
3188
- stream.codecs = 'ac-3';
3188
+ // ac-3 is rewritten as ec-3 on Tizen, so for the stability of this
3189
+ // test case, use ec-3.
3190
+ stream.codecs = 'ec-3';
3189
3191
  });
3190
3192
  });
3191
3193
 
@@ -3210,7 +3212,7 @@ describe('Player', () => {
3210
3212
  expect(abrManager.variants.length).toBe(1);
3211
3213
  // It should be the 6-channel variant, based on our preference.
3212
3214
  expect(abrManager.variants[0].audio.channelsCount).toBe(6);
3213
- expect(abrManager.variants[0].audio.codecs).toBe('ac-3');
3215
+ expect(abrManager.variants[0].audio.codecs).toBe('ec-3');
3214
3216
  });
3215
3217
  });
3216
3218
 
@@ -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 */
@@ -325,126 +325,6 @@ shaka.test.Util = class {
325
325
  // https://github.com/shaka-project/closure-compiler/issues/1422
326
326
  return /** @type {Function} */(spy)(...varArgs);
327
327
  }
328
-
329
- /**
330
- * Waits for a particular font to be loaded. Useful in screenshot tests to
331
- * make sure we have consistent results with regard to the web fonts we load
332
- * in the UI.
333
- *
334
- * @param {string} name
335
- * @return {!Promise}
336
- */
337
- static async waitForFont(name) {
338
- await new Promise((resolve, reject) => {
339
- // https://github.com/zachleat/fontfaceonload
340
- // eslint-disable-next-line new-cap
341
- FontFaceOnload(name, {
342
- success: resolve,
343
- error: () => {
344
- reject(new Error('Timeout waiting for font ' + name + ' to load'));
345
- },
346
- timeout: 10 * 1000, // ms
347
- });
348
- });
349
-
350
- // Wait one extra tick to make sure the font rendering on the page has been
351
- // updated. Without this, we saw some rare test flake in Firefox on Mac.
352
- await this.shortDelay();
353
- }
354
-
355
- /**
356
- * Checks with Karma to see if this browser can take a screenshot.
357
- *
358
- * Only WebDriver-connected browsers can take a screenshot, and only Karma
359
- * knows if the browser is connected via WebDriver. So this must be checked
360
- * in Karma via an HTTP request.
361
- *
362
- * @return {!Promise.<boolean>}
363
- */
364
- static async supportsScreenshots() {
365
- // We need our own ID for Karma to look up the WebDriver connection.
366
- // For manually-connected browsers, this ID may not exist. In those cases,
367
- // this method is expected to return false.
368
- const parentUrlParams = window.parent.location.search;
369
-
370
- const buffer = await shaka.test.Util.fetch(
371
- '/screenshot/isSupported' + parentUrlParams);
372
- const json = shaka.util.StringUtils.fromUTF8(buffer);
373
- const ok = /** @type {boolean} */(JSON.parse(json));
374
- return ok;
375
- }
376
-
377
- /**
378
- * Asks Karma to take a screenshot for us via the WebDriver connection and
379
- * compare it to the "official" screenshot for this test and platform. Sets
380
- * an expectation that the new screenshot does not differ from the official
381
- * screenshot more than a fixed threshold.
382
- *
383
- * Only works on browsers connected via WebDriver. Use supportsScreenshots()
384
- * to filter screenshot-dependent tests.
385
- *
386
- * @param {!HTMLElement} element The HTML element to screenshot. Must be
387
- * within the bounds of the viewport.
388
- * @param {string} name An identifier for the screenshot. Use alphanumeric
389
- * plus dash and underscore only.
390
- * @param {number} minSimilarity A minimum similarity score between 0 and 1.
391
- * @return {!Promise}
392
- */
393
- static async checkScreenshot(element, name, minSimilarity=1) {
394
- // Make sure the DOM is up-to-date and layout has settled before continuing.
395
- // Without this delay, or with a shorter delay, we sometimes get missing
396
- // elements in our UITextDisplayer tests on some platforms.
397
- await this.delay(0.1);
398
-
399
- // We need our own ID for Karma to look up the WebDriver connection.
400
- // By this point, we should have passed supportsScreenshots(), so the ID
401
- // should definitely be there.
402
- const parentUrlParams = window.parent.location.search;
403
- goog.asserts.assert(parentUrlParams.includes('id='), 'No ID in URL!');
404
-
405
- // Tests run in an iframe. So we also need the coordinates of that iframe
406
- // within the page, so that the screenshot can be consistently cropped to
407
- // the element we care about.
408
- const iframe = /** @type {HTMLIFrameElement} */(
409
- window.parent.document.getElementById('context'));
410
- const iframeRect = iframe.getBoundingClientRect();
411
- const elementRect = element.getBoundingClientRect();
412
- const x = iframeRect.left + elementRect.left;
413
- const y = iframeRect.top + elementRect.top;
414
- const width = elementRect.width;
415
- const height = elementRect.height;
416
-
417
- // Furthermore, the screenshot may not be at the scale you expect. Measure
418
- // the browser window size in JavaScript and communicate that to Karma, too,
419
- // so it can convert coordinates before cropping. This value, as opposed to
420
- // document.body.getBoundingClientRect(), seems to most accurately reflect
421
- // the size of the screenshot area.
422
- const bodyWidth = window.parent.innerWidth;
423
- const bodyHeight = window.parent.innerHeight;
424
-
425
- // In addition to the id param from the top-level window, pass these
426
- // parameters to the screenshot endpoint in Karma.
427
- const params = {x, y, width, height, bodyWidth, bodyHeight, name};
428
-
429
- let paramsString = '';
430
- for (const k in params) {
431
- paramsString += '&' + k + '=' + params[k];
432
- }
433
-
434
- const buffer = await shaka.test.Util.fetch(
435
- '/screenshot/diff' + parentUrlParams + paramsString);
436
- const json = shaka.util.StringUtils.fromUTF8(buffer);
437
- const similarity = /** @type {number} */(JSON.parse(json));
438
-
439
- // If the minimum similarity is not met, you can review the new screenshot
440
- // and the diff image in the screenshots folder. Look for images that end
441
- // with "-new" and "-diff". (NOTE: The diff is a pixel-wise diff for human
442
- // review, and is not produced with the same structural similarity
443
- // algorithm used to detect changes in the test.) If cropping doesn't work
444
- // right, you can view the full-page screenshot in the image that ends with
445
- // "-full".
446
- expect(similarity).withContext(name).not.toBeLessThan(minSimilarity);
447
- }
448
328
  };
449
329
 
450
330
  /**