[숏폼 프로젝트] 웹에서 비디오 녹화 및 업로드 기능 구현

Manta·2024년 11월 30일
0

숏폼

목록 보기
5/7

이번 포스트에서는 웹 애플리케이션에서 비디오 녹화 기능을 구현하고, 녹화된 비디오를 서버에 업로드하는 과정을 설명합니다. 브라우저의 MediaRecorder API와 프리사인 URL(Presigned URL)을 활용하여 효율적인 비디오 업로드 시스템을 구축할 수 있습니다.

1. 주요 기능 소개

  • 비디오 녹화: 실시간으로 사용자의 카메라와 마이크를 활용하여 비디오를 녹화합니다.

  • 녹화된 비디오 업로드: 녹화된 비디오를 서버로 업로드합니다.

  • 프리사인 URL 사용: 서버에서 AWS S3에 파일을 업로드할 수 있는 프리사인 URL을 생성하여 클라이언트가 직접 파일을 업로드할 수 있도록 합니다.

2. 주요 코드 설명

(1) 비디오 녹화 기능

async function startMediaRecording() {
  try {
    const constraints = {
      video: { width: 1280, height: 720 },
      audio: true,
    };

    const stream = await navigator.mediaDevices.getUserMedia(constraints);
    videoPreview.srcObject = stream;

    mediaRecorder = new MediaRecorder(stream, { mimeType: 'video/webm' });

    mediaRecorder.ondataavailable = (event) => {
      if (event.data.size > 0) {
        recordedChunks.push(event.data);
      }
    };

    mediaRecorder.onstop = () => {
      videoBlob = new Blob(recordedChunks, { type: 'video/webm' });
      recordedChunks = [];
      uploadButton.disabled = false;
    };

    mediaRecorder.start();
    startButton.disabled = true;
    stopButton.disabled = false;
  } catch (error) {
    console.error('Error accessing media devices:', error);
  }
}
  • navigator.mediaDevices.getUserMedia: 사용자의 카메라와 마이크에 접근합니다.
  • MediaRecorder: 입력받은 미디어 스트림을 녹화하며, 녹화된 데이터를 ondataavailable 이벤트를 통해 수집합니다.
  • Blob: 녹화가 종료되면 조각난 데이터를 하나의 Blob으로 합쳐 동영상 파일처럼 처리합니다.

(2) 업로드 기능 구현

async function uploadVideo() {
  try {
    if (!videoBlob) {
      alert('업로드할 영상이 없습니다.');
      return;
    }

    const payload = {
      fileType: videoBlob.type,
      fileSize: videoBlob.size,
      bucket: '*',
      region: '*',
    };

    const response = await fetch('http://localhost:3000/s3/generate-url', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
      body: JSON.stringify(payload),
    });

    const { presignedUrl, key } = await response.json();

    const uploadResponse = await fetch(presignedUrl, {
      method: 'PUT',
      body: videoBlob,
      headers: {
        'Content-Type': videoBlob.type,
      },
    });

    if (!uploadResponse.ok) throw new Error('파일 업로드 실패');

    alert('파일 업로드 성공!');
    sendMetadataToServer(key);
  } catch (error) {
    alert(`업로드 중 오류 발생: ${error.message}`);
  }
}

(3) 메타데이터 전송

async function sendMetadataToServer(key) {
  const metadataPayload = {
    title: titleInput.value || null,
    description: descriptionInput.value || null,
    thumbnailUrl: thumbnailUrlInput.value || null,
    hashtags: hashtagsInput.value ? hashtagsInput.value.split(',') : [],
    visibility: visibilityInput.value || null,
    videoCode: key,
  };

  const metadataResponse = await fetch('http://localhost:3000/video', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${token}`,
    },
    body: JSON.stringify(metadataPayload),
  });

  if (metadataResponse.ok) {
    alert('영상 업로드 및 메타데이터 저장 성공!');
  } else {
    console.error('메타데이터 전송 실패:', metadataResponse.status);
    alert('메타데이터 전송에 실패했습니다.');
  }
}

Blob으로 대기 상태를 저장하는 이유

mediaRecorder.onstop = () => {
  videoBlob = new Blob(recordedChunks, { type: 'video/webm' });
  recordedChunks = [];
  uploadButton.disabled = false;
};

이 코드의 역할은 녹화가 종료된 후 데이터를 Blob 형태로 변환하여 대기 상태로 저장하는 것입니다.
그렇다면 왜 BLob으로 대기상태를 저장하는 걸까요?

  1. 녹화 과정에서 MediaRecorder는 비디오 데이터(예: 각 프레임과 오디오 샘플)를 청크(chunk) 단위로 제공합니다.

  2. ondataavailable 이벤트를 통해 녹화 중에 생성된 데이터를 recordedChunks 배열에 저장합니다.

mediaRecorder.ondataavailable = (event) => {
  if (event.data.size > 0) {
    recordedChunks.push(event.data);
  }
};
  1. 녹화가 중지되면 recordedChunks에 여러 데이터 청크가 쌓여 있게 됩니다.

  2. 업로드나 미리보기에는 하나의 파일 형식으로 된 데이터가 필요합니다. 이를 위해 Blob(Binary Large Object)을 사용하여 조각들을 하나의 완전한 비디오 파일로 합칩니다.

videoBlob = new Blob(recordedChunks, { type: 'video/webm' });
  • new Blob():

    • recordedChunks 배열에 저장된 데이터 청크를 결합하여 하나의 video/webm 형식의 비디오 파일로 변환합니다.

    • 이 Blob 데이터는 업로드 전까지 메모리에 임시로 저장됩니다.

profile
공부할게 너무 만타🫠

0개의 댓글