Merge pull request #4318 from TelepathicWalrus/audio-normalization

This commit is contained in:
Bill Thornton 2023-05-25 08:30:19 -04:00 committed by GitHub
commit 2a016a6d5f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 74 additions and 2 deletions

View file

@ -59,6 +59,7 @@
- [Vankerkom](https://github.com/vankerkom) - [Vankerkom](https://github.com/vankerkom)
- [edvwib](https://github.com/edvwib) - [edvwib](https://github.com/edvwib)
- [Rob Farraher](https://github.com/farraherbg) - [Rob Farraher](https://github.com/farraherbg)
- [TelepathicWalrus](https://github.com/TelepathicWalrus)
- [Pier-Luc Ducharme](https://github.com/pl-ducharme) - [Pier-Luc Ducharme](https://github.com/pl-ducharme)
- [Anantharaju S](https://github.com/Anantharajus) - [Anantharaju S](https://github.com/Anantharajus)
- [Merlin Sievers](https://github.com/dann-merlin) - [Merlin Sievers](https://github.com/dann-merlin)

View file

@ -416,6 +416,8 @@ export function setContentType(parent, contentType) {
} }
} }
parent.querySelector('.chkEnableLUFSScan').classList.toggle('hide', contentType !== 'music');
if (contentType === 'tvshows') { if (contentType === 'tvshows') {
parent.querySelector('.chkEnableEmbeddedEpisodeInfosContainer').classList.remove('hide'); parent.querySelector('.chkEnableEmbeddedEpisodeInfosContainer').classList.remove('hide');
} else { } else {
@ -512,6 +514,7 @@ export function getLibraryOptions(parent) {
EnableArchiveMediaFiles: false, EnableArchiveMediaFiles: false,
EnablePhotos: parent.querySelector('.chkEnablePhotos').checked, EnablePhotos: parent.querySelector('.chkEnablePhotos').checked,
EnableRealtimeMonitor: parent.querySelector('.chkEnableRealtimeMonitor').checked, EnableRealtimeMonitor: parent.querySelector('.chkEnableRealtimeMonitor').checked,
EnableLUFSScan: parent.querySelector('.chkEnableLUFSScan').checked,
ExtractChapterImagesDuringLibraryScan: parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked, ExtractChapterImagesDuringLibraryScan: parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked,
EnableChapterImageExtraction: parent.querySelector('.chkExtractChapterImages').checked, EnableChapterImageExtraction: parent.querySelector('.chkExtractChapterImages').checked,
EnableInternetProviders: true, EnableInternetProviders: true,
@ -573,6 +576,7 @@ export function setLibraryOptions(parent, options) {
parent.querySelector('#txtSeasonZeroName').value = options.SeasonZeroDisplayName || 'Specials'; parent.querySelector('#txtSeasonZeroName').value = options.SeasonZeroDisplayName || 'Specials';
parent.querySelector('.chkEnablePhotos').checked = options.EnablePhotos; parent.querySelector('.chkEnablePhotos').checked = options.EnablePhotos;
parent.querySelector('.chkEnableRealtimeMonitor').checked = options.EnableRealtimeMonitor; parent.querySelector('.chkEnableRealtimeMonitor').checked = options.EnableRealtimeMonitor;
parent.querySelector('.chkEnableLUFSScan').checked = options.EnableLUFSScan;
parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked = options.ExtractChapterImagesDuringLibraryScan; parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked = options.ExtractChapterImagesDuringLibraryScan;
parent.querySelector('.chkExtractChapterImages').checked = options.EnableChapterImageExtraction; parent.querySelector('.chkExtractChapterImages').checked = options.EnableChapterImageExtraction;
parent.querySelector('#chkSaveLocal').checked = options.SaveLocalMetadata; parent.querySelector('#chkSaveLocal').checked = options.SaveLocalMetadata;

View file

@ -55,6 +55,14 @@
<div class="fieldDescription checkboxFieldDescription">${LabelEnableRealtimeMonitorHelp}</div> <div class="fieldDescription checkboxFieldDescription">${LabelEnableRealtimeMonitorHelp}</div>
</div> </div>
<div class="checkboxContainer checkboxContainer-withDescription advanced">
<label>
<input type="checkbox" is="emby-checkbox" class="chkEnableLUFSScan" checked />
<span>${LabelEnableLUFSScan}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${LabelEnableLUFSScanHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription chkAutomaticallyAddToCollectionContainer hide advanced"> <div class="checkboxContainer checkboxContainer-withDescription chkAutomaticallyAddToCollectionContainer hide advanced">
<label> <label>
<input is="emby-checkbox" type="checkbox" id="chkAutomaticallyAddToCollection" /> <input is="emby-checkbox" type="checkbox" id="chkAutomaticallyAddToCollection" />

View file

@ -173,6 +173,7 @@ function loadForm(context, user, userSettings, apiClient) {
context.querySelector('.chkPlayDefaultAudioTrack').checked = user.Configuration.PlayDefaultAudioTrack || false; context.querySelector('.chkPlayDefaultAudioTrack').checked = user.Configuration.PlayDefaultAudioTrack || false;
context.querySelector('.chkPreferFmp4HlsContainer').checked = userSettings.preferFmp4HlsContainer(); context.querySelector('.chkPreferFmp4HlsContainer').checked = userSettings.preferFmp4HlsContainer();
context.querySelector('.chkEnableCinemaMode').checked = userSettings.enableCinemaMode(); context.querySelector('.chkEnableCinemaMode').checked = userSettings.enableCinemaMode();
context.querySelector('.chkEnableAudioNormalization').checked = userSettings.enableAudioNormalization();
context.querySelector('.chkEnableNextVideoOverlay').checked = userSettings.enableNextVideoInfoOverlay(); context.querySelector('.chkEnableNextVideoOverlay').checked = userSettings.enableNextVideoInfoOverlay();
context.querySelector('.chkRememberAudioSelections').checked = user.Configuration.RememberAudioSelections || false; context.querySelector('.chkRememberAudioSelections').checked = user.Configuration.RememberAudioSelections || false;
context.querySelector('.chkRememberSubtitleSelections').checked = user.Configuration.RememberSubtitleSelections || false; context.querySelector('.chkRememberSubtitleSelections').checked = user.Configuration.RememberSubtitleSelections || false;
@ -217,7 +218,7 @@ function saveUser(context, user, userSettingsInstance, apiClient) {
user.Configuration.EnableNextEpisodeAutoPlay = context.querySelector('.chkEpisodeAutoPlay').checked; user.Configuration.EnableNextEpisodeAutoPlay = context.querySelector('.chkEpisodeAutoPlay').checked;
userSettingsInstance.preferFmp4HlsContainer(context.querySelector('.chkPreferFmp4HlsContainer').checked); userSettingsInstance.preferFmp4HlsContainer(context.querySelector('.chkPreferFmp4HlsContainer').checked);
userSettingsInstance.enableCinemaMode(context.querySelector('.chkEnableCinemaMode').checked); userSettingsInstance.enableCinemaMode(context.querySelector('.chkEnableCinemaMode').checked);
userSettingsInstance.enableAudioNormalization(context.querySelector('.chkEnableAudioNormalization').checked);
userSettingsInstance.enableNextVideoInfoOverlay(context.querySelector('.chkEnableNextVideoOverlay').checked); userSettingsInstance.enableNextVideoInfoOverlay(context.querySelector('.chkEnableNextVideoOverlay').checked);
user.Configuration.RememberAudioSelections = context.querySelector('.chkRememberAudioSelections').checked; user.Configuration.RememberAudioSelections = context.querySelector('.chkRememberAudioSelections').checked;
user.Configuration.RememberSubtitleSelections = context.querySelector('.chkRememberSubtitleSelections').checked; user.Configuration.RememberSubtitleSelections = context.querySelector('.chkRememberSubtitleSelections').checked;

View file

@ -72,6 +72,14 @@
${TabAdvanced} ${TabAdvanced}
</h2> </h2>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" class="chkEnableAudioNormalization" />
<span>${EnableAudioNormalization}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${EnableAudioNormalizationHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription"> <div class="checkboxContainer checkboxContainer-withDescription">
<label> <label>
<input type="checkbox" is="emby-checkbox" class="chkPreferFmp4HlsContainer" /> <input type="checkbox" is="emby-checkbox" class="chkPreferFmp4HlsContainer" />
@ -102,7 +110,7 @@
</label> </label>
<div class="fieldDescription checkboxFieldDescription">${RememberAudioSelectionsHelp}</div> <div class="fieldDescription checkboxFieldDescription">${RememberAudioSelectionsHelp}</div>
</div> </div>
<div class="checkboxContainer checkboxContainer-withDescription"> <div class="checkboxContainer checkboxContainer-withDescription">
<label> <label>
<input type="checkbox" is="emby-checkbox" class="chkRememberSubtitleSelections" /> <input type="checkbox" is="emby-checkbox" class="chkRememberSubtitleSelections" />

View file

@ -101,6 +101,7 @@ class HtmlAudioPlayer {
self._currentTime = null; self._currentTime = null;
const elem = createMediaElement(); const elem = createMediaElement();
return setCurrentSrc(elem, options); return setCurrentSrc(elem, options);
}; };
@ -110,6 +111,17 @@ class HtmlAudioPlayer {
let val = options.url; let val = options.url;
console.debug('playing url: ' + val); console.debug('playing url: ' + val);
import('../../scripts/settings/userSettings').then((userSettings) => {
if (userSettings.enableAudioNormalization() && options.item.LUFS != null) {
const dbGain = -18 - options.item.LUFS;
self.gainNode.gain.value = Math.pow(10, (dbGain / 20));
} else {
self.gainNode.gain.value = 1;
}
console.debug('gain:' + self.gainNode.gain.value);
}).catch((err) => {
console.error('Failed to add/change gainNode', err);
});
// Convert to seconds // Convert to seconds
const seconds = (options.playerStartPositionTicks || 0) / 10000000; const seconds = (options.playerStartPositionTicks || 0) / 10000000;
@ -245,9 +257,29 @@ class HtmlAudioPlayer {
self._mediaElement = elem; self._mediaElement = elem;
addGainElement(elem);
return elem; return elem;
} }
function addGainElement(elem) {
try {
const AudioContext = window.AudioContext || window.webkitAudioContext; /* eslint-disable-line compat/compat */
const audioCtx = new AudioContext();
const source = audioCtx.createMediaElementSource(elem);
const gainNode = audioCtx.createGain();
source.connect(gainNode);
gainNode.connect(audioCtx.destination);
self.gainNode = gainNode;
} catch (e) {
console.error('Web Audio API is not supported in this browser', e);
}
}
function onEnded() { function onEnded() {
htmlMediaHelper.onEndedInternal(self, this, onError); htmlMediaHelper.onEndedInternal(self, this, onError);
} }

View file

@ -156,6 +156,19 @@ export class UserSettings {
return toBoolean(this.get('enableCinemaMode', false), true); return toBoolean(this.get('enableCinemaMode', false), true);
} }
/**
* Get or set 'Enable Audio Normalization' state.
* @param {boolean|undefined} val - Flag to enable 'Enable Audio Normalization' or undefined.
* @return {boolean} 'Enable Audio Normalization' state.
*/
enableAudioNormalization(val) {
if (val !== undefined) {
return this.set('enableAudioNormalization', val.toString(), false);
}
return toBoolean(this.get('enableAudioNormalization', false), true);
}
/** /**
* Get or set 'Next Video Info Overlay' state. * Get or set 'Next Video Info Overlay' state.
* @param {boolean|undefined} val - Flag to enable 'Next Video Info Overlay' or undefined. * @param {boolean|undefined} val - Flag to enable 'Next Video Info Overlay' or undefined.
@ -592,6 +605,7 @@ export const serverConfig = currentSettings.serverConfig.bind(currentSettings);
export const allowedAudioChannels = currentSettings.allowedAudioChannels.bind(currentSettings); export const allowedAudioChannels = currentSettings.allowedAudioChannels.bind(currentSettings);
export const preferFmp4HlsContainer = currentSettings.preferFmp4HlsContainer.bind(currentSettings); export const preferFmp4HlsContainer = currentSettings.preferFmp4HlsContainer.bind(currentSettings);
export const enableCinemaMode = currentSettings.enableCinemaMode.bind(currentSettings); export const enableCinemaMode = currentSettings.enableCinemaMode.bind(currentSettings);
export const enableAudioNormalization = currentSettings.enableAudioNormalization.bind(currentSettings);
export const enableNextVideoInfoOverlay = currentSettings.enableNextVideoInfoOverlay.bind(currentSettings); export const enableNextVideoInfoOverlay = currentSettings.enableNextVideoInfoOverlay.bind(currentSettings);
export const enableVideoRemainingTime = currentSettings.enableVideoRemainingTime.bind(currentSettings); export const enableVideoRemainingTime = currentSettings.enableVideoRemainingTime.bind(currentSettings);
export const enableThemeSongs = currentSettings.enableThemeSongs.bind(currentSettings); export const enableThemeSongs = currentSettings.enableThemeSongs.bind(currentSettings);

View file

@ -133,6 +133,7 @@
"ChannelNumber": "Channel number", "ChannelNumber": "Channel number",
"Channels": "Channels", "Channels": "Channels",
"CinemaModeConfigurationHelp": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.", "CinemaModeConfigurationHelp": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.",
"EnableAudioNormalizationHelp": "Audio normalization will add a constant gain to keep the average at a desired level (-18dB).",
"ClearQueue": "Clear queue", "ClearQueue": "Clear queue",
"ClientSettings": "Client Settings", "ClientSettings": "Client Settings",
"Collections": "Collections", "Collections": "Collections",
@ -221,6 +222,7 @@
"EnableBlurHash": "Enable blurred placeholders for images", "EnableBlurHash": "Enable blurred placeholders for images",
"EnableBlurHashHelp": "Images that are still being loaded will be displayed with a unique placeholder.", "EnableBlurHashHelp": "Images that are still being loaded will be displayed with a unique placeholder.",
"EnableCinemaMode": "Cinema mode", "EnableCinemaMode": "Cinema mode",
"EnableAudioNormalization": "Audio Normalization",
"EnableColorCodedBackgrounds": "Color coded backgrounds", "EnableColorCodedBackgrounds": "Color coded backgrounds",
"EnableDecodingColorDepth10Hevc": "Enable 10-bit hardware decoding for HEVC", "EnableDecodingColorDepth10Hevc": "Enable 10-bit hardware decoding for HEVC",
"EnableDecodingColorDepth10Vp9": "Enable 10-bit hardware decoding for VP9", "EnableDecodingColorDepth10Vp9": "Enable 10-bit hardware decoding for VP9",
@ -659,6 +661,8 @@
"LabelEnableIP4Help": "Enable IPv4 functionality.", "LabelEnableIP4Help": "Enable IPv4 functionality.",
"LabelEnableIP6": "Enable IPv6", "LabelEnableIP6": "Enable IPv6",
"LabelEnableIP6Help": "Enable IPv6 functionality.", "LabelEnableIP6Help": "Enable IPv6 functionality.",
"LabelEnableLUFSScan": "Enable LUFS scan",
"LabelEnableLUFSScanHelp": "Enable LUFS scan for music (This will take longer and more resources).",
"LabelEnableRealtimeMonitor": "Enable real time monitoring", "LabelEnableRealtimeMonitor": "Enable real time monitoring",
"LabelEnableRealtimeMonitorHelp": "Changes to files will be processed immediately on supported file systems.", "LabelEnableRealtimeMonitorHelp": "Changes to files will be processed immediately on supported file systems.",
"LabelEnableSingleImageInDidlLimit": "Limit to single embedded image", "LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",