diff options
-rw-r--r-- | client/src/assets/player/shared/stats/stats-card.ts | 167 |
1 files changed, 121 insertions, 46 deletions
diff --git a/client/src/assets/player/shared/stats/stats-card.ts b/client/src/assets/player/shared/stats/stats-card.ts index 1bf631d2c..e9f9b6bd2 100644 --- a/client/src/assets/player/shared/stats/stats-card.ts +++ b/client/src/assets/player/shared/stats/stats-card.ts | |||
@@ -22,15 +22,15 @@ interface PlayerNetworkInfo { | |||
22 | downloadedFromPeers?: string | 22 | downloadedFromPeers?: string |
23 | } | 23 | } |
24 | 24 | ||
25 | interface InfoElement { | ||
26 | root: HTMLElement | ||
27 | value: HTMLElement | ||
28 | } | ||
29 | |||
25 | const Component = videojs.getComponent('Component') | 30 | const Component = videojs.getComponent('Component') |
26 | class StatsCard extends Component { | 31 | class StatsCard extends Component { |
27 | options_: StatsCardOptions | 32 | options_: StatsCardOptions |
28 | 33 | ||
29 | container: HTMLDivElement | ||
30 | |||
31 | list: HTMLDivElement | ||
32 | closeButton: HTMLElement | ||
33 | |||
34 | updateInterval: any | 34 | updateInterval: any |
35 | 35 | ||
36 | mode: 'webtorrent' | 'p2p-media-loader' | 36 | mode: 'webtorrent' | 'p2p-media-loader' |
@@ -40,18 +40,50 @@ class StatsCard extends Component { | |||
40 | intervalMs = 300 | 40 | intervalMs = 300 |
41 | playerNetworkInfo: PlayerNetworkInfo = {} | 41 | playerNetworkInfo: PlayerNetworkInfo = {} |
42 | 42 | ||
43 | private containerEl: HTMLDivElement | ||
44 | private infoListEl: HTMLDivElement | ||
45 | |||
46 | private playerMode: InfoElement | ||
47 | private p2p: InfoElement | ||
48 | private uuid: InfoElement | ||
49 | private viewport: InfoElement | ||
50 | private resolution: InfoElement | ||
51 | private volume: InfoElement | ||
52 | private codecs: InfoElement | ||
53 | private color: InfoElement | ||
54 | private connection: InfoElement | ||
55 | |||
56 | private network: InfoElement | ||
57 | private transferred: InfoElement | ||
58 | private download: InfoElement | ||
59 | |||
60 | private bufferProgress: InfoElement | ||
61 | private bufferState: InfoElement | ||
62 | |||
63 | private liveLatency: InfoElement | ||
64 | |||
43 | createEl () { | 65 | createEl () { |
44 | const container = super.createEl('div', { | 66 | this.containerEl = videojs.dom.createEl('div', { |
45 | className: 'vjs-stats-content', | 67 | className: 'vjs-stats-content' |
46 | innerHTML: this.getMainTemplate() | 68 | }) as HTMLDivElement |
69 | this.containerEl.style.display = 'none' | ||
70 | |||
71 | this.infoListEl = videojs.dom.createEl('div', { | ||
72 | className: 'vjs-stats-list' | ||
47 | }) as HTMLDivElement | 73 | }) as HTMLDivElement |
48 | this.container = container | ||
49 | this.container.style.display = 'none' | ||
50 | 74 | ||
51 | this.closeButton = this.container.getElementsByClassName('vjs-stats-close')[0] as HTMLElement | 75 | const closeButton = videojs.dom.createEl('button', { |
52 | this.closeButton.onclick = () => this.hide() | 76 | className: 'vjs-stats-close', |
77 | tabindex: '0', | ||
78 | title: 'Close stats', | ||
79 | innerText: '[x]' | ||
80 | }, { 'aria-label': 'Close stats' }) as HTMLElement | ||
81 | closeButton.onclick = () => this.hide() | ||
53 | 82 | ||
54 | this.list = this.container.getElementsByClassName('vjs-stats-list')[0] as HTMLDivElement | 83 | this.containerEl.appendChild(closeButton) |
84 | this.containerEl.appendChild(this.infoListEl) | ||
85 | |||
86 | this.populateInfoBlocks() | ||
55 | 87 | ||
56 | this.player_.on('p2pInfo', (event: any, data: EventPlayerNetworkInfo) => { | 88 | this.player_.on('p2pInfo', (event: any, data: EventPlayerNetworkInfo) => { |
57 | if (!data) return // HTTP fallback | 89 | if (!data) return // HTTP fallback |
@@ -74,7 +106,7 @@ class StatsCard extends Component { | |||
74 | } | 106 | } |
75 | }) | 107 | }) |
76 | 108 | ||
77 | return container | 109 | return this.containerEl |
78 | } | 110 | } |
79 | 111 | ||
80 | toggle () { | 112 | toggle () { |
@@ -83,14 +115,15 @@ class StatsCard extends Component { | |||
83 | } | 115 | } |
84 | 116 | ||
85 | show () { | 117 | show () { |
86 | this.container.style.display = 'block' | 118 | this.containerEl.style.display = 'block' |
119 | |||
87 | this.updateInterval = setInterval(async () => { | 120 | this.updateInterval = setInterval(async () => { |
88 | try { | 121 | try { |
89 | const options = this.mode === 'p2p-media-loader' | 122 | const options = this.mode === 'p2p-media-loader' |
90 | ? this.buildHLSOptions() | 123 | ? this.buildHLSOptions() |
91 | : await this.buildWebTorrentOptions() // Default | 124 | : await this.buildWebTorrentOptions() // Default |
92 | 125 | ||
93 | this.list.innerHTML = this.getListTemplate(options) | 126 | this.populateInfoValues(options) |
94 | } catch (err) { | 127 | } catch (err) { |
95 | console.error('Cannot update stats.', err) | 128 | console.error('Cannot update stats.', err) |
96 | clearInterval(this.updateInterval) | 129 | clearInterval(this.updateInterval) |
@@ -100,7 +133,7 @@ class StatsCard extends Component { | |||
100 | 133 | ||
101 | hide () { | 134 | hide () { |
102 | clearInterval(this.updateInterval) | 135 | clearInterval(this.updateInterval) |
103 | this.container.style.display = 'none' | 136 | this.containerEl.style.display = 'none' |
104 | } | 137 | } |
105 | 138 | ||
106 | private buildHLSOptions () { | 139 | private buildHLSOptions () { |
@@ -169,7 +202,44 @@ class StatsCard extends Component { | |||
169 | } | 202 | } |
170 | } | 203 | } |
171 | 204 | ||
172 | private getListTemplate (options: { | 205 | private populateInfoBlocks () { |
206 | this.playerMode = this.buildInfoRow(this.player().localize('Player mode')) | ||
207 | this.p2p = this.buildInfoRow(this.player().localize('P2P')) | ||
208 | this.uuid = this.buildInfoRow(this.player().localize('Video UUID')) | ||
209 | this.viewport = this.buildInfoRow(this.player().localize('Viewport / Frames')) | ||
210 | this.resolution = this.buildInfoRow(this.player().localize('Resolution')) | ||
211 | this.volume = this.buildInfoRow(this.player().localize('Volume')) | ||
212 | this.codecs = this.buildInfoRow(this.player().localize('Codecs')) | ||
213 | this.color = this.buildInfoRow(this.player().localize('Color')) | ||
214 | this.connection = this.buildInfoRow(this.player().localize('Connection Speed')) | ||
215 | |||
216 | this.network = this.buildInfoRow(this.player().localize('Network Activity')) | ||
217 | this.transferred = this.buildInfoRow(this.player().localize('Total Transfered')) | ||
218 | this.download = this.buildInfoRow(this.player().localize('Download Breakdown')) | ||
219 | |||
220 | this.bufferProgress = this.buildInfoRow(this.player().localize('Buffer Progress')) | ||
221 | this.bufferState = this.buildInfoRow(this.player().localize('Buffer State')) | ||
222 | |||
223 | this.liveLatency = this.buildInfoRow(this.player().localize('Live Latency')) | ||
224 | |||
225 | this.infoListEl.appendChild(this.playerMode.root) | ||
226 | this.infoListEl.appendChild(this.p2p.root) | ||
227 | this.infoListEl.appendChild(this.uuid.root) | ||
228 | this.infoListEl.appendChild(this.viewport.root) | ||
229 | this.infoListEl.appendChild(this.resolution.root) | ||
230 | this.infoListEl.appendChild(this.volume.root) | ||
231 | this.infoListEl.appendChild(this.codecs.root) | ||
232 | this.infoListEl.appendChild(this.color.root) | ||
233 | this.infoListEl.appendChild(this.connection.root) | ||
234 | this.infoListEl.appendChild(this.network.root) | ||
235 | this.infoListEl.appendChild(this.transferred.root) | ||
236 | this.infoListEl.appendChild(this.download.root) | ||
237 | this.infoListEl.appendChild(this.bufferProgress.root) | ||
238 | this.infoListEl.appendChild(this.bufferState.root) | ||
239 | this.infoListEl.appendChild(this.liveLatency.root) | ||
240 | } | ||
241 | |||
242 | private populateInfoValues (options: { | ||
173 | playerNetworkInfo: PlayerNetworkInfo | 243 | playerNetworkInfo: PlayerNetworkInfo |
174 | progress: number | 244 | progress: number |
175 | codecs: string | 245 | codecs: string |
@@ -208,45 +278,50 @@ class StatsCard extends Component { | |||
208 | ? `${(progress * 100).toFixed(1)}% (${(progress * duration).toFixed(1)}s)` | 278 | ? `${(progress * 100).toFixed(1)}% (${(progress * duration).toFixed(1)}s)` |
209 | : undefined | 279 | : undefined |
210 | 280 | ||
211 | return ` | 281 | this.setInfoValue(this.playerMode, this.mode || 'HTTP') |
212 | ${this.buildElement(player.localize('Player mode'), this.mode || 'HTTP')} | 282 | this.setInfoValue(this.p2p, player.localize(this.options_.p2pEnabled ? 'enabled' : 'disabled')) |
213 | ${this.buildElement(player.localize('P2P'), player.localize(this.options_.p2pEnabled ? 'enabled' : 'disabled'))} | 283 | this.setInfoValue(this.uuid, this.options_.videoUUID) |
214 | |||
215 | ${this.buildElement(player.localize('Video UUID'), this.options_.videoUUID)} | ||
216 | 284 | ||
217 | ${this.buildElement(player.localize('Viewport / Frames'), frames)} | 285 | this.setInfoValue(this.viewport, frames) |
286 | this.setInfoValue(this.resolution, resolution) | ||
287 | this.setInfoValue(this.volume, volume) | ||
288 | this.setInfoValue(this.codecs, codecs) | ||
289 | this.setInfoValue(this.color, colorSpace) | ||
290 | this.setInfoValue(this.connection, playerNetworkInfo.averageBandwidth) | ||
218 | 291 | ||
219 | ${this.buildElement(player.localize('Resolution'), resolution)} | 292 | this.setInfoValue(this.network, networkActivity) |
293 | this.setInfoValue(this.transferred, totalTransferred) | ||
294 | this.setInfoValue(this.download, downloadBreakdown) | ||
220 | 295 | ||
221 | ${this.buildElement(player.localize('Volume'), volume)} | 296 | this.setInfoValue(this.bufferProgress, bufferProgress) |
297 | this.setInfoValue(this.bufferState, buffer) | ||
222 | 298 | ||
223 | ${this.buildElement(player.localize('Codecs'), codecs)} | 299 | this.setInfoValue(this.liveLatency, latency) |
224 | ${this.buildElement(player.localize('Color'), colorSpace)} | 300 | } |
225 | |||
226 | ${this.buildElement(player.localize('Connection Speed'), playerNetworkInfo.averageBandwidth)} | ||
227 | 301 | ||
228 | ${this.buildElement(player.localize('Network Activity'), networkActivity)} | 302 | private setInfoValue (el: InfoElement, value: string) { |
229 | ${this.buildElement(player.localize('Total Transfered'), totalTransferred)} | 303 | if (!value) { |
230 | ${this.buildElement(player.localize('Download Breakdown'), downloadBreakdown)} | 304 | el.root.style.display = 'none' |
305 | return | ||
306 | } | ||
231 | 307 | ||
232 | ${this.buildElement(player.localize('Buffer Progress'), bufferProgress)} | 308 | el.root.style.display = 'block' |
233 | ${this.buildElement(player.localize('Buffer State'), buffer)} | ||
234 | 309 | ||
235 | ${this.buildElement(player.localize('Live Latency'), latency)} | 310 | if (el.value.innerHTML === value) return |
236 | ` | 311 | el.value.innerHTML = value |
237 | } | 312 | } |
238 | 313 | ||
239 | private getMainTemplate () { | 314 | private buildInfoRow (labelText: string, valueHTML?: string) { |
240 | return ` | 315 | const root = videojs.dom.createEl('div') as HTMLElement |
241 | <button class="vjs-stats-close" tabindex=0 aria-label="Close stats" title="Close stats">[x]</button> | 316 | root.style.display = 'none' |
242 | <div class="vjs-stats-list"></div> | 317 | |
243 | ` | 318 | const label = videojs.dom.createEl('div', { innerText: labelText }) as HTMLElement |
244 | } | 319 | const value = videojs.dom.createEl('span', { innerHTML: valueHTML }) as HTMLElement |
245 | 320 | ||
246 | private buildElement (label: string, value?: string) { | 321 | root.appendChild(label) |
247 | if (!value) return '' | 322 | root.appendChild(value) |
248 | 323 | ||
249 | return `<div><div>${label}</div><span>${value}</span></div>` | 324 | return { root, value } |
250 | } | 325 | } |
251 | 326 | ||
252 | private timeRangesToString (r: videojs.TimeRange) { | 327 | private timeRangesToString (r: videojs.TimeRange) { |