Here is a component that asks user for camera, previews video and then let's user watch it again with video controls (download, nav to specific moment, etc). The thing is the recorded video seems to have "lost" duration, meaning the <video>
doesn't know how long the video lasts. How to avoid this pitfall? Also if you have better ways to code this I'm opened for any change. Thanks!
export default function RecordVideo() {
const [state, setState] = createSignal<State>("idle");
const [blob, setBlob] = createSignal<Blob | null>(null);
const [count, setCount] = createSignal<number>(0);
const [message, setMessage] = createSignal<string>("start recording");
let preview: { srcObject: MediaStream };
let recorder: MediaRecorder;
let interval: NodeJS.Timer;
const startRecording = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true,
});
setState("recording");
preview.srcObject = stream;
recorder = new MediaRecorder(stream);
const chunks: Blob[] = [];
recorder.ondataavailable = ({ data }) => chunks.push(data);
recorder.onstop = () => {
setBlob(new Blob(chunks, { type: "video/mp4" }));
};
recorder.start();
interval = setInterval(() => setCount(prev => prev + 1), 1000);
} catch ({ message }: any) {
setMessage(message);
}
};
const stopRecording = () => {
preview.srcObject.getTracks().forEach(track => track.stop());
recorder.stop();
clearInterval(interval);
setState("stopped");
};
const deleteRecording = () => {
setBlob(null);
setCount(0);
setState("idle");
};
const downloadRecording = () =>
Object.assign(document.createElement("a"), {
href: URL.createObjectURL(blob()),
download: "a.mp4",
type: "video/mp4",
}).click();
onCleanup(() => {
clearInterval(interval);
preview.srcObject?.getTracks().forEach(track => track.stop());
});
return (
<section>
<Switch>
<Match when={state() === "idle"}>
<div onClick={startRecording}>
{message()}
</div>
</Match>
<Match when={state() === "recording"}>
<div>
<video
ref={preview}
autoplay
muted
/>
<div>
<span>{formatCount(count())}</span>
<button onClick={stopRecording}>
stop
</button>
</div>
</div>
</Match>
<Match when={state() === "stopped" && blob()}>
<div>
<video
src={URL.createObjectURL(blob())}
autoplay
controls
/>
<div>
<button onClick={deleteRecording}>delete</button>
<button onClick={downloadRecording}>
download
</button>
</div>
</div>
</Match>
</Switch>
</section>
);
}