本文详解在裸 react native 项目中结合 expo-av 和 react-native-fs 实现本地视频缓存的完整方案,涵盖路径处理、uri 兼容性修复、文件存在性校验及最佳实践,解决因文件协议、权限或路径格式导致的播放失败问题。
本文详解在裸 react native 项目中结合 expo-av 和 react-native-fs 实现本地视频缓存的完整方案,涵盖路径处理、uri 兼容性修复、文件存在性校验及最佳实践,解决因文件协议、权限或路径格式导致的播放失败问题。
在 React Native 中缓存远程视频并离线播放,是提升用户体验(尤其在网络不稳定或需重复播放场景)的关键能力。虽然 react-native-video 对本地文件路径兼容性较好,但 expo-av(尤其是其 <Video> 组件)对本地文件 URI 有更严格的协议要求:必须使用 file:// 协议前缀,且路径需为绝对、可访问的本地路径。你提供的代码中 setVideo(${cachePath}/${filename}) 生成的是纯文件系统路径(如 /Users/.../video.mp4),缺少 file:// 前缀,这是导致 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: 1 }}>
{localUri ? (
<Video
source={{ uri: localUri }} // ✅ 此处 uri 已含 file:// 前缀
resizeMode="cover"
shouldPlay
isMuted={false}
isLooping
style={{ flex: 1 }}
/>
) : (
<View style={{ flex: 1, backgroundColor: '#f0f0f0', justifyContent: 'center', alignItems: 'center' }}>
<Text>正在缓存视频...</Text>
</View>
)}
</View>
);
};
export default App;⚠️ 关键注意事项
- 路径协议必须为 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 兼容性等常见陷阱。如需进一步优化体验,可增加加载状态、错误重试、磁盘空间清理等逻辑。


















