김서버의 프론트엔드 일기
MediaRecorder와 FileSystem으로 실시간 녹화 저장 프로그램 만들기 본문
개요
최근에 웹 컨퍼런스 중 구글의 최홍찬 개발자님의 web의 audio api 연사를 듣고 나서 media 관련으로 간단하게 서비스를 만들어 보자는 생각으로 시작이 되었었습니다.
구현할 기능
- 카메라에 찍히는 자신을 녹화하면서 실시간 자신의 모습을 확인하기
- 면접 질문을 랜덤으로 뽑아서 하나씩 보여주고, 문제를 넘길 때마다, 기록해서 vtt 파일로 저장하기
- vtt파일과 녹화한 영상을 가지고 자막과 함께 자신의 녹화된 모습을 보면서 피드백할 수 있게 하기
사용한 주요 API
- getUserMedia: 사용자의 카메라와 마이크의 입력을 받고, 그 입력값을 stream 방식으로 실시간 입력을 받을 수 있습니다.
- MediaRecorder: js native객체로 stream에서 발행하는 데이터를 Blob 파일로 저장할 수 있도록 도와주는 객체입니다.
- showSaveFilePicker: 사용자 PC에 파일을 저장 할 수 있도록 도와주는 함수입니다.
구현
기능 구현의 순서는 다음과 같은 순서로 진행했습니다
- 사용자의 카메라와 마이크의 입력받는다.
- 그 입력받은 객체를 video tag의 srcObj에 mediaStream object를 넣어서 바로 화면에 보일 수 있게 한다.
- 질문을 랜덤으로 뽑아서 대기하고, vtt 파일을 만들 수 있는 객체를 준비한다.
- 녹화 시작 버튼을 누르면 MediaRecorder 객체를 생성하고, showSaveFilePicker를 열어서 파일을 저장시키는 위치를 지정한다.
- 질문을 하나씩 넘기면서 "현재시간"에서 "녹화 시작시간"을 이용해서 질문을 넘긴 시간을 메모한다.
- 녹화가 종료되면 파일을 정말 저장하고, 메모한 질문을 vtt파일로 다운로드한다.
UI 라이브러리는 리액트를 사용했고, 위의 내용들을 customHook에 구현을 했습니다.
const initialVideoStream = useCallback(
async (mediaTrackConstraints?: MediaTrackConstraints) => {
if (videoStream) return;
const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: mediaTrackConstraints });
setVideoStream(stream);
},
[videoStream],
);
사용자에게 카메라와 마이크의 입력을 받을 수 있게 하는 스트림 초기화 함수입니다.
useEffect(() => {
if (videoRef.current && videoStream) {
videoRef.current.srcObject = videoStream;
}
}, [videoStream]);
videoStream에 상태가 세팅이 되면 videoRef의 srcObject를 설정해서 실시간으로 비디오에 보일 수 있도록 합니다.
const recodingVideo = useCallback(async () => {
if (!videoStream) return;
const handles = await (window as any).showSaveFilePicker({
types: [
{
description: "Text file",
accept: { "video/webm": [".webm"] },
},
],
});
const createWritable = await handles.createWritable();
const rec = new (window as any).MediaRecorder(videoStream, {
audioBitsPerSecond: 128000,
videoBitsPerSecond: 2500000,
mimeType: "video/webm;codecs=vp9",
});
setRecording(rec);
setRecordStartTime(new Date());
rec.ondataavailable = (e: any) => {
createWritable.write(e.data);
};
rec.onstop = () => {
createWritable.close();
setRecording(undefined);
};
rec.start(10);
}, [videoStream]);
showSaveFilePicker에서 CreateWritable 스트림 객체를 이용해서 실시간으로 파일을 쓸 수 있게 하고, mediaRecorder를 videoStream과 연결해서 mediaRecorder에 데이터가 발행이 될 때, 그 데이터를 createWriteable 스트림 객체에도 데이터를 전달해서, 실시간 영상 파일을 저장할 수 있게 합니다.
하지만 이렇게 전달된 파일은 완전 저장 형태가 아니고 편집 상태이기 때문에, createWritable을 닫아주지 않으면 저장이 되지 않습니다. 그러므로 MediaRecorder의 녹화가 종료되는 이벤트를 받으면 createWritable을 닫아주는 것으로 파일을 최종 저장을 완료하도록 작성합니다.
class VTT {
fileContents: string;
contentText?: string;
constructor() {
this.fileContents = "WEBVTT\n\n";
}
resetFile() {
this.fileContents = "WEBVTT\n\n";
this.contentText = undefined;
}
startCheckTimerContentTmpSave(startTime: string, content: string) {
this.fileContents += `${startTime} --> `;
this.contentText = content;
}
finishCheckTimer(endTime: string) {
if (this.contentText === undefined) return;
this.fileContents += `${endTime} \n ${this.contentText}\n\n`;
this.contentText = undefined;
}
download(filename: string) {
const file = new File([this.fileContents], `${filename}.vtt`, { type: "octet/stream" });
const url = URL.createObjectURL(file);
const a = document.createElement("a");
document.body.appendChild(a);
a.href = url;
a.download = `${filename}.vtt`;
a.click();
document.body.removeChild(a);
a.remove();
URL.revokeObjectURL(url);
this.resetFile();
}
}
export default VTT;
vtt 파일을 생성하고, 또 다운로드할 수 있도록 하는 객체로, UI에 변화가 없어서 클래스로 내부 상태를 저장하는 방식으로 작성했습니다.
vtt파일이란 Web Video Text Tracks File 웹 비디오의 text track을 위한 파일로 주로 자막을 생성할 때, 사용하는 파일입니다.
WEBVTT
00:01.000 --> 00:04.000
- Never drink liquid nitrogen.
00:05.000 --> 00:09.000
- It will perforate your stomach.
- You could die.
기본적으로 위와 같은 포맷을 하고 있습니다. 자막 생성할 때, 필요한 기본적으로 필요한 내용은 다음과 같습니다.
{자막 보여주기 시작할 영상의 시간} --> {자막 보여주기 끝나는 영상의 시간} #옵션
- 내용
VTT 클래스에서는 startCheckTimerContentTmpSave으로 시작 시간을 설정하고 finishCheckTimer으로 끝나는 시간과 contents를 세팅하게 됩니다.
결론 및 느낀 점
처음 시작에는 간단히 녹화 프로그램을 만들어 보자라는 것으로 시작해서 간단한 프로그램을 만들기로 처음 목표는 금방 달성할 수 있었습니다. 달성을 하고 나서 이 기능을 잘만 풀이하면 더 좋은 서비스가 될 것 같아서, 서버 없이 오직 클라이언트만을 가지고 만들어도 괜찮겠다 라는 생각이 들어서 이 결과물까지 만들게 되었습니다.
web에서 생각보다 재미있는 API들이 많고 계속해서 웹이 발전할 수 있는 가능성이 있다는 것이 정말 재미있었고, 공부한 만큼 더 좋은 서비스를 만들 수 있는 점이 정말 기대가 많이 되었습니다.
'공부하는거 > Web' 카테고리의 다른 글
WYSIWYG 에디터 개발 - 1 (Selection으로 폰트 적용하기) (0) | 2022.04.02 |
---|---|
Service Worker란? (with PWA) (0) | 2022.02.07 |
HTML Form 그리고 web-form-helper (0) | 2022.01.29 |