From 5bb8b1b9f26494130f52c1705f513e7e51b4b705 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Mon, 6 Jul 2026 15:55:06 +0200 Subject: [PATCH] feat(music): add persistent volume control Independent of the video player volume; persisted via SettingsService and re-applied to every audio player instance. --- .../music/music_playback_service.dart | 11 ++++++++++ .../music/music_playback_service_impl.dart | 21 +++++++++++++++++++ lib/services/settings_service.dart | 6 ++++++ 3 files changed, 38 insertions(+) diff --git a/lib/services/music/music_playback_service.dart b/lib/services/music/music_playback_service.dart index b6eaf23e..80f8b3fe 100644 --- a/lib/services/music/music_playback_service.dart +++ b/lib/services/music/music_playback_service.dart @@ -91,6 +91,11 @@ abstract class MusicPlaybackService extends ChangeNotifier { Future seek(Duration position); + /// Music playback volume, 0–100. Persisted across sessions and applied to + /// every audio player instance; independent of the video player volume. + double get volume; + Future setVolume(double volume); + void setRepeatMode(MusicRepeatMode mode); void toggleShuffle(); @@ -204,6 +209,12 @@ class StubMusicPlaybackService extends MusicPlaybackService { @override Future seek(Duration position) async {} + @override + double get volume => 100; + + @override + Future setVolume(double volume) async {} + @override void setRepeatMode(MusicRepeatMode mode) {} diff --git a/lib/services/music/music_playback_service_impl.dart b/lib/services/music/music_playback_service_impl.dart index 50a7261a..5dd910a1 100644 --- a/lib/services/music/music_playback_service_impl.dart +++ b/lib/services/music/music_playback_service_impl.dart @@ -18,6 +18,7 @@ import '../multi_server_manager.dart'; import '../offline_watch_sync_service.dart'; import '../playback_coordinator.dart'; import '../playback_progress_tracker.dart'; +import '../settings_service.dart'; import 'music_playback_service.dart'; import 'music_queue_controller.dart'; import 'music_source_resolver.dart'; @@ -97,6 +98,11 @@ class MusicPlaybackServiceImpl extends MusicPlaybackService with WidgetsBindingO final MusicQueueController _queue = MusicQueueController(); + /// Persisted music volume (0–100), applied to every audio player instance + /// (the core is recreated after video claims playback). Falls back to full + /// volume when settings aren't bootstrapped (tests). + double _volume = SettingsService.instanceOrNull?.read(SettingsService.musicVolume) ?? 100.0; + Player? _player; final List> _playerSubs = []; @@ -420,6 +426,7 @@ class MusicPlaybackServiceImpl extends MusicPlaybackService with WidgetsBindingO final player = _audioPlayerFactory(); _player = player; _wirePlayerStreams(player); + if (_volume != 100.0) unawaited(player.setVolume(_volume)); return player; } @@ -828,6 +835,20 @@ class MusicPlaybackServiceImpl extends MusicPlaybackService with WidgetsBindingO await _player?.seek(position); } + @override + double get volume => _volume; + + @override + Future setVolume(double volume) async { + final clamped = volume.clamp(0.0, 100.0); + if (clamped == _volume) return; + _volume = clamped; + notifyListeners(); + final settings = SettingsService.instanceOrNull; + if (settings != null) unawaited(settings.write(SettingsService.musicVolume, clamped)); + await _player?.setVolume(clamped); + } + @override Future jumpTo(int index) async { if (index < 0 || index >= _queue.length || index == _queue.cursor) return; diff --git a/lib/services/settings_service.dart b/lib/services/settings_service.dart index a50dae2b..0b22e9a2 100644 --- a/lib/services/settings_service.dart +++ b/lib/services/settings_service.dart @@ -426,6 +426,11 @@ class SettingsService extends BaseSharedPreferencesService { values: AudioQualityPreset.values, defaultValue: AudioQualityPreset.original, ); + + /// Music player volume (0–100), independent of the video player's + /// [volume] so desktop music listening levels don't drag video loudness + /// around. + static const musicVolume = DoublePref('music_volume', defaultValue: 100.0); static const autoPlayNextEpisode = BoolPref('auto_play_next_episode', defaultValue: true); static const useExoPlayer = BoolPref('use_exoplayer', defaultValue: true); static const startupSection = EnumPref( @@ -847,6 +852,7 @@ class SettingsService extends BaseSharedPreferencesService { matchContentFrameRate, tunneledPlayback, dvConversionMode, + musicVolume, defaultPlaybackSpeed, defaultBoxFitMode, autoPlayNextEpisode,