-
Notifications
You must be signed in to change notification settings - Fork 0
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
Changes from all commits
4972b6c
00f330c
feb749b
eb679de
e3d424a
bfd1aef
1815ff4
51fc4f3
5869197
98cb15c
8643b7e
0282c50
163a61b
9383a87
a804d3d
6830ad4
9834846
ab5bba1
71f8dad
81018a0
cf2353a
dd3a3c1
13a4b9e
cbe8a0c
6eea7e2
5391f3e
4118070
b3bb3e6
49e62e3
6c27fb9
4531775
284d735
2140462
852f76d
ec50f77
25108c5
5d9bf11
3003fc9
b9c1630
4a9951a
00940ab
c8c7c0e
7b67b43
dd4efb1
8912f55
23df685
3cff958
32a640d
180e4c3
a26f565
6052e01
fa12516
8e1d119
075eaa1
e9694df
495a3ab
5c22a96
7499e8c
313d4d0
8c82f73
7a6467a
6ec0fa9
80428b8
4049497
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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]); | ||
} | ||
}, []); | ||
|
||
useEffect(() => { | ||
if (!stream) { | ||
void getMedia(); | ||
} | ||
|
||
return () => { | ||
if (stream) { | ||
stream.getTracks().forEach((track) => track.stop()); | ||
} | ||
}; | ||
}, [stream]); | ||
Comment on lines
+20
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (구두로 이야기했지만 메모해둔 내용!) |
||
|
||
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, { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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을 설정해줄 수 있습니다.
|
||
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[p-1] 해당 로직은 필요가 없다고 생각합니다. selectedMineType 함수를 사용하고 있는데 이제 초기값을 받기 위해서 사용하는 로직이라면 state를 사용하지 않고 변수로 사용하는 방법으로 적용해도 똑같이 사용이 가능하다고 생각합니다.
이런식으로요
useLayoutEffect는 랜더링 사이클에서 많은 비용을 소모하고 있어서 사용을 지양하는것이 좋으니 한번 확인해 보시고 수정하는건 어떤가요?