Stats for nerds cleanup and fix linter errors

This commit is contained in:
Preston 2022-02-19 17:17:58 -05:00
parent 1da67594b6
commit cd574be4e7
5 changed files with 146 additions and 160 deletions

View File

@ -25,7 +25,7 @@ function runApp() {
label: 'Show Video Statistics', label: 'Show Video Statistics',
visible: parameters.mediaType === 'video', visible: parameters.mediaType === 'video',
click: () => { click: () => {
browserWindow.webContents.send('showVideoStatistics', 'show') browserWindow.webContents.send('showVideoStatistics')
} }
} }
] ]

View File

@ -11,7 +11,6 @@ import 'videojs-overlay/dist/videojs-overlay.css'
import 'videojs-vtt-thumbnails-freetube' import 'videojs-vtt-thumbnails-freetube'
import 'videojs-contrib-quality-levels' import 'videojs-contrib-quality-levels'
import 'videojs-http-source-selector' import 'videojs-http-source-selector'
import { ipcRenderer } from 'electron'
import { IpcChannels } from '../../../constants' import { IpcChannels } from '../../../constants'
@ -85,6 +84,10 @@ export default Vue.extend({
useHls: false, useHls: false,
selectedDefaultQuality: '', selectedDefaultQuality: '',
selectedQuality: '', selectedQuality: '',
selectedResolution: '',
selectedBitrate: '',
selectedMimeType: '',
selectedFPS: 0,
using60Fps: false, using60Fps: false,
maxFramerate: 0, maxFramerate: 0,
activeSourceList: [], activeSourceList: [],
@ -92,6 +95,10 @@ export default Vue.extend({
mouseTimeout: null, mouseTimeout: null,
touchTimeout: null, touchTimeout: null,
lastTouchTime: null, lastTouchTime: null,
playerStats: null,
statsModal: null,
showStatsModal: false,
statsModalEventName: 'updateStats',
dataSetup: { dataSetup: {
fluid: true, fluid: true,
nativeTextTracks: false, nativeTextTracks: false,
@ -132,44 +139,8 @@ export default Vue.extend({
2.25, 2.25,
2.5, 2.5,
2.75, 2.75,
3, 3
3.25,
3.5,
3.75,
4,
4.25,
4.5,
4.75,
5,
5.25,
5.5,
5.75,
6,
6.25,
6.5,
6.75,
7,
7.25,
7.5,
7.75,
8
] ]
},
stats: {
videoId: '',
playerResolution: null,
frameInfo: null,
volume: 0,
bandwidth: null,
bufferPercent: 0,
fps: null,
display: {
modal: null,
event: 'statsUpdated',
keyboardShortcut: 'KeyI',
rightClickEvent: 'showVideoStatistics',
activated: false
}
} }
} }
}, },
@ -229,36 +200,11 @@ export default Vue.extend({
displayVideoPlayButton: function() { displayVideoPlayButton: function() {
return this.$store.getters.getDisplayVideoPlayButton return this.$store.getters.getDisplayVideoPlayButton
},
formatted_stats: function() {
let resolution = ''
let dropFrame = ''
if (this.stats.playerResolution != null) {
resolution = `(${this.stats.playerResolution.height}X${this.stats.playerResolution.width}) @ ${this.stats.fps} ${this.$t('Video.Stats.fps')}`
}
if (this.stats.frameInfo != null) {
dropFrame = `${this.stats.frameInfo.droppedVideoFrames} ${this.$t('Video.Stats.out of')} ${this.stats.frameInfo.totalVideoFrames}`
}
const stats = [
[this.$t('Video.Stats.video id'), this.stats.videoId],
[this.$t('Video.Stats.frame drop'), dropFrame],
[this.$t('Video.Stats.player resolution'), resolution],
[this.$t('Video.Stats.volume'), `${(this.stats.volume * 100).toFixed(0)} %`],
[this.$t('Video.Stats.bandwidth'), `${(this.stats.bandwidth / 1000).toFixed(2)} Kbps`],
[this.$t('Video.Stats.buffered'), `${(this.stats.bufferPercent * 100).toFixed(0)} %`]
]
let formattedStats = '<ul style="list-style-type: none;text-align:left; padding-left:0px";>'
for (const stat of stats) {
formattedStats += `<li style="font-size: 75%">${stat[0]}: ${stat[1]}</li>`
}
formattedStats += '</ul>'
return formattedStats
} }
}, },
watch: { watch: {
selectedQuality: function() { showStatsModal: function() {
this.currentFps() this.player.trigger(this.statsModalEventName)
} }
}, },
mounted: function () { mounted: function () {
@ -411,6 +357,7 @@ export default Vue.extend({
this.player.on('ready', () => { this.player.on('ready', () => {
this.$emit('ready') this.$emit('ready')
this.checkAspectRatio() this.checkAspectRatio()
this.createStatsModal()
if (this.captionHybridList.length !== 0) { if (this.captionHybridList.length !== 0) {
this.transformAndInsertCaptions() this.transformAndInsertCaptions()
} }
@ -440,11 +387,36 @@ export default Vue.extend({
} }
}) })
this.player.on(this.statsModalEventName, () => {
if (this.showStatsModal) {
this.statsModal.open()
this.player.controls(true)
this.statsModal.contentEl().innerHTML = this.getFormattedStats()
} else {
this.statsModal.close()
}
})
this.player.on('timeupdate', () => {
if (this.format === 'dash') {
this.playerStats = this.player.tech({ IWillNotUseThisInPlugins: true }).vhs.stats
this.updateStatsContent()
}
})
this.player.textTrackSettings.on('modalclose', (_) => { this.player.textTrackSettings.on('modalclose', (_) => {
const settings = this.player.textTrackSettings.getValues() const settings = this.player.textTrackSettings.getValues()
this.updateDefaultCaptionSettings(JSON.stringify(settings)) this.updateDefaultCaptionSettings(JSON.stringify(settings))
}) })
this.addPlayerStatsEvent()
// right click menu
if (this.usingElectron) {
const { ipcRenderer } = require('electron')
ipcRenderer.removeAllListeners('showVideoStatistics')
ipcRenderer.on('showVideoStatistics', (event) => {
this.toggleShowStatsModal()
})
}
} }
}, },
@ -851,6 +823,18 @@ export default Vue.extend({
qualityElement.innerText = selectedQuality qualityElement.innerText = selectedQuality
this.selectedQuality = selectedQuality this.selectedQuality = selectedQuality
if (selectedQuality !== 'auto') {
this.selectedResolution = `${adaptiveFormat.width}x${adaptiveFormat.height}`
this.selectedFPS = adaptiveFormat.fps
this.selectedBitrate = adaptiveFormat.bitrate
this.selectedMimeType = adaptiveFormat.mimeType
} else {
this.selectedResolution = 'auto'
this.selectedFPS = 'auto'
this.selectedBitrate = 'auto'
this.selectedMimeType = 'auto'
}
const qualityItems = $('.quality-item').get() const qualityItems = $('.quality-item').get()
$('.quality-item').removeClass('quality-selected') $('.quality-item').removeClass('quality-selected')
@ -1426,7 +1410,64 @@ export default Vue.extend({
handleTouchEnd: function (event) { handleTouchEnd: function (event) {
clearTimeout(this.touchPauseTimeout) clearTimeout(this.touchPauseTimeout)
}, },
toggleShowStatsModal: function() {
console.log(this.format)
if (this.format !== 'dash') {
this.showToast({
message: 'Video statistics are not available for legacy videos'
})
} else {
this.showStatsModal = !this.showStatsModal
}
},
createStatsModal: function() {
const ModalDialog = videojs.getComponent('ModalDialog')
this.statsModal = new ModalDialog(this.player, {
temporary: false,
pauseOnOpen: false
})
this.player.addChild(this.statsModal)
this.statsModal.el_.classList.add('statsModal')
this.statsModal.on('modalclose', () => {
this.showStatsModal = false
})
},
updateStatsContent: function() {
if (this.showStatsModal) {
this.statsModal.contentEl().innerHTML = this.getFormattedStats()
}
},
getFormattedStats: function() {
const currentVolume = this.player.muted() ? 0 : this.player.volume()
const volume = `${(currentVolume * 100).toFixed(0)}%`
const bandwidth = `${(this.playerStats.bandwidth / 1000).toFixed(2)}kbps`
const buffered = `${(this.player.bufferedPercent() * 100).toFixed(0)}%`
const droppedFrames = this.playerStats.videoPlaybackQuality.droppedVideoFrames
const totalFrames = this.playerStats.videoPlaybackQuality.totalVideoFrames
const frames = `${droppedFrames} / ${totalFrames}`
const resolution = `${this.selectedResolution}@${this.selectedFPS}fps`
const playerDimensions = `${this.playerStats.playerDimensions.width}x${this.playerStats.playerDimensions.height}`
const statsArray = [
[this.$t('Video.Stats.Video ID'), this.videoId],
[this.$t('Video.Stats.Resolution'), resolution],
[this.$t('Video.Stats.Player Dimensions'), playerDimensions],
[this.$t('Video.Stats.Bitrate'), this.selectedBitrate],
[this.$t('Video.Stats.Volume'), volume],
[this.$t('Video.Stats.Bandwidth'), bandwidth],
[this.$t('Video.Stats.Buffered'), buffered],
[this.$t('Video.Stats.Dropped / Total Frames'), frames],
[this.$t('Video.Stats.Mimetype'), this.selectedMimeType]
]
let listContentHTML = ''
statsArray.forEach((stat) => {
const content = `<p>${stat[0]}: ${stat[1]}</p>`
listContentHTML += content
})
return listContentHTML
},
// This function should always be at the bottom of this file
keyboardShortcutHandler: function (event) { keyboardShortcutHandler: function (event) {
const activeInputs = $('.ft-input') const activeInputs = $('.ft-input')
@ -1520,6 +1561,11 @@ export default Vue.extend({
event.preventDefault() event.preventDefault()
this.changeDurationBySeconds(this.defaultSkipInterval * 1) this.changeDurationBySeconds(this.defaultSkipInterval * 1)
break break
case 73:
// I Key
event.preventDefault()
this.toggleShowStatsModal()
break
case 49: case 49:
// 1 Key // 1 Key
// Jump to 10% in the video // Jump to 10% in the video
@ -1619,86 +1665,6 @@ export default Vue.extend({
} }
}, },
addPlayerStatsEvent: function() {
this.stats.videoId = this.videoId
this.player.on('volumechange', () => {
this.stats.volume = this.player.volume()
this.player.trigger(this.stats.display.event)
})
this.player.on('timeupdate', () => {
const stats = this.player.tech({ IWillNotUseThisInPlugins: true }).vhs.stats
this.stats.frameInfo = stats.videoPlaybackQuality
this.player.trigger(this.stats.display.event)
})
this.player.on('progress', () => {
const stats = this.player.tech({ IWillNotUseThisInPlugins: true }).vhs.stats
this.stats.bandwidth = stats.bandwidth
this.stats.bufferPercent = this.player.bufferedPercent()
})
this.player.on('playerresize', () => {
this.stats.playerResolution = this.player.currentDimensions()
this.player.trigger(this.stats.display.event)
})
this.createStatsModal()
this.player.on(this.stats.display.event, () => {
if (this.stats.display.activated) {
this.stats.display.modal.open()
this.player.controls(true)
this.stats.display.modal.contentEl().innerHTML = this.formatted_stats
} else {
this.stats.display.modal.close()
}
})
// keyboard shortcut
window.addEventListener('keyup', (event) => {
if (event.code === this.stats.display.keyboardShortcut) {
if (this.stats.display.activated) {
this.deactivateStatsDisplay()
} else {
this.activateStatsDisplay()
}
}
}, true)
// right click menu
ipcRenderer.on(this.stats.display.rightClickEvent, () => {
this.activateStatsDisplay()
})
},
createStatsModal: function() {
const ModalDialog = videojs.getComponent('ModalDialog')
this.stats.display.modal = new ModalDialog(this.player, {
temporary: false,
pauseOnOpen: false
})
this.player.addChild(this.stats.display.modal)
this.stats.display.modal.height('35%')
this.stats.display.modal.width('50%')
this.stats.display.modal.contentEl().style.backgroundColor = 'rgba(0, 0, 0, 0.55)'
this.stats.display.modal.on('modalclose', () => {
this.deactivateStatsDisplay()
})
},
activateStatsDisplay: function() {
this.stats.display.activated = true
},
deactivateStatsDisplay: function() {
this.stats.display.activated = false
},
currentFps: function() {
for (const el of this.activeAdaptiveFormats) {
if (el.qualityLabel === this.selectedQuality) {
this.stats.fps = el.fps
break
}
}
},
...mapActions([ ...mapActions([
'calculateColorLuminance', 'calculateColorLuminance',
'updateDefaultCaptionSettings', 'updateDefaultCaptionSettings',

View File

@ -4,7 +4,6 @@ import fs from 'fs'
import i18n from '../../i18n/index' import i18n from '../../i18n/index'
import { IpcChannels } from '../../../constants' import { IpcChannels } from '../../../constants'
import { ipcRenderer } from 'electron'
const state = { const state = {
isSideNavOpen: false, isSideNavOpen: false,
@ -223,8 +222,6 @@ const actions = {
}) })
const reader = response.body.getReader() const reader = response.body.getReader()
const contentLength = response.headers.get('Content-Length')
let receivedLength = 0
const chunks = [] const chunks = []
const handleError = (err) => { const handleError = (err) => {
@ -240,9 +237,10 @@ const actions = {
} }
chunks.push(value) chunks.push(value)
receivedLength += value.length
// Can be used in the future to determine download percentage // Can be used in the future to determine download percentage
const percentage = receivedLength / contentLength // const contentLength = response.headers.get('Content-Length')
// const receivedLength = value.length
// const percentage = receivedLength / contentLength
await reader.read().then(processText).catch(handleError) await reader.read().then(processText).catch(handleError)
} }

View File

@ -2160,3 +2160,24 @@ video::-webkit-media-text-track-display {
font-size: xx-large; font-size: xx-large;
max-width: 100% !important; max-width: 100% !important;
} }
.vjs-modal-dialog.statsModal {
line-height: 10px;
width: 550px;
height: 225px;
font-size: 10px;
background-color: rgba(0, 0, 0, 0.5) !important;
}
.vjs-modal-dialog.statsModal p {
line-height: 10px;
position:relative;
bottom: 15px;
}
@media screen and (max-width: 775px) {
.vjs-modal-dialog.statsModal {
width: 100%;
height: 100%;
}
}

View File

@ -556,14 +556,15 @@ Video:
shuffling playlists: shuffling playlists shuffling playlists: shuffling playlists
looping playlists: looping playlists looping playlists: looping playlists
Stats: Stats:
video id: "Video ID (YouTube)" Video ID: Video ID
player resolution: "Viewport" Resolution: Resolution
volume: "Volume" Player Dimensions: Player Dimensions
fps: "FPS" Bitrate: Bitrate
frame drop: "Frame Drop" Volume: Volume
bandwidth: "Connection Speed" Bandwidth: Bandwidth
buffered: "Buffered" Buffered: Buffered
out of: "out of" Dropped / Total Frames: Dropped / Total Frames
Mimetype: Mimetype
#& Videos #& Videos
Videos: Videos:
#& Sort By #& Sort By