+++ /dev/null
-import videojs from 'video.js'
-import { secondsToTime } from '@shared/core-utils'
-import { PlayerNetworkInfo as EventPlayerNetworkInfo } from '../peertube-videojs-typings'
-import { bytes } from '../utils'
-
-interface StatsCardOptions extends videojs.ComponentOptions {
- videoUUID: string
- videoIsLive: boolean
- mode: 'webtorrent' | 'p2p-media-loader'
- p2pEnabled: boolean
-}
-
-interface PlayerNetworkInfo {
- downloadSpeed?: string
- uploadSpeed?: string
- totalDownloaded?: string
- totalUploaded?: string
- numPeers?: number
- averageBandwidth?: string
-
- downloadedFromServer?: string
- downloadedFromPeers?: string
-}
-
-const Component = videojs.getComponent('Component')
-class StatsCard extends Component {
- options_: StatsCardOptions
-
- container: HTMLDivElement
-
- list: HTMLDivElement
- closeButton: HTMLElement
-
- updateInterval: any
-
- mode: 'webtorrent' | 'p2p-media-loader'
-
- metadataStore: any = {}
-
- intervalMs = 300
- playerNetworkInfo: PlayerNetworkInfo = {}
-
- createEl () {
- const container = super.createEl('div', {
- className: 'vjs-stats-content',
- innerHTML: this.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
-
- this.player_.on('p2pInfo', (event: any, data: EventPlayerNetworkInfo) => {
- if (!data) return // HTTP fallback
-
- this.mode = 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(data.bandwidthEstimate).join(' ') + '/s'
-
- if (data.source === 'p2p-media-loader') {
- this.playerNetworkInfo.downloadedFromServer = bytes(httpStats.downloaded).join(' ')
- this.playerNetworkInfo.downloadedFromPeers = bytes(p2pStats.downloaded).join(' ')
- }
- })
-
- return container
- }
-
- toggle () {
- if (this.updateInterval) this.hide()
- else this.show()
- }
-
- show () {
- this.container.style.display = 'block'
- this.updateInterval = setInterval(async () => {
- try {
- const options = this.mode === 'p2p-media-loader'
- ? this.buildHLSOptions()
- : await this.buildWebTorrentOptions() // Default
-
- this.list.innerHTML = this.getListTemplate(options)
- } catch (err) {
- console.error('Cannot update stats.', err)
- clearInterval(this.updateInterval)
- }
- }, this.intervalMs)
- }
-
- hide () {
- clearInterval(this.updateInterval)
- this.container.style.display = 'none'
- }
-
- private buildHLSOptions () {
- const p2pMediaLoader = this.player_.p2pMediaLoader()
- const level = p2pMediaLoader.getCurrentLevel()
-
- const codecs = level?.videoCodec || level?.audioCodec
- ? `${level?.videoCodec || ''} / ${level?.audioCodec || ''}`
- : undefined
-
- const resolution = `${level?.height}p${level?.attrs['FRAME-RATE'] || ''}`
- const buffer = this.timeRangesToString(this.player().buffered())
-
- let progress: number
- let latency: string
-
- if (this.options_.videoIsLive) {
- latency = secondsToTime(p2pMediaLoader.getLiveLatency())
- } else {
- progress = this.player().bufferedPercent()
- }
-
- return {
- playerNetworkInfo: this.playerNetworkInfo,
- resolution,
- codecs,
- buffer,
- latency,
- progress
- }
- }
-
- private async buildWebTorrentOptions () {
- const videoFile = this.player_.webtorrent().getCurrentVideoFile()
-
- if (!this.metadataStore[videoFile.fileUrl]) {
- this.metadataStore[videoFile.fileUrl] = await fetch(videoFile.metadataUrl).then(res => res.json())
- }
-
- const metadata = this.metadataStore[videoFile.fileUrl]
-
- let colorSpace = 'unknown'
- let codecs = 'unknown'
-
- if (metadata?.streams[0]) {
- const stream = metadata.streams[0]
-
- colorSpace = stream['color_space'] !== 'unknown'
- ? stream['color_space']
- : 'bt709'
-
- codecs = stream['codec_name'] || 'avc1'
- }
-
- const resolution = videoFile?.resolution.label + videoFile?.fps
- const buffer = this.timeRangesToString(this.player().buffered())
- const progress = this.player_.webtorrent().getTorrent()?.progress
-
- return {
- playerNetworkInfo: this.playerNetworkInfo,
- progress,
- colorSpace,
- codecs,
- resolution,
- buffer
- }
- }
-
- private getListTemplate (options: {
- playerNetworkInfo: PlayerNetworkInfo
- progress: number
- codecs: string
- resolution: string
- buffer: string
-
- latency?: string
- colorSpace?: string
- }) {
- const { playerNetworkInfo, progress, colorSpace, codecs, resolution, buffer, latency } = options
- const player = this.player()
-
- 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 frames = `${vw}x${vh}*${pr} / ${videoQuality.droppedVideoFrames} dropped of ${videoQuality.totalVideoFrames}`
-
- const duration = player.duration()
-
- let volume = `${Math.round(player.volume() * 100)}`
- if (player.muted()) volume += ' (muted)'
-
- const networkActivity = playerNetworkInfo.downloadSpeed
- ? `${playerNetworkInfo.downloadSpeed} ⇓ / ${playerNetworkInfo.uploadSpeed} ⇑`
- : undefined
-
- const totalTransferred = playerNetworkInfo.totalDownloaded
- ? `${playerNetworkInfo.totalDownloaded} ⇓ / ${playerNetworkInfo.totalUploaded} ⇑`
- : undefined
- const downloadBreakdown = playerNetworkInfo.downloadedFromServer
- ? `${playerNetworkInfo.downloadedFromServer} from servers ยท ${playerNetworkInfo.downloadedFromPeers} from peers`
- : undefined
-
- const bufferProgress = progress !== undefined
- ? `${(progress * 100).toFixed(1)}% (${(progress * duration).toFixed(1)}s)`
- : undefined
-
- return `
- ${this.buildElement(player.localize('Player mode'), this.mode || 'HTTP')}
- ${this.buildElement(player.localize('P2P'), player.localize(this.options_.p2pEnabled ? 'enabled' : 'disabled'))}
-
- ${this.buildElement(player.localize('Video UUID'), this.options_.videoUUID)}
-
- ${this.buildElement(player.localize('Viewport / Frames'), frames)}
-
- ${this.buildElement(player.localize('Resolution'), resolution)}
-
- ${this.buildElement(player.localize('Volume'), volume)}
-
- ${this.buildElement(player.localize('Codecs'), codecs)}
- ${this.buildElement(player.localize('Color'), colorSpace)}
-
- ${this.buildElement(player.localize('Connection Speed'), playerNetworkInfo.averageBandwidth)}
-
- ${this.buildElement(player.localize('Network Activity'), networkActivity)}
- ${this.buildElement(player.localize('Total Transfered'), totalTransferred)}
- ${this.buildElement(player.localize('Download Breakdown'), downloadBreakdown)}
-
- ${this.buildElement(player.localize('Buffer Progress'), bufferProgress)}
- ${this.buildElement(player.localize('Buffer State'), buffer)}
-
- ${this.buildElement(player.localize('Live Latency'), latency)}
- `
- }
-
- private getMainTemplate () {
- return `
- <button class="vjs-stats-close" tabindex=0 aria-label="Close stats" title="Close stats">[x]</button>
- <div class="vjs-stats-list"></div>
- `
- }
-
- private buildElement (label: string, value?: string) {
- if (!value) return ''
-
- return `<div><div>${label}</div><span>${value}</span></div>`
- }
-
- private timeRangesToString (r: videojs.TimeRange) {
- let result = ''
-
- for (let i = 0; i < r.length; i++) {
- const start = Math.floor(r.start(i))
- const end = Math.floor(r.end(i))
-
- result += `[${secondsToTime(start)}, ${secondsToTime(end)}] `
- }
-
- return result
- }
-}
-
-videojs.registerComponent('StatsCard', StatsCard)
-
-export {
- StatsCard,
- StatsCardOptions
-}