Flutter移动端用just_audio播放WebSocket流式音频的延迟问题求助
解决WebSocket音频块无间断播放的延迟问题
当前通过WebSocket接收服务器音频块,将其添加到ConcatenatingAudioSource后调用playQueuedAudio循环播放,但块之间存在明显延迟。问题出在手动轮询的实现逻辑上,没有充分利用just_audio本身的队列自动播放能力,以下是规范的优化方案:
现有代码的核心问题
- Timer轮询引入延迟:100ms的轮询间隔本身会导致播放衔接的滞后,无法实时响应音频块的到达
- 手动控制播放流程:每次播放完手动移除队列元素并重新调用
play(),破坏了just_audio内置的队列自动衔接逻辑 - 状态管理混乱:
_isPlaying的手动维护容易出现竞态,导致播放逻辑冲突
优化实现方案
利用just_audio的ConcatenatingAudioSource自动播放下一个音频源的能力,让播放器状态自动响应队列变化,无需手动轮询控制。
重构后的完整代码
import 'dart:async'; import 'dart:convert'; import 'package:just_audio/just_audio.dart'; import 'package:meddymobile/utils/ws_connection.dart'; class PlayerService { final WSConnection _wsConnection; final AudioPlayer _audioPlayer = AudioPlayer(); final ConcatenatingAudioSource _audioChunkPlaylist = ConcatenatingAudioSource(children: []); StreamSubscription? _playerStateSubscription; PlayerService(this._wsConnection) { _wsConnection.setHandler('audio_3', _handleAudioMessage); _initPlayer(); } void _initPlayer() async { await _audioPlayer.setAudioSource(_audioChunkPlaylist); // 监听播放器状态,自动处理播放/暂停逻辑 _playerStateSubscription = _audioPlayer.playerStateStream.listen((state) { // 队列播放完毕且无新块时暂停 if (state.processingState == ProcessingState.completed && _audioChunkPlaylist.length == 0) { _audioPlayer.pause(); } // 播放器空闲但队列有内容时,自动启动播放 else if (state.processingState == ProcessingState.idle && _audioChunkPlaylist.length > 0) { _audioPlayer.play(); } }); } void _handleAudioMessage(Map<String, dynamic> message) { if (message['audio'] != null) { final audioData = base64Decode(message['audio']); _audioChunkPlaylist.add(MyAudioSource(audioData)); // 首个音频块到达时,若播放器未播放则立即启动 if (!_audioPlayer.playing && _audioChunkPlaylist.length == 1) { _audioPlayer.play(); } } } void playQueuedAudio() { // 只需确保队列有内容时播放器处于播放状态 if (_audioChunkPlaylist.length > 0 && !_audioPlayer.playing) { _audioPlayer.play(); } } Future<void> stopPlayback() async { await _audioPlayer.stop(); await _audioChunkPlaylist.clear(); } Future<void> dispose() async { await _playerStateSubscription?.cancel(); await _audioPlayer.dispose(); } } class MyAudioSource extends StreamAudioSource { final List<int> _buffer; MyAudioSource(this._buffer); @override Future<StreamAudioResponse> request([int? start, int? end]) async { start ??= 0; end ??= _buffer.length; return StreamAudioResponse( sourceLength: _buffer.length, contentLength: end - start, offset: start, stream: Stream.value(_buffer.sublist(start, end)), contentType: 'audio/mp3', ); } }
关键优化点说明
- 移除Timer轮询:通过
playerStateStream实时监听播放器状态,彻底消除轮询带来的延迟 - 自动队列衔接:just_audio会自动处理当前音频源播放完成后的下一个源播放,无需手动调用
play()和移除队列元素 - 智能状态管理:根据播放器状态和队列长度自动切换播放/暂停,避免手动维护状态的错误
- 即时触发播放:首个音频块到达时立即启动播放,后续块自动无缝衔接
额外优化建议
- 确保所有音频块的编码格式一致(如统一为MP3),避免格式切换导致的解码延迟
- 可以在接收音频块时提前完成数据解码,减少播放前的等待时间
- 监控队列长度,若积累过多可调整WebSocket接收策略或音频块大小,避免内存占用过高
内容的提问来源于stack exchange,提问作者Mustafa
相关产品推荐
相关产品推荐

