React/Nextjs MediaRecording 无法使用 setTimeout 停止录制

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

我正在创建一个执行以下操作的网络应用程序:

  1. 获取相机权限
  2. 倒计时3秒后开始录音
  3. 按下停止录制按钮即可录制视频,或者需要停止录制 总计 15 秒后(为此添加逻辑会使应用程序失败)
  4. 应用程序完成录制后,它会上传到 firebasestorage,然后上传到椰子.io 以编码水印。

上面的效果很好,减去 15 秒后停止录制。 每当我将以下内容添加到 startRecording 函数时,它都会失败:

setTimeout(() => {
    stopRecording();
}, 15000);

这是每当我添加超时时给出的错误:

net::ERR_REQUEST_RANGE_NOT_SATISFIABLE

它使用与用户按下按钮相同的功能,所以老实说我不确定为什么它不起作用。

任何关于为什么会发生这种情况的帮助或关于如何在 15 秒后停止录制的想法将不胜感激!预先感谢

这是我的完整代码:

import { useState, useRef, useEffect } from "react";
import useUploadFile from "../hooks/useUploadFile";
import useStepStore from "../stores/stepStore";
import useLanguageStore from "../stores/languageStore";
import Timer from "../components/common/Timer";
import StepBanner from "../components/common/StepBanner";



const mimeType = 'video/webm; codecs="opus,vp8"';

const VideoMessage: React.FC = () => {

    const setStep = useStepStore((state) => state.setStep);

    const language = useLanguageStore((state) => state.language);

    const { uploadFile, downloadURL, uploading, progress, coconutJobId } = useUploadFile();

    const [permission, setPermission] = useState<boolean>(false);
    const [permissionClicked, setPermissionClicked] = useState<boolean>(false);

    const mediaRecorder = useRef<MediaRecorder | null>(null);

    const liveVideoFeed = useRef<HTMLVideoElement | null>(null);

    const [recordingStatus, setRecordingStatus] = useState<"inactive" | "recording">("inactive");

    const [stream, setStream] = useState<MediaStream | null>(null);

    const [recordedVideo, setRecordedVideo] = useState<string | null>(null);

    const [videoChunks, setVideoChunks] = useState<BlobPart[]>([]);

    const [loading, setLoading] = useState(false);

    const [countdown, setCountdown] = useState(0);







    const getCameraPermission = async () => {
        setRecordedVideo(null);

        if ("MediaRecorder" in window) {
            try {
                const videoConstraints = {
                    audio: false,
                    video: true,
                };
                const audioConstraints = { audio: true };

                const audioStream = await navigator.mediaDevices.getUserMedia(audioConstraints);
                const videoStream = await navigator.mediaDevices.getUserMedia(videoConstraints);

                setPermission(true);
                setPermissionClicked(true)

                const combinedStream = new MediaStream([
                    ...videoStream.getVideoTracks(),
                    ...audioStream.getAudioTracks(),
                ]);

                setStream(combinedStream);
                if (liveVideoFeed.current) {
                    liveVideoFeed.current.srcObject = videoStream;
                }
            } catch (err) {
                alert((err as Error).message);
            }
        } else {
            alert("The MediaRecorder API is not supported in your browser.");
        }
    };



    const handleStartRecording = () => {
        setCountdown(3);
        const countdownInterval = setInterval(() => {
            setCountdown(prevCountdown => {
                if (prevCountdown <= 1) {
                    clearInterval(countdownInterval);
                    startRecording(); // Start recording after the countdown
                    return 0;
                } else {
                    return prevCountdown - 1;
                }
            });
        }, 1000);
    };


    const startRecording = async () => {
        setRecordingStatus("recording");

        if (stream) {
            const media = new MediaRecorder(stream, { mimeType });

            mediaRecorder.current = media;

            let localVideoChunks: BlobPart[] = [];

            mediaRecorder.current.ondataavailable = (event) => {
                if (typeof event.data === "undefined") return;
                if (event.data.size === 0) return;
                localVideoChunks.push(event.data);
            };

            mediaRecorder.current.start();
            setVideoChunks(localVideoChunks);

            setTimeout(() => {
                stopRecording();
            }, 15000);
       
        }
        
    };

    const stopRecording = () => {
        setPermission(false);
        setRecordingStatus("inactive");

        if (mediaRecorder.current) {
            mediaRecorder.current.onstop = () => {
                const videoBlob = new Blob(videoChunks, { type: mimeType });
                const videoUrl = URL.createObjectURL(videoBlob);

                const file = new File([videoBlob], "video.webm", {
                    type: "video/webm",
                });

                console.log("file: ", file);

                setRecordedVideo(videoUrl);
                console.log("videoUrl: ", videoUrl);
                uploadFile(videoBlob);
                console.log({ loading, downloadURL, uploading, progress, coconutJobId });

                setVideoChunks([]);
            };
            mediaRecorder.current.stop();
        }
    };





    const getVideo = async () => {
        while (true) {
            try {
                const res = await fetch('/api/status', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({ coconutJobId: coconutJobId })
                })

                const data = await res.json();
                console.log("data: ", data);
                if (data.job.status === "job.completed") {
                    setLoading(false);
                    setStep(4);
                    break;
                }
            } catch (err) {
                console.error(err);

                break;
            }

            // Wait for 5 seconds before the next check
            await new Promise(resolve => setTimeout(resolve, 5000));
        }
    }

    getVideo();


    return (
        <div className="w-3/4 m-auto  flex flex-col justify-center align-center items-center">
            <StepBanner />
            <div className="w-full mb-5">
                <p className="text-left font-bold text-xl">{language === "en" ? "Record a video to send" : "Enregistrer une vidéo à envoyer"}</p>
            </div>
            <div className="indicator ">
                {/* {countdown > 0 ?  <span className="indicator-item indicator-top indicator-middle badge bg-[orange] text-xl"><Timer time={3}/></span> : ''} */}
                {/* {recordingStatus === "recording" ? <span className="indicator-item indicator-top indicator-middle badge bg-[orange] text-xl"><Timer time={15} /></span> : ""} */}

                <div className="video-player m-auto w-[300px] aspect-[4/5] bg-black relative">
                    {/* Positioning the Timer component */}
                    {countdown > 0 ? <div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 mt-[-100px] z-10 text-white font-bold text-[100px]"><Timer time={3} /></div> : ""}
                    {recordingStatus === "recording" &&
                    <div className="absolute top-[35%] left-[85%] transform -translate-x-1/2 -translate-y-1/2 mt-[-100px] z-10 text-white font-bold text-[40px] flex items-center space-x-2">
                        <div className="badge badge-primary badge-md bg-[red]"></div>
                        <Timer time={15} />
                    </div>
                    }

                    {!recordedVideo ? (
                        <video ref={liveVideoFeed} autoPlay muted loop className="live-player w-[300px] aspect-[4/5]" poster="/cards/2023/card_1.jpg"></video>
                    ) : null}

                    {recordedVideo ? (
                        <div className="recorded-player">
                            <video className="recorded aspect-[4/5] w-[300px]" src={recordedVideo} autoPlay muted loop></video>
                        </div>
                    ) : null}
                </div>
            </div>

            <div className="video-controls w-full mb-20">
                {!permission && !permissionClicked ? (
                    <button onClick={getCameraPermission} type="button" className="btn z-9999 w-full  bg-[#c41f3e] my-2 text-center rounded-xl h-10 align-middle text-white border-none">
                        {language === "en" ? "Allow Camera" : "Autoriser la caméra"}
                    </button>
                ) : null}
                {permission && recordingStatus === "inactive" ? (
                    <button onClick={handleStartRecording} type="button" className="btn z-9999 w-full  bg-[#c41f3e] my-2 text-center rounded-xl h-10 align-middle text-white border-none">
                        {language === "en" ? "Start Recording" : "Commencer l'enregistrement"}
                    </button>
                ) : null}
                {recordingStatus === "recording" ? (
                    <button onClick={stopRecording} type="button" className="btn z-9999 w-full  bg-[#c41f3e] my-2 text-center rounded-xl h-10 align-middle text-white border-none">
                        {language === "en" ? "Stop Recording" : "Arrêter l'enregistrement"}
                    </button>
                ) : null}
                {recordedVideo ? (
                    <button className="btn z-9999 w-full min-w-full bg-[#c41f3e] my-2 text-center rounded-xl h-10 align-middle text-white border-none" disabled><span className="loading loading-spinner loading-md"></span>
                    </button>) : null}
            </div>
        </div>
    );
};

export default VideoMessage;
javascript reactjs next.js mediarecorder
1个回答
0
投票

您是否尝试过正确检查 mimeType ?显然我的网络应用程序在我的 Windows 操作系统(视频/webm)上运行良好,但是当我在我的 Android 设备(chrome)上测试它时,它与你的错误相同(它甚至懒得去倒计时,它给出了立即向我发送错误消息)。我所做的就是让它循环遍历一系列 mimeType,然后选择与我们的设备兼容的类型(?)。这样做之后,我的代码又可以正常工作了。希望有帮助 :D

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