webm에서 mp4로 변환할거임.
왜냐하면 모든 기기들이 webm을 이해하지는 못하기 때문.
mp4는 모두 이해할 수 있음.
비디오의 썸네일도 추출할거임.
ffmpeg는 비디오나 오디오 같은, 어떤 종류의 미디어 파일들을 다룰 수가 있음.
비디오를 압축하거나 비디오 포맷을 변환해야 하거나 비디오에서 오디오를 추출하고 싶거나
비디오에서 스크린샷을 찍고 싶거나 등등 할 수 있음.
웹 어셈블리도 이용할 거임.
웹어셈블리는 개방형 표준. 기본적으로 웹사이트가 매우 빠른 코드를 실행 할 수 있게 해줌.
유튜브는 업로된 비디오를 그들의 비싼 서버에서 변환할 거임.
ffmpeg.wasm으로 사용자의 브라우저에서 비디오를 변환할 거임.
즉 사용자(컴퓨터)의 처리 능력을 사용할 거임.
npm install @ffmpeg/ffmpeg @ffmpeg/core
import { createFFmpeg, fetchFile } from '@ffmpeg/ffmpeg';
const actionBtn = document.getElementById('actionBtn');
const video = document.getElementById('preview');
let stream;
let recorder;
let videoFile;
const files = {
input: 'recording.webm',
output: 'output.mp4',
thumb: 'thumbnail.jpg',
};
const downloadFile = (fileUrl, fileName) => {
const a = document.createElement('a');
a.href = fileUrl;
// download는 url을 저장하게 해줌.
a.download = fileName;
document.body.appendChild(a);
a.click();
};
const handleDownload = async () => {
actionBtn.removeEventListener('click', handleDownload);
actionBtn.innerText = 'Transcoding...';
actionBtn.disabled = true;
const ffmpeg = createFFmpeg({
log: true,
corePath: '/static/ffmpeg-core.js',
});
// load를 await하는 이유는 사용자가 소프트웨어를 사용할 것이기 때문.
await ffmpeg.load();
// writeFile은 ffmpeg의 가상의 세계에 파일을 생성해줌.
ffmpeg.FS('writeFile', files.input, await fetchFile(videoFile));
// -i는 input, 인풋:recording.webm, -r은 rate, 60은 영상 초당 60프레임으로 인코딩, 아웃풋:output.mp4
await ffmpeg.run('-i', files.input, '-r', '60', files.output);
// -ss는 영상의 특정 시간대로 갈 수 있게 해줌.
// thumbnail.jpg은 파일시스템(FS)의 메모리에 만들어짐.
// 영상의 1초로 가서 1장의 스크린샷을 찍음.
await ffmpeg.run(
'-i',
files.input,
'-ss',
'00:00:01',
'-frames:v',
'1',
files.thumb
);
// readFile의 return 값은 Uint8Array(양의정수 배열)
// Uint8Array(양의정수 배열)은 수많은 숫자들로 표현된 자바스크립트 방식의 파일
const mp4File = ffmpeg.FS('readFile', files.output);
const thumbFile = ffmpeg.FS('readFile', files.thumb);
//blob은 자바스크립트 세계의 파일 같은 거(binary 정보를 가지고 있는 파일)
//blob은 immutable, raw data인 파일과 같은 객체
//ArrayBuffer는 raw binary data를 나타내는 object
//binary data를 사용하고 싶다면 buffer를 사용해야 함.
//binary data로 mp4 파일 만듬
const mp4Blob = new Blob([mp4File.buffer], { type: 'video/mp4' });
const thumbBlob = new Blob([thumbFile.buffer], { type: 'image/jpg' });
const mp4Url = URL.createObjectURL(mp4Blob);
const thumbUrl = URL.createObjectURL(thumbBlob);
downloadFile(mp4Url, 'MyRecording.mp4');
downloadFile(thumbUrl, 'MyThumbnail.jpg');
// 브라우저가 계속 가지고 있으면 속도에 문제가 되니까 메모리에서 삭제.
ffmpeg.FS('unlink', files.input);
ffmpeg.FS('unlink', files.output);
ffmpeg.FS('unlink', files.thumb);
URL.revokeObjectURL(mp4Url);
URL.revokeObjectURL(thumbUrl);
URL.revokeObjectURL(videoFile);
actionBtn.disabled = false;
actionBtn.innerText = 'Record Again';
actionBtn.addEventListener('click', handleStart);
};
const handleStart = async () => {
actionBtn.innerText = 'Recording';
actionBtn.disabled = true;
actionBtn.removeEventListener('click', handleStart);
// MediaRecorder는 말 그대로 녹화해줄 수 있게 도와줌.
recorder = new MediaRecorder(stream, { mimeType: 'video/webm' });
// ondataavailable는 이벤트 핸들러
recorder.ondataavailable = (e) => {
// createObjectURL은 브라우저 메모리에서만 가능한 URL을 만들어줌.
// 웹사이트 상에 존재하는 URL처럼 보이지만 실제로는 없음.
// 파일을 가리키고 있는 url이라고 생각하면 됨.
// 브라우저가 그 파일에 접근할 수 있는 url을 주는거.
videoFile = URL.createObjectURL(event.data);
video.srcObject = null;
video.src = videoFile;
video.loop = true;
video.play();
actionBtn.innerText = 'Download';
actionBtn.disabled = false;
actionBtn.addEventListener('click', handleDownload);
};
recorder.start();
setTimeout(() => {
// stop 함수는 종료후 ondataavailable가 실행되게 만들어짐.
recorder.stop();
}, 5000);
};
const init = async () => {
// 사용자의 navigator에서 카메라와 오디오를 가져다줌.
stream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: {
width: 1024,
height: 576,
},
});
// 비디오 미리보기 기능 구현.
// srcObject는 MediaStream, MediaSource, Blob, File을 실행할때 video에 주는 무언가를 의미
// url은 src, (MediaStream, MediaSource, Blob, File)은 srcObejct
video.srcObject = stream;
video.play();
};
init();
actionBtn.addEventListener('click', handleStart);