You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用NetInfo与react-query时离线音视频无法播放的问题求助

问题分析

当前核心矛盾是:联网时需强制刷新作业数据,但刷新后的音视频URL可能是临时/签名型URL,离线时无法访问;而不刷新时,旧的缓存URL可被expo-av正常缓存播放。本质是网络URL的时效性与离线播放需求不匹配,同时react-query刷新会覆盖缓存中的可用URL。

解决思路与修改方案

方案一:本地缓存音视频文件(推荐,彻底解决问题)

将音视频文件下载到本地存储,播放时使用本地路径,彻底脱离对网络URL的依赖。

修改步骤:

  1. 安装依赖:
expo install expo-file-system
  1. 在自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 03:35:22