aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/assets/player/stats
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/assets/player/stats')
-rw-r--r--client/src/assets/player/stats/stats-card.ts184
-rw-r--r--client/src/assets/player/stats/stats-plugin.ts31
2 files changed, 215 insertions, 0 deletions
diff --git a/client/src/assets/player/stats/stats-card.ts b/client/src/assets/player/stats/stats-card.ts
new file mode 100644
index 000000000..278899b72
--- /dev/null
+++ b/client/src/assets/player/stats/stats-card.ts
@@ -0,0 +1,184 @@
1import videojs from 'video.js'
2import { PlayerNetworkInfo } from '../peertube-videojs-typings'
3import { getAverageBandwidthInStore } from '../peertube-player-local-storage'
4import { bytes } from '../utils'
5
6interface StatsCardOptions extends videojs.ComponentOptions {
7 videoUUID?: string,
8 videoIsLive?: boolean,
9 mode?: 'webtorrent' | 'p2p-media-loader'
10}
11
12function getListTemplate (
13 options: StatsCardOptions,
14 player: videojs.Player,
15 args: {
16 playerNetworkInfo?: any
17 videoFile?: any
18 progress?: number
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
82function getMainTemplate () {
83 return `
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}
88
89const Component = videojs.getComponent('Component')
90class StatsCard extends Component {
91 options_: StatsCardOptions
92 container: HTMLDivElement
93 list: HTMLDivElement
94 closeButton: HTMLElement
95 update: any
96 source: any
97
98 interval = 300
99 playerNetworkInfo: any = {}
100 statsForNerdsEvents = new videojs.EventTarget()
101
102 constructor (player: videojs.Player, options: StatsCardOptions) {
103 super(player, options)
104 }
105
106 createEl () {
107 const container = super.createEl('div', {
108 className: 'vjs-stats-content',
109 innerHTML: getMainTemplate()
110 }) as HTMLDivElement
111 this.container = container
112 this.container.style.display = 'none'
113
114 this.closeButton = this.container.getElementsByClassName('vjs-stats-close')[0] as HTMLElement
115 this.closeButton.onclick = () => this.hide()
116
117 this.list = this.container.getElementsByClassName('vjs-stats-list')[0] as HTMLDivElement
118
119 console.log(this.player_.qualityLevels())
120
121 this.player_.on('p2pInfo', (event: any, data: PlayerNetworkInfo) => {
122 if (!data) return // HTTP fallback
123
124 this.source = data.source
125
126 const p2pStats = data.p2p
127 const httpStats = data.http
128
129 this.playerNetworkInfo.downloadSpeed = bytes(p2pStats.downloadSpeed + httpStats.downloadSpeed).join(' ')
130 this.playerNetworkInfo.uploadSpeed = bytes(p2pStats.uploadSpeed + httpStats.uploadSpeed).join(' ')
131 this.playerNetworkInfo.totalDownloaded = bytes(p2pStats.downloaded + httpStats.downloaded).join(' ')
132 this.playerNetworkInfo.totalUploaded = bytes(p2pStats.uploaded + httpStats.uploaded).join(' ')
133 this.playerNetworkInfo.numPeers = p2pStats.numPeers
134 this.playerNetworkInfo.averageBandwidth = bytes(getAverageBandwidthInStore() || p2pStats.downloaded + httpStats.downloaded).join(' ')
135
136 if (data.source === 'p2p-media-loader') {
137 this.playerNetworkInfo.downloadedFromServer = bytes(httpStats.downloaded).join(' ')
138 this.playerNetworkInfo.downloadedFromPeers = bytes(p2pStats.downloaded).join(' ')
139 }
140 })
141
142 return container
143 }
144
145 toggle () {
146 this.update
147 ? this.hide()
148 : this.show()
149 }
150
151 show (options?: StatsCardOptions) {
152 if (options) this.options_ = options
153
154 let metadata = {}
155
156 this.container.style.display = 'block'
157 this.update = setInterval(async () => {
158 try {
159 if (this.source === 'webtorrent') {
160 const progress = this.player_.webtorrent().getTorrent()?.progress
161 const videoFile = this.player_.webtorrent().getCurrentVideoFile()
162 videoFile.metadata = metadata[videoFile.fileUrl] = videoFile.metadata || metadata[videoFile.fileUrl] || videoFile.metadataUrl && await fetch(videoFile.metadataUrl).then(res => res.json())
163 this.list.innerHTML = getListTemplate(this.options_, this.player_, { playerNetworkInfo: this.playerNetworkInfo, videoFile, progress })
164 } else {
165 this.list.innerHTML = getListTemplate(this.options_, this.player_, { playerNetworkInfo: this.playerNetworkInfo })
166 }
167 } catch (e) {
168 clearInterval(this.update)
169 }
170 }, this.interval)
171 }
172
173 hide () {
174 clearInterval(this.update)
175 this.container.style.display = 'none'
176 }
177}
178
179videojs.registerComponent('StatsCard', StatsCard)
180
181export {
182 StatsCard,
183 StatsCardOptions
184}
diff --git a/client/src/assets/player/stats/stats-plugin.ts b/client/src/assets/player/stats/stats-plugin.ts
new file mode 100644
index 000000000..3402e7861
--- /dev/null
+++ b/client/src/assets/player/stats/stats-plugin.ts
@@ -0,0 +1,31 @@
1import videojs from 'video.js'
2import { StatsCard, StatsCardOptions } from './stats-card'
3
4const Plugin = videojs.getPlugin('plugin')
5
6class StatsForNerdsPlugin extends Plugin {
7 private statsCard: StatsCard
8
9 constructor (player: videojs.Player, options: Partial<StatsCardOptions> = {}) {
10 const settings = {
11 ...options
12 }
13
14 super(player)
15
16 this.player.ready(() => {
17 player.addClass('vjs-stats-for-nerds')
18 })
19
20 this.statsCard = new StatsCard(player, options)
21
22 player.addChild(this.statsCard, settings)
23 }
24
25 show (options?: StatsCardOptions) {
26 this.statsCard.show(options)
27 }
28}
29
30videojs.registerPlugin('stats', StatsForNerdsPlugin)
31export { StatsForNerdsPlugin }