// puzzleweave/audio/audio_controller.dart import 'package:flutter/widgets.dart'; import 'package:audioplayers/audioplayers.dart'; import 'package:puzzleweave/settings/settings_controller.dart'; import 'package:logging/logging.dart'; enum SfxType { drop, click, tap, kick, pop, appear, alert, star, success, flip, card, cardShort, panstart, panend, panend2 } /// 允许播放音乐和声音效果。 class AudioControllerX { static final _log = Logger('AudioController'); SettingsController? _settings; ValueNotifier? _lifecycleNotifier; // 1. BGM 播放器 (使用常规模式进行流式传输,专用于音乐) final AudioPlayer _musicPlayer = AudioPlayer(); // 2. 音效音频池 (用于低延迟播放 SFX) // Key: SfxType, Value: AudioPool 实例 final Map _sfxPools = {}; // 预加载的 BGM 文件路径 final String _bgmAsset = 'audio/bgm/canon.mp3'; // 所有音效文件映射 static const Map _sfxPaths = { SfxType.drop: 'audio/sfx/button_click8.mp3', SfxType.click: 'audio/sfx/click2.mp3', SfxType.tap: 'audio/sfx/tap.mp3', SfxType.kick: 'audio/sfx/kick.mp3', SfxType.pop: 'audio/sfx/pop.mp3', SfxType.appear: 'audio/sfx/appear.mp3', SfxType.alert: 'audio/sfx/alert.mp3', SfxType.star: 'audio/sfx/star.mp3', SfxType.success: 'audio/sfx/success3.mp3', SfxType.card: 'audio/sfx/card.mp3', SfxType.cardShort: 'audio/sfx/card2.mp3', SfxType.flip: 'audio/sfx/flip.mp3', SfxType.panstart: 'audio/sfx/pan_start.mp3', SfxType.panend: 'audio/sfx/pan_end.mp3', SfxType.panend2: 'audio/sfx/pan_end2.mp3', }; AudioControllerX() { // 配置 BGM 播放器:设置为循环模式 _musicPlayer.setReleaseMode(ReleaseMode.loop); } /// 在应用启动时异步调用,用于预加载所有音乐和音效。 Future initialize() async { // 1. 预加载 BGM await _musicPlayer.setSource(AssetSource(_bgmAsset)); // 2. 预加载所有 SFX 到 AudioPool 中 (实现真正的低延迟) final futures = _sfxPaths.entries.map((entry) async { final type = entry.key; final path = entry.value; // ⚠️ 修复:使用 AudioPool.createFromAssetSource 替换 fromAsset final pool = await AudioPool.createFromAsset( path: path, // 直接传入 String 路径 maxPlayers: type == SfxType.card ? 10 : 3, ); _sfxPools[type] = pool; }).toList(); await Future.wait(futures); _log.info('All sounds and music preloaded successfully via AudioPool.'); } void dispose() { _lifecycleNotifier?.removeListener(_handleAppLifecycle); stopMusic(); // 停止并清理所有的音频池,释放原生资源 for (final pool in _sfxPools.values) { pool.dispose(); } _sfxPools.clear(); _musicPlayer.dispose(); } /// Enables the [AudioController] to listen to [AppLifecycleState] events... void attachLifecycleNotifier(ValueNotifier lifecycleNotifier) { _lifecycleNotifier?.removeListener(_handleAppLifecycle); lifecycleNotifier.addListener(_handleAppLifecycle); _lifecycleNotifier = lifecycleNotifier; } /// Enables the [AudioController] to track changes to settings... void attachSettings(SettingsController settingsController) { if (_settings == settingsController) return; final oldSettings = _settings; if (oldSettings != null) { oldSettings.music.removeListener(_musicOnHandler); oldSettings.sound.removeListener(_soundOnHandler); } _settings = settingsController; settingsController.music.addListener(_musicOnHandler); settingsController.sound.addListener(_soundOnHandler); } /// Plays a single sound effect, defined by [type]. /// 使用 AudioPool 实现极低延迟播放。 void playSfx(SfxType type, {Duration? duration}) async { final soundsOn = _settings?.sound.value ?? false; if (!soundsOn) { _log.info(() => 'Ignoring playing sound ($type) because sounds are turned off.'); return; } final pool = _sfxPools[type]; if (pool == null) { _log.severe('Missing audio pool for SFX type: $type'); return; } // 核心优化:从 AudioPool 快速启动播放。 await pool.start(volume: 1.0); if (duration != null) { _log.warning('Duration control is complex with AudioPool; generally SFX should be short.'); } } void _handleAppLifecycle() { switch (_lifecycleNotifier!.value) { case AppLifecycleState.paused: case AppLifecycleState.detached: case AppLifecycleState.hidden: case AppLifecycleState.inactive: pauseMusic(); break; case AppLifecycleState.resumed: startMusic(); break; } } void _musicOnHandler() { if (_settings!.music.value) { startMusic(); } else { stopMusic(); } } void _soundOnHandler() { // 声音设置关闭时,不需要特殊处理 AudioPool 播放的短音效 } void setMusicVolume(double volume) { _musicPlayer.setVolume(volume); _log.info('Music volume set to $volume'); } void startMusic() async { _log.info('starting music'); final musicOn = _settings?.music.value ?? false; if (!musicOn) { _log.info(() => 'Ignoring playing music because music are turned off.'); return; } // 假设我们希望 BGM 默认音量为 0.5 const double defaultBGMVolume = 0.1; final state = _musicPlayer.state; if (state == PlayerState.playing) return; // 如果音乐已经停止或未播放,则重新设置源并播放 if (state == PlayerState.stopped || state == PlayerState.disposed) { await _musicPlayer.setSource(AssetSource(_bgmAsset)); await _musicPlayer.setVolume(defaultBGMVolume); await _musicPlayer.resume(); } else { // 从暂停状态恢复 await _musicPlayer.resume(); } } void stopMusic() { _log.info('Stopping music'); _musicPlayer.stop(); } void pauseMusic() { _log.info('pause music'); _musicPlayer.pause(); } Future resumeMusic() async { _log.info('Resuming music'); final musicOn = _settings?.music.value ?? false; if (musicOn) { _musicPlayer.resume(); } } void _stopSound() { // AudioPool 播放的短音效不需要手动停止 } }