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