使用Riverpod和Flutter多Provider正确实现音频播放实时字幕渲染
可行解决方案
核心思路
将业务逻辑按职责拆分到不同Provider,避免单个大状态频繁刷新导致的性能问题,利用Riverpod的派生Provider能力做轻量计算,既保证更新实时性也不会出现性能损耗。
步骤1:新增实时播放进度流Provider
单独暴露音频播放进度的流,可自行调整流的发射频率平衡性能和精准度,Riverpod的StreamProvider会自动管理订阅生命周期,无需手动取消监听:
final currentAudioPositionProvider = StreamProvider<Duration>((ref) { final audioPlayer = ref.watch(voiceoverAudioPlayerProvider); // 可通过throttleTime限制发射频率,示例为每100毫秒发射一次进度 return audioPlayer.onAudioPositionChanged .throttleTime(const Duration(milliseconds: 100)) .handleError((_) => Duration.zero); });
步骤2:新增高亮单词索引计算Provider
创建只读派生Provider,仅依赖播放进度和字幕数据计算当前需要高亮的单词下标,计算逻辑完全独立,只有依赖项变更时才会重新计算,性能开销极低:
final currentHighlightWordIndexProvider = Provider<int?>((ref) { final currentPosition = ref.watch(currentAudioPositionProvider).valueOrNull; final voiceoverData = ref.watch(voiceoverTextProvider).valueOrNull; if (currentPosition == null || voiceoverData == null) return null; // 单词数量较多时建议替换为二分查找,进一步提升计算效率 for (int i = 0; i < voiceoverData.paragraph.length; i++) { final word = voiceoverData.paragraph[i]; if (currentPosition >= word.startTime && currentPosition <= word.endTime) { return i; } } return null; });
步骤3:调整Widget层实现
Widget层仅做状态监听和视图渲染,无需处理任何业务逻辑:
@override Widget build(BuildContext context, WidgetRef ref) { final voiceoverTextState = ref.watch(voiceoverTextProvider); final highlightIndex = ref.watch(currentHighlightWordIndexProvider); return voiceoverTextState.when( data: (voiceoverText) => Wrap( children: List.generate(voiceoverText.paragraph.length, (index) { final isHighlight = index == highlightIndex; return Text( voiceoverText.paragraph[index].text, style: TextStyle( color: isHighlight ? Colors.red : Colors.black, ), ); }), ), loading: () => const CircularProgressIndicator(), error: (error, stackTrace) => Text('加载失败:$error'), ); }
优化建议
- 若单词总数超过100,将遍历匹配逻辑替换为二分查找,计算效率可提升一个数量级
- 根据实际业务需求调整
throttleTime的时长,最低建议不低于50毫秒,避免无效刷新过多导致性能损耗 - 原有
voiceoverTextNotifier无需修改,仅保留字幕数据请求、音频播放/停止控制逻辑即可
内容的提问来源于stack exchange,提问作者claptimes
相关产品推荐
相关产品推荐

