我和我的团队正在开发一个 expo/react-native 应用程序,其中包括实习生类型用户录制他们执行培训模块的视频,以及导师类型用户提供对这些视频的反馈。提交后,我想将视频上传到 Cloudinary 云。每当我使用 iOS 设备将视频上传到云端时,效果似乎都很好。然而,对于 Android,我观察到较大视频(大约 80 秒或更长)的 TypeError: Network Request Failed 情况。这是使用 fetch API 的客户端代码:
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>");
const cloudinaryVideoUrl = await fetch(
"http://api.cloudinary.com/v1_1/<CLOUD_NAME>/video/upload",
{
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "multipart/form-data",
},
body: submissionData,
}
)
.then((cloudinaryResponse) => cloudinaryResponse.json())
.then((cloudinaryData) => cloudinaryData.secure_url)
.catch((error) => console.log(error.message));
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) {
console.log("Could not submit video!");
}
const data = await response.json();
return data;
};
这是调用此函数的组件:
import React, { useState } from "react";
import { Text, StyleSheet } from "react-native";
import AppButton from "../ui/AppButton";
import Colors from "../../constants/colors";
import Fonts from "../../constants/fonts";
import { submitModuleVideo } from "../../util/submission";
import { StackActions, useNavigation } from "@react-navigation/native";
import { postReviewRequest } from "../../util/review-request";
import { createNotification } from "../../util/notification";
import { useAppSelector } from "../../store/redux/hooks";
type ModuleSubmitButtonProps = {
video: { uri?: string };
attemptDuration: number;
difficulty: "EASY" | "MEDIUM" | "DIFFICULT";
isDisabled: boolean;
};
const ModuleSubmitButton: React.FC<ModuleSubmitButtonProps> = ({
video,
attemptDuration,
difficulty,
isDisabled,
}) => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const userID = useAppSelector((state) => state.auth.userID!);
const userName = useAppSelector((state) => state.user.name!);
const userMentors = useAppSelector((state) => state.user.mentors!);
const module = useAppSelector(
(state) => state.module.activeFundamentalModule!
);
const navigation = useNavigation();
const moduleName = module.name;
const videoURL = video.uri!;
const submitVideoHandler = async () => {
try {
setIsLoading(true);
const submissionID = (
await submitModuleVideo(
userID,
moduleName,
videoURL,
attemptDuration.toString(),
difficulty
)
).data.submission.submissionID;
userMentors.forEach(async (mentor) => {
await postReviewRequest(submissionID, mentor.userID);
await createNotification(
mentor.userID,
submissionID,
`A new ${moduleName} submission from ${userName} is awaiting your review. You may access it from your review dashboard. Thank you for your continued support in mentoring our trainees.`
);
});
navigation.dispatch(StackActions.popToTop());
setIsLoading(false);
} catch (error) {
console.log(error);
}
};
return (
<AppButton
style={
isLoading || isDisabled
? [styles.submitButton, styles.disableButton]
: styles.submitButton
}
onPress={submitVideoHandler}
disabled={isLoading || isDisabled}
>
<Text style={styles.submitButtonText}>
{isLoading ? "Submitting..." : "Submit"}
</Text>
</AppButton>
);
};
export default ModuleSubmitButton;
const styles = StyleSheet.create({
submitButton: {
borderRadius: 20,
padding: 15,
margin: 10,
marginTop: 0,
borderWidth: 1,
justifyContent: "center",
alignItems: "center",
borderColor: Colors.green100,
backgroundColor: Colors.green100,
},
disableButton: {
borderColor: Colors.primary200,
backgroundColor: Colors.primary200,
},
submitButtonText: {
fontFamily: Fonts.family.medium,
fontSize: Fonts.size.size20,
color: Colors.accent0,
},
});
错误消息的控制台日志附在本文中。
我尝试过的其他事情:
再次强调,这些问题仅适用于 Android。不适用于 iOS。
我观察到的另一个有趣的事情是:对于在 Android 和 iOS 上录制的短视频(均长约 2 秒),视频的比特率和大小截然不同。
安卓: 比特率:11602168 字节:3194077 持续时间:2.2024
iOS: 比特率:1296616 字节:324154 持续时间:2
对此的任何帮助将不胜感激。非常感谢!
在 Cloudinary 中,转录过程是一个漫长的过程,可能需要很长时间,具体取决于视频的大小和您的帐户限制。
通常系统的错误是420-RateLimited,你可以尝试使用分块上传,或者使用Android SDK(更好):https://cloudinary.com/documentation/android_integration
如果我在这里说的没有帮助,我建议开一个新的私人支持票:https://support.cloudinary.com/hc/en-us/requests/new