@gcorevideo/player 2.22.16 → 2.22.18
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/clappr-nerd-stats/clappr-nerd-stats.ejs +76 -78
- package/assets/clappr-nerd-stats/clappr-nerd-stats.scss +10 -7
- package/dist/core.js +10 -14
- package/dist/index.css +1441 -1440
- package/dist/index.js +589 -522
- package/dist/player.d.ts +216 -159
- package/dist/plugins/index.css +1204 -1203
- package/dist/plugins/index.js +581 -506
- package/docs/api/player.clapprstats.exportmetrics.md +1 -1
- package/docs/api/player.clapprstats.md +5 -15
- package/docs/api/player.clapprstatssettings.md +13 -0
- package/docs/api/player.clips.destroy.md +18 -0
- package/docs/api/player.clips.disable.md +18 -0
- package/docs/api/player.clips.enable.md +18 -0
- package/docs/api/player.clips.md +170 -0
- package/docs/api/player.clips.render.md +18 -0
- package/docs/api/player.clips.supportedversion.md +16 -0
- package/docs/api/player.clips.version.md +14 -0
- package/docs/api/player.clipspluginsettings.md +2 -2
- package/docs/api/player.clipspluginsettings.text.md +1 -1
- package/docs/api/player.md +27 -18
- package/docs/api/player.mediacontrol.md +1 -1
- package/docs/api/{player.mediacontrol.getelement.md → player.mediacontrol.mount.md} +20 -7
- package/docs/api/player.mediacontrolleftelement.md +1 -1
- package/docs/api/{player.clapprnerdstats._constructor_.md → player.nerdstats._constructor_.md} +3 -3
- package/docs/api/{player.clapprnerdstats.md → player.nerdstats.md} +5 -5
- package/docs/api/player.qualitylevel.height.md +1 -1
- package/docs/api/player.qualitylevel.level.md +1 -1
- package/docs/api/player.qualitylevel.md +4 -4
- package/docs/api/player.qualitylevel.width.md +1 -1
- package/docs/api/player.timeposition.current.md +1 -1
- package/docs/api/player.timeposition.md +2 -2
- package/docs/api/player.timeposition.total.md +1 -1
- package/docs/api/player.timeprogress.md +6 -4
- package/docs/api/player.timevalue.md +1 -1
- package/lib/index.plugins.d.ts +2 -1
- package/lib/index.plugins.d.ts.map +1 -1
- package/lib/index.plugins.js +2 -1
- 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 +9 -12
- 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 +24 -12
- package/lib/playback.types.d.ts.map +1 -1
- package/lib/plugins/clappr-nerd-stats/ClapprNerdStats.d.ts +4 -0
- package/lib/plugins/clappr-nerd-stats/ClapprNerdStats.d.ts.map +1 -1
- package/lib/plugins/clappr-nerd-stats/ClapprNerdStats.js +20 -23
- package/lib/plugins/clappr-nerd-stats/NerdStats.d.ts +86 -0
- package/lib/plugins/clappr-nerd-stats/NerdStats.d.ts.map +1 -0
- package/lib/plugins/clappr-nerd-stats/NerdStats.js +390 -0
- 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 +27 -32
- package/lib/plugins/clappr-stats/ClapprStats.d.ts.map +1 -1
- package/lib/plugins/clappr-stats/ClapprStats.js +94 -202
- package/lib/plugins/clappr-stats/types.d.ts +65 -25
- package/lib/plugins/clappr-stats/types.d.ts.map +1 -1
- package/lib/plugins/clappr-stats/types.js +37 -2
- package/lib/plugins/clappr-stats/utils.d.ts +1 -1
- package/lib/plugins/clappr-stats/utils.d.ts.map +1 -1
- package/lib/plugins/clappr-stats/utils.js +1 -3
- 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/testUtils.d.ts +2 -1
- package/lib/testUtils.d.ts.map +1 -1
- package/lib/testUtils.js +3 -2
- package/package.json +1 -1
- package/src/index.plugins.ts +2 -1
- package/src/playback/dash-playback/DashPlayback.ts +10 -15
- package/src/playback/hls-playback/HlsPlayback.ts +2 -4
- package/src/playback.types.ts +25 -11
- package/src/plugins/clappr-nerd-stats/NerdStats.ts +503 -0
- 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 +242 -306
- package/src/plugins/clappr-stats/__tests__/ClapprStats.test.ts +133 -0
- package/src/plugins/clappr-stats/types.ts +93 -47
- package/src/plugins/clappr-stats/utils.ts +4 -6
- package/src/plugins/error-screen/__tests__/ErrorScreen.test.ts +3 -4
- package/src/plugins/seek-time/SeekTime.ts +4 -5
- package/src/plugins/subtitles/__tests__/ClosedCaptions.test.ts +1 -0
- package/src/testUtils.ts +3 -2
- package/temp/player.api.json +311 -159
- package/tsconfig.tsbuildinfo +1 -1
- package/docs/api/player.clapprstats.setupdatemetrics.md +0 -56
- package/docs/api/player.clipsplugin.gettext.md +0 -58
- package/docs/api/player.clipsplugin.md +0 -59
- package/src/plugins/clappr-nerd-stats/ClapprNerdStats.ts +0 -435
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
import {
|
|
2
|
+
UICorePlugin,
|
|
3
|
+
Events,
|
|
4
|
+
template,
|
|
5
|
+
Core,
|
|
6
|
+
Container,
|
|
7
|
+
Playback,
|
|
8
|
+
} from '@clappr/core'
|
|
9
|
+
import { reportError, trace } from '@gcorevideo/utils'
|
|
10
|
+
import Mousetrap from 'mousetrap'
|
|
11
|
+
import assert from 'assert'
|
|
12
|
+
|
|
13
|
+
import { CLAPPR_VERSION } from '../../build.js'
|
|
14
|
+
import {
|
|
15
|
+
ClapprStatsEvents,
|
|
16
|
+
Metrics as PerfMetrics,
|
|
17
|
+
} from '../clappr-stats/types.js'
|
|
18
|
+
import { newMetrics as newBaseMetrics } from '../clappr-stats/utils.js'
|
|
19
|
+
import Formatter from './formatter.js'
|
|
20
|
+
import {
|
|
21
|
+
clearSpeedTestResults,
|
|
22
|
+
configureSpeedTest,
|
|
23
|
+
drawSpeedTestResults,
|
|
24
|
+
initSpeedTest,
|
|
25
|
+
startSpeedtest,
|
|
26
|
+
stopSpeedtest,
|
|
27
|
+
} from './speedtest/index.js'
|
|
28
|
+
import { SpeedtestMetrics } from './speedtest/types.js'
|
|
29
|
+
import { PlaybackType } from '../../types.js'
|
|
30
|
+
|
|
31
|
+
import '../../../assets/clappr-nerd-stats/clappr-nerd-stats.scss'
|
|
32
|
+
import pluginHtml from '../../../assets/clappr-nerd-stats/clappr-nerd-stats.ejs'
|
|
33
|
+
import buttonHtml from '../../../assets/clappr-nerd-stats/button.ejs'
|
|
34
|
+
import statsIcon from '../../../assets/icons/new/stats.svg'
|
|
35
|
+
import { BottomGear, GearEvents } from '../bottom-gear/BottomGear.js'
|
|
36
|
+
import { drawSummary, getPingQuality } from './utils.js'
|
|
37
|
+
import { getDownloadQuality } from './utils.js'
|
|
38
|
+
|
|
39
|
+
const PLAYBACK_NAMES: Record<string, string> = {
|
|
40
|
+
dash: 'DASH.js',
|
|
41
|
+
hls: 'HLS.js',
|
|
42
|
+
html5_video: 'Native',
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type IconPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
|
|
46
|
+
|
|
47
|
+
type Metrics = PerfMetrics & {
|
|
48
|
+
general: {
|
|
49
|
+
displayResolution?: string
|
|
50
|
+
resolution: {
|
|
51
|
+
width: number
|
|
52
|
+
height: number
|
|
53
|
+
}
|
|
54
|
+
volume: number
|
|
55
|
+
}
|
|
56
|
+
custom: SpeedtestMetrics & {
|
|
57
|
+
vodQuality?: string
|
|
58
|
+
liveQuality?: string
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const T = 'plugins.nerd_stats'
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* `PLUGIN` that displays useful network-related statistics.
|
|
66
|
+
* @beta
|
|
67
|
+
*
|
|
68
|
+
* @remarks
|
|
69
|
+
* Depends on:
|
|
70
|
+
*
|
|
71
|
+
* - {@link BottomGear}
|
|
72
|
+
*
|
|
73
|
+
* - {@link ClapprStats}
|
|
74
|
+
*
|
|
75
|
+
* The plugin is rendered as an item in the gear menu.
|
|
76
|
+
*
|
|
77
|
+
* When clicked, it shows an overlay window with the information about the network speed, latency, etc,
|
|
78
|
+
* and recommended quality level.
|
|
79
|
+
*/
|
|
80
|
+
export class NerdStats extends UICorePlugin {
|
|
81
|
+
private container: Container | null = null
|
|
82
|
+
|
|
83
|
+
private speedtestMetrics: SpeedtestMetrics = {
|
|
84
|
+
connectionSpeed: 0,
|
|
85
|
+
ping: 0,
|
|
86
|
+
jitter: 0,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private metrics: Metrics = newMetrics()
|
|
90
|
+
|
|
91
|
+
private open = false
|
|
92
|
+
|
|
93
|
+
private shortcut: string[]
|
|
94
|
+
|
|
95
|
+
private iconPosition: IconPosition
|
|
96
|
+
|
|
97
|
+
private static readonly buttonTemplate = template(buttonHtml)
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @internal
|
|
101
|
+
*/
|
|
102
|
+
get name() {
|
|
103
|
+
return 'nerd_stats'
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @internal
|
|
108
|
+
*/
|
|
109
|
+
get supportedVersion() {
|
|
110
|
+
return { min: CLAPPR_VERSION }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private static readonly template = template(pluginHtml)
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @internal
|
|
117
|
+
*/
|
|
118
|
+
override get attributes() {
|
|
119
|
+
return {
|
|
120
|
+
class: 'clappr-nerd-stats',
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @internal
|
|
126
|
+
*/
|
|
127
|
+
override get events() {
|
|
128
|
+
return {
|
|
129
|
+
click: 'clicked',
|
|
130
|
+
'click #nerd-stats-close': 'hide',
|
|
131
|
+
'click #nerd-stats-refresh': 'refreshSpeedTest',
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private clicked(e: MouseEvent) {
|
|
136
|
+
e.stopPropagation()
|
|
137
|
+
e.preventDefault()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private get statsBoxElem() {
|
|
141
|
+
return this.$el.find('#nerd-stats-box')
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private get statsBoxWidthThreshold() {
|
|
145
|
+
return 720
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private get playerWidth() {
|
|
149
|
+
return this.core.$el.width()
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private get playerHeight() {
|
|
153
|
+
return this.core.$el.height()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
constructor(core: Core) {
|
|
157
|
+
super(core)
|
|
158
|
+
this.shortcut = core.options.clapprNerdStats?.shortcut ?? [
|
|
159
|
+
'command+shift+s',
|
|
160
|
+
'ctrl+shift+s',
|
|
161
|
+
]
|
|
162
|
+
this.iconPosition =
|
|
163
|
+
core.options.clapprNerdStats?.iconPosition ?? 'bottom-right'
|
|
164
|
+
this.speedtestMetrics = {
|
|
165
|
+
connectionSpeed: 0,
|
|
166
|
+
ping: 0,
|
|
167
|
+
jitter: 0,
|
|
168
|
+
}
|
|
169
|
+
configureSpeedTest(core.options.clapprNerdStats?.speedTestServers ?? [])
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* @internal
|
|
174
|
+
*/
|
|
175
|
+
override bindEvents() {
|
|
176
|
+
this.listenToOnce(this.core, Events.CORE_READY, this.onCoreReady)
|
|
177
|
+
this.listenTo(this.core, Events.CORE_RESIZE, this.onPlayerResize)
|
|
178
|
+
this.listenTo(
|
|
179
|
+
this.core,
|
|
180
|
+
Events.CORE_ACTIVE_CONTAINER_CHANGED,
|
|
181
|
+
this.onActiveContainerChanged,
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private onCoreReady() {
|
|
186
|
+
const bottomGear = this.core.getPlugin('bottom_gear') as BottomGear
|
|
187
|
+
assert(bottomGear, 'bottom_gear plugin is required')
|
|
188
|
+
this.listenTo(bottomGear, GearEvents.RENDERED, this.attach)
|
|
189
|
+
|
|
190
|
+
Mousetrap.bind(this.shortcut, this.toggle)
|
|
191
|
+
this.updateResolution()
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private onActiveContainerChanged() {
|
|
195
|
+
this.container = this.core.activeContainer
|
|
196
|
+
const clapprStats = this.container?.getPlugin('clappr_stats')
|
|
197
|
+
assert(
|
|
198
|
+
clapprStats,
|
|
199
|
+
'clappr-stats not available. Please, include it as a plugin of your Clappr instance.\n' +
|
|
200
|
+
'For more info, visit: https://github.com/clappr/clappr-stats.',
|
|
201
|
+
)
|
|
202
|
+
this.listenTo(clapprStats, ClapprStatsEvents.REPORT, this.updateMetrics)
|
|
203
|
+
this.listenTo(this.core.activeContainer, Events.CONTAINER_VOLUME, () => {
|
|
204
|
+
this.metrics.general.volume = this.container?.volume ?? 0
|
|
205
|
+
this.$el
|
|
206
|
+
.find('#nerd-stats-volume')
|
|
207
|
+
.text(Formatter.formatVolume(this.metrics.general.volume))
|
|
208
|
+
})
|
|
209
|
+
this.listenTo(
|
|
210
|
+
this.core.activePlayback,
|
|
211
|
+
Events.PLAYBACK_LOADEDMETADATA,
|
|
212
|
+
() => {
|
|
213
|
+
this.$el
|
|
214
|
+
.find('#nerd-stats-playback-type')
|
|
215
|
+
.text(
|
|
216
|
+
this.formatPlaybackName(this.core.activePlayback.getPlaybackType()),
|
|
217
|
+
)
|
|
218
|
+
},
|
|
219
|
+
)
|
|
220
|
+
this.updateMetrics(clapprStats.exportMetrics())
|
|
221
|
+
this.$el
|
|
222
|
+
.find('#nerd-stats-playback-name')
|
|
223
|
+
.text(PLAYBACK_NAMES[this.core.activePlayback.name] ?? '-')
|
|
224
|
+
this.core.activeContainer.$el.append(this.$el)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* @internal
|
|
229
|
+
*/
|
|
230
|
+
override destroy() {
|
|
231
|
+
Mousetrap.unbind(this.shortcut)
|
|
232
|
+
return super.destroy()
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private toggle = () => {
|
|
236
|
+
if (this.open) {
|
|
237
|
+
this.hide()
|
|
238
|
+
} else {
|
|
239
|
+
this.show()
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private show() {
|
|
244
|
+
this.$el.show()
|
|
245
|
+
this.statsBoxElem.scrollTop(this.statsBoxElem.scrollTop())
|
|
246
|
+
this.open = true
|
|
247
|
+
|
|
248
|
+
this.refreshSpeedTest()
|
|
249
|
+
initSpeedTest(this.speedtestMetrics)
|
|
250
|
+
.then(() => {
|
|
251
|
+
startSpeedtest()
|
|
252
|
+
})
|
|
253
|
+
.catch((e) => {
|
|
254
|
+
reportError(e)
|
|
255
|
+
this.disable()
|
|
256
|
+
})
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private hide() {
|
|
260
|
+
this.$el.hide()
|
|
261
|
+
this.open = false
|
|
262
|
+
stopSpeedtest()
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private onPlayerResize() {
|
|
266
|
+
this.setStatsBoxSize()
|
|
267
|
+
this.updateResolution()
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private updateResolution() {
|
|
271
|
+
this.metrics.general.resolution = {
|
|
272
|
+
width: this.playerWidth,
|
|
273
|
+
height: this.playerHeight,
|
|
274
|
+
}
|
|
275
|
+
this.$el
|
|
276
|
+
.find('#nerd-stats-resolution-width')
|
|
277
|
+
.text(this.metrics.general.resolution.width)
|
|
278
|
+
this.$el
|
|
279
|
+
.find('#nerd-stats-resolution-height')
|
|
280
|
+
.text(this.metrics.general.resolution.height)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private estimateQuality() {
|
|
284
|
+
trace(`${T} estimateQuality`)
|
|
285
|
+
const videoQualityNames = [
|
|
286
|
+
'SD (480p)',
|
|
287
|
+
'HD (720p)',
|
|
288
|
+
'Full HD (1080p)',
|
|
289
|
+
'2K (1440p)',
|
|
290
|
+
'4K (2160p)',
|
|
291
|
+
]
|
|
292
|
+
const { connectionSpeed, ping } = this.speedtestMetrics
|
|
293
|
+
|
|
294
|
+
if (!connectionSpeed || !ping) {
|
|
295
|
+
const calculatingText = this.core.i18n.t('stats.calculating')
|
|
296
|
+
this.metrics.custom.vodQuality = calculatingText
|
|
297
|
+
this.metrics.custom.liveQuality = calculatingText
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const downloadQuality = getDownloadQuality(connectionSpeed)
|
|
302
|
+
const pingQuality = getPingQuality(ping)
|
|
303
|
+
const liveQuality = Math.min(downloadQuality, pingQuality)
|
|
304
|
+
|
|
305
|
+
const prefix = 'Optimal for '
|
|
306
|
+
|
|
307
|
+
this.metrics.custom.vodQuality =
|
|
308
|
+
prefix + videoQualityNames[downloadQuality - 1]
|
|
309
|
+
this.metrics.custom.liveQuality =
|
|
310
|
+
prefix + videoQualityNames[liveQuality - 1]
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private updateMetrics(metrics: PerfMetrics) {
|
|
314
|
+
trace(`${T} updateMetrics`, { custom: this.speedtestMetrics })
|
|
315
|
+
Object.assign(this.metrics, metrics)
|
|
316
|
+
this.updateEstimatedQuality()
|
|
317
|
+
|
|
318
|
+
this.$el
|
|
319
|
+
.find('#nerd-stats-current-time')
|
|
320
|
+
.text(Formatter.formatTime(this.metrics.extra.currentTime))
|
|
321
|
+
this.$el
|
|
322
|
+
.find('#nerd-stats-video-duration')
|
|
323
|
+
.text(Formatter.formatTime(this.metrics.extra.duration))
|
|
324
|
+
this.$el
|
|
325
|
+
.find('#nerd-stats-buffer-size')
|
|
326
|
+
.text(Formatter.formatTime(this.metrics.extra.buffersize))
|
|
327
|
+
|
|
328
|
+
this.$el
|
|
329
|
+
.find('#nerd-stats-bitrate-weighted-mean')
|
|
330
|
+
.text(Formatter.formatBitrate(this.metrics.extra.bitrateWeightedMean))
|
|
331
|
+
this.$el
|
|
332
|
+
.find('#nerd-stats-bitrate-most-used')
|
|
333
|
+
.text(Formatter.formatBitrate(this.metrics.extra.bitrateMostUsed))
|
|
334
|
+
this.$el
|
|
335
|
+
.find('#nerd-stats-watched-percentage')
|
|
336
|
+
.text(Formatter.formatPercentage(this.metrics.extra.watchedPercentage))
|
|
337
|
+
this.$el
|
|
338
|
+
.find('#nerd-stats-buffering-percentage')
|
|
339
|
+
.text(Formatter.formatPercentage(this.metrics.extra.bufferingPercentage))
|
|
340
|
+
|
|
341
|
+
this.$el
|
|
342
|
+
.find('#nerd-stats-startup-time')
|
|
343
|
+
.text(Formatter.formatTime(this.metrics.chrono.startup))
|
|
344
|
+
this.$el
|
|
345
|
+
.find('#nerd-stats-watch-time')
|
|
346
|
+
.text(Formatter.formatTime(this.metrics.chrono.watch))
|
|
347
|
+
this.$el
|
|
348
|
+
.find('#nerd-stats-pause-time')
|
|
349
|
+
.text(Formatter.formatTime(this.metrics.chrono.pause))
|
|
350
|
+
this.$el
|
|
351
|
+
.find('#nerd-stats-buffering-time')
|
|
352
|
+
.text(Formatter.formatTime(this.metrics.chrono.buffering))
|
|
353
|
+
this.$el
|
|
354
|
+
.find('#nerd-stats-session-time')
|
|
355
|
+
.text(Formatter.formatTime(this.metrics.chrono.session))
|
|
356
|
+
|
|
357
|
+
this.$el.find('#nerd-stats-plays').text(this.metrics.counters.play)
|
|
358
|
+
this.$el.find('#nerd-stats-pauses').text(this.metrics.counters.pause)
|
|
359
|
+
this.$el.find('#nerd-stats-errors').text(this.metrics.counters.error)
|
|
360
|
+
this.$el
|
|
361
|
+
.find('#nerd-stats-bufferings')
|
|
362
|
+
.text(this.metrics.counters.buffering)
|
|
363
|
+
this.$el
|
|
364
|
+
.find('#nerd-stats-decoded-frames')
|
|
365
|
+
.text(this.metrics.counters.decodedFrames)
|
|
366
|
+
this.$el
|
|
367
|
+
.find('#nerd-stats-dropped-frames')
|
|
368
|
+
.text(this.metrics.counters.droppedFrames)
|
|
369
|
+
|
|
370
|
+
this.$el
|
|
371
|
+
.find('#nerd-stats-bitrate-changes')
|
|
372
|
+
.text(this.metrics.counters.changeLevel)
|
|
373
|
+
this.$el.find('#nerd-stats-seeks').text(this.metrics.counters.seek)
|
|
374
|
+
this.$el
|
|
375
|
+
.find('#nerd-stats-fullscreen')
|
|
376
|
+
.text(this.metrics.counters.fullscreen)
|
|
377
|
+
this.$el.find('#nerd-stats-dvr-usage').text(this.metrics.counters.dvrUsage)
|
|
378
|
+
|
|
379
|
+
this.$el
|
|
380
|
+
.find('#nerd-stats-fps')
|
|
381
|
+
.text(Formatter.formatFps(this.metrics.counters.fps))
|
|
382
|
+
|
|
383
|
+
this.setStatsBoxSize()
|
|
384
|
+
drawSpeedTestResults()
|
|
385
|
+
drawSummary(
|
|
386
|
+
this.speedtestMetrics,
|
|
387
|
+
this.$el.find('#nerd-stats-quality-vod'),
|
|
388
|
+
this.$el.find('#nerd-stats-quality-live'),
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
if (!this.open) {
|
|
392
|
+
this.hide()
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
private updateEstimatedQuality() {
|
|
397
|
+
this.estimateQuality()
|
|
398
|
+
this.$el
|
|
399
|
+
.find('#nerd-stats-quality-vod-text')
|
|
400
|
+
.html(this.metrics.custom.vodQuality)
|
|
401
|
+
this.$el
|
|
402
|
+
.find('#nerd-stats-quality-live-text')
|
|
403
|
+
.html(this.metrics.custom.liveQuality)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
private setStatsBoxSize() {
|
|
407
|
+
if (this.playerWidth >= this.statsBoxWidthThreshold) {
|
|
408
|
+
this.statsBoxElem.addClass('wide')
|
|
409
|
+
this.statsBoxElem.removeClass('narrow')
|
|
410
|
+
} else {
|
|
411
|
+
this.statsBoxElem.removeClass('wide')
|
|
412
|
+
this.statsBoxElem.addClass('narrow')
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* @internal
|
|
418
|
+
*/
|
|
419
|
+
override render() {
|
|
420
|
+
this.$el
|
|
421
|
+
.html(
|
|
422
|
+
NerdStats.template({
|
|
423
|
+
metrics: Formatter.format(this.metrics ?? newMetrics()),
|
|
424
|
+
iconPosition: this.iconPosition,
|
|
425
|
+
i18n: this.core.i18n,
|
|
426
|
+
}),
|
|
427
|
+
)
|
|
428
|
+
.hide()
|
|
429
|
+
|
|
430
|
+
return this
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
private attach() {
|
|
434
|
+
trace(`${T} attach`)
|
|
435
|
+
const gear = this.core.getPlugin('bottom_gear') as BottomGear
|
|
436
|
+
gear
|
|
437
|
+
.addItem('nerd_stats')
|
|
438
|
+
.html(
|
|
439
|
+
NerdStats.buttonTemplate({
|
|
440
|
+
icon: statsIcon,
|
|
441
|
+
i18n: this.core.i18n,
|
|
442
|
+
}),
|
|
443
|
+
)
|
|
444
|
+
.on('click', (e: MouseEvent) => {
|
|
445
|
+
e.stopPropagation()
|
|
446
|
+
this.toggle()
|
|
447
|
+
})
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
private clearSpeedtestMetrics() {
|
|
451
|
+
const clapprStats = this.container?.getPlugin('clappr_stats')
|
|
452
|
+
|
|
453
|
+
this.speedtestMetrics.connectionSpeed = 0
|
|
454
|
+
this.speedtestMetrics.ping = 0
|
|
455
|
+
this.speedtestMetrics.jitter = 0
|
|
456
|
+
|
|
457
|
+
if (clapprStats) {
|
|
458
|
+
this.updateMetrics(clapprStats.exportMetrics())
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
private refreshSpeedTest() {
|
|
463
|
+
stopSpeedtest()
|
|
464
|
+
setTimeout(() => {
|
|
465
|
+
this.clearSpeedtestMetrics()
|
|
466
|
+
clearSpeedTestResults()
|
|
467
|
+
drawSpeedTestResults()
|
|
468
|
+
}, 200)
|
|
469
|
+
setTimeout(() => {
|
|
470
|
+
startSpeedtest()
|
|
471
|
+
}, 800)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
private formatPlaybackName(playbackType: PlaybackType): string {
|
|
475
|
+
switch (playbackType) {
|
|
476
|
+
case Playback.VOD:
|
|
477
|
+
return this.core.i18n.t('vod')
|
|
478
|
+
case Playback.LIVE:
|
|
479
|
+
return this.core.i18n.t('live')
|
|
480
|
+
default:
|
|
481
|
+
return '-'
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function newMetrics(): Metrics {
|
|
487
|
+
return {
|
|
488
|
+
...newBaseMetrics(),
|
|
489
|
+
general: {
|
|
490
|
+
displayResolution: '',
|
|
491
|
+
resolution: {
|
|
492
|
+
width: 0,
|
|
493
|
+
height: 0,
|
|
494
|
+
},
|
|
495
|
+
volume: 0,
|
|
496
|
+
},
|
|
497
|
+
custom: {
|
|
498
|
+
connectionSpeed: 0,
|
|
499
|
+
ping: 0,
|
|
500
|
+
jitter: 0,
|
|
501
|
+
},
|
|
502
|
+
}
|
|
503
|
+
}
|
|
@@ -1,109 +1,153 @@
|
|
|
1
|
-
import humanFormat, { ScaleLike } from 'human-format'
|
|
2
|
-
import type { MetricsKind, MetricsType } from './types'
|
|
1
|
+
import humanFormat, { ScaleLike } from 'human-format'
|
|
2
|
+
import type { MetricName, MetricsKind, MetricsType } from './types'
|
|
3
3
|
|
|
4
4
|
const timeScale = new humanFormat.Scale({
|
|
5
5
|
ms: 1,
|
|
6
6
|
sec: 1000,
|
|
7
7
|
min: 60000,
|
|
8
|
-
hours: 3600000
|
|
9
|
-
})
|
|
8
|
+
hours: 3600000,
|
|
9
|
+
})
|
|
10
10
|
|
|
11
11
|
const percentScale = new humanFormat.Scale({
|
|
12
|
-
'%': 1
|
|
13
|
-
})
|
|
12
|
+
'%': 1,
|
|
13
|
+
})
|
|
14
14
|
|
|
15
15
|
type FormatParams = {
|
|
16
|
-
scale?: ScaleLike
|
|
17
|
-
unit?: 'bps'
|
|
18
|
-
decimals?: number
|
|
16
|
+
scale?: ScaleLike
|
|
17
|
+
unit?: 'bps'
|
|
18
|
+
decimals?: number
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
const
|
|
21
|
+
const metricTemplates: Partial<Record<MetricName, FormatParams>> = {
|
|
22
|
+
fps: {
|
|
23
|
+
scale: 'SI',
|
|
24
|
+
decimals: 0,
|
|
25
|
+
},
|
|
26
|
+
volume: {
|
|
27
|
+
scale: percentScale,
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const formattingTemplate: Record<
|
|
32
|
+
MetricsKind,
|
|
33
|
+
Partial<Record<MetricsType, FormatParams>>
|
|
34
|
+
> = {
|
|
22
35
|
general: {
|
|
23
36
|
volume: {
|
|
24
|
-
scale: percentScale
|
|
25
|
-
}
|
|
37
|
+
scale: percentScale,
|
|
38
|
+
},
|
|
26
39
|
},
|
|
27
40
|
timers: {
|
|
28
41
|
startup: {
|
|
29
|
-
scale: timeScale
|
|
42
|
+
scale: timeScale,
|
|
30
43
|
},
|
|
31
44
|
watch: {
|
|
32
|
-
scale: timeScale
|
|
45
|
+
scale: timeScale,
|
|
33
46
|
},
|
|
34
47
|
pause: {
|
|
35
|
-
scale: timeScale
|
|
48
|
+
scale: timeScale,
|
|
36
49
|
},
|
|
37
50
|
buffering: {
|
|
38
|
-
scale: timeScale
|
|
51
|
+
scale: timeScale,
|
|
39
52
|
},
|
|
40
53
|
session: {
|
|
41
|
-
scale: timeScale
|
|
54
|
+
scale: timeScale,
|
|
42
55
|
},
|
|
43
56
|
latency: {
|
|
44
|
-
scale: timeScale
|
|
45
|
-
}
|
|
57
|
+
scale: timeScale,
|
|
58
|
+
},
|
|
46
59
|
},
|
|
47
60
|
extra: {
|
|
48
61
|
buffersize: {
|
|
49
|
-
scale: timeScale
|
|
62
|
+
scale: timeScale,
|
|
50
63
|
},
|
|
51
64
|
duration: {
|
|
52
|
-
scale: timeScale
|
|
65
|
+
scale: timeScale,
|
|
53
66
|
},
|
|
54
67
|
currentTime: {
|
|
55
|
-
scale: timeScale
|
|
68
|
+
scale: timeScale,
|
|
56
69
|
},
|
|
57
70
|
bitrateWeightedMean: {
|
|
58
|
-
unit: 'bps'
|
|
71
|
+
unit: 'bps',
|
|
59
72
|
},
|
|
60
73
|
bitrateMostUsed: {
|
|
61
|
-
unit: 'bps'
|
|
74
|
+
unit: 'bps',
|
|
62
75
|
},
|
|
63
76
|
bandwidth: {
|
|
64
|
-
unit: 'bps'
|
|
77
|
+
unit: 'bps',
|
|
65
78
|
},
|
|
66
79
|
watchedPercentage: {
|
|
67
|
-
scale: percentScale
|
|
80
|
+
scale: percentScale,
|
|
68
81
|
},
|
|
69
82
|
bufferingPercentage: {
|
|
70
|
-
scale: percentScale
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
83
|
+
scale: percentScale,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
}
|
|
74
87
|
|
|
75
|
-
type MetricsValue = number | string
|
|
76
|
-
type Metrics = Partial<
|
|
88
|
+
type MetricsValue = number | string
|
|
89
|
+
type Metrics = Partial<
|
|
90
|
+
Record<MetricsKind, Partial<Record<MetricsType, MetricsValue>>>
|
|
91
|
+
>
|
|
77
92
|
|
|
78
93
|
export default class Formatter {
|
|
79
94
|
static format(metrics: Metrics): Metrics {
|
|
80
|
-
const formattedMetrics: Metrics = {}
|
|
95
|
+
const formattedMetrics: Metrics = {}
|
|
81
96
|
|
|
82
97
|
Object.entries(metrics).forEach(([type, mm]) => {
|
|
83
|
-
const fmt: Partial<Record<MetricsType, MetricsValue>> = {}
|
|
84
|
-
formattedMetrics[type as MetricsKind] = fmt
|
|
85
|
-
const typeTemplate = formattingTemplate[type as MetricsKind]
|
|
98
|
+
const fmt: Partial<Record<MetricsType, MetricsValue>> = {}
|
|
99
|
+
formattedMetrics[type as MetricsKind] = fmt
|
|
100
|
+
const typeTemplate = formattingTemplate[type as MetricsKind]
|
|
86
101
|
|
|
87
102
|
Object.entries(mm).forEach(([name, value]) => {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
103
|
+
if (
|
|
104
|
+
typeTemplate &&
|
|
105
|
+
typeTemplate[name as MetricsType] &&
|
|
106
|
+
typeof value === 'number' &&
|
|
107
|
+
!isNaN(value)
|
|
108
|
+
) {
|
|
91
109
|
// @ts-ignore
|
|
92
|
-
const templateScale = typeTemplate[name as MetricsType].scale || 'SI'
|
|
110
|
+
const templateScale = typeTemplate[name as MetricsType].scale || 'SI'
|
|
93
111
|
// @ts-ignore
|
|
94
|
-
const templateUnit = typeTemplate[name as MetricsType].unit || ''
|
|
112
|
+
const templateUnit = typeTemplate[name as MetricsType].unit || ''
|
|
95
113
|
|
|
96
114
|
fmt[name as MetricsType] = humanFormat(value, {
|
|
97
115
|
scale: templateScale,
|
|
98
116
|
unit: templateUnit,
|
|
99
|
-
decimals: 2
|
|
100
|
-
})
|
|
117
|
+
decimals: 2,
|
|
118
|
+
})
|
|
101
119
|
} else {
|
|
102
|
-
fmt[name as MetricsType] = value
|
|
120
|
+
fmt[name as MetricsType] = value
|
|
103
121
|
}
|
|
104
|
-
})
|
|
105
|
-
})
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
return formattedMetrics
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
static formatVolume(volume: number): string {
|
|
129
|
+
return humanFormat(volume, metricTemplates.volume)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
static formatTime(time: number): string {
|
|
133
|
+
return humanFormat(time, {
|
|
134
|
+
scale: timeScale,
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
static formatFps(fps: number): string {
|
|
139
|
+
return humanFormat(fps, metricTemplates.fps)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
static formatPercentage(percentage: number): string {
|
|
143
|
+
return humanFormat(percentage, {
|
|
144
|
+
scale: percentScale,
|
|
145
|
+
})
|
|
146
|
+
}
|
|
106
147
|
|
|
107
|
-
|
|
148
|
+
static formatBitrate(bitrate: number): string {
|
|
149
|
+
return humanFormat(bitrate, {
|
|
150
|
+
unit: 'bps',
|
|
151
|
+
})
|
|
108
152
|
}
|
|
109
153
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Server, type TestStatusInfo, Speedtest } from './Speedtest.js';
|
|
2
|
-
import {
|
|
2
|
+
import { SpeedtestMetrics } from './types.js';
|
|
3
3
|
|
|
4
4
|
const DIGITS_THRESHOLD = 99999;
|
|
5
5
|
const DEFAULT_DOWNLOAD_SPEED = '0.00';
|
|
@@ -59,7 +59,7 @@ export function drawSpeedTestResults() {
|
|
|
59
59
|
|
|
60
60
|
let inited: Promise<void> | null = null;
|
|
61
61
|
|
|
62
|
-
export const initSpeedTest = (customMetrics:
|
|
62
|
+
export const initSpeedTest = (customMetrics: SpeedtestMetrics): Promise<void> => {
|
|
63
63
|
if (inited !== null) {
|
|
64
64
|
return inited;
|
|
65
65
|
}
|