Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NDD-87] Interview 페이지 camera 컴포넌트 기능 부여 (7h/8h) #30

Merged
merged 64 commits into from
Nov 11, 2023
Merged
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
4972b6c
feat: InterviewCamera 내부 state 정의
adultlee Nov 9, 2023
00f330c
feat: 현재 브라우저에서 지원가능 한 MimeType을 명시
adultlee Nov 9, 2023
feb749b
feat: 현재 Media에 연결 기능 구현
adultlee Nov 9, 2023
eb679de
feat: 마운트시 바로 stream 연결
adultlee Nov 9, 2023
e3d424a
feat: Record 시작함수 구현
adultlee Nov 9, 2023
bfd1aef
feat: record stop 함수 구현
adultlee Nov 9, 2023
1815ff4
feat: Record Download 함수 구현
adultlee Nov 9, 2023
51fc4f3
feat: InterviewCamera 내부 state 정의
adultlee Nov 9, 2023
5869197
feat: 현재 브라우저에서 지원가능 한 MimeType을 명시
adultlee Nov 9, 2023
98cb15c
feat: 현재 Media에 연결 기능 구현
adultlee Nov 9, 2023
8643b7e
feat: 마운트시 바로 stream 연결
adultlee Nov 9, 2023
0282c50
feat: Record 시작함수 구현
adultlee Nov 9, 2023
163a61b
feat: record stop 함수 구현
adultlee Nov 9, 2023
9383a87
feat: Record Download 함수 구현
adultlee Nov 9, 2023
a804d3d
Merge remote-tracking branch 'origin/feautre/NDD-87' into feautre/NDD-87
adultlee Nov 9, 2023
6830ad4
feat: InterviewCamera 내부 state 정의
adultlee Nov 9, 2023
9834846
feat: 현재 브라우저에서 지원가능 한 MimeType을 명시
adultlee Nov 9, 2023
ab5bba1
feat: 현재 Media에 연결 기능 구현
adultlee Nov 9, 2023
71f8dad
feat: 마운트시 바로 stream 연결
adultlee Nov 9, 2023
81018a0
feat: Record 시작함수 구현
adultlee Nov 9, 2023
cf2353a
feat: record stop 함수 구현
adultlee Nov 9, 2023
dd3a3c1
feat: Record Download 함수 구현
adultlee Nov 9, 2023
13a4b9e
feat: InterviewCamera 내부 state 정의
adultlee Nov 9, 2023
cbe8a0c
feat: 현재 브라우저에서 지원가능 한 MimeType을 명시
adultlee Nov 9, 2023
6eea7e2
feat: 현재 Media에 연결 기능 구현
adultlee Nov 9, 2023
5391f3e
feat: 마운트시 바로 stream 연결
adultlee Nov 9, 2023
4118070
feat: Record 시작함수 구현
adultlee Nov 9, 2023
b3bb3e6
feat: record stop 함수 구현
adultlee Nov 9, 2023
49e62e3
feat: Record Download 함수 구현
adultlee Nov 9, 2023
6c27fb9
Merge remote-tracking branch 'origin/feautre/NDD-87' into feautre/NDD-87
adultlee Nov 11, 2023
4531775
feat: InterviewCamera 내부 state 정의
adultlee Nov 9, 2023
284d735
feat: 현재 브라우저에서 지원가능 한 MimeType을 명시
adultlee Nov 9, 2023
2140462
feat: 현재 Media에 연결 기능 구현
adultlee Nov 9, 2023
852f76d
feat: 마운트시 바로 stream 연결
adultlee Nov 9, 2023
ec50f77
feat: Record 시작함수 구현
adultlee Nov 9, 2023
25108c5
feat: record stop 함수 구현
adultlee Nov 9, 2023
5d9bf11
feat: Record Download 함수 구현
adultlee Nov 9, 2023
3003fc9
feat: InterviewCamera 내부 state 정의
adultlee Nov 9, 2023
b9c1630
feat: 현재 브라우저에서 지원가능 한 MimeType을 명시
adultlee Nov 9, 2023
4a9951a
feat: 현재 Media에 연결 기능 구현
adultlee Nov 9, 2023
00940ab
feat: 마운트시 바로 stream 연결
adultlee Nov 9, 2023
c8c7c0e
feat: Record 시작함수 구현
adultlee Nov 9, 2023
7b67b43
feat: record stop 함수 구현
adultlee Nov 9, 2023
dd4efb1
feat: Record Download 함수 구현
adultlee Nov 9, 2023
8912f55
feat: InterviewCamera 내부 state 정의
adultlee Nov 9, 2023
23df685
feat: 현재 브라우저에서 지원가능 한 MimeType을 명시
adultlee Nov 9, 2023
3cff958
feat: 현재 Media에 연결 기능 구현
adultlee Nov 9, 2023
32a640d
feat: 마운트시 바로 stream 연결
adultlee Nov 9, 2023
180e4c3
feat: Record 시작함수 구현
adultlee Nov 9, 2023
a26f565
feat: record stop 함수 구현
adultlee Nov 9, 2023
6052e01
feat: Record Download 함수 구현
adultlee Nov 9, 2023
fa12516
feat: InterviewCamera 내부 state 정의
adultlee Nov 9, 2023
8e1d119
feat: 현재 브라우저에서 지원가능 한 MimeType을 명시
adultlee Nov 9, 2023
075eaa1
feat: 현재 Media에 연결 기능 구현
adultlee Nov 9, 2023
e9694df
feat: 마운트시 바로 stream 연결
adultlee Nov 9, 2023
495a3ab
feat: Record 시작함수 구현
adultlee Nov 9, 2023
5c22a96
feat: record stop 함수 구현
adultlee Nov 9, 2023
7499e8c
feat: Record Download 함수 구현
adultlee Nov 9, 2023
313d4d0
feat: video UI 기능 구현
adultlee Nov 11, 2023
8c82f73
fix: Interview 경로상 발생하는 문제 해결
adultlee Nov 11, 2023
7a6467a
fix: log 문제
adultlee Nov 11, 2023
6ec0fa9
fix: InterviewCamera 파일시스템 문제 해결
adultlee Nov 11, 2023
80428b8
chore: 상대경로 수정
adultlee Nov 11, 2023
4049497
chore: CameraRef 이름 변경
adultlee Nov 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 129 additions & 5 deletions FE/src/components/interviewPage/InterviewCamera.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,143 @@
import React, { useState, useRef, useEffect, useLayoutEffect } from 'react';
import { css } from '@emotion/react';

const InterviewCamera: React.FC = () => {
const [stream, setStream] = useState<MediaStream | null>(null);
const [recording, setRecording] = useState(false);
const [recordedBlobs, setRecordedBlobs] = useState<Blob[]>([]);
const [selectedMimeType, setSelectedMimeType] = useState('');

const mirrorVideoRef = useRef<HTMLVideoElement>(null);
const mediaRecorderRef = useRef<MediaRecorder | null>(null);

useLayoutEffect(() => {
const mimeTypes = getSupportedMimeTypes();
if (mimeTypes.length > 0) {
setSelectedMimeType(mimeTypes[0]);
}
}, []);

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[p-1] 해당 로직은 필요가 없다고 생각합니다. selectedMineType 함수를 사용하고 있는데 이제 초기값을 받기 위해서 사용하는 로직이라면 state를 사용하지 않고 변수로 사용하는 방법으로 적용해도 똑같이 사용이 가능하다고 생각합니다.

  const getSupportedMimeTypes = () => {
    const types = [
      'video/webm; codecs=vp8',
      'video/webm; codecs=vp9',
      'video/webm; codecs=h264',
      'video/mp4; codecs=h264',
    ];
    return types.filter((type) => MediaRecorder.isTypeSupported(type));
  };
  const selectedMimeType = getSupportedMimeTypes();

이런식으로요
useLayoutEffect는 랜더링 사이클에서 많은 비용을 소모하고 있어서 사용을 지양하는것이 좋으니 한번 확인해 보시고 수정하는건 어떤가요?

useEffect(() => {
if (!stream) {
void getMedia();
}

return () => {
if (stream) {
stream.getTracks().forEach((track) => track.stop());
}
};
}, [stream]);
Comment on lines +20 to +30
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(구두로 이야기했지만 메모해둔 내용!)
useEffect의 return에 있는 로직이 컴포넌트가 언마운트 될 때만 실행되는 것이 아니라 종속성 배열에 있는 상태에 대한 사이클이 변경될 때 실행되는 것이라서 위 코드에서 stream이 변경될 때 마다 useEffect가 실행되는 사이클이 일어날 것이라고 생각했습니다.
하지만 현재 로직에서 stream은 두번의 변경이 일어나고, 이마저도 if문으로 막혀있어서 계속해서 실행되는 문제는 없을 것이라고 판단했습니다!


const getMedia = async () => {
try {
const constraints = {
audio: {
echoCancellation: { exact: true },
},
video: {
width: 1280,
height: 720,
},
milk717 marked this conversation as resolved.
Show resolved Hide resolved
};
const mediaStream =
await navigator.mediaDevices.getUserMedia(constraints);
setStream(mediaStream);
if (mirrorVideoRef.current) {
mirrorVideoRef.current.srcObject = mediaStream;
}
} catch (e) {
console.log(`현재 마이크와 카메라가 연결되지 않았습니다`);
}
};

const handleStartRecording = () => {
setRecordedBlobs([]);
try {
mediaRecorderRef.current = new MediaRecorder(stream as MediaStream, {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/MediaRecorder

해당 코드에 오디오, 비디오 bitrate option을 설정해줄 수 있습니다.
v:3mbps, a: 128kbps

const option = {
   audioBitsPerSecond: 128000,
   videoBitsPerSecond: 3000000,
   mimeType: "video/mp4",
}

new MediaRecorder(stream, options);

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`);
}
};

const handleStopRecording = () => {
if (mediaRecorderRef.current) {
mediaRecorderRef.current.stop();
}
setRecording(false);
};

const handleDownload = () => {
const blob = new Blob(recordedBlobs, { type: selectedMimeType });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = 'recorded.webm';
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 100);
};

const getSupportedMimeTypes = () => {
const types = [
'video/webm; codecs=vp8',
'video/webm; codecs=vp9',
'video/webm; codecs=h264',
'video/mp4; codecs=h264',
];
return types.filter((type) => MediaRecorder.isTypeSupported(type));
};

return (
<div
css={css`
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
width: 100%;
height: 75%;
border: 0.0625rem solid red;
background-color: black;
`}
>
면접페이지의 카메라 입니다.
<video
ref={mirrorVideoRef}
playsInline
autoPlay
muted
css={css`
width: 100%;
height: 80%;
transform: scaleX(-1);
`}
/>

<div>
<button onClick={handleStartRecording} disabled={recording}>
시작
</button>
<button onClick={handleStopRecording} disabled={!recording}>
종료
</button>
<button
onClick={handleDownload}
disabled={recording || recordedBlobs.length === 0}
>
저장
</button>
</div>
</div>
);
};

export default InterviewCamera;
Loading