shaka-player 4.2.6 → 4.2.8
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.
- package/.github/workflows/selenium-lab-tests.yaml +22 -0
- package/CHANGELOG.md +36 -0
- package/dist/deps.js +4 -3
- package/dist/locales.js +30 -30
- package/dist/shaka-player.compiled.d.ts +10 -0
- package/dist/shaka-player.compiled.debug.d.ts +10 -0
- package/dist/shaka-player.compiled.debug.externs.js +12 -0
- package/dist/shaka-player.compiled.debug.js +177 -166
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +12 -0
- package/dist/shaka-player.compiled.js +672 -666
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +10 -0
- package/dist/shaka-player.ui.debug.d.ts +10 -0
- package/dist/shaka-player.ui.debug.externs.js +12 -0
- package/dist/shaka-player.ui.debug.js +189 -179
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +12 -0
- package/dist/shaka-player.ui.js +820 -815
- package/dist/shaka-player.ui.map +4 -4
- package/lib/dash/segment_template.js +17 -5
- package/lib/hls/hls_parser.js +53 -16
- package/lib/media/media_source_engine.js +34 -4
- package/lib/media/segment_index.js +5 -10
- package/lib/media/segment_reference.js +39 -0
- package/lib/media/streaming_engine.js +15 -3
- package/lib/player.js +7 -1
- package/lib/polyfill/media_capabilities.js +4 -1
- package/lib/text/simple_text_displayer.js +2 -64
- package/lib/text/text_utils.js +88 -0
- package/lib/text/ui_text_displayer.js +24 -9
- package/lib/text/vtt_text_parser.js +256 -95
- package/lib/text/web_vtt_generator.js +2 -52
- package/lib/util/dom_utils.js +3 -1
- package/lib/util/platform.js +8 -0
- package/lib/util/stream_utils.js +6 -0
- package/lib/util/timer.js +17 -0
- package/package.json +1 -1
- package/support.html +1 -0
- package/test/dash/dash_parser_manifest_unit.js +0 -36
- package/test/hls/hls_live_unit.js +8 -30
- package/test/hls/hls_parser_unit.js +30 -0
- package/test/media/segment_index_unit.js +28 -0
- package/test/player_integration.js +27 -0
- package/test/player_unit.js +4 -2
- package/test/test/util/layout_tests.js +433 -0
- package/test/test/util/util.js +0 -120
- package/test/{ui → text}/text_displayer_layout_unit.js +59 -235
- package/test/text/ui_text_displayer_unit.js +72 -4
- package/test/text/vtt_text_parser_unit.js +123 -6
- package/test/text/web_vtt_layout_integration.js +528 -0
|
@@ -520,20 +520,24 @@ describe('HlsParser live', () => {
|
|
|
520
520
|
});
|
|
521
521
|
});
|
|
522
522
|
|
|
523
|
-
it('sets
|
|
523
|
+
it('sets discontinuity sequence numbers', async () => {
|
|
524
524
|
const ref1 = makeReference(
|
|
525
525
|
'test:/main.mp4', 0, 2, /* syncTime= */ null,
|
|
526
526
|
/* baseUri= */ '', /* startByte= */ 0, /* endByte= */ null,
|
|
527
527
|
/* timestampOffset= */ 0);
|
|
528
|
+
ref1.discontinuitySequence = 30;
|
|
528
529
|
|
|
529
|
-
// Expect the timestamp offset to be set for the segment after the
|
|
530
|
-
// EXT-X-DISCONTINUITY tag.
|
|
531
530
|
const ref2 = makeReference(
|
|
532
531
|
'test:/main2.mp4', 2, 4, /* syncTime= */ null,
|
|
533
532
|
/* baseUri= */ '', /* startByte= */ 0, /* endByte= */ null,
|
|
534
533
|
/* timestampOffset= */ 0);
|
|
534
|
+
ref2.discontinuitySequence = 31;
|
|
535
535
|
|
|
536
|
-
await testInitialManifest(
|
|
536
|
+
const manifest = await testInitialManifest(
|
|
537
|
+
master, mediaWithDiscontinuity, [ref1, ref2]);
|
|
538
|
+
|
|
539
|
+
await testUpdate(
|
|
540
|
+
manifest, mediaWithUpdatedDiscontinuitySegment, [ref2]);
|
|
537
541
|
});
|
|
538
542
|
|
|
539
543
|
// Test for https://github.com/shaka-project/shaka-player/issues/4223
|
|
@@ -725,32 +729,6 @@ describe('HlsParser live', () => {
|
|
|
725
729
|
shaka.net.NetworkingEngine.RequestType.MANIFEST);
|
|
726
730
|
});
|
|
727
731
|
|
|
728
|
-
it('reuses cached timestamp offset for segments with discontinuity',
|
|
729
|
-
async () => {
|
|
730
|
-
const ref1 = makeReference(
|
|
731
|
-
'test:/main.mp4', 0, 2, /* syncTime= */ null);
|
|
732
|
-
const ref2 = makeReference(
|
|
733
|
-
'test:/main2.mp4', 2, 4, /* syncTime= */ null);
|
|
734
|
-
|
|
735
|
-
const manifest = await testInitialManifest(
|
|
736
|
-
master, mediaWithDiscontinuity, [ref1, ref2]);
|
|
737
|
-
|
|
738
|
-
fakeNetEngine.request.calls.reset();
|
|
739
|
-
await testUpdate(
|
|
740
|
-
manifest, mediaWithUpdatedDiscontinuitySegment, [ref2]);
|
|
741
|
-
|
|
742
|
-
// Only one request should be made, and it's for the playlist.
|
|
743
|
-
// Expect to use the cached timestamp offset for the main2.mp4
|
|
744
|
-
// segment, without fetching the start time again.
|
|
745
|
-
expect(fakeNetEngine.request).toHaveBeenCalledTimes(1);
|
|
746
|
-
fakeNetEngine.expectRequest(
|
|
747
|
-
'test:/video',
|
|
748
|
-
shaka.net.NetworkingEngine.RequestType.MANIFEST);
|
|
749
|
-
fakeNetEngine.expectNoRequest(
|
|
750
|
-
'test:/main.mp4',
|
|
751
|
-
shaka.net.NetworkingEngine.RequestType.SEGMENT);
|
|
752
|
-
});
|
|
753
|
-
|
|
754
732
|
it('request playlist delta updates to skip segments', async () => {
|
|
755
733
|
const mediaWithDeltaUpdates = [
|
|
756
734
|
'#EXTM3U\n',
|
|
@@ -2524,6 +2524,9 @@ describe('HlsParser', () => {
|
|
|
2524
2524
|
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
2525
2525
|
'#EXTINF:5,\n',
|
|
2526
2526
|
'#EXT-X-BYTERANGE:121090@616\n',
|
|
2527
|
+
'main.mp4\n',
|
|
2528
|
+
'#EXTINF:5,\n',
|
|
2529
|
+
'#EXT-X-BYTERANGE:121090@616\n',
|
|
2527
2530
|
'main.mp4',
|
|
2528
2531
|
].join('');
|
|
2529
2532
|
|
|
@@ -2535,6 +2538,10 @@ describe('HlsParser', () => {
|
|
|
2535
2538
|
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
2536
2539
|
'#EXTINF:5,\n',
|
|
2537
2540
|
'#EXT-X-BYTERANGE:121090@616\n',
|
|
2541
|
+
'main.mp4\n',
|
|
2542
|
+
'#EXTINF:5,\n',
|
|
2543
|
+
'#EXT-X-BYTERANGE:121090@616\n',
|
|
2544
|
+
'#EXT-X-KEY:METHOD=NONE\n',
|
|
2538
2545
|
'main.mp4',
|
|
2539
2546
|
].join('');
|
|
2540
2547
|
|
|
@@ -2544,6 +2551,9 @@ describe('HlsParser', () => {
|
|
|
2544
2551
|
'#EXT-X-KEY:METHOD=AES-128,',
|
|
2545
2552
|
'URI="800k.key"\n',
|
|
2546
2553
|
'#EXTINF:5,\n',
|
|
2554
|
+
'main.ts\n',
|
|
2555
|
+
'#EXTINF:5,\n',
|
|
2556
|
+
'#EXT-X-KEY:METHOD=NONE\n',
|
|
2547
2557
|
'main.ts',
|
|
2548
2558
|
].join('');
|
|
2549
2559
|
|
|
@@ -2596,6 +2606,26 @@ describe('HlsParser', () => {
|
|
|
2596
2606
|
|
|
2597
2607
|
const actual = await parser.start('test:/master', playerInterface);
|
|
2598
2608
|
expect(actual).toEqual(manifest);
|
|
2609
|
+
|
|
2610
|
+
const mp4AesEncryptionVideo = actual.variants[1].video;
|
|
2611
|
+
await mp4AesEncryptionVideo.createSegmentIndex();
|
|
2612
|
+
goog.asserts.assert(mp4AesEncryptionVideo.segmentIndex != null,
|
|
2613
|
+
'Null segmentIndex!');
|
|
2614
|
+
|
|
2615
|
+
const firstMp4Segment = mp4AesEncryptionVideo.segmentIndex.get(0);
|
|
2616
|
+
expect(firstMp4Segment.hlsAes128Key).toBeDefined();
|
|
2617
|
+
const secondMp4Segment = mp4AesEncryptionVideo.segmentIndex.get(1);
|
|
2618
|
+
expect(secondMp4Segment.hlsAes128Key).toBeNull();
|
|
2619
|
+
|
|
2620
|
+
const tsAesEncryptionVideo = actual.variants[2].video;
|
|
2621
|
+
await tsAesEncryptionVideo.createSegmentIndex();
|
|
2622
|
+
goog.asserts.assert(tsAesEncryptionVideo.segmentIndex != null,
|
|
2623
|
+
'Null segmentIndex!');
|
|
2624
|
+
|
|
2625
|
+
const firstTsSegment = tsAesEncryptionVideo.segmentIndex.get(0);
|
|
2626
|
+
expect(firstTsSegment.hlsAes128Key).toBeDefined();
|
|
2627
|
+
const secondTsSegment = tsAesEncryptionVideo.segmentIndex.get(1);
|
|
2628
|
+
expect(secondTsSegment.hlsAes128Key).toBeNull();
|
|
2599
2629
|
});
|
|
2600
2630
|
|
|
2601
2631
|
it('fails on AES-128 if WebCrypto APIs are not available', async () => {
|
|
@@ -532,6 +532,34 @@ describe('SegmentIndex', /** @suppress {accessControls} */ () => {
|
|
|
532
532
|
goog.asserts.assert(position1 != null, 'Position should not be null!');
|
|
533
533
|
expect(index1.get(position1)).toBe(references2[1]);
|
|
534
534
|
});
|
|
535
|
+
|
|
536
|
+
it('does not duplicate references with rounding errors', () => {
|
|
537
|
+
/** @type {!Array.<!shaka.media.SegmentReference>} */
|
|
538
|
+
const references1 = [
|
|
539
|
+
makeReference(uri(10), 10, 20),
|
|
540
|
+
makeReference(uri(20), 20, 30),
|
|
541
|
+
];
|
|
542
|
+
const index1 = new shaka.media.SegmentIndex(references1);
|
|
543
|
+
|
|
544
|
+
// 0.24 microseconds: an insignificant rounding error.
|
|
545
|
+
const tinyError = 0.24e-6;
|
|
546
|
+
|
|
547
|
+
/** @type {!Array.<!shaka.media.SegmentReference>} */
|
|
548
|
+
const references2 = [
|
|
549
|
+
makeReference(uri(10), 10, 20),
|
|
550
|
+
// The difference between this and the equivalent old reference is an
|
|
551
|
+
// insignificant rounding error.
|
|
552
|
+
makeReference(uri(20), 20 + tinyError, 30 + tinyError),
|
|
553
|
+
makeReference(uri(30), 30 + tinyError, 40),
|
|
554
|
+
];
|
|
555
|
+
|
|
556
|
+
index1.merge(references2);
|
|
557
|
+
expect(index1.references.length).toBe(3);
|
|
558
|
+
expect(index1.references[0]).toEqual(references1[0]);
|
|
559
|
+
// The new references replaced the old one.
|
|
560
|
+
expect(index1.references[1]).toEqual(references2[1]);
|
|
561
|
+
expect(index1.references[2]).toEqual(references2[2]);
|
|
562
|
+
});
|
|
535
563
|
});
|
|
536
564
|
|
|
537
565
|
describe('evict', () => {
|
|
@@ -72,6 +72,33 @@ describe('Player', () => {
|
|
|
72
72
|
});
|
|
73
73
|
}); // describe('attach')
|
|
74
74
|
|
|
75
|
+
describe('destroy', () => {
|
|
76
|
+
// Regression test for:
|
|
77
|
+
// https://github.com/shaka-project/shaka-player/issues/4850
|
|
78
|
+
it('does not leave any lingering timers', async () => {
|
|
79
|
+
shaka.util.Timer.activeTimers.clear();
|
|
80
|
+
|
|
81
|
+
// Unlike the other tests in this file, this uses an uncompiled build of
|
|
82
|
+
// Shaka, so that we don't need to expose shaka.util.Timer.activeTimers.
|
|
83
|
+
player = new shaka.Player(video);
|
|
84
|
+
waiter.setPlayer(player);
|
|
85
|
+
|
|
86
|
+
// Play the video for a little while.
|
|
87
|
+
await player.load('test:sintel');
|
|
88
|
+
video.play();
|
|
89
|
+
await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 1, 10);
|
|
90
|
+
|
|
91
|
+
// Destroy the player.
|
|
92
|
+
await player.destroy();
|
|
93
|
+
|
|
94
|
+
// Are there any timers left?
|
|
95
|
+
for (const timer of shaka.util.Timer.activeTimers.keys()) {
|
|
96
|
+
const stackTrace = shaka.util.Timer.activeTimers.get(timer);
|
|
97
|
+
fail('Lingering timer exists! Stack trace of creation: ' + stackTrace);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
75
102
|
describe('updateStartTime() in manifestparsed event handler', () => {
|
|
76
103
|
it('does not get segments prior to startTime', async () => {
|
|
77
104
|
player.addEventListener('manifestparsed', () => {
|
package/test/player_unit.js
CHANGED
|
@@ -3390,7 +3390,9 @@ describe('Player', () => {
|
|
|
3390
3390
|
variant.addAudio(0, (stream) => {
|
|
3391
3391
|
stream.channelsCount = 6;
|
|
3392
3392
|
stream.audioSamplingRate = 48000;
|
|
3393
|
-
|
|
3393
|
+
// ac-3 is rewritten as ec-3 on Tizen, so for the stability of this
|
|
3394
|
+
// test case, use ec-3.
|
|
3395
|
+
stream.codecs = 'ec-3';
|
|
3394
3396
|
});
|
|
3395
3397
|
});
|
|
3396
3398
|
|
|
@@ -3415,7 +3417,7 @@ describe('Player', () => {
|
|
|
3415
3417
|
expect(abrManager.variants.length).toBe(1);
|
|
3416
3418
|
// It should be the 6-channel variant, based on our preference.
|
|
3417
3419
|
expect(abrManager.variants[0].audio.channelsCount).toBe(6);
|
|
3418
|
-
expect(abrManager.variants[0].audio.codecs).toBe('
|
|
3420
|
+
expect(abrManager.variants[0].audio.codecs).toBe('ec-3');
|
|
3419
3421
|
});
|
|
3420
3422
|
});
|
|
3421
3423
|
|
|
@@ -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 */
|