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),导致大视频体积暴增。
核心原因分析
- 安卓视频录制默认码率过高:大视频体积远超预期,超出网络请求处理阈值
- fetch API在安卓大文件上传的兼容性问题:手动设置
multipart/form-data头可能导致boundary错误,原生fetch对安卓本地大文件流处理不佳 - 网络超时:大文件上传耗时过长,触发系统或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,若优化后的视频超过此值需调整)
- 账户的带宽和存储配额是否充足
验证步骤
- 先测试降低码率后的短视频上传,确认安卓端可正常提交
- 测试80秒以上的优化后视频,验证上传稳定性
- 若仍有问题,切换到
FileSystem.uploadAsync方案,排查是否为fetch的兼容性问题
内容的提问来源于stack exchange,提问作者aryaman shodhan
相关产品推荐
相关产品推荐

