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
+11
View File
@@ -45,6 +45,9 @@ class SaveRepository {
_soundEnabled =
(json['flags'] as Map<String, dynamic>?)?['soundEnabled'] as bool? ??
true;
_musicEnabled =
(json['flags'] as Map<String, dynamic>?)?['musicEnabled'] as bool? ??
true;
}
}
@@ -60,12 +63,14 @@ class SaveRepository {
int _endlessBest = 0;
bool _adsRemoved = false;
bool _soundEnabled = true;
bool _musicEnabled = true;
StreakState get streak => _streak;
bool get tutorialDone => _tutorialDone;
int get endlessBest => _endlessBest;
bool get adsRemoved => _adsRemoved;
bool get soundEnabled => _soundEnabled;
bool get musicEnabled => _musicEnabled;
Future<void> markTutorialDone() {
_tutorialDone = true;
@@ -82,6 +87,11 @@ class SaveRepository {
return _flush();
}
Future<void> setMusicEnabled(bool value) {
_musicEnabled = value;
return _flush();
}
Future<void> recordEndlessScore(int score) {
if (score > _endlessBest) _endlessBest = score;
return _flush();
@@ -154,6 +164,7 @@ class SaveRepository {
'tutorialDone': _tutorialDone,
'adsRemoved': _adsRemoved,
'soundEnabled': _soundEnabled,
'musicEnabled': _musicEnabled,
},
'endless': {'best': _endlessBest},
}),