diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index adfecb0eef..bab7d098bd 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -309,7 +309,8 @@ function getAudioStreamUrl(item, transcodingProfile, directPlayContainers, apiCl PlaySessionId: startingPlaySession, StartTimeTicks: startPosition || 0, EnableRedirection: true, - EnableRemoteMedia: appHost.supports('remoteaudio') + EnableRemoteMedia: appHost.supports('remoteaudio'), + EnableAudioVbrEncoding: transcodingProfile.EnableAudioVbrEncoding }); } diff --git a/src/components/playbackSettings/playbackSettings.js b/src/components/playbackSettings/playbackSettings.js index 021b9a8cc7..69b748bb3c 100644 --- a/src/components/playbackSettings/playbackSettings.js +++ b/src/components/playbackSettings/playbackSettings.js @@ -190,6 +190,9 @@ function loadForm(context, user, userSettings, systemInfo, apiClient) { context.querySelector('.chkLimitSupportedVideoResolution').checked = appSettings.limitSupportedVideoResolution(); context.querySelector('#selectPreferredTranscodeVideoCodec').value = appSettings.preferredTranscodeVideoCodec(); context.querySelector('#selectPreferredTranscodeVideoAudioCodec').value = appSettings.preferredTranscodeVideoAudioCodec(); + context.querySelector('.chkDisableVbrAudioEncoding').checked = appSettings.disableVbrAudio(); + context.querySelector('.chkAlwaysRemuxFlac').checked = appSettings.alwaysRemuxFlac(); + context.querySelector('.chkAlwaysRemuxMp3').checked = appSettings.alwaysRemuxMp3(); setMaxBitrateIntoField(context.querySelector('.selectVideoInNetworkQuality'), true, 'Video'); setMaxBitrateIntoField(context.querySelector('.selectVideoInternetQuality'), false, 'Video'); @@ -232,6 +235,9 @@ function saveUser(context, user, userSettingsInstance, apiClient) { appSettings.enableTrueHd(context.querySelector('.chkEnableTrueHd').checked); appSettings.enableHi10p(context.querySelector('.chkEnableHi10p').checked); + appSettings.disableVbrAudio(context.querySelector('.chkDisableVbrAudioEncoding').checked); + appSettings.alwaysRemuxFlac(context.querySelector('.chkAlwaysRemuxFlac').checked); + appSettings.alwaysRemuxMp3(context.querySelector('.chkAlwaysRemuxMp3').checked); setMaxBitrateFromField(context.querySelector('.selectVideoInNetworkQuality'), true, 'Video'); setMaxBitrateFromField(context.querySelector('.selectVideoInternetQuality'), false, 'Video'); diff --git a/src/components/playbackSettings/playbackSettings.template.html b/src/components/playbackSettings/playbackSettings.template.html index bc708ee1ec..ee8d8da2ba 100644 --- a/src/components/playbackSettings/playbackSettings.template.html +++ b/src/components/playbackSettings/playbackSettings.template.html @@ -22,6 +22,14 @@ ${LabelPlayDefaultAudioTrack} + +
+ +
${DisableVbrAudioEncodingHelp}
+
@@ -212,6 +220,28 @@
+
+

+ ${HeaderAudioAdvanced} +

+ +
+ +
${AlwaysRemuxFlacAudioFilesHelp}
+
+ +
+ +
${AlwaysRemuxMp3AudioFilesHelp}
+
+
+ diff --git a/src/scripts/browserDeviceProfile.js b/src/scripts/browserDeviceProfile.js index 908ab59de0..34fad3c237 100644 --- a/src/scripts/browserDeviceProfile.js +++ b/src/scripts/browserDeviceProfile.js @@ -738,10 +738,19 @@ export default function (options) { }); } - profile.DirectPlayProfiles.push({ - Container: audioFormat, - Type: 'Audio' - }); + if (audioFormat === 'flac' && appSettings.alwaysRemuxFlac()) { + // force remux flac in fmp4. Clients not supporting this configuration should disable this option + profile.DirectPlayProfiles.push({ + Container: 'mp4', + AudioCodec: 'flac', + Type: 'Audio' + }); + } else if (audioFormat !== 'mp3' || !appSettings.alwaysRemuxMp3()) { // mp3 remux profile is already injected + profile.DirectPlayProfiles.push({ + Container: audioFormat, + Type: 'Audio' + }); + } // https://www.webmproject.org/about/faq/ if (audioFormat === 'opus' || audioFormat === 'webma') { @@ -794,7 +803,8 @@ export default function (options) { Protocol: 'hls', MaxAudioChannels: physicalAudioChannels.toString(), MinSegments: browser.iOS || browser.osx ? '2' : '1', - BreakOnNonKeyFrames: hlsBreakOnNonKeyFrames + BreakOnNonKeyFrames: hlsBreakOnNonKeyFrames, + EnableAudioVbrEncoding: !appSettings.disableVbrAudio() }); } diff --git a/src/scripts/settings/appSettings.js b/src/scripts/settings/appSettings.js index 07c028850d..cf6cbea284 100644 --- a/src/scripts/settings/appSettings.js +++ b/src/scripts/settings/appSettings.js @@ -195,6 +195,45 @@ class AppSettings { return toBoolean(this.get('enableHi10p'), false); } + /** + * Get or set 'Disable VBR audio encoding' state. + * @param {boolean|undefined} val - Flag to enable 'Disable VBR audio encoding' or undefined. + * @return {boolean} 'Disable VBR audio encoding' state. + */ + disableVbrAudio(val) { + if (val !== undefined) { + return this.set('disableVbrAudio', val.toString()); + } + + return toBoolean(this.get('disableVbrAudio'), false); + } + + /** + * Get or set 'Always remux FLAC audio files' state. + * @param {boolean|undefined} val - Flag to enable 'Always remux FLAC audio files' or undefined. + * @return {boolean} 'Always remux FLAC audio files' state. + */ + alwaysRemuxFlac(val) { + if (val !== undefined) { + return this.set('alwaysRemuxFlac', val.toString()); + } + + return toBoolean(this.get('alwaysRemuxFlac'), false); + } + + /** + * Get or set 'Always remux MP3 audio files' state. + * @param {boolean|undefined} val - Flag to enable 'Always remux MP3 audio files' or undefined. + * @return {boolean} 'Always remux MP3 audio files' state. + */ + alwaysRemuxMp3(val) { + if (val !== undefined) { + return this.set('alwaysRemuxMp3', val.toString()); + } + + return toBoolean(this.get('alwaysRemuxMp3'), false); + } + set(name, value, userId) { const currentValue = this.get(name, userId); localStorage.setItem(this.#getKey(name, userId), value); diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 560b469b90..642d082cc7 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -47,6 +47,8 @@ "AllowTonemappingSoftwareHelp": "Tone-mapping can transform the dynamic range of a video from HDR to SDR while maintaining image details and colors, which are very important information for representing the original scene. Currently works only with 10bit HDR10, HLG and DoVi videos.", "AlwaysPlaySubtitles": "Always Play", "AlwaysPlaySubtitlesHelp": "Subtitles matching the language preference will be loaded regardless of the audio language.", + "AlwaysRemuxFlacAudioFilesHelp": "If you have files that your browser rejects to play or where it inaccurately calculates timestamps, enable this as a workaround.", + "AlwaysRemuxMp3AudioFilesHelp": "If you have files that your browser inaccurately calculates timestamps, enable this as a workaround.", "AndOtherArtists": "{0} and {1} other artists.", "AnyLanguage": "Any Language", "Anytime": "Anytime", @@ -223,6 +225,7 @@ "EnablePlugin": "Enable", "DisableCustomCss": "Disable server-provided custom CSS code", "DisablePlugin": "Disable", + "DisableVbrAudioEncodingHelp": "Prevent the server from encoding audio with VBR for this client.", "Disc": "Disc", "Disconnect": "Disconnect", "Display": "Display", @@ -365,6 +368,7 @@ "HeaderApp": "App", "HeaderAppearsOn": "Appears On", "HeaderAudioBooks": "Audio Books", + "HeaderAudioAdvanced": "Audio Advanced", "HeaderAudioSettings": "Audio Settings", "HeaderAutoDiscovery": "Network Discovery", "HeaderBlockItemsWithNoRating": "Block items with no or unrecognized rating information", @@ -564,6 +568,8 @@ "LabelAllowedRemoteAddresses": "Remote IP address filter", "LabelAllowedRemoteAddressesMode": "Remote IP address filter mode", "LabelAllowHWTranscoding": "Allow hardware transcoding", + "LabelAlwaysRemuxFlacAudioFiles": "Always remux FLAC audio files", + "LabelAlwaysRemuxMp3AudioFiles": "Always remux MP3 audio files", "LabelAppName": "App name", "LabelAppNameExample": "A human readable name for identifying API keys. This setting will not affect functionality.", "LabelArtists": "Artists", @@ -635,6 +641,7 @@ "LabelDeinterlaceMethod": "Deinterlacing method", "LabelDeveloper": "Developer", "LabelDisableCustomCss": "Disable custom CSS code for theming/branding provided from the server.", + "LabelDisableVbrAudioEncoding": "Disable VBR audio encoding", "LabelDiscNumber": "Disc number", "LabelDisplayLanguage": "Display language", "LabelDisplayLanguageHelp": "Translating Jellyfin is an ongoing project.",