React + Media Streams API๋ฅผ ํตํ Web Recorder ๊ธฐ๋ฅ ๊ตฌํ
ํด๋น ๋์์ธ์์๋ ํ์ธํ ์ ์๋ฏ, ์ฐ๋ฆฌ ์๋น์ค์์ ํ๋ฉด์ก์ถ์ ๋น๋กฏํด์ ๋ นํ๊ธฐ๋ฅ์ ๊ฐ์ฅ ํ์์ ์ด๋ผ๊ณ ํ ์ ์๋ค. ์ฌ๊ธฐ์ ์คํจํ๋ค๋ฉด, ์ฌ์ค ๊ทธ ์ด๋ค ๊ธฐ๋ฅ๋ ์ํํ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค. ๊ทธ ๋๋ฌธ์ ์ข ๋ ์ฌํ์ ๊ธฐ์ธ์ฌ ์์ ์ ์งํํ์๋ค.
๋ นํ ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ ์ํด์ ์ฌ์ด ๋ฐฉ๋ฒ์ ํด๋น ๊ธฐ๋ฅ์ ์ง์ํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฐพ์์ ์ฌ์ฉํ๋๊ฒ์ด์๋ค.
ํ์ง๋ง ๋น์ฐํ๊ฒ๋, ์ฐ๋ฆฌ๋ ์์ง ๋ฐฐ์ฐ๋ ์ ์ฅ์ด๊ณ , ํด๋น ํ์ด์ง์ ๊ธฐ๋ฅ์ ํ๋์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ข ์๋๊ธฐ ๋ณด๋ค๋ ์ฐ๋ฆฌ๊ฐ ๋์์์ด ์์ ํ๊ณ ์ฌ์ฉํ ์ ์๋๋ก ์ ์ง๋ณด์๊ฐ ์ฉ์ดํด์ผํ๋ค๊ณ ์๊ฐํ๋ค. ๊ทธ์ ๋ฐ๋ผ ์๊ฐ์ด ๋ค์ ๊ฑธ๋ฆฌ๋๋ผ๋ ์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ ์ ์๋๋ก, ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ด Web API์ ์ง์๋ง์ผ๋ก ํด๊ฒฐํ๊ณ ์ ํ๋ค.
์๋๋ถํฐ๋ ๋ด๊ฐ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์ ์งํํ ์๊ฐ์ ํ๋ฆ์ ๋ด๊ณ ์๋ค.
- ๊ฑฐ์ธ์ ๊ธฐ๋ฅ์ ์ํํ MediaStream๊ณผ ๋ นํ๋ฅผ ์งํํ MediaRecord ๋ฅผ ๋๊ฐ์ง๋ฅผ ๋ณ๋ ฌ์ ์ผ๋ก ์ฌ์ฉ
- ๋ด๋ถ์ stream์ ๊ด๋ฆฌํ stream state์, record์ฌ๋ถ๋ฅผ ํ๋จํ๋ booleanํ์ ์ state ๋ ๊ฐ์ง๋ฅผ ์ฌ์ฉ
์ฐ์ ๊ฐ๋ฐ์ ์งํํ๊ธฐ ์ , ํด๋น ํ ์คํฌ๋ฅผ ์งํํ๊ธฐ์ ์ด๋ค ๊ธฐ๋ฅ์ด ํ์ํ์ง์ ๋ํด์ ๋ ํผ์ ๋ฆฌ์คํธ์ ์ ํด๋ณด์๋ค,
- ๋ฉด์ ์๊ฐ ๋ณด์ฌ์ผ๋ง ํ๋ค. (media ๊ธฐ๋ฅ์ ์ฐ๊ฒฐํ๋ค.)
- ๋ นํ ์์ ํจ์๋ฅผ ํตํด์ ๋ นํ๋ฅผ ์์ํ ์ ์๋ค.
- ๋ นํ ์ข ๋ฃ ํจ์๋ฅผ ํตํด์ ๋ นํ๋ฅผ ์ข ๋ฃํ ์ ์๋ค.
- ํด๋น ํ์ด์ง์์ ์ด๋ํ์ฌ ๋ค๋ฅธ ํ์ด์ง๋ก ์ด๋ํ ๋, ๋ นํ, ๋ น์ ๋ฑ์ ๊ธฐ๋ฅ์ด ์ค์ง ๋์ด์ผํ๋ค.
์ฐ์ ํ์ํ ๊ธฐ๋ฅ์ ๋ํด์ ์ ํ์ผ๋ ํ๋ํ๋ ํด๊ฒฐํด๋ณด๋ฉฐ ์ดํดํด๋ณด๊ฒ ์ต๋๋ค.
Media Capture and Streams API๋ ์น ๋ธ๋ผ์ฐ์ ์์ ์ค๋์ค ๋ฐ ๋น๋์ค ๋ฏธ๋์ด๋ฅผ ์บก์ฒํ๊ณ , ์กฐ์ํ๋ฉฐ, ์ ์กํ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ๊ฐ๋ ฅํ API์ ๋๋ค. ์ด API๋ ์ผ๋ฐ์ ์ผ๋ก "getUserMedia", "MediaStream", "MediaStreamTrack" ๋ฑ์ JavaScript ์ธํฐํ์ด์ค๋ก ๊ตฌ์ฑ๋์ด ์์ต๋๋ค. ์ฃผ์ ๊ธฐ๋ฅ๊ณผ ์ฌ์ฉ ๋ฐฉ๋ฒ์ ๋ํด ์ค๋ช ํ๊ฒ ์ต๋๋ค.
navigator.mediaDevices.getUserMedia ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ์ฉ์์ ์นด๋ฉ๋ผ์ ๋ง์ดํฌ ๊ฐ์ ๋ฏธ๋์ด ์ ๋ ฅ ์ฅ์น์ ์ ๊ทผํ ์ ์์ต๋๋ค. ์ด ๋ฉ์๋๋ MediaStream ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ฉฐ, ์ด ๊ฐ์ฒด๋ ์ค๋์ค์ ๋น๋์ค ํธ๋์ ํฌํจํฉ๋๋ค. ์ฌ์ฉ์์ ๋ช ์์ ์ธ ํ๊ฐ๊ฐ ํ์ํ๊ธฐ ๋๋ฌธ์, ์ด API๋ฅผ ์ฌ์ฉํ ๋๋ ๋ณด์ ๋ฌธ์ ์ ์ฌ์ฉ์ ๊ฐ์ธ์ ๋ณด ๋ณดํธ๋ฅผ ๊ณ ๋ คํด์ผ ํฉ๋๋ค.
MediaStream ๊ฐ์ฒด๋ ํ๋ ์ด์์ MediaStreamTrack(์ค๋์ค ๋๋ ๋น๋์ค ํธ๋)์ ํฌํจํ ์ ์์ต๋๋ค.
์ด ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํด ์ค๋์ค ๋ฐ ๋น๋์ค ์คํธ๋ฆผ์ HTML์ <audio>
๋ <video>
์์์ ์ฐ๊ฒฐํ๊ฑฐ๋, ์นRTC(Web Real-Time Communications)๋ฅผ ํตํด ๋คํธ์ํฌ๋ก ์ ์กํ ์ ์์ต๋๋ค.
MediaStreamTrack:
MediaStream์ ๊ฐ ํธ๋์ MediaStreamTrack ๊ฐ์ฒด๋ก ํํ๋ฉ๋๋ค. ์ด๋ฅผ ํตํด ๊ฐ๋ณ ์ค๋์ค๋ ๋น๋์ค ํธ๋์ ์์ฑ์ ์กฐ์ ํ๊ฑฐ๋, ํธ๋์ ํ์ฑํ ๋๋ ๋นํ์ฑํํ ์ ์์ต๋๋ค.
getUserMedia() ๋ฉ์๋๋ MediaDevices ์ธํฐํ์ด์ค์ ์ผ๋ถ๋ก, ์ฌ์ฉ์์ ์นด๋ฉ๋ผ, ๋ง์ดํฌ ๋ฑ๊ณผ ๊ฐ์ ๋ฏธ๋์ด ์ ๋ ฅ ์ฅ์น์ ์ ๊ทผํ ์ ์๋ ๋ฐฉ๋ฒ์ ์ ๊ณตํฉ๋๋ค. ์ด ๋ฉ์๋๋ฅผ ํตํด ์ค์๊ฐ์ผ๋ก ์ค๋์ค์ ๋น๋์ค ์คํธ๋ฆผ์ ์บก์ฒํ ์ ์์ต๋๋ค.
์์ฒญ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
navigator.mediaDevices.getUserMedia() ํจ์๋ฅผ ํธ์ถํ์ฌ ๋ฏธ๋์ด ์ ๋ ฅ ์ฅ์น์ ์ ๊ทผ์ ์์ฒญํฉ๋๋ค. ์ด ํจ์๋ constraints ๊ฐ์ฒด๋ฅผ ๋งค๊ฐ๋ณ์๋ก ๋ฐ์ผ๋ฉฐ, ์ด ๊ฐ์ฒด๋ฅผ ํตํด ํ์ํ ๋ฏธ๋์ด ์ ํ(์ค๋์ค, ๋น๋์ค) ๋ฐ ๊ธฐํ ์ค์ ์ ์ง์ ํ ์ ์์ต๋๋ค.
async function getMedia() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true,
});
// ๋ฏธ๋์ด ์คํธ๋ฆผ ์ฌ์ฉ
// ์: ๋น๋์ค ์์์ ์คํธ๋ฆผ ์ฐ๊ฒฐ
// ์ธ์๋ก ๋ค์ด๊ฐ๋ ๊ฐ์ด constraint(์กฐ๊ฑด)์ด ๋ฉ๋๋ค.
document.querySelector("video").srcObject = stream;
} catch (error) {
console.error("๋ฏธ๋์ด ์ ๊ทผ์ ์คํจํ์ต๋๋ค.", error);
}
}
getMedia();
์ด๋ ๋น๋๊ธฐ๋ก ๋์ํ๋ navigator.mediaDevices.getUserMedia(constraints)๋ฅผ ํตํด์ stream์ ๋ฐ์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ด ๊ณผ์ ์ try catch๋ก ์ค๋ฅ๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค.
๊ทธ ์ดํ ์ ์ธํ video ํ๊ทธ๋ฅผ ํตํด์ stream ์ ์ก์ถํ ์ ์๋๋ก ์ฐ๊ฒฐํฉ๋๋ค. ํด๋น ๊ณผ์ ์ ref๋ก ๋์ฒดํ์ฌ ์งํํ ์ ์์ต๋๋ค.
๊ทธ๋์ ์ ๋ ๋ค์๊ณผ ๊ฐ์ด getMedia ํจ์๋ฅผ ์์ฑํ ์ ์์์ต๋๋ค.
const getMedia = async () => {
try {
const constraints = {
audio: {
echoCancellation: { exact: true },
},
video: {
width: 1280,
height: 720,
},
};
const mediaStream = await navigator.mediaDevices.getUserMedia(constraints);
setStream(mediaStream); // ํด๋น stream์ ์๋์ recorder์์๋ ๋์ผํ๊ฒ ์ฌ์ฉ๋ฉ๋๋ค.
if (mirrorVideoRef.current) {
// ํ์ฌ ํ์ด์ง์์ ๊ฑฐ์ธ ์ญํ ์ ํ video ํ๊ทธ์
๋๋ค.
mirrorVideoRef.current.srcObject = mediaStream;
}
} catch (e) {
console.log(`ํ์ฌ ๋ง์ดํฌ์ ์นด๋ฉ๋ผ๊ฐ ์ฐ๊ฒฐ๋์ง ์์์ต๋๋ค`);
}
};
๋ค์๊ณผ ๊ฐ์ด mediaStream์ ์ฌ์ฉํ ์ ์์์ต๋๋ค.
์ ๊ฐ ์ ์ํ 5๊ฐ์ ์ํ์ ์ฐธ์กฐ๋ฅผ ์ค์ฌ์ผ๋ก ์ค๋ช ํด๋ณด๊ฒ ์ต๋๋ค.
// MediaStream์ ๋ฐ์์ ์ฐ๊ฒฐํจ, ๋ชจ๋ ref์ audio, video๋ฑ web Api๋ฅผ ์ฐ๊ฒฐํ๋ ์ญํ
// ํด๋น ๊ฐ์ด null์ด ๋๋ค๋ฉด ์ฐ๊ฒฐ์ด ์ข
๋ฃ๋์์์ ์๋ฏธ
const [stream, setStream] = useState<MediaStream | null>(null);
// ๋
นํ ์ฌ๋ถ๋ฅผ ํ์ธํ๋ state, ํด๋น ํ์ด์ง์์ ํญ๋๊ฒ ์ฌ์ฉ๋ ์์
const [recording, setRecording] = useState(false);
// ๋
นํ๋ ๊ฒฐ๊ณผ๋ฌผ์ด ์ ์ฅ๋ฉ๋๋ค. type์ Blob[] ์
๋๋ค.
const [recordedBlobs, setRecordedBlobs] = useState<Blob[]>([]);
// ํต์ฌ์ด ๋๋ ๋ด์ฉ์
๋๋ค. ์ค๋ก์ง "๊ฑฐ์ธ"์ญํ ๋ง์ ์ํํ๋ฉฐ, ๋ง์น ํ๋ฉด์ด ๋
นํ๋๋ ๋ฏํ UX๋ฅผ ์ ๊ณตํฉ๋๋ค.
const mirrorVideoRef = useRef<HTMLVideoElement>(null);
// dom๋ด๋ถ์์ ๋ณด์ฌ์ง์ง ์์ง๋ง, ๋ด๋ถ์์ "๋
นํ"์ ๋ํ ๊ธฐ๋ฅ๋ง์ ์ํํฉ๋๋ค.
const mediaRecorderRef = useRef<MediaRecorder | null>(null);
media ์ฐ๊ฒฐ์ ์์ด์ ๊ฐ์ฅ ์ค์ฌ์ด ๋๋ ์ญํ ์ ์ํํฉ๋๋ค.
์์์ ์ค๋ช ํ getUserMedia๋ฅผ ํตํด์ mediaStream ์ ์ค์ ํฉ๋๋ค. ํด๋น ๊ฐ์ด ์ค์ ๋์๋ค๋ ์๋ฏธ๋, ํด๋น ์ปดํฌ๋ํธ์ ์๋น์ค๋ ํ ์์ ๋ถํฐ web API๋ฅผ ํตํด ์ ์ ์ audio์ camera๋ฅผ ์ฐ๊ฒฐ๋ฐ์๋ค๋๊ฒ์ ์๋ฏธํฉ๋๋ค. ๋ง์ฝ ์ฌ๊ธฐ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ error๋ฅผ ๋ฐํํฉ๋๋ค.
๋ํ ๋ฐ๋ก ์๋ซ์ค์์ ๋ค์๊ณผ ๊ฐ์ด ์ฐจํ ์ค๋ช ํ mirrorVideo์๋ ํด๋น stream์ ์ฐ๊ฒฐํฉ๋๋ค. ์ด๋ฅผ ํตํด videoํ๊ทธ๋ฅผ ํตํด camera์ ์ ๋ ฅ๊ฐ์ด ์ก์ถ๋ฉ๋๋ค. ์๋๋ record๋ถ๋ถ์์ mediaRecorderRef๊ฐ ์ด๊ธฐํ ๋๋ ๋ถ๋ถ์ ๋๋ค. ์ด ๋ํ ๋ง์ฐฌ๊ฐ์ง๋ก mimeType์ ์ค์ ๋ฐ๊ธด ํ์ง๋ง, ์ด ์ญ์ ๋ง์ฐฌ๊ฐ์ง๋ก ๋์ผํ stream์ด ์ฐ๊ฒฐ๋๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
๋ค์์ ์ ๊ฐ ํ๋ก์ ํธ์์ ์ฌ์ฉํ useEffect๋ฅผ ํตํด์ stream ์ ์ค์ ํ ๋ฐฉ์์ ๋๋ค.
useEffect์ ์ฒซ ๋ฒ์งธ ์ธ์๋ ์คํํ side effect๋ฅผ ๋ด์ ํจ์์
๋๋ค. ์ด ์์ ์์๋ ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ๋์์ ๋, ์ฆ ์ฒ์ ๋ ๋๋ง๋ ๋ ์คํ๋ฉ๋๋ค.
if (!stream) { void getMedia(); }:
์ด ์กฐ๊ฑด๋ฌธ์ stream ์ํ๊ฐ null์ธ ๊ฒฝ์ฐ, ์ฆ ์์ง ๋ฏธ๋์ด ์คํธ๋ฆผ์ด ์ค์ ๋์ง ์์์ ๋ getMedia ํจ์๋ฅผ ํธ์ถํฉ๋๋ค. getMedia ํจ์๋ ๋ฏธ๋์ด ์ฅ์น์ ๋ํ ์ ๊ทผ์ ์์ฒญํ๊ณ , ์ฑ๊ณต์ ์ผ๋ก ์ ๊ทผ์ด ์ด๋ฃจ์ด์ง๋ฉด stream ์ํ๋ฅผ ์
๋ฐ์ดํธํฉ๋๋ค.
useEffect
๋ด์์ return ํจ์๋ ์ปดํฌ๋ํธ์ ์ธ๋ง์ดํธ ์์
์ ์ํํ๊ธฐ ์ํด ์ฌ์ฉ๋ฉ๋๋ค. ์ด ํจ์๋ ์ปดํฌ๋ํธ๊ฐ ์ธ๋ง์ดํธ๋ ๋ ์คํ๋ฉ๋๋ค. return () => { ... }
์ด ๋ก์ง์ ์ปดํฌ๋ํธ๊ฐ ์ธ๋ง์ดํธ๋๊ธฐ ์ ์ ์คํ๋๋ฉฐ, stream ์ํ์ ํ ๋น๋ ๋ฏธ๋์ด ์คํธ๋ฆผ์ ๊ฐ ํธ๋์ ์ค์ง(stop())ํฉ๋๋ค.
ํด๋น ํจ์๋ stream์ ๋์ด์ ์ฌ์ฉํ์ง ์์์ผ ํ ๋, stream ์ฐ๊ฒฐ์ ์ข
๋ฃํ๊ธฐ ์ํด ์ฌ์ฉ๋ฉ๋๋ค. ์ฆ ์ปดํฌ๋ํธ๊ฐ DOM์์ ์ ๊ฑฐ๋ ๋ useEffect ๋ด์ ์ ๋ฆฌ ํจ์๊ฐ ์คํ๋ฉ๋๋ค. ์ด๋ ๋ฏธ๋์ด ์คํธ๋ฆผ์ ํธ๋๋ค์ ์ค์ง์ํค๋ ์์
์ ํตํด ์์์ ํด์ ํ๊ณ ํ์ํ ๊ฒฝ์ฐ๊ฐ ์๋๋ผ๋ฉด ์นด๋ฉ๋ผ๋ฅผ ์ข
๋ฃํ์ฌ ์ข๋ ์ ๋ขฐ๊ฐ ๊ฐ๋ UX๋ฅผ ์ง์ํฉ๋๋ค.
์ ์ ๋ ์ธ์ ๋ ์ง ํ์ด์ง๋ฅผ ์ดํํ๊ฑฐ๋ ์ปดํฌ๋ํธ๊ฐ ์ธ๋ง์ดํธ ๋จ์ ์ ๋ํ๋ ์ด๋ค ํ๋์ด๋ผ๋ ์ทจํ๊ฒ ๋๋ค๋ฉด, ์นด๋ฉ๋ผ๋ฅผ ๋น๋กฏํ ๋ชจ๋ ๋ฏธ๋์ด ์คํธ๋ฆผ์ ์ ์งํ ์ ์์ต๋๋ค.
const [recording, setRecording] = useState(false);
ํ๋ก์ ํธ ๋ด๋ถ์์ record์ํ์ ๋ฐ๋ผ UI ๋ฐ ๊ธฐ๋ฅ์ด ๋ณ๊ฒฝ๋ฉ๋๋ค. ๋ฐ๋ผ์ ๊ฐ์ฅ ํ์๋ก ์ง์๋์ด์ผํ๋ ๊ธฐ๋ฅ์ผ๋ก ํ๋จํ๊ณ , ๋ค์๊ณผ ๊ฐ์ด startํ๋ ํจ์์ ๊ฒฝ์ฐ true๋ก ์ ์ธํ๊ฑฐ๋
๋ค์์ ํตํด์ false๋ก record ์ฌ๋ถ๋ฅผ ํ๋จํฉ๋๋ค.
const [recordedBlobs, setRecordedBlobs] = useState<Blob[]>([]);
Blob (Binary Large Object) ๊ฐ์ฒด๋ ๋ฐ์ด๋๋ฆฌ ๋ฐ์ดํฐ๋ฅผ ๋ํ๋ด๋ ๋ถ๋ณ(immutable) ๊ฐ์ฒด์ ๋๋ค. ์น ๊ฐ๋ฐ์์ Blob์ ์ฃผ๋ก ํ์ผ๊ณผ ๊ฐ์ ๋์ฉ๋์ ์์ ๋ฐ์ดํฐ(raw data)๋ฅผ ๋ค๋ฃจ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. Blob ๊ฐ์ฒด๋ ์ด๋ฏธ์ง, ์ฌ์ด๋ ํ์ผ, ๋น๋์ค ํ์ผ๊ณผ ๊ฐ์ ๋ฉํฐ๋ฏธ๋์ด ๋ฐ์ดํฐ ๋๋ ๋์ฉ๋ ํ ์คํธ ํ์ผ ๋ฑ์ ๋ํ๋ผ ์ ์์ต๋๋ค.
์๋์ ๋ นํ๋ฅผ ํต์ ํ ๋, ๋ค์์ setRecordedBlobs๋ฅผ ํตํด์ ๋ นํ๋ ์์์ ์ ์ฅํฉ๋๋ค.
์ ๋ ํด๋น ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด mirrorVideoRef
์ mediaRecorderRef
๋๊ฐ์ง๋ฅผ ์ ์ธํ์ต๋๋ค.
์ฒ์ mediaStream API์ ๋ํด์ ํ์ตํ ๋๋ ํ๋์ video tag๋ด๋ถ์์ ์ดฌ์๊ณผ ์ถ๋ ฅ, ๋
นํ๋ฅผ ๋ชจ๋ ์งํํ๋ ์ค ์์์ต๋๋ค๋ง, ํด๋น ๋ฐฉ์์ผ๋ก ์งํํ๊ฒ ๋๋ฉด, ๋
นํ์์ง์ด ๊นจ์ง๋ฉฐ
echo
๊ฐ ๋ฐ์ํ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค.
๊ทธ์ ๋ฐ๋ผ ์ ๋ ๋๊ฐ์ง mirrorVideoRef
์ mediaRecorderRef
dom ์์๋ฅผ ์ ์ธํจ์ผ๋ก ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ณ ์ ํ์ต๋๋ค.
์ฒ์ useRef๋ก ์ ์ธ๋ ๋ ๋ง์ฐฌ๊ฐ์ง๋ก ํด๋น ๋ณ์๋ <video>
ํ๊ทธ์ ๋ํ ์ฐธ์กฐ๋ฅผ ์์ฑํฉ๋๋ค. useRef<HTMLVideoElement>(null)
์ mirrorVideoRef
๊ฐ HTML
์ <video>
์์๋ฅผ ์ฐธ์กฐํ๋ ๋ฐ ์ฌ์ฉ๋ ์ ์์์ ๋ช
์ํฉ๋๋ค. ์ฌ๊ธฐ์ <HTMLVideoElement>
๋ TypeScript์ ํ์
์ฃผ์์ผ๋ก, ์ฐธ์กฐ๋๋ ์์๊ฐ <video>
ํ๊ทธ์์ ๋ํ๋
๋๋ค.
๋ํ ํจ์ getMedia()
๋ฅผ ํตํด์ ํธ์ถ๋์ด ์ฒ์์ผ๋ก stream ๊ฐ์ฒด๋ฅผ ๋ฐ์
mirrorVideoRef.current.srcObject = mediaStream;
๋ค์๊ณผ ๊ฐ์ด ์ด๊ธฐํ ๋ฉ๋๋ค. ํด๋น ๊ฐ์ ์ฐจํ ์๋ ๋ฐํ๊ฐ์ธ
video ํ๊ทธ ๋ด๋ถ์์ ref์ value๋ก ์ฌ์ฉ๋์ด ๊ฑฐ์ธ๋ก์ ๊ธฐ๋ฅํ๊ฒ ๋ฉ๋๋ค. ์๋์ ์์ฑ์ค ๋์ ๋๋๊ฒ์ 3๊ฐ์ง๊ฐ ์์ต๋๋ค.
์ฆ ์ฌ๊ธฐ์ mirrorVideoRef๋ ๊ฑฐ์ธ ์ญํ ์ ์ํํ๊ธฐ ์ํด ์ ์ธ๋์์ต๋๋ค
์์์ ์ ์ธํ mirrorVideoRef์๋ ๋ค๋ฅด๊ฒ mediaRecord ๊ธฐ๋ฅ์ ์ํด์ ์ ์ธ๋์์ต๋๋ค.
const mediaRecorderRef = (useRef < MediaRecorder) | (null > null);
ํด๋น ์ฝ๋๋ MediaRecorder ๊ฐ์ฒด์ ๋ํ ์ฐธ์กฐ๋ฅผ ์์ฑํฉ๋๋ค. MediaRecorder๋ ๋ฏธ๋์ด ์คํธ๋ฆผ์ ๋
นํํ๋ ๋ฐ ์ฌ์ฉ๋๋ ์น API์ ์ผ๋ถ์
๋๋ค.
useRef<MediaRecorder | null>(null)
๋ mediaRecorderRef๊ฐ MediaRecorder ๊ฐ์ฒด ๋๋ null์ ์ฐธ์กฐํ ์ ์์์ ๋ํ๋
๋๋ค. ์ด๊ธฐ ๊ฐ์ null์
๋๋ค.
์ด ref๋ ๋์ค์ MediaRecorder ๊ฐ์ฒด์ ์ธ์คํด์ค๋ก ์ค์ ๋ ์ ์์ผ๋ฉฐ, ์ด๋ฅผ ํตํด ๋
นํ ์ ์ด(์์, ์ค์ง ๋ฑ)์ ์ฌ์ฉ๋ ์ ์์ต๋๋ค.
์ฐจํ์ ์ ์ธํ ๋ นํ์์, ํน์ ๋ นํ ์ค์ง์ ๋ํ ํจ์์์ ์ฐธ์กฐ๋๊ธฐ ์ํด์ ์ฌ์ฉํฉ๋๋ค.
// ๋
นํํ๋ ํจ์
const handleStartRecording = () => {
setRecordedBlobs([]);
try {
mediaRecorderRef.current = new MediaRecorder(stream as MediaStream, {
mimeType: selectedMimeType,
});
mediaRecorderRef.current.ondataavailable = (event) => {
if (event.data && event.data.size > 0) {
setRecordedBlobs((prev) => [...prev, event.data]);
}
};
mediaRecorderRef.current.start();
setRecording(true);
} catch (e) {
console.log(`MediaRecorder error`);
}
};
ํด๋น ์ฝ๋๋ ๊ต์ฅํ ํฅ๋ฏธ๋กญ์ต๋๋ค. ์ฐ์ ์ด์ ์ ์ฌ์ฉํ stream state๊ฐ ์ฌ์ฉ๋ฉ๋๋ค. ๋ํ ondataavailable๋ MediaRecorder ์ธ์คํด์ค์์ ๋ฐ์ํ๋ ์ด๋ฒคํธ๋ก, ๋ นํ๋ ๋ฏธ๋์ด ๋ฐ์ดํฐ๊ฐ ์ฌ์ฉ ๊ฐ๋ฅํ ๋ ํธ๋ฆฌ๊ฑฐ๋ฉ๋๋ค. ๋ฐ๋ผ์ ํธ๋ฆฌ๊ฑฐ ๋๋ event์ ๋ง์ถ์ด setRecorderedBlob๋๋ฉฐ ๋ นํ๋ ๊ฒฐ๊ณผ๋ฅผ ์ ์ฅํฉ๋๋ค. (๋ นํ์ข ๋ฃ๋ฒํผ์ clickํ์๋ event๊ฐ ๋ฐ์ํฉ๋๋ค.)
์ฑ๊ณต์ ์ผ๋ก ๋ นํ๊ฐ ์ ์งํ๋จ์ ํ์ธํ ์ ์๋ค.
์ถ๊ฐ์ ์ผ๋ก ์งํํด์ผํ ํ ์คํฌ
ํด๋น ์์ ๊ณผ์ ์ ๋ํ ์์ธํ ์ฝ๋๋ฅผ ํ์ธํ๊ณ ์ถ๋ค๋ฉด ์ฌ๊ธฐ์ ํ์ธํ ์ ์์ต๋๋ค.