Flutter中WebSocket对接ElevenLabs音频流播放不流畅问题求助
修复Flutter中ElevenLabs WebSocket音频流播放的跳词与中断问题
问题根源
你的代码存在两个核心问题导致播放异常:
- 每次收到音频chunk就新建
StreamController并重新调用setAudioSource,强制播放器中断当前播放、加载新源,引发跳词和重启。 MyCustomSource仅返回创建时已缓存的字节数据,后续接收的音频chunk无法被播放器获取,导致播放中断。
修复方案与代码修改
1. 复用StreamController,单次初始化音频源
将StreamController改为全局复用,仅初始化一次音频源,持续向流中添加新的音频chunk:
final socketChannel = IOWebSocketChannel.connect( 'wss://api.elevenlabs.io/v1/text-to-speech/$voiceId/stream-input?model_id=$model'); final AudioPlayer audioPlayer = AudioPlayer(); // 全局复用的流控制器,广播模式支持多监听 final StreamController<List<int>> audioStreamController = StreamController.broadcast(); final bosMessage = { "text": " ", "voice_settings": {"stability": 0.5, "similarity_boost": true}, "xi_api_key": apiKey, }; // 仅初始化一次音频源 await audioPlayer.setAudioSource(MyCustomSource(audioStreamController.stream)); // 提前启动播放,等待音频chunk到达 await audioPlayer.play(); socketChannel.sink.add(jsonEncode(bosMessage)); socketChannel.stream.listen((message) async { final response = jsonDecode(message); if (response['audio'] != null) { try { final audioChunk = response['audio'] as String; final uint8List = Uint8List.fromList(base64.decode(audioChunk)); // 向同一个流控制器添加新的音频chunk audioStreamController.add(uint8List); } catch (e) { print("处理音频chunk出错: $e"); } } });
2. 改进MyCustomSource的流式播放支持
修改自定义音频源,使其能动态响应播放器的请求,持续提供后续接收的音频数据:
import 'package:just_audio/just_audio.dart'; class MyCustomSource extends StreamAudioSource { final Stream<List<int>> byteStream; final _buffer = <int>[]; bool _isStreamCompleted = false; MyCustomSource(this.byteStream) { // 监听音频流,持续更新缓冲区 byteStream.listen( (data) { _buffer.addAll(data); }, onDone: () { _isStreamCompleted = true; }, onError: (error) => print("音频流错误: $error"), ); } @override Future<StreamAudioResponse> request([int? start, int? end]) async { start ??= 0; end ??= _buffer.length; // 如果请求的范围超出当前缓存,等待新数据(流未结束时) while (end > _buffer.length && !_isStreamCompleted) { await Future.delayed(const Duration(milliseconds: 50)); } // 修正end值到实际可用的缓冲区长度 end = end.clamp(start, _buffer.length); return StreamAudioResponse( sourceLength: _isStreamCompleted ? _buffer.length : null, // 流未结束时不返回总长度 contentLength: end - start, offset: start, stream: Stream.value(_buffer.sublist(start, end)), contentType: 'audio/mpeg', ); } @override Future<void> dispose() async { super.dispose(); } }
3. 资源释放优化
在页面销毁时,确保释放所有资源避免内存泄漏:
@override void dispose() { audioStreamController.close(); audioPlayer.dispose(); socketChannel.sink.close(); super.dispose(); }
额外优化建议
- 添加WebSocket重连逻辑:当连接断开时自动重试,避免播放中断。
- 缓冲区内存管理:定期清理已播放完成的音频数据,防止缓冲区过大占用内存(需结合播放器的当前播放位置实现)。
内容的提问来源于stack exchange,提问作者Sahil Singh
相关产品推荐
相关产品推荐

