From 001b6791836b5ae30d680f98c41049204c2c04d4 Mon Sep 17 00:00:00 2001 From: constraintAutomaton Date: Tue, 23 Nov 2021 06:34:04 -0500 Subject: [PATCH] Stats for nerds (#1867) * transition duration of 0.5s added to watched videos * small code reformating * extra white spaces deleted * typo in the word transition corrected * original whitespaces respected * transition added when hovering end * video stat components started and properties chosen * ft-video-stats integraded into the video player for dev and debugging * using a timer to get video stats and a method to update the statistic every second * getting statistic from vhs and adaptativeFormat * frame drop capture * stats capture in the form of event * useless comment deleted * stats render with a for loop in the template * stats correctly displayed * overlay stats added * video stats component deleted * video stats component deleted inside template video player * video stats component fully deleted * modal solution working need more styling and code messy * lint * modal working with stats * keyboard shortcut for stats * lint fix * network state is now a string * new line deleted * useless whitespace deleted * package-lock.json remove and ignore * keyboard shortcut restricted to up arrow * stats overlay made larger * align to left corner * useless formatting of string deleted * renaming of variable formatedStrats for formattedStats * keyboard shortcut made into a variable * lint-fix * key change for i * label translated * whitespace added for gitignore * lock file not ignored * videoId stat deleted * ft-video-player.js, en-US.yaml, fr-FR.yaml: changing percentage stats display changing the display for percentage stats for the format 'x%' instead of 'xx.xx' * ft-video-player.js, en-US.yaml, fr-FR.yaml: network state video statistic deleted * ft-video-player.js: made stats modal background color darker * ft-video-player.js, en-US.yaml, fr-FR.yaml: video id are now related to the one of youtube * ft-video-player.js, en-US.yaml, fr-FR.yaml: stats displayed made closet to the youtube implementation the name are capitalized, the order of display is changed and fps is combined with viewport * lint-fix * en-US.yaml, fr-FR.yaml: network state possibilities deleted because not used * package.json.lock: deleted * ft-video-player.js: formated_stats renamed for formatted_stats * lock file deleted * index.js, ft-video-player.js: handling of right click context menu via electon ipc bus an event is send to tell the vue component to show the stats modal * ft-video-player.js, index.js: renaming of video stats display event and definition of it as a variable * index.js, en-US.yaml: inconsistant capitalization of video statistics label solved * index.js: pluralized video stats * ft-video-player.js: fix right click undefined this.player change the arrow function inside the closure for a function with a bind to this * ft-video-player.js: handling of the case when this.player is not defined the property this.stats.display.activated as been added and manage when the to show the stats. In this way in the runtime (it is still refered in the run time but it is capture in an event loop) with dont have to refer to this.player so when it is not defined it doesnt affect the behavior. * lint fix * src/renderer/components/ft-video-player/ft-video-player.js: modal.close move into the display event of the statistic context * lint fix * src/renderer/components/ft-video-player/ft-video-player.js, static/locales/en-US.yaml, static/locales/fr-FR.yaml: better capitalization of the stats labels * static/locales/en-US.yaml: fps capitalized * static/locales/fr-FR.yaml, static/locales/en-US.yaml: capitalized label --- src/main/index.js | 10 +- .../ft-video-player/ft-video-player.js | 129 +++++++++++++++++- static/locales/en-US.yaml | 9 ++ static/locales/fr-FR.yaml | 9 ++ 4 files changed, 155 insertions(+), 2 deletions(-) diff --git a/src/main/index.js b/src/main/index.js index b00d5e36..66c3ade0 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -18,7 +18,15 @@ function runApp() { showSearchWithGoogle: false, showSaveImageAs: true, showCopyImageAddress: true, - prepend: (params, browserWindow) => [] + prepend: (defaultActions, parameters, browserWindow) => [ + { + label: 'Show Video Statistics', + visible: parameters.mediaType === 'video', + click: () => { + browserWindow.webContents.send('showVideoStatistics', 'show') + } + } + ] }) const localDataStorage = app.getPath('userData') // Grabs the userdata directory based on the user's OS diff --git a/src/renderer/components/ft-video-player/ft-video-player.js b/src/renderer/components/ft-video-player/ft-video-player.js index 1c89a2ac..5389dc05 100644 --- a/src/renderer/components/ft-video-player/ft-video-player.js +++ b/src/renderer/components/ft-video-player/ft-video-player.js @@ -11,6 +11,7 @@ import 'videojs-overlay/dist/videojs-overlay.css' import 'videojs-vtt-thumbnails-freetube' import 'videojs-contrib-quality-levels' import 'videojs-http-source-selector' +import { ipcRenderer } from 'electron' export default Vue.extend({ name: 'FtVideoPlayer', @@ -132,6 +133,22 @@ export default Vue.extend({ 2.75, 3 ] + }, + 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 + } } } }, @@ -187,6 +204,36 @@ export default Vue.extend({ displayVideoPlayButton: function() { 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 = '' + return formattedStats + } + }, + watch: { + selectedQuality: function() { + this.currentFps() } }, mounted: function () { @@ -334,6 +381,7 @@ export default Vue.extend({ const settings = this.player.textTrackSettings.getValues() this.updateDefaultCaptionSettings(JSON.stringify(settings)) }) + this.addPlayerStatsEvent() } }, @@ -1483,6 +1531,85 @@ export default Vue.extend({ 'updateDefaultCaptionSettings', 'showToast', 'sponsorBlockSkipSegments' - ]) + ]), + 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 + } + } + } } }) diff --git a/static/locales/en-US.yaml b/static/locales/en-US.yaml index 0e9af49c..a585d1e8 100644 --- a/static/locales/en-US.yaml +++ b/static/locales/en-US.yaml @@ -541,6 +541,15 @@ Video: reversing playlists: reversing playlists shuffling playlists: shuffling playlists looping playlists: looping playlists + Stats: + video id: "Video ID (YouTube)" + player resolution: "Viewport" + volume: "Volume" + fps: "FPS" + frame drop: "Frame Drop" + bandwidth: "Connection Speed" + buffered: "Buffered" + out of: "out of" #& Videos Videos: #& Sort By diff --git a/static/locales/fr-FR.yaml b/static/locales/fr-FR.yaml index a1050ab6..61ce44ef 100644 --- a/static/locales/fr-FR.yaml +++ b/static/locales/fr-FR.yaml @@ -589,6 +589,15 @@ Video: UnsupportedActionTemplate: '$ non pris en charge : %' OpeningTemplate: Ouverture de $ en %… OpenInTemplate: Ouvrir avec $ + Stats: + video id: "Id de la Vidéo (YouTube)" + player resolution: "Résolution du Lecteur" + volume: "Volume" + fps: "IPS" + frame drop: "Perte d'image" + bandwidth: "Bande Passante" + buffered: "En Tampon" + out of: "sur" Premieres on: Première le Videos: #& Sort By