使用NetInfo与react-query时离线音视频无法播放的问题求助
问题分析
当前核心矛盾是:联网时需强制刷新作业数据,但刷新后的音视频URL可能是临时/签名型URL,离线时无法访问;而不刷新时,旧的缓存URL可被expo-av正常缓存播放。本质是网络URL的时效性与离线播放需求不匹配,同时react-query刷新会覆盖缓存中的可用URL。
解决思路与修改方案
方案一:本地缓存音视频文件(推荐,彻底解决问题)
将音视频文件下载到本地存储,播放时使用本地路径,彻底脱离对网络URL的依赖。
修改步骤:
- 安装依赖:
expo install expo-file-system
- 在自定义Hook中添加下载逻辑,替换网络URL为本地路径:
import * as FileSystem from 'expo-file-system'; import { useQueryClient } from 'react-query'; export const useStudentAssignment = (assignmentId: Assignment['id']): UseQueryResult<Assignment, Error> => { const listKey = studentAssignmentKeys.list({ assignedToIdEq: studentData?.id }); const queryClient = useQueryClient(); // 下载音视频到本地的工具函数 const downloadMedia = async (url: string, uniqueName: string) => { const localDir = FileSystem.documentDirectory; const localPath = `${localDir}${uniqueName}`; const fileInfo = await FileSystem.getInfoAsync(localPath); // 仅当文件不存在时才下载 if (!fileInfo.exists) { await FileSystem.downloadAsync(url, localPath); } return localPath; }; const data = useQuery<Assignment, Error>( studentAssignmentKeys.detail(assignmentId), async () => { const { data: assignment } = await SystemAPI.fetchAssignment(assignmentId); // 处理每个问题的音视频,替换为本地路径 const processedQuestions = await Promise.all( assignment.questions.map(async (question) => { const updatedQuestion = { ...question }; if (question.videoUrl) { updatedQuestion.videoUrl = await downloadMedia( question.videoUrl, `video-${assignmentId}-${question.id}.mp4` ); } if (question.audioUrl) { updatedQuestion.audioUrl = await downloadMedia( question.audioUrl, `audio-${assignmentId}-${question.id}.mp3` ); } return updatedQuestion; }) ); return Assignment.deserialize({ ...assignment, questions: processedQuestions, }); }, { staleTime: 1000 * 60 * 30, initialData: () => { const cache = queryClient.getQueryData<Assignment[]>(listKey); return cache?.find((assignment) => assignment.id === assignmentId); }, initialDataUpdatedAt: queryClient.getQueryState(listKey)?.dataUpdatedAt, } ); // 修复useFocusEffect的依赖问题,避免不必要的重复执行 useFocusEffect( useCallback(() => { NetInfo.fetch().then((state) => { if (state.isConnected) { data.refetch(); } }); }, [data.refetch, assignmentId]) ); return data; };
优势:
- 彻底解决离线播放问题,本地文件不受网络状态影响
- 联网刷新时会自动下载最新的音视频文件(URL变化时触发重新下载)
- 不影响其他业务数据的更新逻辑
方案二:保留旧的可离线访问URL(临时妥协方案)
如果暂时无法实现本地下载,可通过react-query的select函数,在刷新时保留旧的音视频URL,仅更新其他业务数据。
修改步骤:
在useStudentAssignment的useQuery配置中添加select函数:
const data = useQuery<Assignment, Error>( studentAssignmentKeys.detail(assignmentId), async () => { const { data: assignment } = await SystemAPI.fetchAssignment(assignmentId); return Assignment.deserialize({ ...assignment }); }, { staleTime: 1000 * 60 * 30, initialData: () => { const cache = queryClient.getQueryData<Assignment[]>(listKey); return cache?.find((assignment) => assignment.id === assignmentId); }, initialDataUpdatedAt: queryClient.getQueryState(listKey)?.dataUpdatedAt, // 合并新旧数据,保留旧的音视频URL select: (newData) => { const oldData = queryClient.getQueryData<Assignment>(studentAssignmentKeys.detail(assignmentId)); if (!oldData) return newData; return { ...newData, questions: newData.questions.map((newQ) => { const oldQ = oldData.questions.find(q => q.id === newQ.id); return oldQ ? { ...newQ, videoUrl: oldQ.videoUrl, audioUrl: oldQ.audioUrl } : newQ; }), }; }, } );
优势:
- 代码改动小,快速修复离线播放问题
- 不影响其他业务数据的更新
劣势:
- 无法获取最新的音视频内容(如果音视频有更新,用户会继续播放旧版本)
方案三:优化网络检测与刷新逻辑
原代码中useFocusEffect的依赖为[data],而data是useQuery返回的对象,每次渲染都会重新创建,导致多次执行网络检测。修改依赖为[data.refetch, assignmentId],避免不必要的重复刷新:
useFocusEffect( useCallback(() => { NetInfo.fetch().then((state) => { if (state.isConnected) { data.refetch(); } }); }, [data.refetch, assignmentId]) // 修改依赖为稳定的refetch函数和assignmentId );
这是辅助优化项,需配合上述方案一起使用,单独使用无法解决核心问题。
内容的提问来源于stack exchange,提问作者SJaka
相关产品推荐
相关产品推荐

