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 = '
'
+ for (const stat of stats) {
+ formattedStats += `- ${stat[0]}: ${stat[1]}
`
+ }
+ 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