在博览会应用程序中将视频文件上传到云端会出现 Android 网络请求失败错误

问题描述 投票:0回答:1

我和我的团队正在开发一个 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,
  },
});

错误消息的控制台日志附在本文中。

我尝试过的其他事情:

  1. 后端的 Multer 获取缓冲文件,然后将其上传到谷歌云存储(不是 cloudinary);这适用于 Android 上的任何大小的视频,但速度非常慢 -> 上传 3 分钟的视频大约需要 20 分钟。
  2. 在本地 URL 上使用 fetch API 获取 blob 文件后,使用 Firebase 和 uploadBytesResumable 方法。这再次导致网络请求失败错误。
  3. 我尝试使用 XmlHttpRequest 而不是 fetch API,但这也给出了相同的错误;打印状态码是0,没有responseText。

再次强调,这些问题仅适用于 Android。不适用于 iOS。

我观察到的另一个有趣的事情是:对于在 Android 和 iOS 上录制的短视频(均长约 2 秒),视频的比特率和大小截然不同。

安卓: 比特率:11602168 字节:3194077 持续时间:2.2024

iOS: 比特率:1296616 字节:324154 持续时间:2

对此的任何帮助将不胜感激。非常感谢!

typescript react-native file-upload expo cloudinary
1个回答
0
投票

在 Cloudinary 中,转录过程是一个漫长的过程,可能需要很长时间,具体取决于视频的大小和您的帐户限制。

通常系统的错误是420-RateLimited,你可以尝试使用分块上传,或者使用Android SDK(更好):https://cloudinary.com/documentation/android_integration

如果我在这里说的没有帮助,我建议开一个新的私人支持票:https://support.cloudinary.com/hc/en-us/requests/new

© www.soinside.com 2019 - 2024. All rights reserved.