Flutter audio_waveforms库onPlayerStateChanged监听失效问题排查
audio_waveforms包播放状态监听未触发问题解决
核心问题分析
你的代码存在两个关键问题导致监听失效:
- 未保留StreamSubscription引用:调用
_playerController.onPlayerStateChanged.listen()后,没有保存返回的订阅对象,Dart垃圾回收可能会回收该订阅,导致后续状态变化无法触发回调。 - 缺少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
相关产品推荐
相关产品推荐

