You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 17:06:03