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

Flutter just_audio无法播放流式音频问题求助

问题分析与修复方案

你的分块加载音频无法播放,核心问题出在MyCustomSource的流处理逻辑和参数设置上,以下是具体问题和修复方案:

主要问题点

  1. 初始数据未传入流:request方法生成的byteStream没有包含第一块已加载的initialBytes,播放器启动时无可用数据初始化解码器。
  2. 错误设置固定源长度:分块加载时音频总长度未知,sourceLength设为当前bytes.length会导致播放器误判音频已结束。
  3. 流转换逻辑错误:handleData中没有正确传递后续音频块,而是重复截取已保存的bytes列表,导致数据流异常。

修复后的MyCustomSource代码

class MyCustomSource extends StreamAudioSource {
  final StreamController<List<int>> _controller = StreamController<List<int>>.broadcast();
  List<int> bytes = [];
  bool isClosed = false;

  MyCustomSource() {
    _controller.stream.listen((chunk) {
      bytes.addAll(chunk);
    }, onDone: () {
      isClosed = true;
      print("Stream closed");
    });
  }

  void addBytes(List<int> chunk) {
    if (!isClosed) {
      _controller.add(chunk);
      print("Bytes added, total length: ${bytes.length}");
    }
  }

  void complete() {
    if (!isClosed) {
      _controller.close();
    }
  }

  @override
  Future<StreamAudioResponse> request([int? start, int? end]) async {
    start ??= 0;
    end ??= int.maxValue; // 用最大值表示直到流结束

    // 处理初始已加载字节中符合范围的部分
    final initialChunk = bytes.sublist(
      start,
      bytes.length > end ? end : bytes.length,
    );

    // 构建包含初始数据和后续流的组合流
    final byteStream = Stream.value(initialChunk)
        .where((chunk) => chunk.isNotEmpty) // 过滤空块
        .asyncExpand((_) => _controller.stream)
        .transform(StreamTransformer.fromHandlers(
          handleData: (chunk, sink) {
            // 计算当前chunk中需要发送的片段
            int chunkStart = 0;
            int chunkEnd = chunk.length;
            if (start >= bytes.length - chunk.length) {
              chunkStart = start - (bytes.length - chunk.length);
              chunkStart = chunkStart < 0 ? 0 : chunkStart;
            }
            if (end < bytes.length) {
              chunkEnd = end - (bytes.length - chunk.length);
              chunkEnd = chunkEnd > chunk.length ? chunk.length : chunkEnd;
            }
            if (chunkStart < chunkEnd) {
              sink.add(chunk.sublist(chunkStart, chunkEnd));
            }
            start = bytes.length;
            // 达到指定范围则关闭流
            if (start >= end) {
              sink.close();
            }
          },
          handleDone: (sink) => sink.close(),
        ));

    return StreamAudioResponse(
      sourceLength: null, // 分块加载时总长度未知,设为null
      contentLength: end - start,
      offset: start,
      stream: byteStream,
      contentType: 'audio/mpeg',
    );
  }
}

控制器代码调整

确保设置音频源后触发播放,同时优化逻辑避免重复创建资源:

// 提前初始化customSource
customSource = MyCustomSource();
bool isFirstChunk = true;

await for (var chunk in res!) {
  customSource.addBytes(chunk);
  audioData.addAll(chunk);
  
  if (isFirstChunk) {
    await player.setAudioSource(customSource);
    await player.play(); // 触发播放
    isFirstChunk = false;
  }
}
// 所有块加载完成后关闭流
customSource.complete();

额外注意事项

  • 确保第一块音频数据足够让解码器识别格式(比如MP3需包含ID3头和第一个有效帧),若第一块太小,可等待加载前2-3块再初始化播放器。
  • 检查res的分块是否正确,确保每个chunk都是有效的音频字节片段,无关键数据截断。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 04:41:10