]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - client/src/assets/player/stats/stats-card.ts
add stats videojs plugin
[github/Chocobozzz/PeerTube.git] / client / src / assets / player / stats / stats-card.ts
1 import videojs from 'video.js'
2 import { PlayerNetworkInfo } from '../peertube-videojs-typings'
3 import { getAverageBandwidthInStore } from '../peertube-player-local-storage'
4 import { bytes } from '../utils'
5
6 interface StatsCardOptions extends videojs.ComponentOptions {
7 videoUUID?: string,
8 videoIsLive?: boolean,
9 mode?: 'webtorrent' | 'p2p-media-loader'
10 }
11
12 function 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
82 function 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
89 const Component = videojs.getComponent('Component')
90 class 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
179 videojs.registerComponent('StatsCard', StatsCard)
180
181 export {
182 StatsCard,
183 StatsCardOptions
184 }