shaka-player 3.3.1 → 3.3.2
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/build_and_test.yaml +73 -0
- package/.github/workflows/release.yaml +53 -0
- package/CHANGELOG.md +13 -1
- package/build/checkversion.py +1 -1
- package/build/test.py +6 -0
- package/dist/controls.css +1 -1
- package/dist/deps.js +4 -4
- package/dist/locales.js +29 -29
- package/dist/shaka-player.compiled.d.ts +2 -1
- package/dist/shaka-player.compiled.debug.d.ts +2 -1
- package/dist/shaka-player.compiled.debug.externs.js +17 -12
- package/dist/shaka-player.compiled.debug.js +93 -90
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +17 -12
- package/dist/shaka-player.compiled.js +687 -686
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.d.ts +4 -12
- package/dist/shaka-player.ui.debug.d.ts +4 -12
- package/dist/shaka-player.ui.debug.externs.js +19 -23
- package/dist/shaka-player.ui.debug.js +107 -105
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +19 -23
- package/dist/shaka-player.ui.js +952 -952
- package/dist/shaka-player.ui.map +2 -2
- package/karma.conf.js +19 -15
- package/lib/ads/ad_manager.js +1 -0
- package/lib/cast/cast_proxy.js +3 -0
- package/lib/cast/cast_receiver.js +3 -0
- package/lib/dash/dash_parser.js +31 -19
- package/lib/hls/manifest_text_parser.js +1 -0
- package/lib/media/quality_observer.js +17 -34
- package/lib/media/region_observer.js +39 -33
- package/lib/media/region_timeline.js +17 -19
- package/lib/media/streaming_engine.js +1 -1
- package/lib/net/networking_engine.js +4 -0
- package/lib/offline/storage.js +8 -9
- package/lib/player.js +32 -10
- package/lib/util/fake_event_target.js +21 -1
- package/package.json +3 -6
- package/test/dash/dash_parser_live_unit.js +43 -0
- package/test/dash/dash_parser_manifest_unit.js +2 -1
- package/test/hls/hls_parser_unit.js +74 -0
- package/test/media/quality_observer_unit.js +4 -1
- package/test/media/region_observer_unit.js +40 -5
- package/test/media/region_timeline_unit.js +40 -22
- package/test/test/util/util.js +10 -7
- package/test/ui/text_displayer_layout_unit.js +8 -7
- package/ui/controls.js +3 -0
- package/ui/localization.js +12 -36
package/lib/player.js
CHANGED
|
@@ -787,6 +787,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
787
787
|
await this.networkingEngine_.destroy();
|
|
788
788
|
this.networkingEngine_ = null;
|
|
789
789
|
}
|
|
790
|
+
|
|
791
|
+
// FakeEventTarget implements IReleasable
|
|
792
|
+
super.release();
|
|
790
793
|
}
|
|
791
794
|
|
|
792
795
|
/**
|
|
@@ -1691,17 +1694,26 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1691
1694
|
// initialize it now even through it appears a little out-of-place.
|
|
1692
1695
|
this.regionTimeline_ =
|
|
1693
1696
|
new shaka.media.RegionTimeline(() => this.seekRange());
|
|
1694
|
-
this.regionTimeline_.
|
|
1697
|
+
this.regionTimeline_.addEventListener('regionadd', (event) => {
|
|
1698
|
+
/** @type {shaka.extern.TimelineRegionInfo} */
|
|
1699
|
+
const region = event['region'];
|
|
1695
1700
|
this.onRegionEvent_(shaka.Player.EventName.TimelineRegionAdded, region);
|
|
1701
|
+
|
|
1696
1702
|
if (this.adManager_) {
|
|
1697
1703
|
this.adManager_.onDashTimedMetadata(region);
|
|
1698
1704
|
}
|
|
1699
1705
|
});
|
|
1706
|
+
|
|
1700
1707
|
this.qualityObserver_ = null;
|
|
1701
1708
|
if (this.config_.streaming.observeQualityChanges) {
|
|
1702
1709
|
this.qualityObserver_ = new shaka.media.QualityObserver(
|
|
1703
1710
|
() => this.getBufferedInfo());
|
|
1704
|
-
|
|
1711
|
+
|
|
1712
|
+
this.qualityObserver_.addEventListener('qualitychange', (event) => {
|
|
1713
|
+
/** @type {shaka.extern.MediaQualityInfo} */
|
|
1714
|
+
const mediaQualityInfo = event['quality'];
|
|
1715
|
+
/** @type {number} */
|
|
1716
|
+
const position = event['position'];
|
|
1705
1717
|
this.onMediaQualityChange_(mediaQualityInfo, position);
|
|
1706
1718
|
});
|
|
1707
1719
|
}
|
|
@@ -2674,21 +2686,31 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2674
2686
|
// Create the region observer. This will allow us to notify the app when we
|
|
2675
2687
|
// move in and out of timeline regions.
|
|
2676
2688
|
const regionObserver = new shaka.media.RegionObserver(this.regionTimeline_);
|
|
2677
|
-
|
|
2689
|
+
|
|
2690
|
+
regionObserver.addEventListener('enter', (event) => {
|
|
2691
|
+
/** @type {shaka.extern.TimelineRegionInfo} */
|
|
2692
|
+
const region = event['region'];
|
|
2678
2693
|
this.onRegionEvent_(shaka.Player.EventName.TimelineRegionEnter, region);
|
|
2679
|
-
};
|
|
2680
|
-
|
|
2694
|
+
});
|
|
2695
|
+
|
|
2696
|
+
regionObserver.addEventListener('exit', (event) => {
|
|
2697
|
+
/** @type {shaka.extern.TimelineRegionInfo} */
|
|
2698
|
+
const region = event['region'];
|
|
2681
2699
|
this.onRegionEvent_(shaka.Player.EventName.TimelineRegionExit, region);
|
|
2682
|
-
};
|
|
2683
|
-
|
|
2700
|
+
});
|
|
2701
|
+
|
|
2702
|
+
regionObserver.addEventListener('skip', (event) => {
|
|
2703
|
+
/** @type {shaka.extern.TimelineRegionInfo} */
|
|
2704
|
+
const region = event['region'];
|
|
2705
|
+
/** @type {boolean} */
|
|
2706
|
+
const seeking = event['seeking'];
|
|
2684
2707
|
// If we are seeking, we don't want to surface the enter/exit events since
|
|
2685
2708
|
// they didn't play through them.
|
|
2686
2709
|
if (!seeking) {
|
|
2687
2710
|
this.onRegionEvent_(shaka.Player.EventName.TimelineRegionEnter, region);
|
|
2688
2711
|
this.onRegionEvent_(shaka.Player.EventName.TimelineRegionExit, region);
|
|
2689
2712
|
}
|
|
2690
|
-
};
|
|
2691
|
-
regionObserver.setListeners(onEnterRegion, onExitRegion, onSkipRegion);
|
|
2713
|
+
});
|
|
2692
2714
|
|
|
2693
2715
|
// Now that we have all our observers, create a manager for them.
|
|
2694
2716
|
const manager = new shaka.media.PlayheadObserverManager(this.video_);
|
|
@@ -6536,7 +6558,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6536
6558
|
* @define {string} A version number taken from git at compile time.
|
|
6537
6559
|
* @export
|
|
6538
6560
|
*/
|
|
6539
|
-
shaka.Player.version = 'v3.3.
|
|
6561
|
+
shaka.Player.version = 'v3.3.2-uncompiled'; // x-release-please-version
|
|
6540
6562
|
|
|
6541
6563
|
// Initialize the deprecation system using the version string we just set
|
|
6542
6564
|
// on the player.
|
|
@@ -9,6 +9,7 @@ goog.provide('shaka.util.FakeEventTarget');
|
|
|
9
9
|
goog.require('goog.asserts');
|
|
10
10
|
goog.require('shaka.log');
|
|
11
11
|
goog.require('shaka.util.FakeEvent');
|
|
12
|
+
goog.require('shaka.util.IReleasable');
|
|
12
13
|
goog.require('shaka.util.MultiMap');
|
|
13
14
|
|
|
14
15
|
|
|
@@ -18,13 +19,14 @@ goog.require('shaka.util.MultiMap');
|
|
|
18
19
|
* to non-DOM classes. Only FakeEvents should be dispatched.
|
|
19
20
|
*
|
|
20
21
|
* @implements {EventTarget}
|
|
22
|
+
* @implements {shaka.util.IReleasable}
|
|
21
23
|
* @exportInterface
|
|
22
24
|
*/
|
|
23
25
|
shaka.util.FakeEventTarget = class {
|
|
24
26
|
/** */
|
|
25
27
|
constructor() {
|
|
26
28
|
/**
|
|
27
|
-
* @private {
|
|
29
|
+
* @private {shaka.util.MultiMap.<shaka.util.FakeEventTarget.ListenerType>}
|
|
28
30
|
*/
|
|
29
31
|
this.listeners_ = new shaka.util.MultiMap();
|
|
30
32
|
|
|
@@ -46,6 +48,9 @@ shaka.util.FakeEventTarget = class {
|
|
|
46
48
|
* @exportInterface
|
|
47
49
|
*/
|
|
48
50
|
addEventListener(type, listener, options) {
|
|
51
|
+
if (!this.listeners_) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
49
54
|
this.listeners_.push(type, listener);
|
|
50
55
|
}
|
|
51
56
|
|
|
@@ -73,6 +78,9 @@ shaka.util.FakeEventTarget = class {
|
|
|
73
78
|
* @exportInterface
|
|
74
79
|
*/
|
|
75
80
|
removeEventListener(type, listener, options) {
|
|
81
|
+
if (!this.listeners_) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
76
84
|
this.listeners_.remove(type, listener);
|
|
77
85
|
}
|
|
78
86
|
|
|
@@ -90,6 +98,10 @@ shaka.util.FakeEventTarget = class {
|
|
|
90
98
|
goog.asserts.assert(event instanceof shaka.util.FakeEvent,
|
|
91
99
|
'FakeEventTarget can only dispatch FakeEvents!');
|
|
92
100
|
|
|
101
|
+
if (!this.listeners_) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
|
|
93
105
|
let listeners = this.listeners_.get(event.type) || [];
|
|
94
106
|
const universalListeners =
|
|
95
107
|
this.listeners_.get(shaka.util.FakeEventTarget.ALL_EVENTS_);
|
|
@@ -129,6 +141,14 @@ shaka.util.FakeEventTarget = class {
|
|
|
129
141
|
|
|
130
142
|
return event.defaultPrevented;
|
|
131
143
|
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @override
|
|
147
|
+
* @exportInterface
|
|
148
|
+
*/
|
|
149
|
+
release() {
|
|
150
|
+
this.listeners_ = null;
|
|
151
|
+
}
|
|
132
152
|
};
|
|
133
153
|
|
|
134
154
|
/**
|
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.3.
|
|
4
|
+
"version": "3.3.2",
|
|
5
5
|
"homepage": "https://github.com/google/shaka-player",
|
|
6
6
|
"author": "Google",
|
|
7
7
|
"maintainers": [
|
|
@@ -11,8 +11,6 @@
|
|
|
11
11
|
}
|
|
12
12
|
],
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@chiragrupani/karma-chromium-edge-launcher": "^2.1.0",
|
|
15
|
-
"@onslip/karma-safari-launcher": "^1.0.1",
|
|
16
14
|
"@teppeis/clutz": "^1.0.29-4c95e12.v20190929",
|
|
17
15
|
"awesomplete": "^1.1.4",
|
|
18
16
|
"babel-core": "^6.26.3",
|
|
@@ -37,13 +35,11 @@
|
|
|
37
35
|
"jsdoc": "github:joeyparrish/jsdoc#45c271fa",
|
|
38
36
|
"karma": "^6.2.0",
|
|
39
37
|
"karma-babel-preprocessor": "^7.0.0",
|
|
40
|
-
"karma-chrome-launcher": "^2.2.0",
|
|
41
38
|
"karma-coverage": "^1.1.2",
|
|
42
|
-
"karma-edge-launcher": "^0.4.2",
|
|
43
|
-
"karma-firefox-launcher": "^1.1.0",
|
|
44
39
|
"karma-ie-launcher": "^1.0.0",
|
|
45
40
|
"karma-jasmine": "^3.3.1",
|
|
46
41
|
"karma-jasmine-ajax": "^0.1.13",
|
|
42
|
+
"karma-local-wd-launcher": "^1.4.1",
|
|
47
43
|
"karma-opera-launcher": "^1.0.0",
|
|
48
44
|
"karma-sourcemap-loader": "^0.3.7",
|
|
49
45
|
"karma-spec-reporter": "0.0.32",
|
|
@@ -55,6 +51,7 @@
|
|
|
55
51
|
"pwacompat": "^2.0.10",
|
|
56
52
|
"rimraf": "^2.6.3",
|
|
57
53
|
"sprintf-js": "^1.1.2",
|
|
54
|
+
"ssim.js": "^3.5.0",
|
|
58
55
|
"stylelint": "^13.8.0",
|
|
59
56
|
"stylelint-config-standard": "^20.0.0",
|
|
60
57
|
"tippy.js": "^4.3.1",
|
|
@@ -1389,6 +1389,49 @@ describe('DashParser Live', () => {
|
|
|
1389
1389
|
.toHaveBeenCalledWith(
|
|
1390
1390
|
jasmine.objectContaining({id: '2', startTime: 10, endTime: 25}));
|
|
1391
1391
|
});
|
|
1392
|
+
|
|
1393
|
+
it('will not add timeline regions outside the DVR window', async () => {
|
|
1394
|
+
const manifest = [
|
|
1395
|
+
'<MPD type="dynamic" minimumUpdatePeriod="PT' + updateTime + 'S"',
|
|
1396
|
+
' availabilityStartTime="1970-01-01T00:00:00Z"',
|
|
1397
|
+
' suggestedPresentationDelay="PT0S"',
|
|
1398
|
+
' timeShiftBufferDepth="PT30S">',
|
|
1399
|
+
' <Period id="1">',
|
|
1400
|
+
' <EventStream schemeIdUri="http://example.com" timescale="1">',
|
|
1401
|
+
' <Event id="0" presentationTime="0" duration="10"/>',
|
|
1402
|
+
' <Event id="1" presentationTime="10" duration="10"/>',
|
|
1403
|
+
' <Event id="2" presentationTime="20" duration="10"/>',
|
|
1404
|
+
' <Event id="3" presentationTime="30" duration="10"/>',
|
|
1405
|
+
' <Event id="4" presentationTime="40" duration="10"/>',
|
|
1406
|
+
' </EventStream>',
|
|
1407
|
+
' <AdaptationSet mimeType="video/mp4">',
|
|
1408
|
+
' <Representation bandwidth="1">',
|
|
1409
|
+
' <SegmentTemplate startNumber="1" media="s$Number$.mp4"',
|
|
1410
|
+
' duration="2" />',
|
|
1411
|
+
' </Representation>',
|
|
1412
|
+
' </AdaptationSet>',
|
|
1413
|
+
' </Period>',
|
|
1414
|
+
'</MPD>',
|
|
1415
|
+
].join('\n');
|
|
1416
|
+
|
|
1417
|
+
// 45 seconds after the epoch, which is also 45 seconds after the
|
|
1418
|
+
// presentation started. Only the last 30 seconds are available, from
|
|
1419
|
+
// time 15 to 45.
|
|
1420
|
+
Date.now = () => 45 * 1000;
|
|
1421
|
+
|
|
1422
|
+
fakeNetEngine.setResponseText('dummy://foo', manifest);
|
|
1423
|
+
await parser.start('dummy://foo', playerInterface);
|
|
1424
|
+
|
|
1425
|
+
expect(onTimelineRegionAddedSpy).toHaveBeenCalledTimes(4);
|
|
1426
|
+
expect(onTimelineRegionAddedSpy).toHaveBeenCalledWith(
|
|
1427
|
+
jasmine.objectContaining({id: '1'}));
|
|
1428
|
+
expect(onTimelineRegionAddedSpy).toHaveBeenCalledWith(
|
|
1429
|
+
jasmine.objectContaining({id: '2'}));
|
|
1430
|
+
expect(onTimelineRegionAddedSpy).toHaveBeenCalledWith(
|
|
1431
|
+
jasmine.objectContaining({id: '3'}));
|
|
1432
|
+
expect(onTimelineRegionAddedSpy).toHaveBeenCalledWith(
|
|
1433
|
+
jasmine.objectContaining({id: '4'}));
|
|
1434
|
+
});
|
|
1392
1435
|
});
|
|
1393
1436
|
|
|
1394
1437
|
it('honors clockSyncUri for in-progress recordings', async () => {
|
|
@@ -985,7 +985,8 @@ describe('DashParser Manifest', () => {
|
|
|
985
985
|
|
|
986
986
|
it('duplicate Representation ids with live', async () => {
|
|
987
987
|
const source = [
|
|
988
|
-
'<MPD minBufferTime="PT75S" type="dynamic"
|
|
988
|
+
'<MPD minBufferTime="PT75S" type="dynamic"',
|
|
989
|
+
' availabilityStartTime="1970-01-01T00:00:00Z">',
|
|
989
990
|
' <Period id="1" duration="PT30S">',
|
|
990
991
|
' <AdaptationSet mimeType="video/mp4">',
|
|
991
992
|
' <Representation id="1" bandwidth="1">',
|
|
@@ -211,6 +211,80 @@ describe('HlsParser', () => {
|
|
|
211
211
|
expect(actual).toEqual(manifest);
|
|
212
212
|
});
|
|
213
213
|
|
|
214
|
+
it('parses manifest attributes with space', async () => {
|
|
215
|
+
const master = [
|
|
216
|
+
'#EXTM3U\n',
|
|
217
|
+
'#EXT-X-MEDIA:TYPE=AUDIO, GROUP-ID="aud1", LANGUAGE="eng", ',
|
|
218
|
+
'CHANNELS="16/JOC", URI="audio"\n',
|
|
219
|
+
'#EXT-X-MEDIA:TYPE=SUBTITLES, GROUP-ID="sub1", LANGUAGE="eng", ',
|
|
220
|
+
'URI="text"\n',
|
|
221
|
+
'#EXT-X-MEDIA:TYPE=SUBTITLES, GROUP-ID="sub2", LANGUAGE="es", ',
|
|
222
|
+
'URI="text2"\n',
|
|
223
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200, CODECS="avc1,mp4a", ',
|
|
224
|
+
'RESOLUTION=960x540, FRAME-RATE=60, AUDIO="aud1"\n',
|
|
225
|
+
'video\n',
|
|
226
|
+
].join('');
|
|
227
|
+
|
|
228
|
+
const media = [
|
|
229
|
+
'#EXTM3U\n',
|
|
230
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
231
|
+
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
232
|
+
'#EXTINF:5,\n',
|
|
233
|
+
'#EXT-X-BYTERANGE:121090@616\n',
|
|
234
|
+
'main.mp4',
|
|
235
|
+
].join('');
|
|
236
|
+
|
|
237
|
+
const textMedia = [
|
|
238
|
+
'#EXTM3U\n',
|
|
239
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
240
|
+
'#EXTINF:5,\n',
|
|
241
|
+
'#EXT-X-BYTERANGE:121090@616\n',
|
|
242
|
+
'main.vtt',
|
|
243
|
+
].join('');
|
|
244
|
+
|
|
245
|
+
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
246
|
+
manifest.anyTimeline();
|
|
247
|
+
manifest.addPartialVariant((variant) => {
|
|
248
|
+
variant.language = 'en';
|
|
249
|
+
variant.bandwidth = 200;
|
|
250
|
+
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
|
251
|
+
stream.frameRate = 60;
|
|
252
|
+
stream.mime('video/mp4', 'avc1');
|
|
253
|
+
stream.size(960, 540);
|
|
254
|
+
});
|
|
255
|
+
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
|
256
|
+
stream.language = 'en';
|
|
257
|
+
stream.channelsCount = 16;
|
|
258
|
+
stream.spatialAudio = true;
|
|
259
|
+
stream.mime('audio/mp4', 'mp4a');
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
manifest.addPartialTextStream((stream) => {
|
|
263
|
+
stream.language = 'en';
|
|
264
|
+
stream.kind = TextStreamKind.SUBTITLE;
|
|
265
|
+
stream.mime('text/vtt', '');
|
|
266
|
+
});
|
|
267
|
+
manifest.addPartialTextStream((stream) => {
|
|
268
|
+
stream.language = 'es';
|
|
269
|
+
stream.kind = TextStreamKind.SUBTITLE;
|
|
270
|
+
stream.mime('text/vtt', '');
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
fakeNetEngine
|
|
275
|
+
.setResponseText('test:/master', master)
|
|
276
|
+
.setResponseText('test:/audio', media)
|
|
277
|
+
.setResponseText('test:/video', media)
|
|
278
|
+
.setResponseText('test:/text', textMedia)
|
|
279
|
+
.setResponseText('test:/text2', textMedia)
|
|
280
|
+
.setResponseText('test:/main.vtt', vttText)
|
|
281
|
+
.setResponseValue('test:/init.mp4', initSegmentData)
|
|
282
|
+
.setResponseValue('test:/main.mp4', segmentData);
|
|
283
|
+
|
|
284
|
+
const actual = await parser.start('test:/master', playerInterface);
|
|
285
|
+
expect(actual).toEqual(manifest);
|
|
286
|
+
});
|
|
287
|
+
|
|
214
288
|
it('prioritize AVERAGE-BANDWIDTH to BANDWIDTH', async () => {
|
|
215
289
|
const master = [
|
|
216
290
|
'#EXTM3U\n',
|
|
@@ -50,7 +50,10 @@ describe('QualityObserver', () => {
|
|
|
50
50
|
beforeEach(() => {
|
|
51
51
|
onQualityChange = jasmine.createSpy('onQualityChange');
|
|
52
52
|
observer = new shaka.media.QualityObserver(getBufferedInfo);
|
|
53
|
-
observer.
|
|
53
|
+
observer.addEventListener('qualitychange', (event) => {
|
|
54
|
+
shaka.test.Util.spyFunc(onQualityChange)(
|
|
55
|
+
event['quality'], event['position']);
|
|
56
|
+
});
|
|
54
57
|
emptyBuffer = true;
|
|
55
58
|
bufferStart = 0;
|
|
56
59
|
bufferEnd = 0;
|
|
@@ -15,6 +15,9 @@ describe('RegionObserver', () => {
|
|
|
15
15
|
/** @type {!shaka.media.RegionObserver} */
|
|
16
16
|
let observer;
|
|
17
17
|
|
|
18
|
+
/** @type {!jasmine.Spy} */
|
|
19
|
+
let onSeekRange;
|
|
20
|
+
|
|
18
21
|
/** @type {!jasmine.Spy} */
|
|
19
22
|
let onEnterRegion;
|
|
20
23
|
/** @type {!jasmine.Spy} */
|
|
@@ -23,18 +26,26 @@ describe('RegionObserver', () => {
|
|
|
23
26
|
let onSkipRegion;
|
|
24
27
|
|
|
25
28
|
beforeEach(() => {
|
|
29
|
+
onSeekRange = jasmine.createSpy('onSeekRange');
|
|
30
|
+
onSeekRange.and.returnValue({start: 0, end: 100});
|
|
31
|
+
|
|
26
32
|
onEnterRegion = jasmine.createSpy('onEnterRegion');
|
|
27
33
|
onExitRegion = jasmine.createSpy('onExitRegion');
|
|
28
34
|
onSkipRegion = jasmine.createSpy('onSkipRegion');
|
|
29
35
|
|
|
30
36
|
timeline = new shaka.media.RegionTimeline(
|
|
31
|
-
()
|
|
37
|
+
shaka.test.Util.spyFunc(onSeekRange));
|
|
32
38
|
|
|
33
39
|
observer = new shaka.media.RegionObserver(timeline);
|
|
34
|
-
observer.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
observer.addEventListener('enter', (event) => {
|
|
41
|
+
shaka.test.Util.spyFunc(onEnterRegion)(event['region'], event['seeking']);
|
|
42
|
+
});
|
|
43
|
+
observer.addEventListener('exit', (event) => {
|
|
44
|
+
shaka.test.Util.spyFunc(onExitRegion)(event['region'], event['seeking']);
|
|
45
|
+
});
|
|
46
|
+
observer.addEventListener('skip', (event) => {
|
|
47
|
+
shaka.test.Util.spyFunc(onSkipRegion)(event['region'], event['seeking']);
|
|
48
|
+
});
|
|
38
49
|
});
|
|
39
50
|
|
|
40
51
|
it('fires enter event when adding a region the playhead is in', () => {
|
|
@@ -313,6 +324,30 @@ describe('RegionObserver', () => {
|
|
|
313
324
|
expect(onSkipRegion).not.toHaveBeenCalled();
|
|
314
325
|
});
|
|
315
326
|
|
|
327
|
+
// See https://github.com/google/shaka-player/issues/3949
|
|
328
|
+
it('cleans up references to regions', async () => {
|
|
329
|
+
const region = createRegion('my-region', 0, 10);
|
|
330
|
+
timeline.addRegion(region);
|
|
331
|
+
|
|
332
|
+
// Poll so that RegionObserver will store regions.
|
|
333
|
+
poll(observer,
|
|
334
|
+
/* timeInSeconds= */ 5,
|
|
335
|
+
/* seeking= */ false);
|
|
336
|
+
|
|
337
|
+
// Hack to get a private member without the compiler complaining:
|
|
338
|
+
/** @type {Map} */
|
|
339
|
+
const regionMap = (/** @type {?} */(observer))['oldPosition_'];
|
|
340
|
+
expect(regionMap.size).not.toBe(0);
|
|
341
|
+
|
|
342
|
+
// Move the seek range so that the region will be removed.
|
|
343
|
+
onSeekRange.and.returnValue({start: 20, end: 100});
|
|
344
|
+
// Give the timeline time to filter regions
|
|
345
|
+
await shaka.test.Util.delay(
|
|
346
|
+
shaka.media.RegionTimeline.REGION_FILTER_INTERVAL * 2);
|
|
347
|
+
|
|
348
|
+
expect(regionMap.size).toBe(0);
|
|
349
|
+
});
|
|
350
|
+
|
|
316
351
|
/**
|
|
317
352
|
* @param {string} id
|
|
318
353
|
* @param {number} startTimeSeconds
|
|
@@ -14,18 +14,27 @@ describe('RegionTimeline', () => {
|
|
|
14
14
|
/** @type {!jasmine.Spy} */
|
|
15
15
|
let onNewRegion;
|
|
16
16
|
|
|
17
|
+
/** @type {!jasmine.Spy} */
|
|
18
|
+
let onRemoveRegion;
|
|
19
|
+
|
|
17
20
|
/** @type {!jasmine.Spy} */
|
|
18
21
|
let onSeekRange;
|
|
19
22
|
|
|
20
23
|
beforeEach(() => {
|
|
21
|
-
onNewRegion = jasmine.createSpy('onNewRegion');
|
|
22
|
-
|
|
23
24
|
onSeekRange = jasmine.createSpy('onSeekRange');
|
|
24
25
|
onSeekRange.and.returnValue({start: 0, end: 100});
|
|
25
|
-
|
|
26
26
|
timeline = new shaka.media.RegionTimeline(
|
|
27
27
|
shaka.test.Util.spyFunc(onSeekRange));
|
|
28
|
-
|
|
28
|
+
|
|
29
|
+
onNewRegion = jasmine.createSpy('onNewRegion');
|
|
30
|
+
timeline.addEventListener('regionadd', (event) => {
|
|
31
|
+
shaka.test.Util.spyFunc(onNewRegion)(event['region']);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
onRemoveRegion = jasmine.createSpy('onRemoveRegion');
|
|
35
|
+
timeline.addEventListener('regionremove', (event) => {
|
|
36
|
+
shaka.test.Util.spyFunc(onRemoveRegion)(event['region']);
|
|
37
|
+
});
|
|
29
38
|
});
|
|
30
39
|
|
|
31
40
|
afterEach(() => {
|
|
@@ -85,24 +94,33 @@ describe('RegionTimeline', () => {
|
|
|
85
94
|
]);
|
|
86
95
|
});
|
|
87
96
|
|
|
88
|
-
it('filters
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
97
|
+
it('filters regions that end before the seek range', async () => {
|
|
98
|
+
onSeekRange.and.returnValue({start: 5, end: 100});
|
|
99
|
+
|
|
100
|
+
const region1 = createRegion('urn:foo', 'my-region', 0, 3);
|
|
101
|
+
const region2 = createRegion('urn:foo', 'my-region', 3, 10);
|
|
102
|
+
const region3 = createRegion('urn:foo', 'my-region', 5, 10);
|
|
103
|
+
|
|
104
|
+
timeline.addRegion(region1);
|
|
105
|
+
timeline.addRegion(region2);
|
|
106
|
+
timeline.addRegion(region3);
|
|
107
|
+
expect(onNewRegion).toHaveBeenCalledTimes(3);
|
|
108
|
+
expect(onNewRegion).toHaveBeenCalledWith(region1);
|
|
109
|
+
expect(onNewRegion).toHaveBeenCalledWith(region2);
|
|
110
|
+
expect(onNewRegion).toHaveBeenCalledWith(region3);
|
|
111
|
+
|
|
112
|
+
let regions = Array.from(timeline.regions());
|
|
113
|
+
expect(regions.length).toBe(3);
|
|
114
|
+
|
|
115
|
+
// Give the timeline time to filter regions
|
|
116
|
+
await shaka.test.Util.delay(
|
|
117
|
+
shaka.media.RegionTimeline.REGION_FILTER_INTERVAL * 2);
|
|
118
|
+
|
|
119
|
+
regions = Array.from(timeline.regions());
|
|
120
|
+
expect(regions).toEqual([region2, region3]);
|
|
121
|
+
|
|
122
|
+
expect(onRemoveRegion).toHaveBeenCalledWith(region1);
|
|
123
|
+
});
|
|
106
124
|
|
|
107
125
|
/**
|
|
108
126
|
* @param {string} schemeIdUri
|
package/test/test/util/util.js
CHANGED
|
@@ -401,10 +401,10 @@ shaka.test.Util = class {
|
|
|
401
401
|
* within the bounds of the viewport.
|
|
402
402
|
* @param {string} name An identifier for the screenshot. Use alphanumeric
|
|
403
403
|
* plus dash and underscore only.
|
|
404
|
-
* @param {number}
|
|
404
|
+
* @param {number} minSimilarity A minimum similarity score between 0 and 1.
|
|
405
405
|
* @return {!Promise}
|
|
406
406
|
*/
|
|
407
|
-
static async checkScreenshot(element, name,
|
|
407
|
+
static async checkScreenshot(element, name, minSimilarity=1) {
|
|
408
408
|
// Make sure the DOM is up-to-date and layout has settled before continuing.
|
|
409
409
|
// Without this delay, or with a shorter delay, we sometimes get missing
|
|
410
410
|
// elements in our UITextDisplayer tests on some platforms.
|
|
@@ -448,13 +448,16 @@ shaka.test.Util = class {
|
|
|
448
448
|
const buffer = await shaka.test.Util.fetch(
|
|
449
449
|
'/screenshot/diff' + parentUrlParams + paramsString);
|
|
450
450
|
const json = shaka.util.StringUtils.fromUTF8(buffer);
|
|
451
|
-
const
|
|
451
|
+
const similarity = /** @type {number} */(JSON.parse(json));
|
|
452
452
|
|
|
453
|
-
// If the
|
|
453
|
+
// If the minimum similarity is not met, you can review the new screenshot
|
|
454
454
|
// and the diff image in the screenshots folder. Look for images that end
|
|
455
|
-
// with "-new" and "-diff".
|
|
456
|
-
//
|
|
457
|
-
|
|
455
|
+
// with "-new" and "-diff". (NOTE: The diff is a pixel-wise diff for human
|
|
456
|
+
// review, and is not produced with the same structural similarity
|
|
457
|
+
// algorithm used to detect changes in the test.) If cropping doesn't work
|
|
458
|
+
// right, you can view the full-page screenshot in the image that ends with
|
|
459
|
+
// "-full".
|
|
460
|
+
expect(similarity).withContext(name).not.toBeLessThan(minSimilarity);
|
|
458
461
|
}
|
|
459
462
|
};
|
|
460
463
|
|
|
@@ -34,11 +34,8 @@ filterDescribe('TextDisplayer layout', supportsScreenshots, () => {
|
|
|
34
34
|
*/
|
|
35
35
|
let beforeScreenshot;
|
|
36
36
|
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
// to have this change tolerance in our tests. So far, all past bugs in our
|
|
40
|
-
// implementation that we have tests for would exceed this threshold by a lot.
|
|
41
|
-
const threshold = 160; // px
|
|
37
|
+
// A minimum similarity score for screenshots, between 0 and 1.
|
|
38
|
+
const minSimilarity = 0.95;
|
|
42
39
|
|
|
43
40
|
const originalCast = window.chrome && window.chrome.cast;
|
|
44
41
|
|
|
@@ -196,6 +193,7 @@ filterDescribe('TextDisplayer layout', supportsScreenshots, () => {
|
|
|
196
193
|
|
|
197
194
|
// The video must be played a little now, after the cues were appended,
|
|
198
195
|
// but before the screenshot.
|
|
196
|
+
video.playbackRate = 1;
|
|
199
197
|
video.play();
|
|
200
198
|
await waiter.failOnTimeout(false).timeoutAfter(5)
|
|
201
199
|
.waitForMovement(video);
|
|
@@ -203,10 +201,13 @@ filterDescribe('TextDisplayer layout', supportsScreenshots, () => {
|
|
|
203
201
|
|
|
204
202
|
// Seek to a time when cues should be showing.
|
|
205
203
|
video.currentTime = time;
|
|
204
|
+
// Get into a playing state, but without movement.
|
|
205
|
+
video.playbackRate = 0;
|
|
206
|
+
video.play();
|
|
206
207
|
|
|
207
208
|
// Add a short delay to ensure that the system has caught up and that
|
|
208
209
|
// native text displayers have been updated by the browser.
|
|
209
|
-
await Util.
|
|
210
|
+
await Util.delay(0.1);
|
|
210
211
|
};
|
|
211
212
|
});
|
|
212
213
|
|
|
@@ -475,6 +476,6 @@ filterDescribe('TextDisplayer layout', supportsScreenshots, () => {
|
|
|
475
476
|
return Util.checkScreenshot(
|
|
476
477
|
/* element= */ videoContainer,
|
|
477
478
|
prefix + '-' + baseName,
|
|
478
|
-
|
|
479
|
+
minSimilarity);
|
|
479
480
|
}
|
|
480
481
|
});
|