From 4c3095412b35d5a9b3db99e65adc319c0f9fae55 Mon Sep 17 00:00:00 2001 From: gnattu Date: Sat, 14 Sep 2024 16:46:02 +0800 Subject: [PATCH 1/8] Separate bitrate control from resolution --- src/components/playback/playersettingsmenu.js | 2 + src/components/qualityOptions.js | 64 +++++++++---------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/components/playback/playersettingsmenu.js b/src/components/playback/playersettingsmenu.js index 1587bf6fd08..543158b424f 100644 --- a/src/components/playback/playersettingsmenu.js +++ b/src/components/playback/playersettingsmenu.js @@ -10,12 +10,14 @@ function showQualityMenu(player, btn) { })[0]; const videoWidth = videoStream ? videoStream.Width : null; const videoHeight = videoStream ? videoStream.Height : null; + const videoBitRate = videoStream ? videoStream.BitRate : null; const options = qualityoptions.getVideoQualityOptions({ currentMaxBitrate: playbackManager.getMaxStreamingBitrate(player), isAutomaticBitrateEnabled: playbackManager.enableAutomaticBitrateDetection(player), videoWidth: videoWidth, videoHeight: videoHeight, + videoBitRate, enableAuto: true }); diff --git a/src/components/qualityOptions.js b/src/components/qualityOptions.js index dabce52bb55..f31542ed7c9 100644 --- a/src/components/qualityOptions.js +++ b/src/components/qualityOptions.js @@ -1,3 +1,4 @@ +import minBy from 'lodash-es/minBy'; import { appHost } from '../components/apphost'; import globalize from '../lib/globalize'; import appSettings from '../scripts/settings/appSettings'; @@ -6,17 +7,26 @@ export function getVideoQualityOptions(options) { const maxStreamingBitrate = options.currentMaxBitrate; let videoWidth = options.videoWidth; const videoHeight = options.videoHeight; + const videoBitRate = options.videoBitRate ?? -1; - // If the aspect ratio is less than 16/9 (1.77), set the width as if it were pillarboxed. - // 4:3 1440x1080 -> 1920x1080 - if (videoWidth / videoHeight < 16 / 9) { - videoWidth = videoHeight * (16 / 9); - } - - const maxVideoWidth = options.maxVideoWidth == null ? appSettings.maxVideoWidth() : options.maxVideoWidth; - - const hostScreenWidth = (maxVideoWidth < 0 ? appHost.screen()?.maxAllowedWidth : maxVideoWidth) || 4096; - const maxAllowedWidth = videoWidth || 4096; + // Quality options are indexed by bitrate. If you must duplicate them, make sure each of them are unique (by making the last digit a 1) + // Question: the maxHeight field seems not be used anywhere, is it safe to remove those? + const bitrateConfigurations = [ + { name: '120 Mbps', maxHeight: 2160, bitrate: 120000000 }, + { name: '80 Mbps', maxHeight: 2160, bitrate: 80000000 }, + { name: '60 Mbps', maxHeight: 2160, bitrate: 60000000 }, + { name: '40 Mbps', maxHeight: 2160, bitrate: 40000000 }, + { name: '20 Mbps', maxHeight: 2160, bitrate: 20000000 }, + { name: '15 Mbps', maxHeight: 1440, bitrate: 15000000 }, + { name: '10 Mbps', maxHeight: 1440, bitrate: 10000000 }, + { name: '8 Mbps', maxHeight: 1080, bitrate: 8000000 }, + { name: '6 Mbps', maxHeight: 1080, bitrate: 6000000 }, + { name: '4 Mbps', maxHeight: 720, bitrate: 4000000 }, + { name: '3 Mbps', maxHeight: 720, bitrate: 3000000 }, + { name: '1.5 Mbps', maxHeight: 720, bitrate: 1500000 }, + { name: '720 kbps', maxHeight: 480, bitrate: 720000 }, + { name: '420 kbps', maxHeight: 360, bitrate: 420000 }, + ] const qualityOptions = []; @@ -30,31 +40,19 @@ export function getVideoQualityOptions(options) { qualityOptions.push(autoQualityOption); } - // Quality options are indexed by bitrate. If you must duplicate them, make sure each of them are unique (by making the last digit a 1) - if (maxAllowedWidth >= 3800 && hostScreenWidth >= 1930) { - qualityOptions.push({ name: '4K - 120 Mbps', maxHeight: 2160, bitrate: 120000000 }); - qualityOptions.push({ name: '4K - 80 Mbps', maxHeight: 2160, bitrate: 80000000 }); - } - // Some 1080- videos are reported as 1912? - if (maxAllowedWidth >= 1900 && hostScreenWidth >= 1290) { - qualityOptions.push({ name: '1080p - 60 Mbps', maxHeight: 1080, bitrate: 60000000 }); - qualityOptions.push({ name: '1080p - 40 Mbps', maxHeight: 1080, bitrate: 40000000 }); - qualityOptions.push({ name: '1080p - 20 Mbps', maxHeight: 1080, bitrate: 20000000 }); - qualityOptions.push({ name: '1080p - 15 Mbps', maxHeight: 1080, bitrate: 15000000 }); - qualityOptions.push({ name: '1080p - 10 Mbps', maxHeight: 1080, bitrate: 10000000 }); - } - if (maxAllowedWidth >= 1260 && hostScreenWidth >= 650) { - qualityOptions.push({ name: '720p - 8 Mbps', maxHeight: 720, bitrate: 8000000 }); - qualityOptions.push({ name: '720p - 6 Mbps', maxHeight: 720, bitrate: 6000000 }); - qualityOptions.push({ name: '720p - 4 Mbps', maxHeight: 720, bitrate: 4000000 }); - } - if (maxAllowedWidth >= 620) { - qualityOptions.push({ name: '480p - 3 Mbps', maxHeight: 480, bitrate: 3000000 }); - qualityOptions.push({ name: '480p - 1.5 Mbps', maxHeight: 480, bitrate: 1500000 }); - qualityOptions.push({ name: '480p - 720 kbps', maxHeight: 480, bitrate: 720000 }); + if (videoBitRate > 0 && videoBitRate < 120000000) { + // Push one entry that has higher limit than video bitrate to allow using source bitrate when Auto is also limited + const sourceOptions = minBy(bitrateConfigurations.filter((c) => c.bitrate > videoBitRate), (x) => x.bitrate); + qualityOptions.push(sourceOptions); } - qualityOptions.push({ name: '360p - 420 kbps', maxHeight: 360, bitrate: 420000 }); + bitrateConfigurations.forEach((c) => { + if (videoBitRate > 0 && c.bitrate < videoBitRate) { + qualityOptions.push(c); + } else if (videoBitRate < 0) { + qualityOptions.push(c); + } + }) if (maxStreamingBitrate) { let selectedIndex = qualityOptions.length - 1; From 16a141652159f75a527b25eae2acdb9edcf4817b Mon Sep 17 00:00:00 2001 From: gnattu Date: Sat, 14 Sep 2024 17:06:37 +0800 Subject: [PATCH 2/8] Code cleanup --- src/components/qualityOptions.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/components/qualityOptions.js b/src/components/qualityOptions.js index f31542ed7c9..42e2101bbe0 100644 --- a/src/components/qualityOptions.js +++ b/src/components/qualityOptions.js @@ -1,12 +1,8 @@ import minBy from 'lodash-es/minBy'; -import { appHost } from '../components/apphost'; import globalize from '../lib/globalize'; -import appSettings from '../scripts/settings/appSettings'; export function getVideoQualityOptions(options) { const maxStreamingBitrate = options.currentMaxBitrate; - let videoWidth = options.videoWidth; - const videoHeight = options.videoHeight; const videoBitRate = options.videoBitRate ?? -1; // Quality options are indexed by bitrate. If you must duplicate them, make sure each of them are unique (by making the last digit a 1) @@ -25,8 +21,8 @@ export function getVideoQualityOptions(options) { { name: '3 Mbps', maxHeight: 720, bitrate: 3000000 }, { name: '1.5 Mbps', maxHeight: 720, bitrate: 1500000 }, { name: '720 kbps', maxHeight: 480, bitrate: 720000 }, - { name: '420 kbps', maxHeight: 360, bitrate: 420000 }, - ] + { name: '420 kbps', maxHeight: 360, bitrate: 420000 } + ]; const qualityOptions = []; @@ -47,9 +43,7 @@ export function getVideoQualityOptions(options) { } bitrateConfigurations.forEach((c) => { - if (videoBitRate > 0 && c.bitrate < videoBitRate) { - qualityOptions.push(c); - } else if (videoBitRate < 0) { + if (videoBitRate < 0 || (videoBitRate > 0 && c.bitrate < videoBitRate)) { qualityOptions.push(c); } }) From 2bb60e22415d70adafe42e01b2eb6b1d28abaa03 Mon Sep 17 00:00:00 2001 From: gnattu Date: Sat, 14 Sep 2024 17:09:03 +0800 Subject: [PATCH 3/8] Fix lint --- src/components/qualityOptions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/qualityOptions.js b/src/components/qualityOptions.js index 42e2101bbe0..1038ab6068a 100644 --- a/src/components/qualityOptions.js +++ b/src/components/qualityOptions.js @@ -46,7 +46,7 @@ export function getVideoQualityOptions(options) { if (videoBitRate < 0 || (videoBitRate > 0 && c.bitrate < videoBitRate)) { qualityOptions.push(c); } - }) + }); if (maxStreamingBitrate) { let selectedIndex = qualityOptions.length - 1; From 994118b899337874ffa454e743254d06b746b572 Mon Sep 17 00:00:00 2001 From: gnattu Date: Sat, 14 Sep 2024 17:42:41 +0800 Subject: [PATCH 4/8] Apply suggestions from code review Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- src/components/qualityOptions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/qualityOptions.js b/src/components/qualityOptions.js index 1038ab6068a..c7cb27e7521 100644 --- a/src/components/qualityOptions.js +++ b/src/components/qualityOptions.js @@ -36,14 +36,14 @@ export function getVideoQualityOptions(options) { qualityOptions.push(autoQualityOption); } - if (videoBitRate > 0 && videoBitRate < 120000000) { + if (videoBitRate > 0 && videoBitRate < bitrateConfigurations[0].bitrate) { // Push one entry that has higher limit than video bitrate to allow using source bitrate when Auto is also limited const sourceOptions = minBy(bitrateConfigurations.filter((c) => c.bitrate > videoBitRate), (x) => x.bitrate); qualityOptions.push(sourceOptions); } bitrateConfigurations.forEach((c) => { - if (videoBitRate < 0 || (videoBitRate > 0 && c.bitrate < videoBitRate)) { + if (videoBitRate <= 0 || c.bitrate <= videoBitRate) { qualityOptions.push(c); } }); From 28552b2d1a532f99122ec38ea17a97e8b4bb7be9 Mon Sep 17 00:00:00 2001 From: gnattu Date: Sat, 14 Sep 2024 17:55:13 +0800 Subject: [PATCH 5/8] Use pop instead of minBy --- src/components/qualityOptions.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/qualityOptions.js b/src/components/qualityOptions.js index 1038ab6068a..f47e1f7f80e 100644 --- a/src/components/qualityOptions.js +++ b/src/components/qualityOptions.js @@ -1,4 +1,3 @@ -import minBy from 'lodash-es/minBy'; import globalize from '../lib/globalize'; export function getVideoQualityOptions(options) { @@ -38,7 +37,7 @@ export function getVideoQualityOptions(options) { if (videoBitRate > 0 && videoBitRate < 120000000) { // Push one entry that has higher limit than video bitrate to allow using source bitrate when Auto is also limited - const sourceOptions = minBy(bitrateConfigurations.filter((c) => c.bitrate > videoBitRate), (x) => x.bitrate); + const sourceOptions = bitrateConfigurations.filter((c) => c.bitrate > videoBitRate).pop(); qualityOptions.push(sourceOptions); } From 33a5533b1144894ce49d661a2fc1cf9f0640f4bd Mon Sep 17 00:00:00 2001 From: gnattu Date: Tue, 17 Sep 2024 12:25:06 +0800 Subject: [PATCH 6/8] Remove unused functions --- .../playbackSettings/playbackSettings.js | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/src/components/playbackSettings/playbackSettings.js b/src/components/playbackSettings/playbackSettings.js index 69b748bb3cf..a7d7c9240fd 100644 --- a/src/components/playbackSettings/playbackSettings.js +++ b/src/components/playbackSettings/playbackSettings.js @@ -277,36 +277,6 @@ function save(instance, context, userId, userSettings, apiClient, enableSaveConf }); } -function setSelectValue(select, value, defaultValue) { - select.value = value; - - if (select.selectedIndex < 0) { - select.value = defaultValue; - } -} - -function onMaxVideoWidthChange(e) { - const context = this.options.element; - - const selectVideoInNetworkQuality = context.querySelector('.selectVideoInNetworkQuality'); - const selectVideoInternetQuality = context.querySelector('.selectVideoInternetQuality'); - const selectChromecastVideoQuality = context.querySelector('.selectChromecastVideoQuality'); - - const selectVideoInNetworkQualityValue = selectVideoInNetworkQuality.value; - const selectVideoInternetQualityValue = selectVideoInternetQuality.value; - const selectChromecastVideoQualityValue = selectChromecastVideoQuality.value; - - const maxVideoWidth = parseInt(e.target.value || '0', 10) || 0; - - fillQuality(selectVideoInNetworkQuality, true, 'Video', maxVideoWidth); - fillQuality(selectVideoInternetQuality, false, 'Video', maxVideoWidth); - fillChromecastQuality(selectChromecastVideoQuality, maxVideoWidth); - - setSelectValue(selectVideoInNetworkQuality, selectVideoInNetworkQualityValue, ''); - setSelectValue(selectVideoInternetQuality, selectVideoInternetQualityValue, ''); - setSelectValue(selectChromecastVideoQuality, selectChromecastVideoQualityValue, ''); -} - function onSubmit(e) { const self = this; const apiClient = ServerConnections.getApiClient(self.options.serverId); From 020dad88678c8feb0f849a7798659c1184555d48 Mon Sep 17 00:00:00 2001 From: gnattu Date: Tue, 17 Sep 2024 17:01:42 +0800 Subject: [PATCH 7/8] Increase reference bitrate for high efficiency codecs --- src/components/playback/playersettingsmenu.js | 7 +++---- src/components/qualityOptions.js | 11 +++++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/components/playback/playersettingsmenu.js b/src/components/playback/playersettingsmenu.js index 543158b424f..8cd2c52ec4d 100644 --- a/src/components/playback/playersettingsmenu.js +++ b/src/components/playback/playersettingsmenu.js @@ -8,15 +8,14 @@ function showQualityMenu(player, btn) { const videoStream = playbackManager.currentMediaSource(player).MediaStreams.filter(function (stream) { return stream.Type === 'Video'; })[0]; - const videoWidth = videoStream ? videoStream.Width : null; - const videoHeight = videoStream ? videoStream.Height : null; + + const videoCodec = videoStream ? videoStream.Codec : null; const videoBitRate = videoStream ? videoStream.BitRate : null; const options = qualityoptions.getVideoQualityOptions({ currentMaxBitrate: playbackManager.getMaxStreamingBitrate(player), isAutomaticBitrateEnabled: playbackManager.enableAutomaticBitrateDetection(player), - videoWidth: videoWidth, - videoHeight: videoHeight, + videoCodec, videoBitRate, enableAuto: true }); diff --git a/src/components/qualityOptions.js b/src/components/qualityOptions.js index cffd738525c..4520c508474 100644 --- a/src/components/qualityOptions.js +++ b/src/components/qualityOptions.js @@ -3,6 +3,8 @@ import globalize from '../lib/globalize'; export function getVideoQualityOptions(options) { const maxStreamingBitrate = options.currentMaxBitrate; const videoBitRate = options.videoBitRate ?? -1; + const videoCodec = options.videoCodec; + let referenceBitRate = videoBitRate; // Quality options are indexed by bitrate. If you must duplicate them, make sure each of them are unique (by making the last digit a 1) // Question: the maxHeight field seems not be used anywhere, is it safe to remove those? @@ -36,13 +38,18 @@ export function getVideoQualityOptions(options) { } if (videoBitRate > 0 && videoBitRate < bitrateConfigurations[0].bitrate) { + // Slightly increase reference bitrate for high efficiency codecs when it is not too high + // Ideally we only need to do this for transcoding to h264, but we need extra api request to get that info which is not ideal for this + if (videoCodec && ['hevc', 'av1', 'vp9'].includes(videoCodec) && referenceBitRate <= 20000000) { + referenceBitRate *= 1.5; + } // Push one entry that has higher limit than video bitrate to allow using source bitrate when Auto is also limited - const sourceOptions = bitrateConfigurations.filter((c) => c.bitrate > videoBitRate).pop(); + const sourceOptions = bitrateConfigurations.filter((c) => c.bitrate > referenceBitRate).pop(); qualityOptions.push(sourceOptions); } bitrateConfigurations.forEach((c) => { - if (videoBitRate <= 0 || c.bitrate <= videoBitRate) { + if (videoBitRate <= 0 || c.bitrate <= referenceBitRate) { qualityOptions.push(c); } }); From d9d2f8d0be6ff06cb23dd265441dfed0761b3403 Mon Sep 17 00:00:00 2001 From: gnattu Date: Tue, 17 Sep 2024 17:05:18 +0800 Subject: [PATCH 8/8] Cleanup removed function --- src/components/playbackSettings/playbackSettings.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/playbackSettings/playbackSettings.js b/src/components/playbackSettings/playbackSettings.js index a7d7c9240fd..1461dbb54d1 100644 --- a/src/components/playbackSettings/playbackSettings.js +++ b/src/components/playbackSettings/playbackSettings.js @@ -304,8 +304,6 @@ function embed(options, self) { options.element.querySelector('.btnSave').classList.remove('hide'); } - options.element.querySelector('.selectMaxVideoWidth').addEventListener('change', onMaxVideoWidthChange.bind(self)); - self.loadData(); if (options.autoFocus) {