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
相关产品推荐
相关产品推荐

