Flutter video_player缓存异常:跳转后重下且isBuffering始终为false
我之前也踩过一模一样的坑——video_player本身确实没有内置HTTP缓存机制,而且底层播放器(Android的ExoPlayer、iOS的AVPlayer)的状态回调经常"掉链子",导致isBuffering始终显示false。下面分享几个亲测有效的解决方案,按优先级排序:
1. 配置底层播放器原生缓存(最简单,优先尝试)
video_player是原生播放器的上层封装,我们可以直接配置原生播放器的缓存策略,让它自动缓存已播放的视频片段。
Android端(ExoPlayer)
- 在
android/app/src/main/res/xml目录下创建exoplayer_cache_config.xml(没有xml文件夹就新建一个):
<?xml version="1.0" encoding="utf-8"?> <cache-config xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 配置缓存最大容量为100MB,最多缓存10个文件 --> <cache android:name="exoplayerCache" android:maxSizeBytes="104857600" android:maxFileCount="10"/> </cache-config>
- 在
AndroidManifest.xml的<application>标签内添加配置,让ExoPlayer加载这个缓存规则:
<application ...> <meta-data android:name="com.google.android.exoplayer2.CACHE_CONFIG_FILE" android:resource="@xml/exoplayer_cache_config"/> ... </application>
配置完成后,ExoPlayer会自动缓存已播放片段,跳转已播放位置时直接读取缓存,无需重新下载。
iOS端(AVPlayer)
AVPlayer默认会缓存已加载的视频数据,但缓存有效期较短。如果需要持久化缓存,可以通过自定义AVAssetResourceLoader实现,不过步骤稍复杂。追求快速见效的话,直接用下面的文件缓存方案更省心。
2. 手动实现文件缓存(跨平台通用)
如果原生配置不满足需求,我们可以把视频下载到本地缓存目录,直接播放本地文件——这样绝对不会出现重复下载的问题。
这里用dio做下载、path_provider获取缓存目录,示例代码如下:
import 'dart:io'; import 'package:dio/dio.dart'; import 'package:path_provider/path_provider.dart'; import 'package:video_player/video_player.dart'; Future<VideoPlayerController> getCachedVideoController(String videoUrl) async { // 获取系统临时缓存目录 final tempDir = await getTemporaryDirectory(); // 用视频URL的最后一段作为缓存文件名,避免重名冲突 final fileName = Uri.parse(videoUrl).pathSegments.last; final cacheFile = File('${tempDir.path}/$fileName'); // 检查缓存是否存在 if (await cacheFile.exists()) { // 直接加载本地缓存文件 return VideoPlayerController.file(cacheFile)..initialize(); } else { // 下载视频到缓存目录 await Dio().download( videoUrl, cacheFile.path, // 可选:监听下载进度 onReceiveProgress: (count, total) { final progress = count / total; print('下载进度:${(progress * 100).toStringAsFixed(1)}%'); }, ); return VideoPlayerController.file(cacheFile)..initialize(); } }
如果需要边下边播,可以先初始化网络URL的控制器,同时后台下载缓存,下次播放直接切换到本地文件。
3. 修复isBuffering始终为false的问题
原生isBuffering不准,大多是因为底层播放器在缓存命中或加载速度快时,没有触发缓冲回调。我们可以自己通过buffered和position属性计算真实的缓冲状态:
bool _isCustomBuffering = false; @override void initState() { super.initState(); _controller.addListener(_checkBufferingState); } void _checkBufferingState() { final playerValue = _controller.value; if (!playerValue.isPlaying) { _isCustomBuffering = false; return; } // 获取已缓冲的最后位置 final bufferedRanges = playerValue.buffered; if (bufferedRanges.isEmpty) { _isCustomBuffering = true; return; } final lastBufferedEnd = bufferedRanges.last.end; // 如果当前播放位置超过已缓冲范围,说明正在缓冲 _isCustomBuffering = playerValue.position > lastBufferedEnd; setState(() {}); } @override void dispose() { _controller.removeListener(_checkBufferingState); _controller.dispose(); super.dispose(); }
之后用自定义的_isCustomBuffering代替原生的isBuffering即可。
4. 用封装好缓存的第三方插件(省心之选)
你提到其他插件底层依赖video_player,其实很多插件已经帮我们封装好了缓存逻辑,比如cached_video_player(基于video_player扩展),它会自动处理缓存逻辑,不用自己写大量代码。注意选择维护活跃的版本即可。
内容的提问来源于stack exchange,提问作者Mohamad Alzabibi

