Flutter使用flutter_bloc与audioplayers包设置PlayerState问题求助
问题根源
- State类的构造参数没有赋值给实例属性,导致
copyWith方法完全失效,每次生成新状态都会创建全新的AudioPlayer实例,所以停止操作的不是正在播放的那个实例 - 播放器状态监听放在播放事件回调中,异步监听还没拿到状态就先执行了yield,导致拿到的
PlayerState为null playMusic、stopMusic方法没有返回值,你在copyWith中给audioPlayer赋值await playMusic()相当于赋值null,逻辑错误
修复后代码
1. State代码
part of 'audioplayersbloc_bloc.dart'; class AudioPlayersBlocState { final AudioPlayer audioPlayer; final PlayerState playerState; final AudioCache audioCache; // 把默认值移到构造函数的默认参数里,传入的参数会正确赋值给实例属性 AudioPlayersBlocState({ AudioPlayer? audioPlayer, PlayerState? playerState, AudioCache? audioCache, }) : audioPlayer = audioPlayer ?? AudioPlayer(), playerState = playerState ?? PlayerState.paused, audioCache = audioCache ?? AudioCache(); AudioPlayersBlocState copyWith({ AudioPlayer? audioPlayer, PlayerState? playerState, AudioCache? audioCache, }) => AudioPlayersBlocState( audioPlayer: audioPlayer ?? this.audioPlayer, playerState: playerState ?? this.playerState, audioCache: audioCache ?? this.audioCache, ); }
2. Event代码(可保留原有逻辑,也可按需扩展暂停事件)
part of 'audioplayersbloc_bloc.dart'; @immutable abstract class AudioPlayersBlocEvent {} class OnPlayPlayerRemote extends AudioPlayersBlocEvent {} class OnStopPlayer extends AudioPlayersBlocEvent {} // 可按需新增暂停事件 class OnPausePlayer extends AudioPlayersBlocEvent {}
3. Bloc代码
import 'dart:async'; import 'package:audioplayers/audioplayers.dart'; import 'package:bloc/bloc.dart'; import 'package:meta/meta.dart'; part 'audioplayersbloc_event.dart'; part 'audioplayersbloc_state.dart'; class AudioPlayersBloc extends Bloc<AudioPlayersBlocEvent, AudioPlayersBlocState> { AudioPlayersBloc() : super(AudioPlayersBlocState()) { // 初始化时就监听播放器状态变化,自动同步状态 _initPlayerListener(); } final String audioUrl = '你的私有MP3地址'; void _initPlayerListener() { state.audioPlayer.onPlayerStateChanged.listen((playerState) { // 状态变化时直接emit新状态,无需手动读取赋值 emit(state.copyWith(playerState: playerState)); }); } @override Stream<AudioPlayersBlocState> mapEventToState( AudioPlayersBlocEvent event, ) async* { if (event is OnPlayPlayerRemote) { // audioplayers 4.x以上版本需要用UrlSource包裹网络地址,低版本可直接传字符串 await state.audioPlayer.play(UrlSource(audioUrl)); yield state.copyWith(); } else if (event is OnPausePlayer) { await state.audioPlayer.pause(); yield state.copyWith(); } else if (event is OnStopPlayer) { await state.audioPlayer.stop(); yield state.copyWith(); } } // bloc销毁时释放播放器资源,避免内存泄漏 @override Future<void> close() { state.audioPlayer.dispose(); return super.close(); } }
4. 首页按钮代码(支持根据播放状态自动切换UI)
// 用BlocBuilder监听状态变化,自动更新按钮显示 BlocBuilder<AudioPlayersBloc, AudioPlayersBlocState>( builder: (context, state) { return Row( children: [ // 播放/暂停按钮根据状态自动切换 if (state.playerState == PlayerState.playing) IconButton( icon: const Icon(Icons.pause), onPressed: () { context.read<AudioPlayersBloc>().add(OnPausePlayer()); }, ) else IconButton( icon: const Icon(Icons.play_arrow), onPressed: () { context.read<AudioPlayersBloc>().add(OnPlayPlayerRemote()); }, ), IconButton( icon: const Icon(Icons.stop), onPressed: () { context.read<AudioPlayersBloc>().add(OnStopPlayer()); }, ), ], ); }, )
注意事项
- 全程共用同一个
AudioPlayer实例,不用每次操作都替换实例,避免操作错位 - 播放器状态变化通过初始化时的监听自动同步,不用手动在操作方法里读取状态
- State类的构造参数必须正确赋值给实例属性,否则
copyWith不会生效
内容的提问来源于stack exchange,提问作者Dubbain
相关产品推荐
相关产品推荐

