audio_controller.dart 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. // puzzleweave/audio/audio_controller.dart
  2. import 'package:flutter/widgets.dart';
  3. import 'package:audioplayers/audioplayers.dart';
  4. import 'package:puzzleweave/settings/settings_controller.dart';
  5. import 'package:logging/logging.dart';
  6. enum SfxType { drop, click, tap, kick, pop, appear, alert, star, success, flip, card, cardShort, panstart, panend, panend2 }
  7. /// 允许播放音乐和声音效果。
  8. class AudioControllerX {
  9. static final _log = Logger('AudioController');
  10. SettingsController? _settings;
  11. ValueNotifier<AppLifecycleState>? _lifecycleNotifier;
  12. // 1. BGM 播放器 (使用常规模式进行流式传输,专用于音乐)
  13. final AudioPlayer _musicPlayer = AudioPlayer();
  14. // 2. 音效音频池 (用于低延迟播放 SFX)
  15. // Key: SfxType, Value: AudioPool 实例
  16. final Map<SfxType, AudioPool> _sfxPools = {};
  17. // 预加载的 BGM 文件路径
  18. final String _bgmAsset = 'audio/bgm/canon.mp3';
  19. // 所有音效文件映射
  20. static const Map<SfxType, String> _sfxPaths = {
  21. SfxType.drop: 'audio/sfx/button_click8.mp3',
  22. SfxType.click: 'audio/sfx/click2.mp3',
  23. SfxType.tap: 'audio/sfx/tap.mp3',
  24. SfxType.kick: 'audio/sfx/kick.mp3',
  25. SfxType.pop: 'audio/sfx/pop.mp3',
  26. SfxType.appear: 'audio/sfx/appear.mp3',
  27. SfxType.alert: 'audio/sfx/alert.mp3',
  28. SfxType.star: 'audio/sfx/star.mp3',
  29. SfxType.success: 'audio/sfx/success3.mp3',
  30. SfxType.card: 'audio/sfx/card.mp3',
  31. SfxType.cardShort: 'audio/sfx/card2.mp3',
  32. SfxType.flip: 'audio/sfx/flip.mp3',
  33. SfxType.panstart: 'audio/sfx/pan_start.mp3',
  34. SfxType.panend: 'audio/sfx/pan_end.mp3',
  35. SfxType.panend2: 'audio/sfx/pan_end2.mp3',
  36. };
  37. AudioControllerX() {
  38. // 配置 BGM 播放器:设置为循环模式
  39. _musicPlayer.setReleaseMode(ReleaseMode.loop);
  40. }
  41. /// 在应用启动时异步调用,用于预加载所有音乐和音效。
  42. Future<void> initialize() async {
  43. // 1. 预加载 BGM
  44. await _musicPlayer.setSource(AssetSource(_bgmAsset));
  45. // 2. 预加载所有 SFX 到 AudioPool 中 (实现真正的低延迟)
  46. final futures = _sfxPaths.entries.map((entry) async {
  47. final type = entry.key;
  48. final path = entry.value;
  49. // ⚠️ 修复:使用 AudioPool.createFromAssetSource 替换 fromAsset
  50. final pool = await AudioPool.createFromAsset(
  51. path: path, // 直接传入 String 路径
  52. maxPlayers: type == SfxType.card ? 10 : 3,
  53. );
  54. _sfxPools[type] = pool;
  55. }).toList();
  56. await Future.wait(futures);
  57. _log.info('All sounds and music preloaded successfully via AudioPool.');
  58. }
  59. void dispose() {
  60. _lifecycleNotifier?.removeListener(_handleAppLifecycle);
  61. stopMusic();
  62. // 停止并清理所有的音频池,释放原生资源
  63. for (final pool in _sfxPools.values) {
  64. pool.dispose();
  65. }
  66. _sfxPools.clear();
  67. _musicPlayer.dispose();
  68. }
  69. /// Enables the [AudioController] to listen to [AppLifecycleState] events...
  70. void attachLifecycleNotifier(ValueNotifier<AppLifecycleState> lifecycleNotifier) {
  71. _lifecycleNotifier?.removeListener(_handleAppLifecycle);
  72. lifecycleNotifier.addListener(_handleAppLifecycle);
  73. _lifecycleNotifier = lifecycleNotifier;
  74. }
  75. /// Enables the [AudioController] to track changes to settings...
  76. void attachSettings(SettingsController settingsController) {
  77. if (_settings == settingsController) return;
  78. final oldSettings = _settings;
  79. if (oldSettings != null) {
  80. oldSettings.music.removeListener(_musicOnHandler);
  81. oldSettings.sound.removeListener(_soundOnHandler);
  82. }
  83. _settings = settingsController;
  84. settingsController.music.addListener(_musicOnHandler);
  85. settingsController.sound.addListener(_soundOnHandler);
  86. }
  87. /// Plays a single sound effect, defined by [type].
  88. /// 使用 AudioPool 实现极低延迟播放。
  89. void playSfx(SfxType type, {Duration? duration}) async {
  90. final soundsOn = _settings?.sound.value ?? false;
  91. if (!soundsOn) {
  92. _log.info(() => 'Ignoring playing sound ($type) because sounds are turned off.');
  93. return;
  94. }
  95. final pool = _sfxPools[type];
  96. if (pool == null) {
  97. _log.severe('Missing audio pool for SFX type: $type');
  98. return;
  99. }
  100. // 核心优化:从 AudioPool 快速启动播放。
  101. await pool.start(volume: 1.0);
  102. if (duration != null) {
  103. _log.warning('Duration control is complex with AudioPool; generally SFX should be short.');
  104. }
  105. }
  106. void _handleAppLifecycle() {
  107. switch (_lifecycleNotifier!.value) {
  108. case AppLifecycleState.paused:
  109. case AppLifecycleState.detached:
  110. case AppLifecycleState.hidden:
  111. case AppLifecycleState.inactive:
  112. pauseMusic();
  113. break;
  114. case AppLifecycleState.resumed:
  115. startMusic();
  116. break;
  117. }
  118. }
  119. void _musicOnHandler() {
  120. if (_settings!.music.value) {
  121. startMusic();
  122. } else {
  123. stopMusic();
  124. }
  125. }
  126. void _soundOnHandler() {
  127. // 声音设置关闭时,不需要特殊处理 AudioPool 播放的短音效
  128. }
  129. void setMusicVolume(double volume) {
  130. _musicPlayer.setVolume(volume);
  131. _log.info('Music volume set to $volume');
  132. }
  133. void startMusic() async {
  134. _log.info('starting music');
  135. final musicOn = _settings?.music.value ?? false;
  136. if (!musicOn) {
  137. _log.info(() => 'Ignoring playing music because music are turned off.');
  138. return;
  139. }
  140. // 假设我们希望 BGM 默认音量为 0.5
  141. const double defaultBGMVolume = 0.1;
  142. final state = _musicPlayer.state;
  143. if (state == PlayerState.playing) return;
  144. // 如果音乐已经停止或未播放,则重新设置源并播放
  145. if (state == PlayerState.stopped || state == PlayerState.disposed) {
  146. await _musicPlayer.setSource(AssetSource(_bgmAsset));
  147. await _musicPlayer.setVolume(defaultBGMVolume);
  148. await _musicPlayer.resume();
  149. } else {
  150. // 从暂停状态恢复
  151. await _musicPlayer.resume();
  152. }
  153. }
  154. void stopMusic() {
  155. _log.info('Stopping music');
  156. _musicPlayer.stop();
  157. }
  158. void pauseMusic() {
  159. _log.info('pause music');
  160. _musicPlayer.pause();
  161. }
  162. Future<void> resumeMusic() async {
  163. _log.info('Resuming music');
  164. final musicOn = _settings?.music.value ?? false;
  165. if (musicOn) {
  166. _musicPlayer.resume();
  167. }
  168. }
  169. void _stopSound() {
  170. // AudioPool 播放的短音效不需要手动停止
  171. }
  172. }