From 8a5f1ddea9ec66108628d97fa9d95a84f23c9729 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Sun, 1 Mar 2026 02:49:48 +0100 Subject: [PATCH] feat: add audio normalization toggle (MPV loudnorm filter) --- lib/i18n/de.i18n.json | 3 +- lib/i18n/en.i18n.json | 3 +- lib/i18n/es.i18n.json | 3 +- lib/i18n/fr.i18n.json | 3 +- lib/i18n/it.i18n.json | 3 +- lib/i18n/ko.i18n.json | 3 +- lib/i18n/nl.i18n.json | 3 +- lib/i18n/strings_de.g.dart | 2 ++ lib/i18n/strings_en.g.dart | 4 +++ lib/i18n/strings_es.g.dart | 2 ++ lib/i18n/strings_fr.g.dart | 2 ++ lib/i18n/strings_it.g.dart | 2 ++ lib/i18n/strings_ko.g.dart | 2 ++ lib/i18n/strings_nl.g.dart | 2 ++ lib/i18n/strings_sv.g.dart | 2 ++ lib/i18n/strings_zh.g.dart | 2 ++ lib/i18n/sv.i18n.json | 3 +- lib/i18n/zh.i18n.json | 3 +- lib/services/settings_service.dart | 11 +++++++ .../sheets/video_settings_sheet.dart | 30 +++++++++++++++++++ 20 files changed, 79 insertions(+), 9 deletions(-) diff --git a/lib/i18n/de.i18n.json b/lib/i18n/de.i18n.json index 310e3143..7809526b 100644 --- a/lib/i18n/de.i18n.json +++ b/lib/i18n/de.i18n.json @@ -807,7 +807,8 @@ "hdr": "HDR", "audioOutput": "Audioausgabe", "performanceOverlay": "Leistungsanzeige", - "audioPassthrough": "Audio-Durchleitung" + "audioPassthrough": "Audio-Durchleitung", + "audioNormalization": "Audio-Normalisierung" }, "externalPlayer": { "title": "Externer Player", diff --git a/lib/i18n/en.i18n.json b/lib/i18n/en.i18n.json index eb938889..66d6d471 100644 --- a/lib/i18n/en.i18n.json +++ b/lib/i18n/en.i18n.json @@ -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", diff --git a/lib/i18n/es.i18n.json b/lib/i18n/es.i18n.json index bfe8e54c..fbcc5040 100644 --- a/lib/i18n/es.i18n.json +++ b/lib/i18n/es.i18n.json @@ -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", diff --git a/lib/i18n/fr.i18n.json b/lib/i18n/fr.i18n.json index b81f1c23..e716530f 100644 --- a/lib/i18n/fr.i18n.json +++ b/lib/i18n/fr.i18n.json @@ -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", diff --git a/lib/i18n/it.i18n.json b/lib/i18n/it.i18n.json index 8e9f5558..990d31fd 100644 --- a/lib/i18n/it.i18n.json +++ b/lib/i18n/it.i18n.json @@ -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", diff --git a/lib/i18n/ko.i18n.json b/lib/i18n/ko.i18n.json index 0450b81b..d275569a 100644 --- a/lib/i18n/ko.i18n.json +++ b/lib/i18n/ko.i18n.json @@ -807,7 +807,8 @@ "hdr": "HDR", "audioOutput": "오디오 출력", "performanceOverlay": "성능 오버레이", - "audioPassthrough": "오디오 패스스루" + "audioPassthrough": "오디오 패스스루", + "audioNormalization": "오디오 정규화" }, "externalPlayer": { "title": "외부 플레이어", diff --git a/lib/i18n/nl.i18n.json b/lib/i18n/nl.i18n.json index 3ff7e563..e929d31b 100644 --- a/lib/i18n/nl.i18n.json +++ b/lib/i18n/nl.i18n.json @@ -807,7 +807,8 @@ "hdr": "HDR", "audioOutput": "Audio-uitvoer", "performanceOverlay": "Prestatie-overlay", - "audioPassthrough": "Audio-doorvoer" + "audioPassthrough": "Audio-doorvoer", + "audioNormalization": "Audionormalisatie" }, "externalPlayer": { "title": "Externe speler", diff --git a/lib/i18n/strings_de.g.dart b/lib/i18n/strings_de.g.dart index 55049c72..7523dd31 100644 --- a/lib/i18n/strings_de.g.dart +++ b/lib/i18n/strings_de.g.dart @@ -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', diff --git a/lib/i18n/strings_en.g.dart b/lib/i18n/strings_en.g.dart index 09a1d73d..d4a20f84 100644 --- a/lib/i18n/strings_en.g.dart +++ b/lib/i18n/strings_en.g.dart @@ -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', diff --git a/lib/i18n/strings_es.g.dart b/lib/i18n/strings_es.g.dart index 4824a5ea..ecde52e4 100644 --- a/lib/i18n/strings_es.g.dart +++ b/lib/i18n/strings_es.g.dart @@ -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', diff --git a/lib/i18n/strings_fr.g.dart b/lib/i18n/strings_fr.g.dart index 45e2a918..81cc059f 100644 --- a/lib/i18n/strings_fr.g.dart +++ b/lib/i18n/strings_fr.g.dart @@ -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é', diff --git a/lib/i18n/strings_it.g.dart b/lib/i18n/strings_it.g.dart index d49f792c..a8b63ac8 100644 --- a/lib/i18n/strings_it.g.dart +++ b/lib/i18n/strings_it.g.dart @@ -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', diff --git a/lib/i18n/strings_ko.g.dart b/lib/i18n/strings_ko.g.dart index 24936b1e..856103ec 100644 --- a/lib/i18n/strings_ko.g.dart +++ b/lib/i18n/strings_ko.g.dart @@ -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' => '내장 플레이어 대신 외부 앱에서 동영상 열기', diff --git a/lib/i18n/strings_nl.g.dart b/lib/i18n/strings_nl.g.dart index d56dd4b5..e7aad0f8 100644 --- a/lib/i18n/strings_nl.g.dart +++ b/lib/i18n/strings_nl.g.dart @@ -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', diff --git a/lib/i18n/strings_sv.g.dart b/lib/i18n/strings_sv.g.dart index 8c1d34c5..c94897e5 100644 --- a/lib/i18n/strings_sv.g.dart +++ b/lib/i18n/strings_sv.g.dart @@ -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', diff --git a/lib/i18n/strings_zh.g.dart b/lib/i18n/strings_zh.g.dart index a877e844..f6dbcb8e 100644 --- a/lib/i18n/strings_zh.g.dart +++ b/lib/i18n/strings_zh.g.dart @@ -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' => '在外部应用中打开视频,而不是使用内置播放器', diff --git a/lib/i18n/sv.i18n.json b/lib/i18n/sv.i18n.json index c66ac4d8..6a08c73f 100644 --- a/lib/i18n/sv.i18n.json +++ b/lib/i18n/sv.i18n.json @@ -807,7 +807,8 @@ "hdr": "HDR", "audioOutput": "Ljudutgång", "performanceOverlay": "Prestandaöverlägg", - "audioPassthrough": "Ljudgenomkoppling" + "audioPassthrough": "Ljudgenomkoppling", + "audioNormalization": "Ljudnormalisering" }, "externalPlayer": { "title": "Extern spelare", diff --git a/lib/i18n/zh.i18n.json b/lib/i18n/zh.i18n.json index 3349eb43..3976017e 100644 --- a/lib/i18n/zh.i18n.json +++ b/lib/i18n/zh.i18n.json @@ -807,7 +807,8 @@ "hdr": "HDR", "audioOutput": "音频输出", "performanceOverlay": "性能监控", - "audioPassthrough": "音频直通" + "audioPassthrough": "音频直通", + "audioNormalization": "音频标准化" }, "externalPlayer": { "title": "外部播放器", diff --git a/lib/services/settings_service.dart b/lib/services/settings_service.dart index 3fd86334..9e4afabc 100644 --- a/lib/services/settings_service.dart +++ b/lib/services/settings_service.dart @@ -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 setAudioNormalization(bool enabled) async { + await prefs.setBool(_keyAudioNormalization, enabled); + } + + bool getAudioNormalization() { + return prefs.getBool(_keyAudioNormalization) ?? false; + } + // Reset all settings to defaults Future 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), ]); } diff --git a/lib/widgets/video_controls/sheets/video_settings_sheet.dart b/lib/widgets/video_controls/sheets/video_settings_sheet.dart index af2140c9..76cbb5d8 100644 --- a/lib/widgets/video_controls/sheets/video_settings_sheet.dart +++ b/lib/widgets/video_controls/sheets/video_settings_sheet.dart @@ -129,6 +129,7 @@ class _VideoSettingsSheetState extends State { bool _showPerformanceOverlay = false; bool _autoPlayNextEpisode = true; bool _audioPassthrough = false; + bool _audioNormalization = false; @override void initState() { @@ -146,6 +147,7 @@ class _VideoSettingsSheetState extends State { _showPerformanceOverlay = settings.getShowPerformanceOverlay(); _autoPlayNextEpisode = settings.getAutoPlayNextEpisode(); _audioPassthrough = settings.getAudioPassthrough(); + _audioNormalization = settings.getAudioNormalization(); }); } @@ -192,6 +194,17 @@ class _VideoSettingsSheetState extends State { await widget.player.setAudioPassthrough(newValue); } + Future _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 { 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(