本文详解在裸 react native 项目中结合 expo-av 和 react-native-fs 实现本地视频缓存的完整方案,涵盖路径处理、uri 兼容性修复、文件存在性校验及最佳实践,解决因文件协议、权限或路径格式导致的播放失败问题。
本文详解在裸 react native 项目中结合 expo-av 和 react-native-fs 实现本地视频缓存的完整方案,涵盖路径处理、uri 兼容性修复、文件存在性校验及最佳实践,解决因文件协议、权限或路径格式导致的播放失败问题。
在 React Native 中缓存远程视频并离线播放,是提升用户体验(尤其在网络不稳定或需重复播放场景)的关键能力。虽然 react-native-video 对本地文件路径兼容性较好,但 expo-av(尤其是其
此外,直接用 fetch().blob() 写入文件存在兼容性风险(Blob 在某些 RN 版本中不可靠),推荐改用 RNFS.downloadFile 进行流式下载,既高效又稳定。
以下是优化后的完整实现方案:
✅ 正确缓存与播放流程(适配 expo-av)
import React, { useState, useEffect } from 'react';
import { View } from 'react-native';
import * as FileSystem from 'expo-file-system'; // 推荐:比 react-native-fs 更兼容 Expo 生态
import { Video } from 'expo-av';
const App = ({ videoUrl }: { videoUrl: string }) => {
const [localUri, setLocalUri] = useState<string null>(null);
useEffect(() => {
downloadAndCacheVideo();
}, []);
const downloadAndCacheVideo = async () => {
if (!videoUrl) return;
// 生成唯一缓存文件名(避免特殊字符)
const filename = `${Date.now()}-${encodeURIComponent(
videoUrl.substring(videoUrl.lastIndexOf('/') + 1)
)}`;
const cachePath = `${FileSystem.DocumentDirectoryPath}/${filename}`;
try {
// 1. 检查是否已存在
const exists = await FileSystem.exists(cachePath);
if (exists) {
setLocalUri(cachePath); // ✅ 直接赋值(expo-av 自动识别 file://)
return;
}
// 2. 流式下载(安全、内存友好)
const downloadRes = await FileSystem.downloadAsync(videoUrl, cachePath);
if (downloadRes.status === 200) {
setLocalUri(downloadRes.uri); // ✅ downloadAsync 返回带 file:// 的 uri
}
} catch (err) {
console.error('视频缓存失败:', err);
}
};
return (
<view style="{{" flex:>
{localUri ? (
<video source="{{" uri: localuri uri file: resizemode="cover" shouldplay ismuted="{false}" islooping style="{{" flex:></video>
) : (
<view style="{{" flex: backgroundcolor: justifycontent: alignitems:><text>正在缓存视频...</text></view>
)}
</view>
);
};
export default App;</string>
⚠️ 关键注意事项
- 路径协议必须为 file://:expo-av 不接受无协议的相对路径(如 /data/.../video.mp4)。expo-file-system 的 downloadAsync 和 DocumentDirectoryPath 均自动返回标准 file:// URI,无需手动拼接。
- 避免 fetch + blob 方案:RN 环境下 Blob 行为不一致,易导致写入损坏或空文件;downloadAsync 是官方推荐、经充分测试的方案。
- iOS 注意事项:确保 Info.plist 中已添加 NSAppTransportSecurity 配置(允许 HTTP 下载)及 UIBackgroundModes(如需后台下载)。
- Android 权限:Android 10+ 需配置 android:requestLegacyExternalStorage="true"(临时方案)或适配 Scoped Storage(推荐使用 expo-file-system,它已内部处理)。
- 缓存策略增强(进阶):若服务端支持,可结合 ETag 或 Last-Modified 头做条件请求(如你答案中提到的 If-None-Match),避免重复下载。客户端需存储 ETag 并在请求头中携带,服务端返回 304 Not Modified 时跳过下载。
✅ 总结
成功在 expo-av 中播放缓存视频的关键在于:使用 expo-file-system 替代 react-native-fs,依赖 downloadAsync 获取标准 file:// URI,并确保组件 source.uri 直接传入该 URI。此方案跨平台稳定、符合 Expo 官方最佳实践,且规避了路径协议、Blob 兼容性等常见陷阱。如需进一步优化体验,可增加加载状态、错误重试、磁盘空间清理等逻辑。











