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

Flutter audio_waveforms库onPlayerStateChanged监听失效问题排查

audio_waveforms包播放状态监听未触发问题解决

核心问题分析

你的代码存在两个关键问题导致监听失效:

  1. 未保留StreamSubscription引用:调用_playerController.onPlayerStateChanged.listen()后,没有保存返回的订阅对象,Dart垃圾回收可能会回收该订阅,导致后续状态变化无法触发回调。
  2. 缺少onCompletion监听逻辑:代码中未添加播放完成的监听代码,自然无法收到完成回调。

修复后的代码

class AudioPlayerViewModel extends StateNotifier<AudioPlayerState> {
  final Ref ref;

  AudioPlayerViewModel(this.ref) : super(const AudioPlayerState());

  final recorderController = RecorderController()
    ..androidEncoder = AndroidEncoder.aac
    ..androidOutputFormat = AndroidOutputFormat.mpeg4
    ..iosEncoder = IosEncoder.kAudioFormatMPEG4AAC
    ..sampleRate = 44100;
  late PlayerController _playerController;
  // 保存播放状态订阅,防止被GC回收
  StreamSubscription<PlayerState>? _playerStateSubscription;
  // 保存播放完成订阅
  StreamSubscription<void>? _playerCompletionSubscription;

  Future<void> initialize() async {
    _playerController = PlayerController();
    
    // 保存播放状态监听订阅
    _playerStateSubscription = _playerController.onPlayerStateChanged.listen((playerState) {
      print('Player state changed: $playerState');
      if (playerState == PlayerState.stopped) {
        print('Playback stopped');
        state = state.copyWith(
          isPlaying: false,
        );
      } else if (playerState == PlayerState.playing) {
        print('Playback started');
        state = state.copyWith(
          isPlaying: true,
        );
      }
    });

    // 添加播放完成监听
    _playerCompletionSubscription = _playerController.onCompletion.listen(() {
      print('Playback completed');
      state = state.copyWith(
        isPlaying: false,
        currentlyPlayingController: null,
        currentlyPlayingPath: null,
      );
    });
  }

  Future<String> _generateUniqueFilePath() async {
    final appDocDirectory = await getApplicationDocumentsDirectory();
    final uuid = Uuid();
    final uniqueId = uuid.v4();
    return '${appDocDirectory.path}/audio_recording_$uniqueId.m4a';
  }

  Future<void> startRecording() async {
    await recorderController.checkPermission();

    if (recorderController.hasPermission) {
      final path = await _generateUniqueFilePath();
      print("Recording to path: $path");

      await recorderController.record(path: path);

      state = state.copyWith(
        isRecording: true,
        audioFiles: [...state.audioFiles, path],
      );
    } else {
      print("Permission not granted for recording.");
    }
  }

  Future<void> stopRecording() async {
    final recordedFilePath = await recorderController.stop();
    print("Recording stopped, file saved to: $recordedFilePath");
    final chatBotViewModel = ref.read(chatBotViewModelProvider.notifier);
    await chatBotViewModel.sendAudioMessage(recordedFilePath!);
    state = state.copyWith(isRecording: false);
  }

  Future<void> stopPlayer() async {
    await _playerController.stopPlayer();
    state = state.copyWith(
      currentlyPlayingController: null,
      isPlaying: false,
      currentlyPlayingPath: null,
    );
  }

  Future<void> preparePlayer(String path) async {
    await _playerController.preparePlayer(
        path: path, shouldExtractWaveform: true);
    state = state.copyWith(currentlyPlayingPath: path);
  }

  Future<void> playAudio(String path) async {
    await _playerController.stopAllPlayers();
    // 移除提前设置isPlaying的逻辑,由监听回调触发状态更新
    await preparePlayer(path);
    await _playerController.startPlayer();
    state = state.copyWith(
      currentlyPlayingController: _playerController,
    );
  }

  @override
  void dispose() {
    recorderController.dispose();
    // 取消所有订阅,避免内存泄漏
    _playerStateSubscription?.cancel();
    _playerCompletionSubscription?.cancel();
    _playerController.dispose();
    super.dispose();
  }
}

额外优化说明

  • 移除了playAudio中提前设置isPlaying: true的逻辑,改为由监听回调触发状态更新,保证状态与实际播放状态完全同步。
  • 在dispose方法中主动取消所有订阅,避免内存泄漏问题。

内容的提问来源于stack exchange,提问作者Homerunner

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 13:11:00