| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209 |
- // 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<AppLifecycleState>? _lifecycleNotifier;
- // 1. BGM 播放器 (使用常规模式进行流式传输,专用于音乐)
- final AudioPlayer _musicPlayer = AudioPlayer();
- // 2. 音效音频池 (用于低延迟播放 SFX)
- // Key: SfxType, Value: AudioPool 实例
- final Map<SfxType, AudioPool> _sfxPools = {};
- // 预加载的 BGM 文件路径
- final String _bgmAsset = 'audio/bgm/canon.mp3';
- // 所有音效文件映射
- static const Map<SfxType, String> _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<void> 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<AppLifecycleState> 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<void> resumeMusic() async {
- _log.info('Resuming music');
- final musicOn = _settings?.music.value ?? false;
- if (musicOn) {
- _musicPlayer.resume();
- }
- }
- void _stopSound() {
- // AudioPool 播放的短音效不需要手动停止
- }
- }
|