Flutter跳转新页面前如何提前下载并缓存多个视频?
问题核心原因
你的现有方案存在两个致命问题,直接导致顺序混乱、加载失败甚至崩溃:
- Dart中
forEach不会等待内部异步函数执行完成,调用fetchFile时还没等所有视频缓存完毕,就会提前返回未完成的videos数组 - 仅缓存视频文件没有预初始化播放器,哪怕是本地文件,首次创建播放器加载时仍会出现延迟,同时无校验的空数组传递也会引发空指针崩溃
修复方案
1. 修复批量缓存逻辑,保证顺序与完整性
使用Future.wait批量处理异步请求,它会严格按照输入的URL顺序返回缓存文件,且会等待所有缓存任务完成后再返回结果:
Future<List<File>> fetchVideos(List<String> urls) async { // 输出顺序与输入URL顺序完全一致,所有任务完成后才返回 final List<File> cachedFiles = await Future.wait( urls.map((url) => DefaultCacheManager().getSingleFile(url)) ); return cachedFiles; }
2. 预初始化视频控制器实现零延迟播放
如果要完全消除播放延迟,不要只传递视频文件到播放页,跳转前直接预初始化所有视频播放器控制器,预加载视频首帧:
// 先获取所有已缓存的本地视频文件 final cachedVideos = await fetchVideos(videoUrls); // 预初始化所有播放器控制器 List<VideoPlayerController> videoControllers = []; for (var file in cachedVideos) { final controller = VideoPlayerController.file(file); // 等待控制器初始化完成,预加载视频资源 await controller.initialize(); // 可根据需求配置默认音量、循环模式等 controller.setVolume(1); videoControllers.add(controller); } // 确认页面存活后再跳转,传递预加载完成的控制器 if (context.mounted) { Navigator.of(context).push( MaterialPageRoute(builder: (context) => LessonScreen(controllers: videoControllers)) ); }
3. 播放页逻辑实现
直接使用预初始化的控制器,切换播放时无任何加载过程:
class LessonScreen extends StatefulWidget { final List<VideoPlayerController> controllers; const LessonScreen({super.key, required this.controllers}); @override State<LessonScreen> createState() => _LessonScreenState(); } class _LessonScreenState extends State<LessonScreen> { int currentPlayIndex = 0; @override void initState() { super.initState(); // 进入页面直接播放第一个视频 widget.controllers[currentPlayIndex].play(); } // 点击按钮播放下一个 void playNextVideo() { if (currentPlayIndex >= widget.controllers.length - 1) return; // 重置当前视频进度到开头,暂停释放播放资源 widget.controllers[currentPlayIndex].pause(); widget.controllers[currentPlayIndex].seekTo(Duration.zero); // 切换到下一个视频直接播放,无加载延迟 currentPlayIndex++; widget.controllers[currentPlayIndex].play(); setState(() {}); } @override void dispose() { // 销毁页面时释放所有控制器,避免内存泄漏 for (var controller in widget.controllers) { controller.dispose(); } super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: AspectRatio( aspectRatio: widget.controllers[currentPlayIndex].value.aspectRatio, child: VideoPlayer(widget.controllers[currentPlayIndex]), ), ), floatingActionButton: FloatingActionButton( onPressed: playNextVideo, child: const Icon(Icons.skip_next), ), ); } }
可选优化:自定义缓存配置
如果默认缓存空间不足导致视频被自动清理,可以自定义缓存管理器调整缓存规则:
class CustomVideoCacheManager { static const cacheKey = 'shortVideoCache'; static final CacheManager instance = CacheManager( Config( cacheKey, stalePeriod: const Duration(days: 7), // 缓存有效期7天 maxNrOfCacheObjects: 200, // 最多缓存200个视频文件 ), ); }
使用时替换原有代码中的DefaultCacheManager()为CustomVideoCacheManager.instance即可。
内容的提问来源于stack exchange,提问作者Amro Saad
相关产品推荐
相关产品推荐

