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

Expo/React Native安卓端大视频上传Cloudinary触发Network Request Failed

安卓端大视频上传Cloudinary失败的解决方案

问题核心

Expo/React Native应用中,iOS设备可正常上传任意大小视频到Cloudinary,但安卓设备上传80秒及以上视频时触发TypeError: Network Request Failed。已尝试后端中转上传(速度极慢)、Firebase分片上传、XmlHttpRequest等方案均未解决,同时发现安卓录制的短视频码率远高于iOS(2秒视频安卓码率11.6Mbps,iOS仅1.3Mbps),导致大视频体积暴增。

核心原因分析

  1. 安卓视频录制默认码率过高:大视频体积远超预期,超出网络请求处理阈值
  2. fetch API在安卓大文件上传的兼容性问题:手动设置multipart/form-data头可能导致boundary错误,原生fetch对安卓本地大文件流处理不佳
  3. 网络超时:大文件上传耗时过长,触发系统或Cloudinary的超时限制

针对性解决方案

1. 降低安卓视频录制码率,减小文件体积

安卓默认录制的视频码率极高,是大文件上传失败的根源之一。通过Expo的录制API强制设置码率和分辨率:

// 示例:使用Expo Camera录制视频时配置参数
import { Camera } from 'expo-camera';

const recordVideo = async () => {
  if (cameraRef.current) {
    const video = await cameraRef.current.recordAsync({
      videoBitrate: 2000000, // 设置为2Mbps,可根据需求调整
      quality: Camera.Constants.VideoQuality['480p'], // 降低分辨率进一步减小体积
    });
    // 处理录制后的视频uri
  }
};

// 或使用ImagePicker启动相机时配置
import * as ImagePicker from 'expo-image-picker';

const launchCamera = async () => {
  const result = await ImagePicker.launchCameraAsync({
    mediaTypes: ImagePicker.MediaTypeOptions.Videos,
    videoQuality: ImagePicker.VideoQuality.Medium,
    videoBitrate: 2000000, // 强制设置码率
  });
};

2. 修复Cloudinary上传的FormData配置

移除手动设置的Content-Type头,让fetch自动生成带正确boundary的请求头(安卓对boundary的处理要求严格):

export const submitModuleVideo = async (
  userID: string,
  moduleName: string,
  videoURL: string,
  attemptDuration: string,
  reportedDifficulty: "EASY" | "MEDIUM" | "DIFFICULT"
) => {
  const timeSubmitted = Date.now().toString();

  const submissionData = new FormData();
  submissionData.append("file", {
    name: `${userID}_${moduleName.split(" ").join("")}_${timeSubmitted}.mp4`,
    type: "video/mp4",
    uri: videoURL,
  } as any);
  submissionData.append("upload_preset", "<UPLOAD_PRESET>");
  submissionData.append("cloud_name", "<CLOUD_NAME>");

  // 移除手动设置的Content-Type,让fetch自动处理
  const cloudinaryResponse = await fetch(
    "http://api.cloudinary.com/v1_1/<CLOUD_NAME>/video/upload",
    {
      method: "POST",
      headers: {
        Accept: "application/json",
      },
      body: submissionData,
    }
  );

  if (!cloudinaryResponse.ok) {
    throw new Error(`Cloudinary upload failed: ${cloudinaryResponse.statusText}`);
  }

  const cloudinaryData = await cloudinaryResponse.json();
  const cloudinaryVideoUrl = cloudinaryData.secure_url;

  // 后续提交到自有后端的代码保持不变
  const response = await fetch(BASE_URL + "/submissions/create", {
    method: "POST",
    headers: {
      accept: "application/json",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      userID,
      moduleName,
      videoUrl: cloudinaryVideoUrl,
      timeSubmitted,
      attemptDuration,
      reportedDifficulty,
    }),
  });

  if (!response.ok) {
    throw new Error("Could not submit video metadata!");
  }

  const data = await response.json();
  return data;
};

3. 改用Expo FileSystem.uploadAsync处理大文件

Expo的FileSystem.uploadAsync专门针对本地大文件上传做了优化,兼容性远优于原生fetch,支持进度回调和更稳定的流处理:

import * as FileSystem from 'expo-file-system';

export const submitModuleVideo = async (
  userID: string,
  moduleName: string,
  videoURL: string,
  attemptDuration: string,
  reportedDifficulty: "EASY" | "MEDIUM" | "DIFFICULT"
) => {
  const timeSubmitted = Date.now().toString();
  const fileName = `${userID}_${moduleName.split(" ").join("")}_${timeSubmitted}.mp4`;

  // 使用FileSystem上传视频到Cloudinary
  const uploadResult = await FileSystem.uploadAsync(
    "https://api.cloudinary.com/v1_1/<CLOUD_NAME>/video/upload",
    videoURL,
    {
      httpMethod: 'POST',
      uploadType: FileSystem.FileSystemUploadType.MULTIPART,
      fieldName: 'file',
      parameters: {
        upload_preset: "<UPLOAD_PRESET>",
        cloud_name: "<CLOUD_NAME>",
        filename: fileName,
      },
      // 可选:添加进度回调
      onProgress: (progress) => {
        console.log(`Upload progress: ${progress.totalBytesSent / progress.totalBytesExpectedToSend * 100}%`);
      },
    }
  );

  if (!uploadResult.success) {
    throw new Error(uploadResult.error?.message || 'Cloudinary upload failed');
  }

  const cloudinaryData = JSON.parse(uploadResult.body);
  const cloudinaryVideoUrl = cloudinaryData.secure_url;

  // 提交元数据到自有后端
  const response = await fetch(BASE_URL + "/submissions/create", {
    method: "POST",
    headers: {
      accept: "application/json",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      userID,
      moduleName,
      videoUrl: cloudinaryVideoUrl,
      timeSubmitted,
      attemptDuration,
      reportedDifficulty,
    }),
  });

  if (!response.ok) {
    throw new Error("Could not submit video metadata!");
  }

  const data = await response.json();
  return data;
};

4. 检查Cloudinary上传限制

登录Cloudinary控制台,确认:

  • 上传预设(Upload Preset)允许的最大文件大小(默认100MB,若优化后的视频超过此值需调整)
  • 账户的带宽和存储配额是否充足

验证步骤

  1. 先测试降低码率后的短视频上传,确认安卓端可正常提交
  2. 测试80秒以上的优化后视频,验证上传稳定性
  3. 若仍有问题,切换到FileSystem.uploadAsync方案,排查是否为fetch的兼容性问题

内容的提问来源于stack exchange,提问作者aryaman shodhan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 12:24:53