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

just_audio自定义StreamAudioSource请求不足致音频播放短暂问题求助

gRPC流式音频播放问题解决思路

问题背景

采用gRPC协议做音乐字节流式传输,无法使用player.setUrl方法,将字节数据存入List<int>类型的musicBytes后,自定义了StreamAudioSource子类MyCustomSource,但request函数仅被调用两次,音频只能短暂播放,平台为Win10。

问题原因

当前实现存在两个核心问题:

  1. sourceLength写死固定值,播放器无法获取准确的总音频长度,导致分段请求逻辑异常;
  2. 一次性返回请求区间内的所有字节,没有实现播放器需要的分块流式返回逻辑,播放器仅能获取前两次请求的数据,后续没有持续的音频块供应。

解决方案

方案1:基于已缓存的完整字节列表实现分块播放

如果musicBytes已经缓存了完整的音频字节,修改MyCustomSource实现分块返回数据,让播放器能根据播放进度持续请求后续音频块:

import 'dart:math' as math;

class MyCustomSource extends StreamAudioSource {
  final MusicService _musicService;

  // 构造函数注入服务,避免每次请求重复获取实例
  MyCustomSource(this._musicService);

  @override
  Future<StreamAudioResponse> request([int? start, int? end]) async {
    start ??= 0;
    final totalLength = _musicService.musicBytes.length;
    end ??= totalLength;

    // 设置单块音频大小(示例为128KB,可根据实际调整)
    const chunkSize = 128 * 1024;
    // 计算实际返回的结束位置,避免超出总长度或请求的end值
    final actualEnd = math.min(start + chunkSize, end);

    return StreamAudioResponse(
      sourceLength: totalLength,
      contentLength: actualEnd - start,
      offset: start,
      stream: Stream.value(_musicService.musicBytes.sublist(start, actualEnd)),
      contentType: 'audio/mpeg',
    );
  }
}

方案2:适配gRPC实时流式传输(未缓存完整数据)

如果gRPC是实时推送音频字节,musicBytes处于持续接收状态,需要直接对接gRPC的流,并处理可能的seek需求:

class MyCustomSource extends StreamAudioSource {
  final Stream<List<int>> _grpcAudioStream;
  final int? _totalLength; // gRPC能提前返回总长度则传入,否则为null
  final List<int> _cachedBytes = [];

  MyCustomSource(this._grpcAudioStream, this._totalLength) {
    // 订阅gRPC流,缓存已接收的字节
    _grpcAudioStream.listen((bytes) {
      _cachedBytes.addAll(bytes);
    });
  }

  @override
  Future<StreamAudioResponse> request([int? start, int? end]) async {
    start ??= 0;
    end ??= _totalLength ?? _cachedBytes.length;

    // 如果请求的起始位置超过已缓存长度,等待数据接收
    while (start >= _cachedBytes.length) {
      await Future.delayed(const Duration(milliseconds: 100));
    }

    const chunkSize = 128 * 1024;
    final actualEnd = math.min(start + chunkSize, end);

    return StreamAudioResponse(
      sourceLength: _totalLength,
      contentLength: actualEnd - start,
      offset: start,
      stream: Stream.value(_cachedBytes.sublist(start, actualEnd)),
      contentType: 'audio/mpeg',
    );
  }
}

关键说明

  • 分块返回是核心:播放器会根据当前播放进度,持续调用request请求下一块音频数据,确保播放不中断;
  • 准确的总长度:sourceLength必须设置为真实的音频总字节数,不能写死固定值,否则播放器无法判断播放进度和剩余数据;
  • 实时流适配:如果是实时接收gRPC数据,需要缓存已接收的字节,并在请求时等待数据可用,保证seek功能正常。

内容的提问来源于stack exchange,提问作者Yusuf Muharrem Daşkaya

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 00:22:30