feat(audio): looping per-season background music system

MusicService (looping audioplayers player, independent of SFX) driven by the
active season theme's new 'bgm' key; switches track on season change, pauses on
app background, all failures swallowed. Separate Music on/off toggle in Settings
(persisted, independent of SFX). Season packs carry bgm keys (menu/season_001/
season_002), manifest regenerated. Assets slot assets/audio/bgm/ ready — drop in
menu.mp3/season_001.mp3/season_002.mp3 (CC0) and it plays; silent until then.
180 tests green, analyze clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-14 09:31:10 +09:00
parent 2310aabdb9
commit 8947221b27
16 changed files with 227 additions and 8 deletions
+12
View File
@@ -9,8 +9,10 @@ import '../services/analytics_service.dart';
import '../services/audio_service.dart';
import '../services/consent_service.dart';
import '../services/iap_service.dart';
import '../services/music_service.dart';
import 'ads_notifier.dart';
import 'endless_best_notifier.dart';
import 'music_notifier.dart';
import 'sound_notifier.dart';
import 'game_session_notifier.dart';
import 'progress_notifier.dart';
@@ -33,6 +35,16 @@ final audioServiceProvider = Provider<AudioService>((ref) {
return service;
});
final musicEnabledProvider =
NotifierProvider<MusicEnabledNotifier, bool>(MusicEnabledNotifier.new);
final musicServiceProvider = Provider<MusicService>((ref) {
final service = MusicService(enabled: ref.read(musicEnabledProvider));
ref.listen<bool>(musicEnabledProvider, (_, next) => service.enabled = next);
ref.onDispose(service.dispose);
return service;
});
/// Overridden with the opened repository in main() (and in tests).
final saveRepositoryProvider = Provider<SaveRepository>(
(ref) => throw UnimplementedError('override with an opened SaveRepository'),