aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts30
-rw-r--r--client/src/assets/player/peertube-videojs-typings.ts6
-rw-r--r--client/src/assets/player/stats/stats-card.ts307
-rw-r--r--client/src/assets/player/stats/stats-plugin.ts6
-rw-r--r--client/src/assets/player/webtorrent/webtorrent-plugin.ts3
-rwxr-xr-xscripts/i18n/create-custom-files.ts16
6 files changed, 248 insertions, 120 deletions
diff --git a/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts b/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts
index e97925ab5..4275a5e5e 100644
--- a/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts
+++ b/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts
@@ -1,10 +1,10 @@
1import * as Hlsjs from 'hls.js/dist/hls.light.js'
2import { Events, Segment } from 'p2p-media-loader-core'
3import { Engine, initHlsJsPlayer, initVideoJsContribHlsJsPlayer } from 'p2p-media-loader-hlsjs'
1import videojs from 'video.js' 4import videojs from 'video.js'
2import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo } from '../peertube-videojs-typings' 5import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo } from '../peertube-videojs-typings'
3import { Engine, initHlsJsPlayer, initVideoJsContribHlsJsPlayer } from 'p2p-media-loader-hlsjs'
4import { Events, Segment } from 'p2p-media-loader-core'
5import { timeToInt } from '../utils' 6import { timeToInt } from '../utils'
6import { registerConfigPlugin, registerSourceHandler } from './hls-plugin' 7import { registerConfigPlugin, registerSourceHandler } from './hls-plugin'
7import * as Hlsjs from 'hls.js/dist/hls.light.js'
8 8
9registerConfigPlugin(videojs) 9registerConfigPlugin(videojs)
10registerSourceHandler(videojs) 10registerSourceHandler(videojs)
@@ -36,6 +36,9 @@ class P2pMediaLoaderPlugin extends Plugin {
36 36
37 private networkInfoInterval: any 37 private networkInfoInterval: any
38 38
39 private hlsjsCurrentLevel: number
40 private hlsjsLevels: Hlsjs.Level[]
41
39 constructor (player: videojs.Player, options?: P2PMediaLoaderPluginOptions) { 42 constructor (player: videojs.Player, options?: P2PMediaLoaderPluginOptions) {
40 super(player) 43 super(player)
41 44
@@ -84,6 +87,16 @@ class P2pMediaLoaderPlugin extends Plugin {
84 clearInterval(this.networkInfoInterval) 87 clearInterval(this.networkInfoInterval)
85 } 88 }
86 89
90 getCurrentLevel () {
91 return this.hlsjsLevels.find(l => l.level === this.hlsjsCurrentLevel)
92 }
93
94 getLiveLatency () {
95 return undefined as number
96 // FIXME: Use latency when hls >= V1
97 // return this.hlsjs.latency
98 }
99
87 getHLSJS () { 100 getHLSJS () {
88 return this.hlsjs 101 return this.hlsjs
89 } 102 }
@@ -140,6 +153,14 @@ class P2pMediaLoaderPlugin extends Plugin {
140 this.p2pEngine.on(Events.PeerConnect, () => this.statsP2PBytes.numPeers++) 153 this.p2pEngine.on(Events.PeerConnect, () => this.statsP2PBytes.numPeers++)
141 this.p2pEngine.on(Events.PeerClose, () => this.statsP2PBytes.numPeers--) 154 this.p2pEngine.on(Events.PeerClose, () => this.statsP2PBytes.numPeers--)
142 155
156 this.hlsjs.on(Hlsjs.Events.MANIFEST_PARSED, (_e, manifest) => {
157 this.hlsjsCurrentLevel = manifest.firstLevel
158 this.hlsjsLevels = manifest.levels
159 })
160 this.hlsjs.on(Hlsjs.Events.LEVEL_LOADED, (_e, level) => {
161 this.hlsjsCurrentLevel = level.levelId || (level as any).id
162 })
163
143 this.networkInfoInterval = setInterval(() => { 164 this.networkInfoInterval = setInterval(() => {
144 const p2pDownloadSpeed = this.arraySum(this.statsP2PBytes.pendingDownload) 165 const p2pDownloadSpeed = this.arraySum(this.statsP2PBytes.pendingDownload)
145 const p2pUploadSpeed = this.arraySum(this.statsP2PBytes.pendingUpload) 166 const p2pUploadSpeed = this.arraySum(this.statsP2PBytes.pendingUpload)
@@ -166,7 +187,8 @@ class P2pMediaLoaderPlugin extends Plugin {
166 numPeers: this.statsP2PBytes.numPeers, 187 numPeers: this.statsP2PBytes.numPeers,
167 downloaded: this.statsP2PBytes.totalDownload, 188 downloaded: this.statsP2PBytes.totalDownload,
168 uploaded: this.statsP2PBytes.totalUpload 189 uploaded: this.statsP2PBytes.totalUpload
169 } 190 },
191 bandwidthEstimate: (this.hlsjs as any).bandwidthEstimate / 8
170 } as PlayerNetworkInfo) 192 } as PlayerNetworkInfo)
171 }, this.CONSTANTS.INFO_SCHEDULER) 193 }, this.CONSTANTS.INFO_SCHEDULER)
172 } 194 }
diff --git a/client/src/assets/player/peertube-videojs-typings.ts b/client/src/assets/player/peertube-videojs-typings.ts
index cf92e5f08..8afb424a7 100644
--- a/client/src/assets/player/peertube-videojs-typings.ts
+++ b/client/src/assets/player/peertube-videojs-typings.ts
@@ -9,6 +9,7 @@ import { PlaylistPlugin } from './playlist/playlist-plugin'
9import { EndCardOptions } from './upnext/end-card' 9import { EndCardOptions } from './upnext/end-card'
10import { StatsCardOptions } from './stats/stats-card' 10import { StatsCardOptions } from './stats/stats-card'
11import { WebTorrentPlugin } from './webtorrent/webtorrent-plugin' 11import { WebTorrentPlugin } from './webtorrent/webtorrent-plugin'
12import { StatsForNerdsPlugin } from './stats/stats-plugin'
12 13
13declare module 'video.js' { 14declare module 'video.js' {
14 15
@@ -37,7 +38,7 @@ declare module 'video.js' {
37 38
38 bezels (): void 39 bezels (): void
39 40
40 stats (options?: Partial<StatsCardOptions>): any 41 stats (options?: StatsCardOptions): StatsForNerdsPlugin
41 42
42 qualityLevels (): QualityLevels 43 qualityLevels (): QualityLevels
43 44
@@ -198,6 +199,9 @@ type PlayerNetworkInfo = {
198 uploaded: number 199 uploaded: number
199 numPeers: number 200 numPeers: number
200 } 201 }
202
203 // In bytes
204 bandwidthEstimate: number
201} 205}
202 206
203type PlaylistItemOptions = { 207type PlaylistItemOptions = {
diff --git a/client/src/assets/player/stats/stats-card.ts b/client/src/assets/player/stats/stats-card.ts
index 278899b72..f66766089 100644
--- a/client/src/assets/player/stats/stats-card.ts
+++ b/client/src/assets/player/stats/stats-card.ts
@@ -1,103 +1,42 @@
1import videojs from 'video.js' 1import videojs from 'video.js'
2import { PlayerNetworkInfo } from '../peertube-videojs-typings' 2import { PlayerNetworkInfo as EventPlayerNetworkInfo } from '../peertube-videojs-typings'
3import { getAverageBandwidthInStore } from '../peertube-player-local-storage' 3import { bytes, secondsToTime } from '../utils'
4import { bytes } from '../utils'
5 4
6interface StatsCardOptions extends videojs.ComponentOptions { 5interface StatsCardOptions extends videojs.ComponentOptions {
7 videoUUID?: string, 6 videoUUID: string
8 videoIsLive?: boolean, 7 videoIsLive: boolean
9 mode?: 'webtorrent' | 'p2p-media-loader' 8 mode: 'webtorrent' | 'p2p-media-loader'
10} 9}
11 10
12function getListTemplate ( 11interface PlayerNetworkInfo {
13 options: StatsCardOptions, 12 downloadSpeed?: string
14 player: videojs.Player, 13 uploadSpeed?: string
15 args: { 14 totalDownloaded?: string
16 playerNetworkInfo?: any 15 totalUploaded?: string
17 videoFile?: any 16 numPeers?: number
18 progress?: number 17 averageBandwidth?: string
19 }) {
20 const { playerNetworkInfo, videoFile, progress } = args
21
22 const videoQuality: VideoPlaybackQuality = player.getVideoPlaybackQuality()
23 const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0)
24 const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0)
25 const pr = (window.devicePixelRatio || 1).toFixed(2)
26 const colorspace = videoFile?.metadata?.streams[0]['color_space'] !== "unknown"
27 ? videoFile?.metadata?.streams[0]['color_space']
28 : undefined
29
30 return `
31 <div>
32 <div>${player.localize('Video UUID')}</div>
33 <span>${options.videoUUID || ''}</span>
34 </div>
35 <div>
36 <div>Viewport / ${player.localize('Frames')}</div>
37 <span>${vw}x${vh}*${pr} / ${videoQuality.droppedVideoFrames} dropped of ${videoQuality.totalVideoFrames}</span>
38 </div>
39 <div${videoFile !== undefined ? '' : ' style="display: none;"'}>
40 <div>${player.localize('Resolution')}</div>
41 <span>${videoFile?.resolution.label + videoFile?.fps}</span>
42 </div>
43 <div>
44 <div>${player.localize('Volume')}</div>
45 <span>${~~(player.volume() * 100)}%${player.muted() ? ' (muted)' : ''}</span>
46 </div>
47 <div${videoFile !== undefined ? '' : ' style="display: none;"'}>
48 <div>${player.localize('Codecs')}</div>
49 <span>${videoFile?.metadata?.streams[0]['codec_name'] || 'avc1'}</span>
50 </div>
51 <div${videoFile !== undefined ? '' : ' style="display: none;"'}>
52 <div>${player.localize('Color')}</div>
53 <span>${colorspace || 'bt709'}</span>
54 </div>
55 <div${playerNetworkInfo.averageBandwidth !== undefined ? '' : ' style="display: none;"'}>
56 <div>${player.localize('Connection Speed')}</div>
57 <span>${playerNetworkInfo.averageBandwidth}</span>
58 </div>
59 <div${playerNetworkInfo.downloadSpeed !== undefined ? '' : ' style="display: none;"'}>
60 <div>${player.localize('Network Activity')}</div>
61 <span>${playerNetworkInfo.downloadSpeed} &dArr; / ${playerNetworkInfo.uploadSpeed} &uArr;</span>
62 </div>
63 <div${playerNetworkInfo.totalDownloaded !== undefined ? '' : ' style="display: none;"'}>
64 <div>${player.localize('Total Transfered')}</div>
65 <span>${playerNetworkInfo.totalDownloaded} &dArr; / ${playerNetworkInfo.totalUploaded} &uArr;</span>
66 </div>
67 <div${playerNetworkInfo.downloadedFromServer ? '' : ' style="display: none;"'}>
68 <div>${player.localize('Download Breakdown')}</div>
69 <span>${playerNetworkInfo.downloadedFromServer} from server ยท ${playerNetworkInfo.downloadedFromPeers} from peers</span>
70 </div>
71 <div${progress !== undefined && videoFile !== undefined ? '' : ' style="display: none;"'}>
72 <div>${player.localize('Buffer Health')}</div>
73 <span>${(progress * 100).toFixed(1)}% (${(progress * videoFile?.metadata?.format.duration).toFixed(1)}s)</span>
74 </div>
75 <div style="display: none;"> <!-- TODO: implement live latency measure -->
76 <div>${player.localize('Live Latency')}</div>
77 <span></span>
78 </div>
79 `
80}
81 18
82function getMainTemplate () { 19 downloadedFromServer?: string
83 return ` 20 downloadedFromPeers?: string
84 <button class="vjs-stats-close" tabindex=0 aria-label="Close stats" title="Close stats">[x]</button>
85 <div class="vjs-stats-list"></div>
86 `
87} 21}
88 22
89const Component = videojs.getComponent('Component') 23const Component = videojs.getComponent('Component')
90class StatsCard extends Component { 24class StatsCard extends Component {
91 options_: StatsCardOptions 25 options_: StatsCardOptions
26
92 container: HTMLDivElement 27 container: HTMLDivElement
28
93 list: HTMLDivElement 29 list: HTMLDivElement
94 closeButton: HTMLElement 30 closeButton: HTMLElement
95 update: any
96 source: any
97 31
98 interval = 300 32 updateInterval: any
99 playerNetworkInfo: any = {} 33
100 statsForNerdsEvents = new videojs.EventTarget() 34 mode: 'webtorrent' | 'p2p-media-loader'
35
36 metadataStore: any = {}
37
38 intervalMs = 300
39 playerNetworkInfo: PlayerNetworkInfo = {}
101 40
102 constructor (player: videojs.Player, options: StatsCardOptions) { 41 constructor (player: videojs.Player, options: StatsCardOptions) {
103 super(player, options) 42 super(player, options)
@@ -106,7 +45,7 @@ class StatsCard extends Component {
106 createEl () { 45 createEl () {
107 const container = super.createEl('div', { 46 const container = super.createEl('div', {
108 className: 'vjs-stats-content', 47 className: 'vjs-stats-content',
109 innerHTML: getMainTemplate() 48 innerHTML: this.getMainTemplate()
110 }) as HTMLDivElement 49 }) as HTMLDivElement
111 this.container = container 50 this.container = container
112 this.container.style.display = 'none' 51 this.container.style.display = 'none'
@@ -116,12 +55,10 @@ class StatsCard extends Component {
116 55
117 this.list = this.container.getElementsByClassName('vjs-stats-list')[0] as HTMLDivElement 56 this.list = this.container.getElementsByClassName('vjs-stats-list')[0] as HTMLDivElement
118 57
119 console.log(this.player_.qualityLevels()) 58 this.player_.on('p2pInfo', (event: any, data: EventPlayerNetworkInfo) => {
120
121 this.player_.on('p2pInfo', (event: any, data: PlayerNetworkInfo) => {
122 if (!data) return // HTTP fallback 59 if (!data) return // HTTP fallback
123 60
124 this.source = data.source 61 this.mode = data.source
125 62
126 const p2pStats = data.p2p 63 const p2pStats = data.p2p
127 const httpStats = data.http 64 const httpStats = data.http
@@ -131,7 +68,7 @@ class StatsCard extends Component {
131 this.playerNetworkInfo.totalDownloaded = bytes(p2pStats.downloaded + httpStats.downloaded).join(' ') 68 this.playerNetworkInfo.totalDownloaded = bytes(p2pStats.downloaded + httpStats.downloaded).join(' ')
132 this.playerNetworkInfo.totalUploaded = bytes(p2pStats.uploaded + httpStats.uploaded).join(' ') 69 this.playerNetworkInfo.totalUploaded = bytes(p2pStats.uploaded + httpStats.uploaded).join(' ')
133 this.playerNetworkInfo.numPeers = p2pStats.numPeers 70 this.playerNetworkInfo.numPeers = p2pStats.numPeers
134 this.playerNetworkInfo.averageBandwidth = bytes(getAverageBandwidthInStore() || p2pStats.downloaded + httpStats.downloaded).join(' ') 71 this.playerNetworkInfo.averageBandwidth = bytes(data.bandwidthEstimate).join(' ') + '/s'
135 72
136 if (data.source === 'p2p-media-loader') { 73 if (data.source === 'p2p-media-loader') {
137 this.playerNetworkInfo.downloadedFromServer = bytes(httpStats.downloaded).join(' ') 74 this.playerNetworkInfo.downloadedFromServer = bytes(httpStats.downloaded).join(' ')
@@ -143,37 +80,187 @@ class StatsCard extends Component {
143 } 80 }
144 81
145 toggle () { 82 toggle () {
146 this.update 83 this.updateInterval
147 ? this.hide() 84 ? this.hide()
148 : this.show() 85 : this.show()
149 } 86 }
150 87
151 show (options?: StatsCardOptions) { 88 show () {
152 if (options) this.options_ = options
153
154 let metadata = {}
155
156 this.container.style.display = 'block' 89 this.container.style.display = 'block'
157 this.update = setInterval(async () => { 90 this.updateInterval = setInterval(async () => {
158 try { 91 try {
159 if (this.source === 'webtorrent') { 92 const options = this.mode === 'webtorrent'
160 const progress = this.player_.webtorrent().getTorrent()?.progress 93 ? await this.buildWebTorrentOptions()
161 const videoFile = this.player_.webtorrent().getCurrentVideoFile() 94 : await this.buildHLSOptions()
162 videoFile.metadata = metadata[videoFile.fileUrl] = videoFile.metadata || metadata[videoFile.fileUrl] || videoFile.metadataUrl && await fetch(videoFile.metadataUrl).then(res => res.json()) 95
163 this.list.innerHTML = getListTemplate(this.options_, this.player_, { playerNetworkInfo: this.playerNetworkInfo, videoFile, progress }) 96 this.list.innerHTML = this.getListTemplate(options)
164 } else { 97 } catch (err) {
165 this.list.innerHTML = getListTemplate(this.options_, this.player_, { playerNetworkInfo: this.playerNetworkInfo }) 98 console.error('Cannot update stats.', err)
166 } 99 clearInterval(this.updateInterval)
167 } catch (e) {
168 clearInterval(this.update)
169 } 100 }
170 }, this.interval) 101 }, this.intervalMs)
171 } 102 }
172 103
173 hide () { 104 hide () {
174 clearInterval(this.update) 105 clearInterval(this.updateInterval)
175 this.container.style.display = 'none' 106 this.container.style.display = 'none'
176 } 107 }
108
109 private async buildHLSOptions () {
110 const p2pMediaLoader = this.player_.p2pMediaLoader()
111 const level = p2pMediaLoader.getCurrentLevel()
112
113 const codecs = level?.videoCodec || level?.audioCodec
114 ? `${level?.videoCodec || ''} / ${level?.audioCodec || ''}`
115 : undefined
116
117 const resolution = `${level?.height}p${level?.attrs['FRAME-RATE'] || ''}`
118 const buffer = this.timeRangesToString(this.player().buffered())
119
120 let progress: number
121 let latency: string
122
123 if (this.options_.videoIsLive) {
124 latency = secondsToTime(p2pMediaLoader.getLiveLatency())
125 } else {
126 progress = this.player().bufferedPercent()
127 }
128
129 return {
130 playerNetworkInfo: this.playerNetworkInfo,
131 resolution,
132 codecs,
133 buffer,
134 latency,
135 progress
136 }
137 }
138
139 private async buildWebTorrentOptions () {
140 const videoFile = this.player_.webtorrent().getCurrentVideoFile()
141
142 if (!this.metadataStore[videoFile.fileUrl]) {
143 this.metadataStore[videoFile.fileUrl] = await fetch(videoFile.metadataUrl).then(res => res.json())
144 }
145
146 const metadata = this.metadataStore[videoFile.fileUrl]
147
148 let colorSpace = 'unknown'
149 let codecs = 'unknown'
150
151 if (metadata?.streams[0]) {
152 const stream = metadata.streams[0]
153
154 colorSpace = stream['color_space'] !== 'unknown'
155 ? stream['color_space']
156 : 'bt709'
157
158 codecs = stream['codec_name'] || 'avc1'
159 }
160
161 const resolution = videoFile?.resolution.label + videoFile?.fps
162 const buffer = this.timeRangesToString(this.player().buffered())
163 const progress = this.player_.webtorrent().getTorrent()?.progress
164
165 return {
166 playerNetworkInfo: this.playerNetworkInfo,
167 progress,
168 colorSpace,
169 codecs,
170 resolution,
171 buffer
172 }
173 }
174
175 private getListTemplate (options: {
176 playerNetworkInfo: PlayerNetworkInfo
177 progress: number
178 codecs: string
179 resolution: string
180 buffer: string
181
182 latency?: string
183 colorSpace?: string
184 }) {
185 const { playerNetworkInfo, progress, colorSpace, codecs, resolution, buffer, latency } = options
186 const player = this.player()
187
188 const videoQuality: VideoPlaybackQuality = player.getVideoPlaybackQuality()
189 const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0)
190 const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0)
191 const pr = (window.devicePixelRatio || 1).toFixed(2)
192 const frames = `${vw}x${vh}*${pr} / ${videoQuality.droppedVideoFrames} dropped of ${videoQuality.totalVideoFrames}`
193
194 const duration = player.duration()
195
196 let volume = `${player.volume() * 100}`
197 if (player.muted()) volume += ' (muted)'
198
199 const networkActivity = playerNetworkInfo.downloadSpeed
200 ? `${playerNetworkInfo.downloadSpeed} &dArr; / ${playerNetworkInfo.uploadSpeed} &uArr;`
201 : undefined
202
203 const totalTransferred = playerNetworkInfo.totalDownloaded
204 ? `${playerNetworkInfo.totalDownloaded} &dArr; / ${playerNetworkInfo.totalUploaded} &uArr;`
205 : undefined
206 const downloadBreakdown = playerNetworkInfo.downloadedFromServer
207 ? `${playerNetworkInfo.downloadedFromServer} from server ยท ${playerNetworkInfo.downloadedFromPeers} from peers`
208 : undefined
209
210 const bufferProgress = progress !== undefined
211 ? `${(progress * 100).toFixed(1)}% (${(progress * duration).toFixed(1)}s)`
212 : undefined
213
214 return `
215 ${this.buildElement(player.localize('Video UUID'), this.options_.videoUUID)}
216
217 ${this.buildElement(player.localize('Viewport / Frames'), frames)}
218
219 ${this.buildElement(player.localize('Resolution'), resolution)}
220
221 ${this.buildElement(player.localize('Volume'), volume)}
222
223 ${this.buildElement(player.localize('Codecs'), codecs)}
224 ${this.buildElement(player.localize('Color'), colorSpace)}
225
226 ${this.buildElement(player.localize('Connection Speed'), playerNetworkInfo.averageBandwidth)}
227
228 ${this.buildElement(player.localize('Network Activity'), networkActivity)}
229 ${this.buildElement(player.localize('Total Transfered'), totalTransferred)}
230 ${this.buildElement(player.localize('Download Breakdown'), downloadBreakdown)}
231
232 ${this.buildElement(player.localize('Buffer Progress'), bufferProgress)}
233 ${this.buildElement(player.localize('Buffer State'), buffer)}
234
235 ${this.buildElement(player.localize('Live Latency'), latency)}
236 `
237 }
238
239 private getMainTemplate () {
240 return `
241 <button class="vjs-stats-close" tabindex=0 aria-label="Close stats" title="Close stats">[x]</button>
242 <div class="vjs-stats-list"></div>
243 `
244 }
245
246 private buildElement (label: string, value?: string) {
247 if (!value) return ''
248
249 return `<div><div>${label}</div><span>${value}</span></div>`
250 }
251
252 private timeRangesToString (r: videojs.TimeRange) {
253 let result = ''
254
255 for (let i = 0; i < r.length; i++) {
256 const start = Math.floor(r.start(i))
257 const end = Math.floor(r.end(i))
258
259 result += `[${secondsToTime(start)}, ${secondsToTime(end)}] `
260 }
261
262 return result
263 }
177} 264}
178 265
179videojs.registerComponent('StatsCard', StatsCard) 266videojs.registerComponent('StatsCard', StatsCard)
diff --git a/client/src/assets/player/stats/stats-plugin.ts b/client/src/assets/player/stats/stats-plugin.ts
index 3402e7861..8aad80e8a 100644
--- a/client/src/assets/player/stats/stats-plugin.ts
+++ b/client/src/assets/player/stats/stats-plugin.ts
@@ -6,7 +6,7 @@ const Plugin = videojs.getPlugin('plugin')
6class StatsForNerdsPlugin extends Plugin { 6class StatsForNerdsPlugin extends Plugin {
7 private statsCard: StatsCard 7 private statsCard: StatsCard
8 8
9 constructor (player: videojs.Player, options: Partial<StatsCardOptions> = {}) { 9 constructor (player: videojs.Player, options: StatsCardOptions) {
10 const settings = { 10 const settings = {
11 ...options 11 ...options
12 } 12 }
@@ -22,8 +22,8 @@ class StatsForNerdsPlugin extends Plugin {
22 player.addChild(this.statsCard, settings) 22 player.addChild(this.statsCard, settings)
23 } 23 }
24 24
25 show (options?: StatsCardOptions) { 25 show () {
26 this.statsCard.show(options) 26 this.statsCard.show()
27 } 27 }
28} 28}
29 29
diff --git a/client/src/assets/player/webtorrent/webtorrent-plugin.ts b/client/src/assets/player/webtorrent/webtorrent-plugin.ts
index e557fe722..6f5fbe4c9 100644
--- a/client/src/assets/player/webtorrent/webtorrent-plugin.ts
+++ b/client/src/assets/player/webtorrent/webtorrent-plugin.ts
@@ -506,7 +506,8 @@ class WebTorrentPlugin extends Plugin {
506 uploadSpeed: this.torrent.uploadSpeed, 506 uploadSpeed: this.torrent.uploadSpeed,
507 downloaded: this.torrent.downloaded, 507 downloaded: this.torrent.downloaded,
508 uploaded: this.torrent.uploaded 508 uploaded: this.torrent.uploaded
509 } 509 },
510 bandwidthEstimate: this.webtorrent.downloadSpeed
510 } as PlayerNetworkInfo) 511 } as PlayerNetworkInfo)
511 }, this.CONSTANTS.INFO_SCHEDULER) 512 }, this.CONSTANTS.INFO_SCHEDULER)
512 } 513 }
diff --git a/scripts/i18n/create-custom-files.ts b/scripts/i18n/create-custom-files.ts
index d4d5b44f0..81b6e3388 100755
--- a/scripts/i18n/create-custom-files.ts
+++ b/scripts/i18n/create-custom-files.ts
@@ -36,7 +36,21 @@ const playerKeys = {
36 'From servers: ': 'From servers: ', 36 'From servers: ': 'From servers: ',
37 'From peers: ': 'From peers: ', 37 'From peers: ': 'From peers: ',
38 'Normal mode': 'Normal mode', 38 'Normal mode': 'Normal mode',
39 'Theater mode': 'Theater mode' 39 'Stats for nerds': 'Stats for nerds',
40 'Theater mode': 'Theater mode',
41 'Video UUID': 'Video UUID',
42 'Viewport / Frames': 'Viewport / Frames',
43 'Resolution': 'Resolution',
44 'Volume': 'Volume',
45 'Codecs': 'Codecs',
46 'Color': 'Color',
47 'Connection Speed': 'Connection Speed',
48 'Network Activity': 'Network Activity',
49 'Total Transfered': 'Total Transfered',
50 'Download Breakdown': 'Download Breakdown',
51 'Buffer Progress': 'Buffer Progress',
52 'Buffer State': 'Buffer State',
53 'Live Latency': 'Live Latency'
40} 54}
41Object.assign(playerKeys, videojs) 55Object.assign(playerKeys, videojs)
42 56