본문 바로가기
Experience/- KT AIVLE School

KT AIVLE School 빅프로젝트 - 웹캠 녹화 구현

by Yoojacha 2023. 7. 19.

댄서블(춤을 연습하는 사람)이 춤을 추고 다음 구간 영상으로 넘어갈 때 POST요청을 보내는데, 댄서블이 춤을 춘 모습을 담기 위해서 react-wecam의 녹화할 필요가 있었습니다. 이를 구간 연습이 끝나거나, 한 번 더 연습하거나, 다음 구간으로 넘어가거나 여러 상황에 따라서 녹화를 시작하고 끝내야 하는 로직이 생각보다 복잡했습니다.

hook 사용은 옳은 방법이 아닐 수 있습니다. 녹화 로직만 참고하시길 바랍니다.

구현 과정

Navigator.mediaDevices는 읽기 전용 속성은 카메라, 마이크, 화면 공유와 같이 현재 연결된 미디어 입력 장치에 접근할 수 있는 MediaDevices 객체를 반환합니다. - mdn web docs
  // 녹화 시작 함수
  const startRecording = useCallback(() => {
    const constraints = { video: true, audio: false };
    console.log("💛 recording started");

    navigator.mediaDevices
      .getUserMedia(constraints)
      .then((stream) => {
        // 녹화 준비
        const mediaRecorderInstance = new MediaRecorder(stream);
        const chunks: Blob[] = [];

        // 녹화 중일 때 실행
        mediaRecorderInstance.addEventListener("dataavailable", (event) => {
          if (event.data && event.data.size > 0) {
            chunks.push(event.data); // chuck들을 이어붙여서 녹화
          }
        });
        
        // 녹화가 끝났을 때 실행
        mediaRecorderInstance.addEventListener("stop", () => {
          const recordedBlob = new Blob(chunks, { type: "video/mp4" }); // Blob으로 생성
          webcamCurrentRecord.current = recordedBlob; // MutableRefObject에 저장
        });
        
        // 녹화 시작
        mediaRecorderInstance.start();
        mediaRecorder.current = mediaRecorderInstance;
      })
      // 녹화 에러
      .catch((error) => {
        console.error("Error accessing webcam:", error);
      });
  }, [webcamCurrentRecord]);

 

MutableRefObject에 녹화 영상을 Blob으로 만들어서 할당했습니다.

  // 녹화 종료 함수
  const stopRecording = useCallback(async () => {
    if (mediaRecorder.current && mediaRecorder.current.state !== "inactive") {
      console.log("💛 recording ended");
      mediaRecorder.current.stop();
    }
  }, []);

녹화 종료시 medaRecoder가 확실히 멈추도록 설정했습니다.

  useEffect(() => {
    if (isPlaying) startRecording();
    if (isFinished) stopRecording();
  }, [isPlaying, isFinished, startRecording, stopRecording]);

RTK에 isPlaying, isFinished를 만들어서 의존성 배열에 의해 트리거가 되도록 했습니다.


후기

navigator.mediaDevices를 통해 쉽게 웹캠 영상을 녹화할 수 있어서 놀랐습니다. 그리고 dataavailable 일 때마다 chuck를 이어붙이는 방식으로 녹화하는 것이 신기했습니다. 이제는 익숙해졌지만 Blob으로 만들어서 폼데이터로 요청을 보낼 수 있도록 하는 것은 익숙해졌습니다.

 

useCallback과 useEffect의 의존성 배열 설정이 옳은 선택이었는지는 다시 고민하게 되었습니다. useCallback은 함수 자체를 메모리에 저장해두는 것인데, 컴포넌트가 다시 마운트 될 때 녹화 시작에 대한 딜레이를 고려한다고 했으나, 큰 고민 없이 진행을 했었습니다. 

 

Dancify v2를 만들어볼 생각인데 그때는 성능을 고려해서 hook을 제대로 사용해볼 예정입니다.

 

댓글