feat: add audio normalization toggle (MPV loudnorm filter)

This commit is contained in:
edde746
2026-03-01 02:49:48 +01:00
parent bbfe7cf783
commit 8a5f1ddea9
20 changed files with 79 additions and 9 deletions
+2 -1
View File
@@ -807,7 +807,8 @@
"hdr": "HDR",
"audioOutput": "Audioausgabe",
"performanceOverlay": "Leistungsanzeige",
"audioPassthrough": "Audio-Durchleitung"
"audioPassthrough": "Audio-Durchleitung",
"audioNormalization": "Audio-Normalisierung"
},
"externalPlayer": {
"title": "Externer Player",
+2 -1
View File
@@ -807,7 +807,8 @@
"hdr": "HDR",
"audioOutput": "Audio Output",
"performanceOverlay": "Performance Overlay",
"audioPassthrough": "Audio Passthrough"
"audioPassthrough": "Audio Passthrough",
"audioNormalization": "Audio Normalization"
},
"externalPlayer": {
"title": "External Player",
+2 -1
View File
@@ -807,7 +807,8 @@
"hdr": "HDR",
"audioOutput": "Salida de audio",
"performanceOverlay": "Indicador de rendimiento",
"audioPassthrough": "Audio Passthrough"
"audioPassthrough": "Audio Passthrough",
"audioNormalization": "Normalización de audio"
},
"externalPlayer": {
"title": "Reproductor externo",
+2 -1
View File
@@ -807,7 +807,8 @@
"hdr": "HDR",
"audioOutput": "Sortie audio",
"performanceOverlay": "Superposition de performance",
"audioPassthrough": "Audio Pass-Through"
"audioPassthrough": "Audio Pass-Through",
"audioNormalization": "Normalisation audio"
},
"externalPlayer": {
"title": "Lecteur externe",
+2 -1
View File
@@ -807,7 +807,8 @@
"hdr": "HDR",
"audioOutput": "Uscita audio",
"performanceOverlay": "Overlay prestazioni",
"audioPassthrough": "Audio Passthrough"
"audioPassthrough": "Audio Passthrough",
"audioNormalization": "Normalizzazione audio"
},
"externalPlayer": {
"title": "Lettore esterno",
+2 -1
View File
@@ -807,7 +807,8 @@
"hdr": "HDR",
"audioOutput": "오디오 출력",
"performanceOverlay": "성능 오버레이",
"audioPassthrough": "오디오 패스스루"
"audioPassthrough": "오디오 패스스루",
"audioNormalization": "오디오 정규화"
},
"externalPlayer": {
"title": "외부 플레이어",
+2 -1
View File
@@ -807,7 +807,8 @@
"hdr": "HDR",
"audioOutput": "Audio-uitvoer",
"performanceOverlay": "Prestatie-overlay",
"audioPassthrough": "Audio-doorvoer"
"audioPassthrough": "Audio-doorvoer",
"audioNormalization": "Audionormalisatie"
},
"externalPlayer": {
"title": "Externe speler",
+2
View File
@@ -1027,6 +1027,7 @@ class _TranslationsVideoSettingsDe implements TranslationsVideoSettingsEn {
@override String get audioOutput => 'Audioausgabe';
@override String get performanceOverlay => 'Leistungsanzeige';
@override String get audioPassthrough => 'Audio-Durchleitung';
@override String get audioNormalization => 'Audio-Normalisierung';
}
// Path: externalPlayer
@@ -2021,6 +2022,7 @@ extension on TranslationsDe {
'videoSettings.audioOutput' => 'Audioausgabe',
'videoSettings.performanceOverlay' => 'Leistungsanzeige',
'videoSettings.audioPassthrough' => 'Audio-Durchleitung',
'videoSettings.audioNormalization' => 'Audio-Normalisierung',
'externalPlayer.title' => 'Externer Player',
'externalPlayer.useExternalPlayer' => 'Externen Player verwenden',
'externalPlayer.useExternalPlayerDescription' => 'Videos in einer externen App statt im integrierten Player öffnen',
+4
View File
@@ -2276,6 +2276,9 @@ class TranslationsVideoSettingsEn {
/// en: 'Audio Passthrough'
String get audioPassthrough => 'Audio Passthrough';
/// en: 'Audio Normalization'
String get audioNormalization => 'Audio Normalization';
}
// Path: externalPlayer
@@ -3625,6 +3628,7 @@ extension on Translations {
'videoSettings.audioOutput' => 'Audio Output',
'videoSettings.performanceOverlay' => 'Performance Overlay',
'videoSettings.audioPassthrough' => 'Audio Passthrough',
'videoSettings.audioNormalization' => 'Audio Normalization',
'externalPlayer.title' => 'External Player',
'externalPlayer.useExternalPlayer' => 'Use External Player',
'externalPlayer.useExternalPlayerDescription' => 'Open videos in an external app instead of the built-in player',
+2
View File
@@ -1027,6 +1027,7 @@ class _TranslationsVideoSettingsEs implements TranslationsVideoSettingsEn {
@override String get audioOutput => 'Salida de audio';
@override String get performanceOverlay => 'Indicador de rendimiento';
@override String get audioPassthrough => 'Audio Passthrough';
@override String get audioNormalization => 'Normalización de audio';
}
// Path: externalPlayer
@@ -2021,6 +2022,7 @@ extension on TranslationsEs {
'videoSettings.audioOutput' => 'Salida de audio',
'videoSettings.performanceOverlay' => 'Indicador de rendimiento',
'videoSettings.audioPassthrough' => 'Audio Passthrough',
'videoSettings.audioNormalization' => 'Normalización de audio',
'externalPlayer.title' => 'Reproductor externo',
'externalPlayer.useExternalPlayer' => 'Usar reproductor externo',
'externalPlayer.useExternalPlayerDescription' => 'Abrir vídeos en una app externa en lugar del reproductor integrado',
+2
View File
@@ -1027,6 +1027,7 @@ class _TranslationsVideoSettingsFr implements TranslationsVideoSettingsEn {
@override String get audioOutput => 'Sortie audio';
@override String get performanceOverlay => 'Superposition de performance';
@override String get audioPassthrough => 'Audio Pass-Through';
@override String get audioNormalization => 'Normalisation audio';
}
// Path: externalPlayer
@@ -2021,6 +2022,7 @@ extension on TranslationsFr {
'videoSettings.audioOutput' => 'Sortie audio',
'videoSettings.performanceOverlay' => 'Superposition de performance',
'videoSettings.audioPassthrough' => 'Audio Pass-Through',
'videoSettings.audioNormalization' => 'Normalisation audio',
'externalPlayer.title' => 'Lecteur externe',
'externalPlayer.useExternalPlayer' => 'Utiliser un lecteur externe',
'externalPlayer.useExternalPlayerDescription' => 'Ouvrir les vidéos dans une application externe au lieu du lecteur intégré',
+2
View File
@@ -1027,6 +1027,7 @@ class _TranslationsVideoSettingsIt implements TranslationsVideoSettingsEn {
@override String get audioOutput => 'Uscita audio';
@override String get performanceOverlay => 'Overlay prestazioni';
@override String get audioPassthrough => 'Audio Passthrough';
@override String get audioNormalization => 'Normalizzazione audio';
}
// Path: externalPlayer
@@ -2021,6 +2022,7 @@ extension on TranslationsIt {
'videoSettings.audioOutput' => 'Uscita audio',
'videoSettings.performanceOverlay' => 'Overlay prestazioni',
'videoSettings.audioPassthrough' => 'Audio Passthrough',
'videoSettings.audioNormalization' => 'Normalizzazione audio',
'externalPlayer.title' => 'Lettore esterno',
'externalPlayer.useExternalPlayer' => 'Usa lettore esterno',
'externalPlayer.useExternalPlayerDescription' => 'Apri i video in un\'app esterna invece del lettore integrato',
+2
View File
@@ -1027,6 +1027,7 @@ class _TranslationsVideoSettingsKo implements TranslationsVideoSettingsEn {
@override String get audioOutput => '오디오 출력';
@override String get performanceOverlay => '성능 오버레이';
@override String get audioPassthrough => '오디오 패스스루';
@override String get audioNormalization => '오디오 정규화';
}
// Path: externalPlayer
@@ -2021,6 +2022,7 @@ extension on TranslationsKo {
'videoSettings.audioOutput' => '오디오 출력',
'videoSettings.performanceOverlay' => '성능 오버레이',
'videoSettings.audioPassthrough' => '오디오 패스스루',
'videoSettings.audioNormalization' => '오디오 정규화',
'externalPlayer.title' => '외부 플레이어',
'externalPlayer.useExternalPlayer' => '외부 플레이어 사용',
'externalPlayer.useExternalPlayerDescription' => '내장 플레이어 대신 외부 앱에서 동영상 열기',
+2
View File
@@ -1027,6 +1027,7 @@ class _TranslationsVideoSettingsNl implements TranslationsVideoSettingsEn {
@override String get audioOutput => 'Audio-uitvoer';
@override String get performanceOverlay => 'Prestatie-overlay';
@override String get audioPassthrough => 'Audio-doorvoer';
@override String get audioNormalization => 'Audionormalisatie';
}
// Path: externalPlayer
@@ -2021,6 +2022,7 @@ extension on TranslationsNl {
'videoSettings.audioOutput' => 'Audio-uitvoer',
'videoSettings.performanceOverlay' => 'Prestatie-overlay',
'videoSettings.audioPassthrough' => 'Audio-doorvoer',
'videoSettings.audioNormalization' => 'Audionormalisatie',
'externalPlayer.title' => 'Externe speler',
'externalPlayer.useExternalPlayer' => 'Externe speler gebruiken',
'externalPlayer.useExternalPlayerDescription' => 'Open video\'s in een externe app in plaats van de ingebouwde speler',
+2
View File
@@ -1027,6 +1027,7 @@ class _TranslationsVideoSettingsSv implements TranslationsVideoSettingsEn {
@override String get audioOutput => 'Ljudutgång';
@override String get performanceOverlay => 'Prestandaöverlägg';
@override String get audioPassthrough => 'Ljudgenomkoppling';
@override String get audioNormalization => 'Ljudnormalisering';
}
// Path: externalPlayer
@@ -2021,6 +2022,7 @@ extension on TranslationsSv {
'videoSettings.audioOutput' => 'Ljudutgång',
'videoSettings.performanceOverlay' => 'Prestandaöverlägg',
'videoSettings.audioPassthrough' => 'Ljudgenomkoppling',
'videoSettings.audioNormalization' => 'Ljudnormalisering',
'externalPlayer.title' => 'Extern spelare',
'externalPlayer.useExternalPlayer' => 'Använd extern spelare',
'externalPlayer.useExternalPlayerDescription' => 'Öppna videor i en extern app istället för den inbyggda spelaren',
+2
View File
@@ -1027,6 +1027,7 @@ class _TranslationsVideoSettingsZh implements TranslationsVideoSettingsEn {
@override String get audioOutput => '音频输出';
@override String get performanceOverlay => '性能监控';
@override String get audioPassthrough => '音频直通';
@override String get audioNormalization => '音频标准化';
}
// Path: externalPlayer
@@ -2021,6 +2022,7 @@ extension on TranslationsZh {
'videoSettings.audioOutput' => '音频输出',
'videoSettings.performanceOverlay' => '性能监控',
'videoSettings.audioPassthrough' => '音频直通',
'videoSettings.audioNormalization' => '音频标准化',
'externalPlayer.title' => '外部播放器',
'externalPlayer.useExternalPlayer' => '使用外部播放器',
'externalPlayer.useExternalPlayerDescription' => '在外部应用中打开视频,而不是使用内置播放器',
+2 -1
View File
@@ -807,7 +807,8 @@
"hdr": "HDR",
"audioOutput": "Ljudutgång",
"performanceOverlay": "Prestandaöverlägg",
"audioPassthrough": "Ljudgenomkoppling"
"audioPassthrough": "Ljudgenomkoppling",
"audioNormalization": "Ljudnormalisering"
},
"externalPlayer": {
"title": "Extern spelare",
+2 -1
View File
@@ -807,7 +807,8 @@
"hdr": "HDR",
"audioOutput": "音频输出",
"performanceOverlay": "性能监控",
"audioPassthrough": "音频直通"
"audioPassthrough": "音频直通",
"audioNormalization": "音频标准化"
},
"externalPlayer": {
"title": "外部播放器",
+11
View File
@@ -79,6 +79,7 @@ class SettingsService extends BaseSharedPreferencesService {
static const String _keyConfirmExitOnBack = 'confirm_exit_on_back';
static const String _keyAmbientLighting = 'ambient_lighting';
static const String _keyAudioPassthrough = 'audio_passthrough';
static const String _keyAudioNormalization = 'audio_normalization';
SettingsService._();
@@ -1149,6 +1150,15 @@ class SettingsService extends BaseSharedPreferencesService {
return prefs.getBool(_keyAudioPassthrough) ?? false;
}
// Audio Normalization
Future<void> setAudioNormalization(bool enabled) async {
await prefs.setBool(_keyAudioNormalization, enabled);
}
bool getAudioNormalization() {
return prefs.getBool(_keyAudioNormalization) ?? false;
}
// Reset all settings to defaults
Future<void> resetAllSettings() async {
await Future.wait([
@@ -1206,6 +1216,7 @@ class SettingsService extends BaseSharedPreferencesService {
prefs.remove(_keyConfirmExitOnBack),
prefs.remove(_keyAmbientLighting),
prefs.remove(_keyAudioPassthrough),
prefs.remove(_keyAudioNormalization),
prefs.remove(_keyBufferSizeMigratedToAuto),
]);
}
@@ -129,6 +129,7 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
bool _showPerformanceOverlay = false;
bool _autoPlayNextEpisode = true;
bool _audioPassthrough = false;
bool _audioNormalization = false;
@override
void initState() {
@@ -146,6 +147,7 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
_showPerformanceOverlay = settings.getShowPerformanceOverlay();
_autoPlayNextEpisode = settings.getAutoPlayNextEpisode();
_audioPassthrough = settings.getAudioPassthrough();
_audioNormalization = settings.getAudioNormalization();
});
}
@@ -192,6 +194,17 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
await widget.player.setAudioPassthrough(newValue);
}
Future<void> _toggleAudioNormalization() async {
final newValue = !_audioNormalization;
final settings = await SettingsService.getInstance();
await settings.setAudioNormalization(newValue);
if (!mounted) return;
setState(() {
_audioNormalization = newValue;
});
await widget.player.setProperty('af', newValue ? 'loudnorm=I=-14:TP=-3:LRA=4' : '');
}
void _navigateTo(_SettingsView view) {
// Sync views open as a compact top bar instead of a sub-view
if (view == _SettingsView.audioSync || view == _SettingsView.subtitleSync) {
@@ -427,6 +440,23 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
onTap: _toggleAudioPassthrough,
),
// Audio Normalization (MPV only)
if (widget.player.playerType == 'mpv')
ListTile(
leading: AppIcon(
Symbols.graphic_eq_rounded,
fill: 1,
color: _audioNormalization ? Colors.amber : tokens(context).textMuted,
),
title: Text(t.videoSettings.audioNormalization),
trailing: Switch(
value: _audioNormalization,
onChanged: (_) => _toggleAudioNormalization(),
activeThumbColor: Colors.amber,
),
onTap: _toggleAudioNormalization,
),
// Shader Preset (MPV only)
if (widget.shaderService != null && widget.shaderService!.isSupported)
_SettingsMenuItem(