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 3bc85f0762d0..7d1b9122dddf 100644 --- a/src/renderer/components/ft-video-player/ft-video-player.js +++ b/src/renderer/components/ft-video-player/ft-video-player.js @@ -28,6 +28,11 @@ videojs.Vhs.xhr.beforeRequest = (options) => { } } +// videojs-http-streaming spits out a warning every time you access videojs.Vhs.BANDWIDTH_VARIANCE +// so we'll get the value once here, to stop it spamming the console +// https://github.com/videojs/http-streaming/blob/main/src/config.js#L8-L10 +const VHS_BANDWIDTH_VARIANCE = videojs.Vhs.BANDWIDTH_VARIANCE + export default defineComponent({ name: 'FtVideoPlayer', props: { @@ -150,7 +155,10 @@ export default defineComponent({ }, defaultQuality: function () { - return parseInt(this.$store.getters.getDefaultQuality) + const valueFromStore = this.$store.getters.getDefaultQuality + if (valueFromStore === 'auto') { return valueFromStore } + + return parseInt(valueFromStore) }, defaultCaptionSettings: function () { @@ -320,6 +328,10 @@ export default defineComponent({ this.dataSetup.playbackRates = this.playbackRates + if (this.format === 'dash') { + this.determineDefaultQualityDash() + } + this.createFullWindowButton() this.createLoopButton() this.createToggleTheatreModeButton() @@ -369,6 +381,18 @@ export default defineComponent({ controlBarItems.splice(index, 1) } + // regardless of what DASH qualities you enable or disable in the qualityLevels plugin + // the first segments videojs-http-streaming requests are chosen based on the available bandwidth, which is set to 0.5MB/s by default + // overriding that to be the same as the quality we requested, makes videojs-http-streamming pick the correct quality + const playerBandwidthOption = {} + + if (this.useDash && this.defaultQuality !== 'auto') { + // https://github.com/videojs/http-streaming#bandwidth + // Cannot be too high to fix https://github.com/FreeTubeApp/FreeTube/issues/595 + // (when default quality is low like 240p) + playerBandwidthOption.bandwidth = this.selectedBitrate * VHS_BANDWIDTH_VARIANCE + 1 + } + this.player = videojs(this.$refs.video, { html5: { preloadTextTracks: false, @@ -376,7 +400,8 @@ export default defineComponent({ limitRenditionByPlayerDimensions: false, smoothQualityChange: false, allowSeeksWithinUnsafeLiveWindow: true, - handlePartialData: true + handlePartialData: true, + ...playerBandwidthOption } } }) @@ -397,6 +422,15 @@ export default defineComponent({ } }) + // disable any quality the isn't the default one, as soon as it gets added + // we don't need to disable any qualities for auto + if (this.useDash && this.defaultQuality !== 'auto') { + const qualityLevels = this.player.qualityLevels() + qualityLevels.on('addqualitylevel', ({ qualityLevel }) => { + qualityLevel.enabled = qualityLevel.bitrate === this.selectedBitrate + }) + } + this.player.volume(this.volume) this.player.muted(this.muted) this.player.playbackRate(this.defaultPlayback) @@ -895,92 +929,67 @@ export default defineComponent({ }, determineDefaultQualityDash: function () { - if (this.defaultQuality === 'auto') { - this.setDashQualityLevel('auto') - } + // TODO add settings and filtering for 60fps and HDR - let formatsToTest - - if (typeof this.activeAdaptiveFormats !== 'undefined' && this.activeAdaptiveFormats.length > 0) { - formatsToTest = this.activeAdaptiveFormats.filter((format) => { - return format.height === this.defaultQuality + if (this.defaultQuality === 'auto') { + this.selectedResolution = 'auto' + this.selectedFPS = 'auto' + this.selectedBitrate = 'auto' + this.selectedMimeType = 'auto' + } else { + const videoFormats = this.adaptiveFormats.filter(format => { + return (format.mimeType || format.type).startsWith('video') && + typeof format.height === 'number' }) - if (formatsToTest.length === 0) { - formatsToTest = this.activeAdaptiveFormats.filter((format) => { - return format.height < this.defaultQuality - }) - } + // Select the quality that is identical to the users chosen default quality if it's available + // otherwise select the next lowest quality - formatsToTest = formatsToTest.sort((a, b) => { - if (a.height === b.height) { - return b.bitrate - a.bitrate - } else { - return b.height - a.height + let formatsToTest = videoFormats.filter(format => { + // For short videos (or vertical videos?) + // Height > width (e.g. H: 1280, W: 720) + if (typeof format.width === 'number' && format.height > format.width) { + return format.width === this.defaultQuality } - }) - } else { - formatsToTest = this.player.qualityLevels().levels_.filter((format) => { + return format.height === this.defaultQuality }) if (formatsToTest.length === 0) { - formatsToTest = this.player.qualityLevels().levels_.filter((format) => { + formatsToTest = videoFormats.filter(format => { + // For short videos (or vertical videos?) + // Height > width (e.g. H: 1280, W: 720) + if (typeof format.width === 'number' && format.height > format.width) { + return format.width < this.defaultQuality + } + return format.height < this.defaultQuality }) } - formatsToTest = formatsToTest.sort((a, b) => { + formatsToTest.sort((a, b) => { if (a.height === b.height) { + // Higher bitrate for video formats with HDR qualities + // `height` and `fps` are the same but `bitrate` would be higher return b.bitrate - a.bitrate } else { return b.height - a.height } }) - } - - // TODO: Test formats to determine if HDR / 60 FPS and skip them based on - // User settings - this.setDashQualityLevel(formatsToTest[0].bitrate) - - // Old logic. Revert if needed - /* this.player.qualityLevels().levels_.sort((a, b) => { - if (a.height === b.height) { - return a.bitrate - b.bitrate - } else { - return a.height - b.height - } - }).forEach((ql, index, arr) => { - const height = ql.height - const width = ql.width - const quality = width < height ? width : height - let upperLevel = null - - if (index < arr.length - 1) { - upperLevel = arr[index + 1] - } - if (this.defaultQuality === quality && upperLevel === null) { - this.setDashQualityLevel(height, true) - } else if (upperLevel !== null) { - const upperHeight = upperLevel.height - const upperWidth = upperLevel.width - const upperQuality = upperWidth < upperHeight ? upperWidth : upperHeight - - if (this.defaultQuality >= quality && this.defaultQuality === upperQuality) { - this.setDashQualityLevel(height, true) - } else if (this.defaultQuality >= quality && this.defaultQuality < upperQuality) { - this.setDashQualityLevel(height) - } - } else if (index === 0 && quality > this.defaultQuality) { - this.setDashQualityLevel(height) - } else if (index === (arr.length - 1) && quality < this.defaultQuality) { - this.setDashQualityLevel(height) - } - }) */ + const selectedFormat = formatsToTest[0] + this.selectedBitrate = selectedFormat.bitrate + this.selectedResolution = `${selectedFormat.width}x${selectedFormat.height}` + this.selectedFPS = selectedFormat.fps + this.selectedMimeType = selectedFormat.mimeType || selectedFormat.type + } }, setDashQualityLevel: function (bitrate) { + if (bitrate === this.selectedBitrate) { + return + } + let adaptiveFormat = null if (bitrate !== 'auto') { @@ -991,24 +1000,39 @@ export default defineComponent({ let qualityLabel = adaptiveFormat ? adaptiveFormat.qualityLabel : '' - this.player.qualityLevels().levels_.sort((a, b) => { - if (a.height === b.height) { - return a.bitrate - b.bitrate - } else { - return a.height - b.height - } - }).forEach((ql, index, arr) => { - if (bitrate === 'auto' || bitrate === ql.bitrate) { + const qualityLevels = Array.from(this.player.qualityLevels()) + if (bitrate === 'auto') { + qualityLevels.forEach(ql => { ql.enabled = true - ql.enabled_(true) - if (bitrate !== 'auto' && qualityLabel === '') { - qualityLabel = ql.height + 'p' + }) + } else { + const previousBitrate = this.selectedBitrate + + // if it was previously set to a specific quality we can disable just that and enable just the new one + // if it was previously set to auto, it means all qualitylevels were enabled, so we need to disable them + + if (previousBitrate !== 'auto') { + const qualityLevel = qualityLevels.find(ql => bitrate === ql.bitrate) + qualityLevel.enabled = true + + if (qualityLabel === '') { + qualityLabel = qualityLevel.height + 'p' } + + qualityLevels.find(ql => previousBitrate === ql.bitrate).enabled = false } else { - ql.enabled = false - ql.enabled_(false) + qualityLevels.forEach(ql => { + if (bitrate === ql.bitrate) { + ql.enabled = true + if (qualityLabel === '') { + qualityLabel = ql.height + 'p' + } + } else { + ql.enabled = false + } + }) } - }) + } const selectedQuality = bitrate === 'auto' ? 'auto' : qualityLabel @@ -1514,6 +1538,8 @@ export default defineComponent({ const adaptiveFormats = this.adaptiveFormats const activeAdaptiveFormats = this.activeAdaptiveFormats const setDashQualityLevel = this.setDashQualityLevel + const defaultQuality = this.defaultQuality + const defaultBitrate = this.selectedBitrate const VjsButton = videojs.getComponent('Button') class dashQualitySelector extends VjsButton { @@ -1531,12 +1557,16 @@ export default defineComponent({
' - let qualityHtml = `