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

Flutter audio_service的MediaState流重进页面持续处于waiting状态问题

问题根因
  • 你使用了Rxdart的combineLatest2组合流,该流的特性是必须两个子流都至少发射过一次数据后,才会向外输出组合后的结果
  • 其中AudioService.position流仅在音频处于播放状态时才会持续发射位置更新,当音频暂停、未播放,或页面重建后重新监听该流时,流不会主动发送当前的位置值,导致组合流一直没有输出,StreamBuilder的ConnectionState停留在waiting状态
  • 你当前的_mediaStateStream是每次页面build时动态创建的新流实例,没有保留之前的缓存值,页面重建后需要等待子流发送新事件才能触发回调
修复方案

方案1:给组合流添加初始值(最快生效)

修改_mediaStateStream实现,给两个子流都添加当前已有的值作为初始数据,流被监听时会立即发射一次组合结果,无需等待新事件:

Stream<MediaState> _mediaStateStream(AudioHandler audioHandler) {
  return Rx.combineLatest2<MediaItem?, Duration, MediaState>(
    // 注入当前已有的mediaItem作为初始值
    audioHandler.mediaItem.startWith(audioHandler.mediaItem.value),
    // 注入当前已有的播放位置作为初始值
    AudioService.position.startWith(audioHandler.playbackState.value.updatePosition),
    (mediaItem, position) => MediaState(mediaItem, position)
  );
}

如果使用的Rxdart 0.27.0+版本不支持直接调用startWith,可以替换为以下写法:

Stream<MediaState> _mediaStateStream(AudioHandler audioHandler) {
  return Rx.combineLatest2<MediaItem?, Duration, MediaState>(
    Rx.concat([Stream.value(audioHandler.mediaItem.value), audioHandler.mediaItem]),
    Rx.concat([Stream.value(audioHandler.playbackState.value.updatePosition), AudioService.position]),
    (mediaItem, position) => MediaState(mediaItem, position)
  );
}

方案2:全局托管MediaState流(推荐)

直接用Riverpod的StreamProvider托管MediaState流,全局仅创建一次实例,页面重建时直接复用已有流,自动拿到最新的缓存值:

// 全局定义MediaState流的Provider
final mediaStateProvider = StreamProvider<MediaState>((ref) {
  final audioHandler = ref.watch(audioHandlerServiceProvider);
  return Rx.combineLatest2<MediaItem?, Duration, MediaState>(
    audioHandler.mediaItem.startWith(audioHandler.mediaItem.value),
    AudioService.position.startWith(audioHandler.playbackState.value.updatePosition),
    (mediaItem, position) => MediaState(mediaItem, position)
  );
});

// 播放页使用时直接监听该Provider
body: Center(
  child: Consumer(builder: (context, watch, child) {
    final mediaStateAsync = watch(mediaStateProvider);
    return mediaStateAsync.when(
      loading: () => const CircularProgressIndicator(),
      error: (err, stack) => const SizedBox(),
      data: (mediaState) => SeekBar(
        duration: mediaState?.mediaItem?.duration ?? Duration.zero,
        position: mediaState?.position ?? Duration.zero,
        onChangeEnd: (newPosition) {
          ref.read(audioHandlerServiceProvider).seek(newPosition);
        },
      )
    );
  }),
)
额外注意事项

如果你的业务逻辑在退出播放页时会调用audioHandler.stop(),需要在重新进入播放页时,主动调用一次audioHandler.seek()传入之前保存的播放位置,强制触发流事件更新。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 09:36:04