@gcorevideo/player 2.22.17 → 2.22.20
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/assets/audio-selector/track-selector.ejs +3 -3
- package/assets/bottom-gear/bottomgear.ejs +3 -3
- package/assets/clappr-nerd-stats/clappr-nerd-stats.ejs +76 -78
- package/assets/clappr-nerd-stats/clappr-nerd-stats.scss +10 -7
- package/assets/dvr-controls/dvr_controls.scss +0 -12
- package/dist/core.js +5 -7
- package/dist/index.css +1245 -1251
- package/dist/index.js +425 -261
- package/dist/player.d.ts +121 -108
- package/dist/plugins/index.css +577 -583
- package/dist/plugins/index.js +355 -187
- package/docs/api/player.bitratetrackrecord.md +20 -0
- package/docs/api/player.clapprstats.exportmetrics.md +2 -2
- package/docs/api/player.clapprstats.md +0 -4
- package/docs/api/player.clapprstatschronograph.md +115 -0
- package/docs/api/player.clapprstatscounter.md +211 -0
- package/docs/api/player.clapprstatsevents.md +51 -0
- package/docs/api/player.clapprstatsmetrics.md +52 -0
- package/docs/api/player.clipspluginsettings.md +1 -1
- package/docs/api/player.md +57 -2
- package/docs/api/player.nerdstats.md +3 -3
- package/docs/api/player.playerconfig.md +1 -1
- package/docs/api/player.playerconfig.playbacktype.md +6 -1
- package/docs/api/player.timeupdate.md +6 -3
- package/lib/playback/dash-playback/DashPlayback.d.ts +0 -1
- package/lib/playback/dash-playback/DashPlayback.d.ts.map +1 -1
- package/lib/playback/dash-playback/DashPlayback.js +4 -5
- package/lib/playback/hls-playback/HlsPlayback.d.ts +1 -1
- package/lib/playback/hls-playback/HlsPlayback.d.ts.map +1 -1
- package/lib/playback/hls-playback/HlsPlayback.js +0 -1
- package/lib/playback.types.d.ts +2 -3
- package/lib/playback.types.d.ts.map +1 -1
- package/lib/plugins/audio-selector/AudioSelector.d.ts +1 -1
- package/lib/plugins/audio-selector/AudioSelector.d.ts.map +1 -1
- package/lib/plugins/audio-selector/AudioSelector.js +15 -8
- package/lib/plugins/bottom-gear/BottomGear.d.ts +1 -1
- package/lib/plugins/bottom-gear/BottomGear.js +2 -2
- package/lib/plugins/clappr-nerd-stats/NerdStats.d.ts +17 -14
- package/lib/plugins/clappr-nerd-stats/NerdStats.d.ts.map +1 -1
- package/lib/plugins/clappr-nerd-stats/NerdStats.js +175 -124
- package/lib/plugins/clappr-nerd-stats/formatter.d.ts +5 -0
- package/lib/plugins/clappr-nerd-stats/formatter.d.ts.map +1 -1
- package/lib/plugins/clappr-nerd-stats/formatter.js +56 -24
- package/lib/plugins/clappr-nerd-stats/speedtest/index.d.ts +2 -2
- package/lib/plugins/clappr-nerd-stats/speedtest/index.d.ts.map +1 -1
- package/lib/plugins/clappr-nerd-stats/speedtest/types.d.ts +1 -1
- package/lib/plugins/clappr-nerd-stats/speedtest/types.d.ts.map +1 -1
- package/lib/plugins/clappr-nerd-stats/types.d.ts +3 -0
- package/lib/plugins/clappr-nerd-stats/types.d.ts.map +1 -1
- package/lib/plugins/clappr-nerd-stats/utils.d.ts +7 -0
- package/lib/plugins/clappr-nerd-stats/utils.d.ts.map +1 -0
- package/lib/plugins/clappr-nerd-stats/utils.js +67 -0
- package/lib/plugins/clappr-stats/ClapprStats.d.ts +5 -2
- package/lib/plugins/clappr-stats/ClapprStats.d.ts.map +1 -1
- package/lib/plugins/clappr-stats/ClapprStats.js +31 -33
- package/lib/plugins/clappr-stats/types.d.ts +21 -22
- package/lib/plugins/clappr-stats/types.d.ts.map +1 -1
- package/lib/plugins/clappr-stats/types.js +22 -22
- package/lib/plugins/clappr-stats/utils.d.ts +2 -2
- package/lib/plugins/clappr-stats/utils.d.ts.map +1 -1
- package/lib/plugins/clappr-stats/utils.js +0 -1
- package/lib/plugins/click-to-pause/ClickToPause.js +1 -1
- package/lib/plugins/clips/Clips.d.ts +1 -1
- package/lib/plugins/dvr-controls/DvrControls.d.ts +6 -2
- package/lib/plugins/dvr-controls/DvrControls.d.ts.map +1 -1
- package/lib/plugins/dvr-controls/DvrControls.js +39 -27
- package/lib/plugins/media-control/MediaControl.d.ts +6 -2
- package/lib/plugins/media-control/MediaControl.d.ts.map +1 -1
- package/lib/plugins/media-control/MediaControl.js +20 -9
- package/lib/plugins/picture-in-picture/PictureInPicture.js +1 -1
- package/lib/plugins/seek-time/SeekTime.d.ts +1 -1
- package/lib/plugins/seek-time/SeekTime.d.ts.map +1 -1
- package/lib/plugins/seek-time/SeekTime.js +3 -4
- package/lib/plugins/subtitles/ClosedCaptions.js +1 -1
- package/lib/plugins/vast-ads/VastAds.js +1 -1
- package/lib/plugins/vast-ads/rollmanager.js +1 -1
- package/lib/testUtils.d.ts.map +1 -1
- package/lib/testUtils.js +7 -4
- package/lib/types.d.ts +1 -1
- package/package.json +3 -3
- package/src/playback/__tests__/HTML5Video.test.ts +2 -2
- package/src/playback/dash-playback/DashPlayback.ts +5 -7
- package/src/playback/hls-playback/HlsPlayback.ts +2 -4
- package/src/playback.types.ts +2 -3
- package/src/plugins/audio-selector/AudioSelector.ts +14 -7
- package/src/plugins/audio-selector/__tests__/AudioSelector.test.ts +8 -8
- package/src/plugins/audio-selector/__tests__/__snapshots__/AudioSelector.test.ts.snap +15 -15
- package/src/plugins/bottom-gear/BottomGear.ts +2 -2
- package/src/plugins/bottom-gear/__tests__/BottomGear.test.ts +8 -5
- package/src/plugins/bottom-gear/__tests__/__snapshots__/BottomGear.test.ts.snap +3 -3
- package/src/plugins/clappr-nerd-stats/NerdStats.ts +216 -143
- package/src/plugins/clappr-nerd-stats/formatter.ts +91 -47
- package/src/plugins/clappr-nerd-stats/speedtest/index.ts +2 -2
- package/src/plugins/clappr-nerd-stats/speedtest/types.ts +1 -1
- package/src/plugins/clappr-nerd-stats/types.ts +43 -3
- package/src/plugins/clappr-nerd-stats/utils.ts +75 -0
- package/src/plugins/clappr-stats/ClapprStats.ts +41 -40
- package/src/plugins/clappr-stats/__tests__/ClapprStats.test.ts +12 -12
- package/src/plugins/clappr-stats/types.ts +43 -44
- package/src/plugins/clappr-stats/utils.ts +4 -5
- package/src/plugins/click-to-pause/ClickToPause.ts +1 -1
- package/src/plugins/clips/Clips.ts +1 -1
- package/src/plugins/clips/__tests__/Clips.test.ts +1 -1
- package/src/plugins/clips/__tests__/__snapshots__/Clips.test.ts.snap +1 -1
- package/src/plugins/dvr-controls/DvrControls.ts +51 -37
- package/src/plugins/dvr-controls/__tests__/DvrControls.test.ts +84 -26
- package/src/plugins/dvr-controls/__tests__/__snapshots__/DvrControls.test.ts.snap +0 -12
- package/src/plugins/media-control/MediaControl.ts +21 -9
- package/src/plugins/media-control/__tests__/MediaControl.test.ts +8 -5
- package/src/plugins/media-control/__tests__/__snapshots__/MediaControl.test.ts.snap +20 -20
- package/src/plugins/picture-in-picture/PictureInPicture.ts +1 -1
- package/src/plugins/seek-time/SeekTime.ts +4 -5
- package/src/plugins/subtitles/ClosedCaptions.ts +1 -1
- package/src/plugins/subtitles/__tests__/ClosedCaptions.test.ts +1 -1
- package/src/plugins/vast-ads/VastAds.ts +1 -1
- package/src/plugins/vast-ads/rollmanager.ts +1 -1
- package/src/testUtils.ts +11 -5
- package/src/types.ts +1 -1
- package/temp/player.api.json +630 -12
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { UICorePlugin, Events, template } from '@clappr/core';
|
|
1
|
+
import { UICorePlugin, Events, template, Playback, } from '@clappr/core';
|
|
2
2
|
import { reportError, trace } from '@gcorevideo/utils';
|
|
3
3
|
import Mousetrap from 'mousetrap';
|
|
4
4
|
import assert from 'assert';
|
|
@@ -12,85 +12,25 @@ import pluginHtml from '../../../assets/clappr-nerd-stats/clappr-nerd-stats.ejs'
|
|
|
12
12
|
import buttonHtml from '../../../assets/clappr-nerd-stats/button.ejs';
|
|
13
13
|
import statsIcon from '../../../assets/icons/new/stats.svg';
|
|
14
14
|
import { GearEvents } from '../bottom-gear/BottomGear.js';
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
'
|
|
19
|
-
'
|
|
20
|
-
'
|
|
21
|
-
];
|
|
22
|
-
const getDownloadQuality = (speedValue) => {
|
|
23
|
-
if (speedValue < 3) {
|
|
24
|
-
return 1;
|
|
25
|
-
}
|
|
26
|
-
else if (speedValue < 7) {
|
|
27
|
-
return 2;
|
|
28
|
-
}
|
|
29
|
-
else if (speedValue < 13) {
|
|
30
|
-
return 3;
|
|
31
|
-
}
|
|
32
|
-
else if (speedValue < 25) {
|
|
33
|
-
return 4;
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
return 5;
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
const getPingQuality = (pingValue) => {
|
|
40
|
-
if (pingValue < 20) {
|
|
41
|
-
return 5;
|
|
42
|
-
}
|
|
43
|
-
else if (pingValue < 50) {
|
|
44
|
-
return 4;
|
|
45
|
-
}
|
|
46
|
-
else if (pingValue < 100) {
|
|
47
|
-
return 3;
|
|
48
|
-
}
|
|
49
|
-
else if (pingValue < 150) {
|
|
50
|
-
return 2;
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
return 1;
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
const generateQualityHtml = (quality) => {
|
|
57
|
-
const html = [];
|
|
58
|
-
const qualityClassName = qualityClasses[quality - 1];
|
|
59
|
-
for (let i = 0; i < qualityClasses.length; i++) {
|
|
60
|
-
if (i < quality) {
|
|
61
|
-
html.push(`<div class="speedtest-quality-content-item ${qualityClassName}"></div>`);
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
html.push('<div class="speedtest-quality-content-item"></div>');
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return html.join('');
|
|
68
|
-
};
|
|
69
|
-
const drawSummary = (customMetrics, vodContainer, liveContainer) => {
|
|
70
|
-
const { connectionSpeed, ping } = customMetrics;
|
|
71
|
-
if (!connectionSpeed || !ping) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
const downloadQuality = getDownloadQuality(connectionSpeed);
|
|
75
|
-
const pingQuality = getPingQuality(ping);
|
|
76
|
-
const liveQuality = Math.min(downloadQuality, pingQuality);
|
|
77
|
-
const vodHtml = generateQualityHtml(downloadQuality);
|
|
78
|
-
const liveHtml = generateQualityHtml(liveQuality);
|
|
79
|
-
vodContainer.html(vodHtml);
|
|
80
|
-
liveContainer.html(liveHtml);
|
|
15
|
+
import { drawSummary, getPingQuality } from './utils.js';
|
|
16
|
+
import { getDownloadQuality } from './utils.js';
|
|
17
|
+
const PLAYBACK_NAMES = {
|
|
18
|
+
dash: 'DASH.js',
|
|
19
|
+
hls: 'HLS.js',
|
|
20
|
+
html5_video: 'Native',
|
|
81
21
|
};
|
|
82
22
|
const T = 'plugins.nerd_stats';
|
|
83
23
|
/**
|
|
84
|
-
* `PLUGIN` that displays useful network
|
|
24
|
+
* `PLUGIN` that displays useful statistics regarding the playback as well as the network quality estimation.
|
|
85
25
|
* @beta
|
|
86
26
|
*
|
|
87
27
|
* @remarks
|
|
88
28
|
* Depends on:
|
|
89
29
|
*
|
|
90
|
-
* - {@link BottomGear}
|
|
91
|
-
*
|
|
92
|
-
* - {@link ClapprStats}
|
|
30
|
+
* - {@link BottomGear} - where the button is attached
|
|
93
31
|
*
|
|
32
|
+
* - {@link ClapprStats} - to get the metrics from
|
|
33
|
+
*
|
|
94
34
|
* The plugin is rendered as an item in the gear menu.
|
|
95
35
|
*
|
|
96
36
|
* When clicked, it shows an overlay window with the information about the network speed, latency, etc,
|
|
@@ -98,13 +38,13 @@ const T = 'plugins.nerd_stats';
|
|
|
98
38
|
*/
|
|
99
39
|
export class NerdStats extends UICorePlugin {
|
|
100
40
|
container = null;
|
|
101
|
-
|
|
41
|
+
speedtestMetrics = {
|
|
102
42
|
connectionSpeed: 0,
|
|
103
43
|
ping: 0,
|
|
104
44
|
jitter: 0,
|
|
105
45
|
};
|
|
106
46
|
metrics = newMetrics();
|
|
107
|
-
|
|
47
|
+
open = false;
|
|
108
48
|
shortcut;
|
|
109
49
|
iconPosition;
|
|
110
50
|
static buttonTemplate = template(buttonHtml);
|
|
@@ -126,7 +66,6 @@ export class NerdStats extends UICorePlugin {
|
|
|
126
66
|
*/
|
|
127
67
|
get attributes() {
|
|
128
68
|
return {
|
|
129
|
-
'data-clappr-nerd-stats': '',
|
|
130
69
|
class: 'clappr-nerd-stats',
|
|
131
70
|
};
|
|
132
71
|
}
|
|
@@ -135,13 +74,17 @@ export class NerdStats extends UICorePlugin {
|
|
|
135
74
|
*/
|
|
136
75
|
get events() {
|
|
137
76
|
return {
|
|
138
|
-
|
|
139
|
-
'click
|
|
140
|
-
'click
|
|
77
|
+
click: 'clicked',
|
|
78
|
+
'click #nerd-stats-close': 'hide',
|
|
79
|
+
'click #nerd-stats-refresh': 'refreshSpeedTest',
|
|
141
80
|
};
|
|
142
81
|
}
|
|
82
|
+
clicked(e) {
|
|
83
|
+
e.stopPropagation();
|
|
84
|
+
e.preventDefault();
|
|
85
|
+
}
|
|
143
86
|
get statsBoxElem() {
|
|
144
|
-
return '
|
|
87
|
+
return this.$el.find('#nerd-stats-box');
|
|
145
88
|
}
|
|
146
89
|
get statsBoxWidthThreshold() {
|
|
147
90
|
return 720;
|
|
@@ -160,7 +103,7 @@ export class NerdStats extends UICorePlugin {
|
|
|
160
103
|
];
|
|
161
104
|
this.iconPosition =
|
|
162
105
|
core.options.clapprNerdStats?.iconPosition ?? 'bottom-right';
|
|
163
|
-
this.
|
|
106
|
+
this.speedtestMetrics = {
|
|
164
107
|
connectionSpeed: 0,
|
|
165
108
|
ping: 0,
|
|
166
109
|
jitter: 0,
|
|
@@ -172,20 +115,38 @@ export class NerdStats extends UICorePlugin {
|
|
|
172
115
|
*/
|
|
173
116
|
bindEvents() {
|
|
174
117
|
this.listenToOnce(this.core, Events.CORE_READY, this.onCoreReady);
|
|
118
|
+
this.listenTo(this.core, Events.CORE_RESIZE, this.onPlayerResize);
|
|
119
|
+
this.listenTo(this.core, Events.CORE_ACTIVE_CONTAINER_CHANGED, this.onActiveContainerChanged);
|
|
175
120
|
}
|
|
176
121
|
onCoreReady() {
|
|
177
122
|
const bottomGear = this.core.getPlugin('bottom_gear');
|
|
178
123
|
assert(bottomGear, 'bottom_gear plugin is required');
|
|
179
|
-
this.listenTo(bottomGear, GearEvents.RENDERED, this.
|
|
124
|
+
this.listenTo(bottomGear, GearEvents.RENDERED, this.attach);
|
|
125
|
+
Mousetrap.bind(this.shortcut, this.toggle);
|
|
126
|
+
this.updateResolution();
|
|
127
|
+
}
|
|
128
|
+
onActiveContainerChanged() {
|
|
180
129
|
this.container = this.core.activeContainer;
|
|
181
130
|
const clapprStats = this.container?.getPlugin('clappr_stats');
|
|
182
131
|
assert(clapprStats, 'clappr-stats not available. Please, include it as a plugin of your Clappr instance.\n' +
|
|
183
132
|
'For more info, visit: https://github.com/clappr/clappr-stats.');
|
|
184
|
-
Mousetrap.bind(this.shortcut, this.toggle);
|
|
185
|
-
this.listenTo(this.core, Events.CORE_RESIZE, this.onPlayerResize);
|
|
186
133
|
this.listenTo(clapprStats, ClapprStatsEvents.REPORT, this.updateMetrics);
|
|
134
|
+
this.listenTo(this.core.activeContainer, Events.CONTAINER_VOLUME, () => {
|
|
135
|
+
this.metrics.general.volume = this.container?.volume ?? 0;
|
|
136
|
+
this.$el
|
|
137
|
+
.find('#nerd-stats-volume')
|
|
138
|
+
.text(Formatter.formatVolume(this.metrics.general.volume));
|
|
139
|
+
});
|
|
140
|
+
this.listenTo(this.core.activePlayback, Events.PLAYBACK_LOADEDMETADATA, () => {
|
|
141
|
+
this.$el
|
|
142
|
+
.find('#nerd-stats-playback-type')
|
|
143
|
+
.text(this.formatPlaybackName(this.core.activePlayback.getPlaybackType()));
|
|
144
|
+
});
|
|
187
145
|
this.updateMetrics(clapprStats.exportMetrics());
|
|
188
|
-
this
|
|
146
|
+
this.$el
|
|
147
|
+
.find('#nerd-stats-playback-name')
|
|
148
|
+
.text(PLAYBACK_NAMES[this.core.activePlayback.name] ?? '-');
|
|
149
|
+
this.core.activeContainer.$el.append(this.$el);
|
|
189
150
|
}
|
|
190
151
|
/**
|
|
191
152
|
* @internal
|
|
@@ -195,7 +156,7 @@ export class NerdStats extends UICorePlugin {
|
|
|
195
156
|
return super.destroy();
|
|
196
157
|
}
|
|
197
158
|
toggle = () => {
|
|
198
|
-
if (this.
|
|
159
|
+
if (this.open) {
|
|
199
160
|
this.hide();
|
|
200
161
|
}
|
|
201
162
|
else {
|
|
@@ -203,10 +164,11 @@ export class NerdStats extends UICorePlugin {
|
|
|
203
164
|
}
|
|
204
165
|
};
|
|
205
166
|
show() {
|
|
206
|
-
this
|
|
207
|
-
this.
|
|
167
|
+
this.$el.show();
|
|
168
|
+
this.statsBoxElem.scrollTop(this.statsBoxElem.scrollTop());
|
|
169
|
+
this.open = true;
|
|
208
170
|
this.refreshSpeedTest();
|
|
209
|
-
initSpeedTest(this.
|
|
171
|
+
initSpeedTest(this.speedtestMetrics)
|
|
210
172
|
.then(() => {
|
|
211
173
|
startSpeedtest();
|
|
212
174
|
})
|
|
@@ -216,21 +178,28 @@ export class NerdStats extends UICorePlugin {
|
|
|
216
178
|
});
|
|
217
179
|
}
|
|
218
180
|
hide() {
|
|
219
|
-
this
|
|
220
|
-
this.
|
|
181
|
+
this.$el.hide();
|
|
182
|
+
this.open = false;
|
|
221
183
|
stopSpeedtest();
|
|
222
184
|
}
|
|
223
185
|
onPlayerResize() {
|
|
224
186
|
this.setStatsBoxSize();
|
|
187
|
+
this.updateResolution();
|
|
225
188
|
}
|
|
226
|
-
|
|
227
|
-
this.metrics.general = {
|
|
228
|
-
|
|
229
|
-
|
|
189
|
+
updateResolution() {
|
|
190
|
+
this.metrics.general.resolution = {
|
|
191
|
+
width: this.playerWidth,
|
|
192
|
+
height: this.playerHeight,
|
|
230
193
|
};
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
194
|
+
this.$el
|
|
195
|
+
.find('#nerd-stats-resolution-width')
|
|
196
|
+
.text(this.metrics.general.resolution.width);
|
|
197
|
+
this.$el
|
|
198
|
+
.find('#nerd-stats-resolution-height')
|
|
199
|
+
.text(this.metrics.general.resolution.height);
|
|
200
|
+
}
|
|
201
|
+
estimateQuality() {
|
|
202
|
+
trace(`${T} estimateQuality`);
|
|
234
203
|
const videoQualityNames = [
|
|
235
204
|
'SD (480p)',
|
|
236
205
|
'HD (720p)',
|
|
@@ -238,9 +207,9 @@ export class NerdStats extends UICorePlugin {
|
|
|
238
207
|
'2K (1440p)',
|
|
239
208
|
'4K (2160p)',
|
|
240
209
|
];
|
|
241
|
-
const { connectionSpeed, ping } = this.
|
|
210
|
+
const { connectionSpeed, ping } = this.speedtestMetrics;
|
|
242
211
|
if (!connectionSpeed || !ping) {
|
|
243
|
-
const calculatingText = '
|
|
212
|
+
const calculatingText = this.core.i18n.t('stats.calculating');
|
|
244
213
|
this.metrics.custom.vodQuality = calculatingText;
|
|
245
214
|
this.metrics.custom.liveQuality = calculatingText;
|
|
246
215
|
return;
|
|
@@ -255,44 +224,109 @@ export class NerdStats extends UICorePlugin {
|
|
|
255
224
|
prefix + videoQualityNames[liveQuality - 1];
|
|
256
225
|
}
|
|
257
226
|
updateMetrics(metrics) {
|
|
227
|
+
trace(`${T} updateMetrics`, { custom: this.speedtestMetrics });
|
|
258
228
|
Object.assign(this.metrics, metrics);
|
|
259
|
-
this.
|
|
260
|
-
this
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
229
|
+
this.updateEstimatedQuality();
|
|
230
|
+
this.$el
|
|
231
|
+
.find('#nerd-stats-current-time')
|
|
232
|
+
.text(Formatter.formatTime(this.metrics.extra.currentTime));
|
|
233
|
+
this.$el
|
|
234
|
+
.find('#nerd-stats-video-duration')
|
|
235
|
+
.text(Formatter.formatTime(this.metrics.extra.duration));
|
|
236
|
+
this.$el
|
|
237
|
+
.find('#nerd-stats-buffer-size')
|
|
238
|
+
.text(Formatter.formatTime(this.metrics.extra.buffersize));
|
|
239
|
+
this.$el
|
|
240
|
+
.find('#nerd-stats-bitrate-weighted-mean')
|
|
241
|
+
.text(Formatter.formatBitrate(this.metrics.extra.bitrateWeightedMean));
|
|
242
|
+
this.$el
|
|
243
|
+
.find('#nerd-stats-bitrate-most-used')
|
|
244
|
+
.text(Formatter.formatBitrate(this.metrics.extra.bitrateMostUsed));
|
|
245
|
+
this.$el
|
|
246
|
+
.find('#nerd-stats-watched-percentage')
|
|
247
|
+
.text(Formatter.formatPercentage(this.metrics.extra.watchedPercentage));
|
|
248
|
+
this.$el
|
|
249
|
+
.find('#nerd-stats-buffering-percentage')
|
|
250
|
+
.text(Formatter.formatPercentage(this.metrics.extra.bufferingPercentage));
|
|
251
|
+
this.$el
|
|
252
|
+
.find('#nerd-stats-startup-time')
|
|
253
|
+
.text(Formatter.formatTime(this.metrics.chrono.startup));
|
|
254
|
+
this.$el
|
|
255
|
+
.find('#nerd-stats-watch-time')
|
|
256
|
+
.text(Formatter.formatTime(this.metrics.chrono.watch));
|
|
257
|
+
this.$el
|
|
258
|
+
.find('#nerd-stats-pause-time')
|
|
259
|
+
.text(Formatter.formatTime(this.metrics.chrono.pause));
|
|
260
|
+
this.$el
|
|
261
|
+
.find('#nerd-stats-buffering-time')
|
|
262
|
+
.text(Formatter.formatTime(this.metrics.chrono.buffering));
|
|
263
|
+
this.$el
|
|
264
|
+
.find('#nerd-stats-session-time')
|
|
265
|
+
.text(Formatter.formatTime(this.metrics.chrono.session));
|
|
266
|
+
this.$el.find('#nerd-stats-plays').text(this.metrics.counters.play);
|
|
267
|
+
this.$el.find('#nerd-stats-pauses').text(this.metrics.counters.pause);
|
|
268
|
+
this.$el.find('#nerd-stats-errors').text(this.metrics.counters.error);
|
|
269
|
+
this.$el
|
|
270
|
+
.find('#nerd-stats-bufferings')
|
|
271
|
+
.text(this.metrics.counters.buffering);
|
|
272
|
+
this.$el
|
|
273
|
+
.find('#nerd-stats-decoded-frames')
|
|
274
|
+
.text(this.metrics.counters.decodedFrames);
|
|
275
|
+
this.$el
|
|
276
|
+
.find('#nerd-stats-dropped-frames')
|
|
277
|
+
.text(this.metrics.counters.droppedFrames);
|
|
278
|
+
this.$el
|
|
279
|
+
.find('#nerd-stats-bitrate-changes')
|
|
280
|
+
.text(this.metrics.counters.changeLevel);
|
|
281
|
+
this.$el.find('#nerd-stats-seeks').text(this.metrics.counters.seek);
|
|
282
|
+
this.$el
|
|
283
|
+
.find('#nerd-stats-fullscreen')
|
|
284
|
+
.text(this.metrics.counters.fullscreen);
|
|
285
|
+
this.$el.find('#nerd-stats-dvr-usage').text(this.metrics.counters.dvrUsage);
|
|
286
|
+
this.$el
|
|
287
|
+
.find('#nerd-stats-fps')
|
|
288
|
+
.text(Formatter.formatFps(this.metrics.counters.fps));
|
|
266
289
|
this.setStatsBoxSize();
|
|
267
290
|
drawSpeedTestResults();
|
|
268
|
-
drawSummary(this.
|
|
269
|
-
|
|
270
|
-
if (!this.showing) {
|
|
291
|
+
drawSummary(this.speedtestMetrics, this.$el.find('#nerd-stats-quality-vod'), this.$el.find('#nerd-stats-quality-live'));
|
|
292
|
+
if (!this.open) {
|
|
271
293
|
this.hide();
|
|
272
294
|
}
|
|
273
295
|
}
|
|
296
|
+
updateEstimatedQuality() {
|
|
297
|
+
this.estimateQuality();
|
|
298
|
+
this.$el
|
|
299
|
+
.find('#nerd-stats-quality-vod-text')
|
|
300
|
+
.html(this.metrics.custom.vodQuality);
|
|
301
|
+
this.$el
|
|
302
|
+
.find('#nerd-stats-quality-live-text')
|
|
303
|
+
.html(this.metrics.custom.liveQuality);
|
|
304
|
+
}
|
|
274
305
|
setStatsBoxSize() {
|
|
275
306
|
if (this.playerWidth >= this.statsBoxWidthThreshold) {
|
|
276
|
-
this
|
|
277
|
-
this
|
|
307
|
+
this.statsBoxElem.addClass('wide');
|
|
308
|
+
this.statsBoxElem.removeClass('narrow');
|
|
278
309
|
}
|
|
279
310
|
else {
|
|
280
|
-
this
|
|
281
|
-
this
|
|
311
|
+
this.statsBoxElem.removeClass('wide');
|
|
312
|
+
this.statsBoxElem.addClass('narrow');
|
|
282
313
|
}
|
|
283
314
|
}
|
|
284
315
|
/**
|
|
285
316
|
* @internal
|
|
286
317
|
*/
|
|
287
318
|
render() {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
319
|
+
this.$el
|
|
320
|
+
.html(NerdStats.template({
|
|
321
|
+
metrics: Formatter.format(this.metrics ?? newMetrics()),
|
|
322
|
+
iconPosition: this.iconPosition,
|
|
323
|
+
i18n: this.core.i18n,
|
|
324
|
+
}))
|
|
325
|
+
.hide();
|
|
292
326
|
return this;
|
|
293
327
|
}
|
|
294
|
-
|
|
295
|
-
trace(`${T}
|
|
328
|
+
attach() {
|
|
329
|
+
trace(`${T} attach`);
|
|
296
330
|
const gear = this.core.getPlugin('bottom_gear');
|
|
297
331
|
gear
|
|
298
332
|
.addItem('nerd_stats')
|
|
@@ -305,11 +339,11 @@ export class NerdStats extends UICorePlugin {
|
|
|
305
339
|
this.toggle();
|
|
306
340
|
});
|
|
307
341
|
}
|
|
308
|
-
|
|
342
|
+
clearSpeedtestMetrics() {
|
|
309
343
|
const clapprStats = this.container?.getPlugin('clappr_stats');
|
|
310
|
-
this.
|
|
311
|
-
this.
|
|
312
|
-
this.
|
|
344
|
+
this.speedtestMetrics.connectionSpeed = 0;
|
|
345
|
+
this.speedtestMetrics.ping = 0;
|
|
346
|
+
this.speedtestMetrics.jitter = 0;
|
|
313
347
|
if (clapprStats) {
|
|
314
348
|
this.updateMetrics(clapprStats.exportMetrics());
|
|
315
349
|
}
|
|
@@ -317,7 +351,7 @@ export class NerdStats extends UICorePlugin {
|
|
|
317
351
|
refreshSpeedTest() {
|
|
318
352
|
stopSpeedtest();
|
|
319
353
|
setTimeout(() => {
|
|
320
|
-
this.
|
|
354
|
+
this.clearSpeedtestMetrics();
|
|
321
355
|
clearSpeedTestResults();
|
|
322
356
|
drawSpeedTestResults();
|
|
323
357
|
}, 200);
|
|
@@ -325,11 +359,28 @@ export class NerdStats extends UICorePlugin {
|
|
|
325
359
|
startSpeedtest();
|
|
326
360
|
}, 800);
|
|
327
361
|
}
|
|
362
|
+
formatPlaybackName(playbackType) {
|
|
363
|
+
switch (playbackType) {
|
|
364
|
+
case Playback.VOD:
|
|
365
|
+
return this.core.i18n.t('vod');
|
|
366
|
+
case Playback.LIVE:
|
|
367
|
+
return this.core.i18n.t('live');
|
|
368
|
+
default:
|
|
369
|
+
return '-';
|
|
370
|
+
}
|
|
371
|
+
}
|
|
328
372
|
}
|
|
329
373
|
function newMetrics() {
|
|
330
374
|
return {
|
|
331
375
|
...newBaseMetrics(),
|
|
332
|
-
general: {
|
|
376
|
+
general: {
|
|
377
|
+
displayResolution: '',
|
|
378
|
+
resolution: {
|
|
379
|
+
width: 0,
|
|
380
|
+
height: 0,
|
|
381
|
+
},
|
|
382
|
+
volume: 0,
|
|
383
|
+
},
|
|
333
384
|
custom: {
|
|
334
385
|
connectionSpeed: 0,
|
|
335
386
|
ping: 0,
|
|
@@ -3,6 +3,11 @@ type MetricsValue = number | string;
|
|
|
3
3
|
type Metrics = Partial<Record<MetricsKind, Partial<Record<MetricsType, MetricsValue>>>>;
|
|
4
4
|
export default class Formatter {
|
|
5
5
|
static format(metrics: Metrics): Metrics;
|
|
6
|
+
static formatVolume(volume: number): string;
|
|
7
|
+
static formatTime(time: number): string;
|
|
8
|
+
static formatFps(fps: number): string;
|
|
9
|
+
static formatPercentage(percentage: number): string;
|
|
10
|
+
static formatBitrate(bitrate: number): string;
|
|
6
11
|
}
|
|
7
12
|
export {};
|
|
8
13
|
//# sourceMappingURL=formatter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../../../src/plugins/clappr-nerd-stats/formatter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../../../src/plugins/clappr-nerd-stats/formatter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAc,WAAW,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAsFnE,KAAK,YAAY,GAAG,MAAM,GAAG,MAAM,CAAA;AACnC,KAAK,OAAO,GAAG,OAAO,CACpB,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC,CAChE,CAAA;AAED,MAAM,CAAC,OAAO,OAAO,SAAS;IAC5B,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO;IAkCxC,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAI3C,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAMvC,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAIrC,MAAM,CAAC,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAMnD,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;CAK9C"}
|
|
@@ -3,63 +3,72 @@ const timeScale = new humanFormat.Scale({
|
|
|
3
3
|
ms: 1,
|
|
4
4
|
sec: 1000,
|
|
5
5
|
min: 60000,
|
|
6
|
-
hours: 3600000
|
|
6
|
+
hours: 3600000,
|
|
7
7
|
});
|
|
8
8
|
const percentScale = new humanFormat.Scale({
|
|
9
|
-
'%': 1
|
|
9
|
+
'%': 1,
|
|
10
10
|
});
|
|
11
|
+
const metricTemplates = {
|
|
12
|
+
fps: {
|
|
13
|
+
scale: 'SI',
|
|
14
|
+
decimals: 0,
|
|
15
|
+
},
|
|
16
|
+
volume: {
|
|
17
|
+
scale: percentScale,
|
|
18
|
+
},
|
|
19
|
+
};
|
|
11
20
|
const formattingTemplate = {
|
|
12
21
|
general: {
|
|
13
22
|
volume: {
|
|
14
|
-
scale: percentScale
|
|
15
|
-
}
|
|
23
|
+
scale: percentScale,
|
|
24
|
+
},
|
|
16
25
|
},
|
|
17
26
|
timers: {
|
|
18
27
|
startup: {
|
|
19
|
-
scale: timeScale
|
|
28
|
+
scale: timeScale,
|
|
20
29
|
},
|
|
21
30
|
watch: {
|
|
22
|
-
scale: timeScale
|
|
31
|
+
scale: timeScale,
|
|
23
32
|
},
|
|
24
33
|
pause: {
|
|
25
|
-
scale: timeScale
|
|
34
|
+
scale: timeScale,
|
|
26
35
|
},
|
|
27
36
|
buffering: {
|
|
28
|
-
scale: timeScale
|
|
37
|
+
scale: timeScale,
|
|
29
38
|
},
|
|
30
39
|
session: {
|
|
31
|
-
scale: timeScale
|
|
40
|
+
scale: timeScale,
|
|
32
41
|
},
|
|
33
42
|
latency: {
|
|
34
|
-
scale: timeScale
|
|
35
|
-
}
|
|
43
|
+
scale: timeScale,
|
|
44
|
+
},
|
|
36
45
|
},
|
|
37
46
|
extra: {
|
|
38
47
|
buffersize: {
|
|
39
|
-
scale: timeScale
|
|
48
|
+
scale: timeScale,
|
|
40
49
|
},
|
|
41
50
|
duration: {
|
|
42
|
-
scale: timeScale
|
|
51
|
+
scale: timeScale,
|
|
43
52
|
},
|
|
44
53
|
currentTime: {
|
|
45
|
-
scale: timeScale
|
|
54
|
+
scale: timeScale,
|
|
46
55
|
},
|
|
47
56
|
bitrateWeightedMean: {
|
|
48
|
-
unit: 'bps'
|
|
57
|
+
unit: 'bps',
|
|
49
58
|
},
|
|
50
59
|
bitrateMostUsed: {
|
|
51
|
-
unit: 'bps'
|
|
60
|
+
unit: 'bps',
|
|
52
61
|
},
|
|
53
62
|
bandwidth: {
|
|
54
|
-
unit: 'bps'
|
|
63
|
+
unit: 'bps',
|
|
55
64
|
},
|
|
56
65
|
watchedPercentage: {
|
|
57
|
-
scale: percentScale
|
|
66
|
+
scale: percentScale,
|
|
58
67
|
},
|
|
59
68
|
bufferingPercentage: {
|
|
60
|
-
scale: percentScale
|
|
61
|
-
}
|
|
62
|
-
}
|
|
69
|
+
scale: percentScale,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
63
72
|
};
|
|
64
73
|
export default class Formatter {
|
|
65
74
|
static format(metrics) {
|
|
@@ -69,8 +78,10 @@ export default class Formatter {
|
|
|
69
78
|
formattedMetrics[type] = fmt;
|
|
70
79
|
const typeTemplate = formattingTemplate[type];
|
|
71
80
|
Object.entries(mm).forEach(([name, value]) => {
|
|
72
|
-
|
|
73
|
-
|
|
81
|
+
if (typeTemplate &&
|
|
82
|
+
typeTemplate[name] &&
|
|
83
|
+
typeof value === 'number' &&
|
|
84
|
+
!isNaN(value)) {
|
|
74
85
|
// @ts-ignore
|
|
75
86
|
const templateScale = typeTemplate[name].scale || 'SI';
|
|
76
87
|
// @ts-ignore
|
|
@@ -78,7 +89,7 @@ export default class Formatter {
|
|
|
78
89
|
fmt[name] = humanFormat(value, {
|
|
79
90
|
scale: templateScale,
|
|
80
91
|
unit: templateUnit,
|
|
81
|
-
decimals: 2
|
|
92
|
+
decimals: 2,
|
|
82
93
|
});
|
|
83
94
|
}
|
|
84
95
|
else {
|
|
@@ -88,4 +99,25 @@ export default class Formatter {
|
|
|
88
99
|
});
|
|
89
100
|
return formattedMetrics;
|
|
90
101
|
}
|
|
102
|
+
static formatVolume(volume) {
|
|
103
|
+
return humanFormat(volume, metricTemplates.volume);
|
|
104
|
+
}
|
|
105
|
+
static formatTime(time) {
|
|
106
|
+
return humanFormat(time, {
|
|
107
|
+
scale: timeScale,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
static formatFps(fps) {
|
|
111
|
+
return humanFormat(fps, metricTemplates.fps);
|
|
112
|
+
}
|
|
113
|
+
static formatPercentage(percentage) {
|
|
114
|
+
return humanFormat(percentage, {
|
|
115
|
+
scale: percentScale,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
static formatBitrate(bitrate) {
|
|
119
|
+
return humanFormat(bitrate, {
|
|
120
|
+
unit: 'bps',
|
|
121
|
+
});
|
|
122
|
+
}
|
|
91
123
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Server } from './Speedtest.js';
|
|
2
|
-
import {
|
|
2
|
+
import { SpeedtestMetrics } from './types.js';
|
|
3
3
|
export declare function drawSpeedTestResults(): void;
|
|
4
|
-
export declare const initSpeedTest: (customMetrics:
|
|
4
|
+
export declare const initSpeedTest: (customMetrics: SpeedtestMetrics) => Promise<void>;
|
|
5
5
|
export declare const stopSpeedtest: () => void;
|
|
6
6
|
export declare const startSpeedtest: () => void;
|
|
7
7
|
export declare const clearSpeedTestResults: () => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/plugins/clappr-nerd-stats/speedtest/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAkC,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/plugins/clappr-nerd-stats/speedtest/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAkC,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAqC9C,wBAAgB,oBAAoB,SAmBnC;AAID,eAAO,MAAM,aAAa,kBAAmB,gBAAgB,KAAG,OAAO,CAAC,IAAI,CA+E3E,CAAC;AAEF,eAAO,MAAM,aAAa,YAIzB,CAAC;AAEF,eAAO,MAAM,cAAc,YAI1B,CAAC;AAEF,eAAO,MAAM,qBAAqB,YAEjC,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,QAGnD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/plugins/clappr-nerd-stats/speedtest/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/plugins/clappr-nerd-stats/speedtest/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;CACzB,CAAA"}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export type MetricsKind = 'general' | 'timers' | 'extra';
|
|
2
|
+
export type MetricType = 'general' | 'timers' | 'extra';
|
|
2
3
|
export type MetricsType = 'volume' | 'startup' | 'watch' | 'pause' | 'buffering' | 'session' | 'latency' | 'buffersize' | 'duration' | 'currentTime' | 'bitrateWeightedMean' | 'bitrateMostUsed' | 'bandwidth' | 'watchedPercentage' | 'bufferingPercentage';
|
|
4
|
+
export type MetricName = 'volume' | 'startup' | 'watch' | 'pause' | 'buffering' | 'session' | 'latency' | 'buffersize' | 'duration' | 'currentTime' | 'bitrateWeightedMean' | 'bitrateMostUsed' | 'bandwidth' | 'watchedPercentage' | 'bufferingPercentage' | 'fps';
|
|
5
|
+
export type MetricKind = 'volume' | 'time' | 'precisetime' | 'percentage' | 'bitrate' | 'bandwidth';
|
|
3
6
|
export type MetricsValue = number;
|
|
4
7
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/plugins/clappr-nerd-stats/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/plugins/clappr-nerd-stats/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAA;AACxD,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAA;AACvD,MAAM,MAAM,WAAW,GACnB,QAAQ,GACR,SAAS,GACT,OAAO,GACP,OAAO,GACP,WAAW,GACX,SAAS,GACT,SAAS,GACT,YAAY,GACZ,UAAU,GACV,aAAa,GACb,qBAAqB,GACrB,iBAAiB,GACjB,WAAW,GACX,mBAAmB,GACnB,qBAAqB,CAAA;AACzB,MAAM,MAAM,UAAU,GAClB,QAAQ,GACR,SAAS,GACT,OAAO,GACP,OAAO,GACP,WAAW,GACX,SAAS,GACT,SAAS,GACT,YAAY,GACZ,UAAU,GACV,aAAa,GACb,qBAAqB,GACrB,iBAAiB,GACjB,WAAW,GACX,mBAAmB,GACnB,qBAAqB,GACrB,KAAK,CAAA;AACT,MAAM,MAAM,UAAU,GAClB,QAAQ,GACR,MAAM,GACN,aAAa,GACb,YAAY,GACZ,SAAS,GACT,WAAW,CAAA;AACf,MAAM,MAAM,YAAY,GAAG,MAAM,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ZeptoResult } from "../../types";
|
|
2
|
+
import { SpeedtestMetrics } from "./speedtest/types";
|
|
3
|
+
export declare const getDownloadQuality: (speedValue: number) => number;
|
|
4
|
+
export declare const getPingQuality: (pingValue: number) => number;
|
|
5
|
+
export declare const generateQualityHtml: (quality: number) => string;
|
|
6
|
+
export declare const drawSummary: (customMetrics: SpeedtestMetrics, vodContainer: ZeptoResult, liveContainer: ZeptoResult) => void;
|
|
7
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/plugins/clappr-nerd-stats/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAUpD,eAAO,MAAM,kBAAkB,eAAgB,MAAM,KAAG,MAYvD,CAAA;AAED,eAAO,MAAM,cAAc,cAAe,MAAM,KAAG,MAYlD,CAAA;AAED,eAAO,MAAM,mBAAmB,YAAa,MAAM,KAAG,MAerD,CAAA;AAED,eAAO,MAAM,WAAW,kBACP,gBAAgB,gBACjB,WAAW,iBACV,WAAW,SAe3B,CAAA"}
|