From ff563914bb10728301a24fb9e548c9efb62387eb Mon Sep 17 00:00:00 2001 From: Rigel Kent Date: Mon, 12 Apr 2021 10:26:30 +0200 Subject: add stats videojs plugin --- client/src/assets/player/stats/stats-card.ts | 184 +++++++++++++++++++++++++ client/src/assets/player/stats/stats-plugin.ts | 31 +++++ 2 files changed, 215 insertions(+) create mode 100644 client/src/assets/player/stats/stats-card.ts create mode 100644 client/src/assets/player/stats/stats-plugin.ts (limited to 'client/src/assets/player/stats') 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 @@ +import videojs from 'video.js' +import { PlayerNetworkInfo } from '../peertube-videojs-typings' +import { getAverageBandwidthInStore } from '../peertube-player-local-storage' +import { bytes } from '../utils' + +interface StatsCardOptions extends videojs.ComponentOptions { + videoUUID?: string, + videoIsLive?: boolean, + mode?: 'webtorrent' | 'p2p-media-loader' +} + +function getListTemplate ( + options: StatsCardOptions, + player: videojs.Player, + args: { + playerNetworkInfo?: any + videoFile?: any + progress?: number + }) { + const { playerNetworkInfo, videoFile, progress } = args + + const videoQuality: VideoPlaybackQuality = player.getVideoPlaybackQuality() + const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0) + const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0) + const pr = (window.devicePixelRatio || 1).toFixed(2) + const colorspace = videoFile?.metadata?.streams[0]['color_space'] !== "unknown" + ? videoFile?.metadata?.streams[0]['color_space'] + : undefined + + return ` +
+
${player.localize('Video UUID')}
+ ${options.videoUUID || ''} +
+
+
Viewport / ${player.localize('Frames')}
+ ${vw}x${vh}*${pr} / ${videoQuality.droppedVideoFrames} dropped of ${videoQuality.totalVideoFrames} +
+ +
${player.localize('Resolution')}
+ ${videoFile?.resolution.label + videoFile?.fps} + +
+
${player.localize('Volume')}
+ ${~~(player.volume() * 100)}%${player.muted() ? ' (muted)' : ''} +
+ +
${player.localize('Codecs')}
+ ${videoFile?.metadata?.streams[0]['codec_name'] || 'avc1'} + + +
${player.localize('Color')}
+ ${colorspace || 'bt709'} + + +
${player.localize('Connection Speed')}
+ ${playerNetworkInfo.averageBandwidth} + + +
${player.localize('Network Activity')}
+ ${playerNetworkInfo.downloadSpeed} ⇓ / ${playerNetworkInfo.uploadSpeed} ⇑ + + +
${player.localize('Total Transfered')}
+ ${playerNetworkInfo.totalDownloaded} ⇓ / ${playerNetworkInfo.totalUploaded} ⇑ + + +
${player.localize('Download Breakdown')}
+ ${playerNetworkInfo.downloadedFromServer} from server ยท ${playerNetworkInfo.downloadedFromPeers} from peers + + +
${player.localize('Buffer Health')}
+ ${(progress * 100).toFixed(1)}% (${(progress * videoFile?.metadata?.format.duration).toFixed(1)}s) + +
+
${player.localize('Live Latency')}
+ +
+ ` +} + +function getMainTemplate () { + return ` + +
+ ` +} + +const Component = videojs.getComponent('Component') +class StatsCard extends Component { + options_: StatsCardOptions + container: HTMLDivElement + list: HTMLDivElement + closeButton: HTMLElement + update: any + source: any + + interval = 300 + playerNetworkInfo: any = {} + statsForNerdsEvents = new videojs.EventTarget() + + constructor (player: videojs.Player, options: StatsCardOptions) { + super(player, options) + } + + createEl () { + const container = super.createEl('div', { + className: 'vjs-stats-content', + innerHTML: getMainTemplate() + }) as HTMLDivElement + this.container = container + this.container.style.display = 'none' + + this.closeButton = this.container.getElementsByClassName('vjs-stats-close')[0] as HTMLElement + this.closeButton.onclick = () => this.hide() + + this.list = this.container.getElementsByClassName('vjs-stats-list')[0] as HTMLDivElement + + console.log(this.player_.qualityLevels()) + + this.player_.on('p2pInfo', (event: any, data: PlayerNetworkInfo) => { + if (!data) return // HTTP fallback + + this.source = data.source + + const p2pStats = data.p2p + const httpStats = data.http + + this.playerNetworkInfo.downloadSpeed = bytes(p2pStats.downloadSpeed + httpStats.downloadSpeed).join(' ') + this.playerNetworkInfo.uploadSpeed = bytes(p2pStats.uploadSpeed + httpStats.uploadSpeed).join(' ') + this.playerNetworkInfo.totalDownloaded = bytes(p2pStats.downloaded + httpStats.downloaded).join(' ') + this.playerNetworkInfo.totalUploaded = bytes(p2pStats.uploaded + httpStats.uploaded).join(' ') + this.playerNetworkInfo.numPeers = p2pStats.numPeers + this.playerNetworkInfo.averageBandwidth = bytes(getAverageBandwidthInStore() || p2pStats.downloaded + httpStats.downloaded).join(' ') + + if (data.source === 'p2p-media-loader') { + this.playerNetworkInfo.downloadedFromServer = bytes(httpStats.downloaded).join(' ') + this.playerNetworkInfo.downloadedFromPeers = bytes(p2pStats.downloaded).join(' ') + } + }) + + return container + } + + toggle () { + this.update + ? this.hide() + : this.show() + } + + show (options?: StatsCardOptions) { + if (options) this.options_ = options + + let metadata = {} + + this.container.style.display = 'block' + this.update = setInterval(async () => { + try { + if (this.source === 'webtorrent') { + const progress = this.player_.webtorrent().getTorrent()?.progress + const videoFile = this.player_.webtorrent().getCurrentVideoFile() + videoFile.metadata = metadata[videoFile.fileUrl] = videoFile.metadata || metadata[videoFile.fileUrl] || videoFile.metadataUrl && await fetch(videoFile.metadataUrl).then(res => res.json()) + this.list.innerHTML = getListTemplate(this.options_, this.player_, { playerNetworkInfo: this.playerNetworkInfo, videoFile, progress }) + } else { + this.list.innerHTML = getListTemplate(this.options_, this.player_, { playerNetworkInfo: this.playerNetworkInfo }) + } + } catch (e) { + clearInterval(this.update) + } + }, this.interval) + } + + hide () { + clearInterval(this.update) + this.container.style.display = 'none' + } +} + +videojs.registerComponent('StatsCard', StatsCard) + +export { + StatsCard, + StatsCardOptions +} 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 @@ +import videojs from 'video.js' +import { StatsCard, StatsCardOptions } from './stats-card' + +const Plugin = videojs.getPlugin('plugin') + +class StatsForNerdsPlugin extends Plugin { + private statsCard: StatsCard + + constructor (player: videojs.Player, options: Partial = {}) { + const settings = { + ...options + } + + super(player) + + this.player.ready(() => { + player.addClass('vjs-stats-for-nerds') + }) + + this.statsCard = new StatsCard(player, options) + + player.addChild(this.statsCard, settings) + } + + show (options?: StatsCardOptions) { + this.statsCard.show(options) + } +} + +videojs.registerPlugin('stats', StatsForNerdsPlugin) +export { StatsForNerdsPlugin } -- cgit v1.2.3