์ผ | ์ | ํ | ์ | ๋ชฉ | ๊ธ | ํ |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- ์ด๋ฏธ์ง ์์
- svgํ์ผ ๋ค๋ฃจ๊ธฐ
- ์ด๋ถํ์
- ๋ถ์คํธ์ปจํผ๋ฐ์ค
- ๋ธ๋ฃจํธํฌ์ค
- ํ๋ก๊ทธ๋๋จธ์ค
- Redux toolkit
- ์๊ณ ๋ฆฌ์ฆ
- ๋์ ๊ณํ๋ฒ
- ๋ฐฑ์ค
- ์ฝ๋ฉํ ์คํธ
- ๋ถ์คํธ์บ ํ์น๋ชจ๋ฐ์ผ
- ์ฝ๋ ํฌ๋ฉง
- ๋ฆฌ๋์ค ํดํท
- custom hook
- ์๋ฐฉํฅ ์ฐ๊ฒฐ ๋ฆฌ์คํธ
- ์๋ฐ์คํฌ๋ฆฝํธ
- Node.js
- icecandidate
- ๋๋๊ทธ ์ด๋ฒคํธ
- router v6
- ์นด์นด์ค
- DP
- ์นด์นด์ค์ฑ์ฉ
- ์ฝํ
- ๊ณผ์ ํ ์คํธ
- React
- JavaScript
- TypeScript
- js
- Today
- Total
๐ฅ dev-ruby
[WebRTC] React+TypeScript+WebRTC ๊ฐ๋ ์ ๋ฆฌ + ๊ตฌํํ๊ธฐ ๋ณธ๋ฌธ
๋ถ์คํธ์บ ํ ์น๋ชจ๋ฐ์ผ ๋ฉค๋ฒ์ญ ๊ทธ๋ฃน ํ๋ก์ ํธ์์ WebRTC๋ฅผ ๋ค๋ฃจ๊ฒ ๋๋ค. ๋ณธ๊ฒฉ์ ์ธ ์์
์ ๋ค์ด๊ฐ๊ธฐ ์์ WebRTC์ ๋ํด ๋ฌด์งํ ์ํ์๊ธฐ์ ๋ฏธ๋ฆฌ ํ์ต์ ํด๋ณด์๋ค
์ฒ์ ํ์ตํ ๋๋ ์ง์ง ์ด๊ฒ ๋ญ์ง ์ถ์๋ค.. ๊ทธ๋์ ์ดํด ๋ชปํ ์ฑ ์ผ๋จ ๊ฐ๋
์ด๋ผ๋ ๋ง๊ตฌ ์ ์ด๋จ๋ค
MDN ๋ฌธ์๋ฅผ ํ๋์ฉ ์ดํด๋ณด๊ณ , ๋ธ๋ก๊ทธ ์๋ฃ๋ฅผ ์์ฒญ ์ฐพ์ ๋์ ๋๊ธฐ๋ค๋ณด๋ ์ด๋์ ๋ ์ ๋ฆฌ๊ฐ ๋๋ค
ํ๋จ์ ์ฐธ๊ณ ํ ๋ฌธ์๋ค ์ ์ด๋จ์ด์ ๐ค
WebRTC(Web Real Time Communication)๋??
๋ธ๋ผ์ฐ์ ์ ๋ชจ๋ฐ์ผ ์ ํ๋ฆฌ์ผ์ด์
์์ ๋ณ๋์ ์ํํธ์จ์ด ์์ด ์์ฑ, ์์ ๋ฏธ๋์ด, ํ
์ค, ํ์ผ๊ณผ ๊ฐ์ ๋ฐ์ดํฐ๋ค์ ์ค์๊ฐ ํต์ (RTC)์ผ๋ก ์ฃผ๊ณ ๋ฐ์ ์ ์๊ฒ ํด์ฃผ๋ ๊ธฐ์ ์ด๋ค.
Peer To Peer ๋ฐฉ์์ผ๋ก ์ ์กํ๋ฉฐ, ์๊ทธ๋๋ง ์๋ฒ ํ๋๋ง ์์ผ๋ฉด ๋๋ค.
์๊ทธ๋๋ง ์๋ฒ๋ ๋ญ๋ฐ?
์๋ก ๋ค๋ฅธ ๋คํธ์ํฌ์ ์๋ ๋๋ฐ์ด์ค๋ค๋ผ๋ฆฌ ํต์ ํ๊ธฐ ์ํด์๋ ๊ฐ์์ ์์น๋ฅผ ์๊ณ , ๋ฏธ๋์ด ํฌ๋งท์ ๋ง์ถ ์ ์์ด์ผ ํ๋ค. ์ด๋ฌํ ๊ณผ์ ์ ์๊ทธ๋๋ง ์ด๋ผ๊ณ ๋ถ๋ฅธ๋ค.
๊ฐ ๋๋ฐ์ด์ค๋ค์ ์๋ฒ๋ก ๋ณธ์ธ์ ์ ๋ณด๋ฅผ ๋ณด๋ด๊ณ , ์๊ทธ๋๋ง ์๋ฒ๋ ๋ฐ์ ๋คํธ์ํฌ ์ ๋ณด๋ฅผ ๊ทธ๋๋ก ์๋ ๋๋ฐ์ด์ค์๊ฒ ์ ๋ฌํด์ค๋ค.
์ค์๊ฐ์ด๋ผ๋ฉด ์น์์ผ๊ณผ ๋ฌด์จ ์ฐจ์ด์ฃ ?
์น์์ผ์ ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ๋ฅผ ํตํด ์ ๋ณด๋ค์ ์ ๋ฌ๋ฐ๋๋ค.

๊ทธ๋ฌ๋ WebRTC๋ ์๊ทธ๋๋ง ์๋ฒ๋ฅผ ํตํด ์๋ก์ ๋คํธ์ํฌ ์ ๋ณด๋ค์ ๋ฐ๋๋ค. (์๊ทธ๋๋ง ์๋ฒ๋ ๋จ์ํ ํด๋ผ์ด์ธํธ์ ๋คํธ์ํฌ ์ ๋ณด๋ฅผ ์ ๋ฌํด์ฃผ๋ ๋งค๊ฐ์ ์ญํ ์ ํ๋ค.)
ํด๋ผ์ด์ธํธ๋ ์๋ ํด๋ผ์ด์ธํธ์ ๋คํธ์ํฌ ์ ๋ณด๋ฅผ ๋ฐ๊ณ โ๋ ๊ฑฐ๊ธฐ ์๊ตฌ๋?โ ๋ฅผ ์๊ฒ ๋๋ฉด ๊ทธ๋๋ถํฐ ํด๋ผ์ด์ธํธ๋ผ๋ฆฌ ์ฐ๊ฒฐ์ ํ ์ ์๊ฒ ๋๋ ๊ฒ์ด๋ค.

STUN๊ณผ TURN
์ด ๊ฐ๋ ์ ์๊ธฐ ์ ์ NAT์ด๋ผ๋ ๊ฐ๋ ์ ๋จผ์ ์์์ผ ํ๋ค.
NAT(network Address Translation)์ด๋?
์ฌ์ค IP๋ฅผ ๊ณต์ธ IP๋ก ๋ณ๊ฒฝํ๋๋ฐ ํ์ํ ์ฃผ์ ๋ณํ ์๋น์ค์ด๋ค.
โ ๋ผ์ฐํฐ ๋ฑ์ ์ฅ๋น๋ฅผ ์ฌ์ฉํ์ฌ ๋ค์์ ์ฌ์ค IP๋ฅผ ๊ณต์ธ IP ์ฃผ์๋ก ๋ณํํ๊ฒ ๋๋ค.
๋ฌด์จ ๋ง์ด๋๋ฉด,
ํ์ฌ๋ง/๋ด๋ถ๋ง(LAN) ์ Private IP์ด๊ธฐ ๋๋ฌธ์ ๋ค๋ฅธ ๋คํธ์ํฌ์์๋ ํต์ฉ๋์ง ์๋๋ค.
๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ์ฐ๋ฆฌ๊ฐ ๋คํธ์ํฌ์์ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ๊ธฐ ์ํด์๋ Public IP ๊ฐ ํ์ํ๋ฐ, NAT๋ Private IP๋ฅผ Public IP๋ก 1๋1 ๋์์์ผ ๋ณํํ๋ ์ฅ์น๋ค.
๊ทธ๋ ๋ค๋ฉด STUN, TURN์ ๋ญ๊น
P2P๋ก ์ฐ๊ฒฐํ๊ธฐ ์ํด์๋ ์ญ์ ์๋๋ฐฉ์ Public IP ์ฃผ์๊ฐ ํ์ํ๋ค. ํ์ง๋ง ํด๋ผ์ด์ธํธ๋ ๋ณดํต ๋ฐฉํ๋ฒฝ์ด๋ NAT ๋ค์์ ๋ณดํธ๋ฐ๊ณ ์๊ธฐ ๋๋ฌธ์ Public IP ์ฃผ์๋ฅผ ์๊ธฐ ์ด๋ ต๋ค.
๊ทธ๋์ WebRTC์์๋ STUN ์๋ฒ ์ฌ์ฉ์ ๊ถ์ฅํ๊ณ ์๋ค.
์๋ก๊ฐ์ ์ฐ๊ฒฐ์ ์ํ ์ ๋ณด๋ฅผ ๊ณต์ ํ์ฌ P2P ํต์ ์ ๊ฐ๋ฅํ๊ฒ ํด์ฃผ๋ ๊ฒ์ด Stun/Turn Server์ด๋ค.
STUN์ ํด๋ผ์ด์ธํธ-์๋ฒ ํ๋กํ ์ฝ์ด๋ค. STUN ํด๋ผ์ด์ธํธ๋ ์ฌ์ค๋ง(private network)์ ์์นํ๊ณ , STUN ์๋ฒ๋ ์ธํฐ๋ท๋ง์ ์์นํ๋ค. STUN ํด๋ผ์ด์ธํธ๋ ์์ ์ ๊ณต์ธ IP ์ฃผ์๋ฅผ ํ์ธํ๊ธฐ ์ํด STUN ์๋ฒ์๊ฒ ์์ฒญํ๊ณ , STUN ์๋ฒ๋ STUN ํด๋ผ์ด์ธํธ๊ฐ ์ฌ์ฉํ๋ ๊ณต์ธ IP ์ฃผ์๋ฅผ ์๋ตํ๋ค.
TURN ์๋ฒ๋ ์์ง ํ์คํ๊ฒ ์ดํด๊ฐ
๋์ง ์์์ ํ๋จ์ ์ฒจ๋ถ๋ ๋
ธ์
์์๋ง ์ ์ด๋จ๊ณ , ์ฌ๊ธฐ์ ์ ์ง ์๊ฒ ๋ค. ๊ถ๊ธํ๋ฉด ๋
ธ์
์ผ๋ก ํ๊ณ ๋ค์ด๊ฐ์ ๋ณผ ์ ์์ด์
์ฌ์ค๋ง(private network): ์ธํฐ๋ท ์ด๋๋ ์ฑ ์ํคํ ์ฒ์์ ์ฌ์ค IP ์ฃผ์ ๊ณต๊ฐ์ ์ด์ฉํ๋ ๋คํธ์ํฌ
์ด๋ ์ ๋ ์ฉ์ด ์ ๋ฆฌ๋ ๋ ๊ฒ ๊ฐ์ผ๋ ๋ณธ๊ฒฉ์ ์ผ๋ก ์๊ทธ๋๋ง ๊ณผ์ ์ ์ค๋ช ํด๋ณด๊ฒ ๋ค

์ฒ์์ ํ ์คํธ๋ก๋ง ์ ํ์ง ๊ธ์ ๋ณด๊ณ ๊ณต๋ถํ์๋๋ฐ, ์ด ์ฌ์ง์ ๋ณด๋๊น ํ๋ฆ์ด ์ด๋ ์ ๋ ์ดํด๊ฐ ๋์๋ค
1. Session Descriptions ๊ตํํ๊ธฐ
- ๋จผ์ , Peer A๊ฐ ์ด๋ฏธ ์ ์ํด ์์๋ค๊ณ ๊ฐ์ ํด๋ณด์.
- ๊ทธ ํ Peer B๊ฐ ๋ค์ด์ค๋ฉด, B๊ฐ A์๊ฒ โ๋ ๋ค์ด์์ด~โ ๋ผ๊ณ ์๋ฆฐ๋ค.
- ๊ทธ๋ผ A๋ createOffer()๋ฅผ ํตํด offer๋ฅผ ๋ง๋ ๋ค. ์ด offer๋ ์์์ ๋งํ SDP๋ฅผ ์๋ฏธํ๋ค.
- ๋ด SDP๋ฅผ setLocalDescription์ ํตํด ๋ก์ปฌ SDP๋ก ์ค์ ํด์ฃผ์ด์ผ ํ๋ค.
- ์์ฑํ SDP๋ฅผ ์๋ B์๊ฒ ์๊ทธ๋๋ง ์๋ฒ๋ฅผ ํตํด ๋ณด๋ด์ค๋ค.
- B๋ ์ ๋ฌ๋ฐ์ SDP๋ฅผ setRemoteDescription์ ํตํด RemoteDescription ์ผ๋ก ์ค์ ํด์ผ ํ๋ค.
- B๋ ๋ฆฌ๋ชจํธ์ A์ SDP๋ฅผ ์ค์ ํ๊ณ ๋๋ฉด createAnswer()๋ฅผ ํตํด A์๊ฒ ๋ณด๋ผ SDP๋ฅผ ๋๊ฐ์ด ์์ฑํ๋ค.
- ์์์ ์์ฑํ createOffer์ ๊ฐ์ ๊ณผ์ ์ด๋ค.
- ๋ง์ฐฌ๊ฐ์ง๋ก A์๊ฒ Answer๋ฅผ ๋ณด๋ด์ฃผ๊ณ , A๋ ๋ฐ์ answer๋ฅผ RemoteDescription ์ผ๋ก ์ค์ ํ๊ฒ ๋๋ค
2. Ice Candidate ๊ตํํ๊ธฐ
SDP๋ฅผ ์๋ก ๊ตํํ ํ์, ๋ ํผ์ด๋ค์ ICE candidate (ICE ํ๋ณด)๋ค์ ๊ตํํ๊ธฐ ์์ํ๋ค.
์๋์ ํต์ ํ ์ ์๋ candidate๋ฅผ ์ฐพ๊ธฐ ์์ํ๋ ๊ณผ์ ์ด๋ค.
- ๊ฐ ํด๋ผ์ด์ธํธ๋ ํ์ฌ ๋ด ๋คํธ์ํฌ ์ ๋ณด๊ฐ ํ๋ณด๋๋ฉด ์คํ๋ callback์ onicecandidate ํธ๋ค๋ฌ์๊ฒ ์ ๋ฌํ๋ค. ๋๊ฐ์ง ๋ฐฉ๋ฒ์ด ์๋ค.
peerConnection.addEventListener('icecandidate', (e) => {});
peerConnection.onicecandidate = (e) => {};
- ๋ด ๋คํธ์ํฌ ์ ๋ณด๊ฐ ํ๋ณด๋๋ฉด ์๊ทธ๋๋ง ์๋ฒ๋ฅผ ํตํด ์๋ Peer์๊ฒ ๋ด ice candidate๋ฅผ ์ ์กํ๋ค.
- ์๋ Peer์ ice candidate๊ฐ ๋์ฐฉํ๋ฉด, ํด๋ผ์ด์ธํธ์ ์ ์ฅํด๋์๋ ์๋ Peer Connection์ ์ฐพ์์ ์๋์ addIceCandidate() ๋ก candidate๋ฅผ ์ค์ ํด์ฃผ์ด์ผ ํ๋ค.
- ์ด ์์ ์ ์๋ก๊ฐ ๋ชจ๋ IceCandidate๋ฅผ ๊ตํํ ๋๊น์ง ์งํ๋๋ค.
- IceCandidate๋ฅผ ๋ชจ๋ ๊ตํํ๊ณ ๋๋ฉด, addEventListener์ track ์ด๋ฒคํธ๋ฅผ ํตํด ์๋์ stream์ ๋ฐ์์ฌ ์ ์๋ค.
peerConnection.addEventListener('track', (e) => {});
peerConnection.ontrack = (e) => {};
ํ๋ฆ์ ์ฌ๊ธฐ๊น์ง! ์ด์ ์ค์ ์ผ๋ก ๋ค์ด๊ฐ์ ์ฝ๋๋ฅผ ์์ฑํด๋ณด์.
์ฌ์ค ํ๋ฆ๋ง ์ฝ์์ ๋๋ ์ด๊ฒ ๋ฌด์จ ๋ง์ธ๊ฐ ์ถ์์๋ ์๋๋ฐ, ์ญ์ ์ง์ ์ฝ๋๋ฅผ ์์ฑํด๋ณด๋ฉด์ ๋ฐฐ์ฐ๋๊ฒ ์ข์ ๊ฒ ๊ฐ๋ค.
Server ์ฝ๋
import { Server } from 'socket.io';
function signalingSocketServer(io: Server) {
const signaling = io.of('namespace');
signaling.on('connection', (socket) => {
socket.on('join', () => {
const senderId = socket.id;
socket.broadcast.emit('join', senderId);
});
// senderId : offer๋ฅผ ๋ณด๋ด๊ธฐ ์์ํ ์ฌ๋์ id, receiveId : offer๋ฅผ ๋ฐ๋ ์ฌ๋์ id
socket.on('offer', ({ receiveId, offer }) => {
const senderId = socket.id;
socket.to(receiveId).emit('offer', { senderId, offer });
});
socket.on('answer', ({ receiveId, answer }) => {
const senderId = socket.id;
socket.to(receiveId).emit('answer', { senderId, answer });
});
socket.on('ice-candidate', ({ receiveId, candidate }) => {
const senderId = socket.id;
socket.to(receiveId).emit('ice-candidate', { senderId, candidate });
});
socket.on('disconnect', () => {
const senderId = socket.id;
socket.broadcast.emit('disconnected', senderId);
});
});
}
export default signalingSocketServer;
์์ ๋ง์ฐฌ๊ฐ์ง๋ก A๊ฐ ์๋ ์ํฉ์์ B๊ฐ ๋ค์ด์จ๋ค๊ณ ๊ฐ์ ํ์.
join
B๊ฐ ๋ค์ด์ค๋ฉด socket.broadcast๋ฅผ ํตํด ์ด์ ์ด ์ด๋ฏธ ์ ์ํด ์๋ ๋ฉค๋ฒ๋ค์๊ฒ B์ ์์ผ ์์ด๋๋ฅผ ์๋ ค์ค๋ค.
socket.broadcast : ๋ ์์ ์ ์ ์ธํ ๋ชจ๋์๊ฒ ์ด๋ฒคํธ๋ฅผ ๋ณด๋
https://socket.io/docs/v3/broadcasting-events/
offer
A๊ฐ B์๊ฒ ๋ณด๋ธ offer๋ฅผ ๋ฐ์์ socket.to ๋ฅผ ํตํด B์๊ฒ๋ง offer๋ฅผ ์ ๋ฌํด์ค๋ค.
ํด๋ผ์ด์ธํธ์์ ๋ฐ์ ์ฌ๋์ Id๋ฅผ ๋ณด๋ด์ฃผ์ด์ผ ํ๋ค.
answer
B๊ฐ ์์ฑํ answer๋ฅผ ๋ฐ์์ socket.to ๋ฅผ ํตํด A์๊ฒ๋ง ๋ณด๋ด์ค๋ค.
๋ง์ฐฌ๊ฐ์ง๋ก ํด๋ผ์ด์ธํธ์์ ๋ฐ์ ์ฌ๋์ Id๋ฅผ ๋ณด๋ด์ฃผ์ด์ผ ํ๋ค.
ice-candidate
์ด ๊ณผ์ ์ A์ B ๋ชจ๋ ๋ณด๋ด๊ฒ ๋๋ค. icecandidate ์ด๋ฒคํธ๋ก ๋ฑ๋กํด๋์ ์ด๋ฒคํธ ํธ๋ค๋ฌ ์์์ ์ด ์์ผ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ๊ฒ์ด๋ค. ๋ง์ฐฌ๊ฐ์ง๋ก ์ ๋ฌ๋ฐ์ candidate๋ฅผ ๊ฐ๊ฐ ์๋๋ฐฉ์๊ฒ ๋ณด๋ธ๋ค.
์๋ฒ๋ ๋จ์ง ์๊ทธ๋๋ง์ ์ํ ์ฉ๋์ด๊ธฐ ๋๋ฌธ์ ๋งค์ฐ ๊ฐ๋จํ๋ค.
Client ์ฝ๋
const socket = io(env.SERVER_PATH + 'namespace');
const [participants, setParticipants] = useState<Map<string, MediaStream>>(new Map(),);
const myStreamRef = useRef<MediaStream | null>(null);
const myVideoRef = useRef<HTMLVideoElement | null>(null);
const peerConnectionRef = new Map();
const setMyStream = async () => {
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
});
myStreamRef.current = stream;
if (myVideoRef.current) {
myVideoRef.current.srcObject = myStreamRef.current;
}
};
/**
* Peer์ ์ฐ๊ฒฐํ๊ธฐ
* @param peerId ์ฐ๊ฒฐํ ํผ์ด์ Id
* @returns ์๋ก ์์ฑํ peerConnection ๊ฐ์ฒด
*/
const setPeerConnection = (peerId: string) => {
const peerConnection = new RTCPeerConnection({
iceServers: [
{
urls: ['stun:stun.l.google.com:19302'],
},
],
});
/* ์ด๋ฒคํธ ํธ๋ค๋ฌ: Peer์๊ฒ candidate๋ฅผ ์ ๋ฌ ํ ํ์๊ฐ ์์๋ ๋ง๋ค ๋ฐ์ */
peerConnection.addEventListener('icecandidate', (e) => {
const candidate = e.candidate;
if (!candidate) return;
socket.emit(RTC_MESSAGE.ICE_CANDIDATE, {
receiveId: peerId,
candidate,
});
});
/* ์ด๋ฒคํธ ํธ๋ค๋ฌ: peerConnection์ ์๋ก์ด ํธ๋์ด ์ถ๊ฐ๋์ ๊ฒฝ์ฐ ํธ์ถ๋จ */
peerConnection.addEventListener('track', (e) => {
if (participants.has(peerId)) {
return;
}
const [peerStream] = e.streams;
// ์๋ก์ด peer๋ฅผ ์ฐธ์ฌ์์ ์ถ๊ฐ
setParticipants((prev) => {
const newState = new Map(prev);
newState.set(peerId, peerStream);
return newState;
});
});
myStreamRef.current?.getTracks().forEach((track) => {
if (!myStreamRef.current) return;
// ๋ค๋ฅธ ์ ์ ์๊ฒ ์ ๋ฌํด์ฃผ๊ธฐ ์ํด ๋ด ๋ฏธ๋์ด๋ฅผ peerConnection ์ ์ถ๊ฐํ๋ค.
// track์ด myStreamRef.current(๋ด ์คํธ๋ฆผ)์ ์ถ๊ฐ๋จ
peerConnection.addTrack(track, myStreamRef.current);
});
return peerConnection;
};
useEffect(() => {
setMyStream();
/* ์ ์ join */
socket.emit(RTC_MESSAGE.JOIN);
/* ์๋ก ๋ค์ด์จ ์ ์ ์ socketId๋ฅผ ๋ฐ์ */
socket.on(RTC_MESSAGE.JOIN, async (socketId) => {
const peerConnection = setPeerConnection(socketId);
peerConnectionRef.set(socketId, peerConnection);
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
socket.emit(RTC_MESSAGE.OFFER, {
receiveId: socketId,
offer,
});
});
/* offer ๋ฐ๊ธฐ */
socket.on(RTC_MESSAGE.OFFER, async ({ senderId, offer }) => {
const peerConnection = setPeerConnection(senderId);
peerConnectionRef.set(senderId, peerConnection);
await peerConnection.setRemoteDescription(offer);
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
/* answer ์ ์ก */
socket.emit(RTC_MESSAGE.ANSWER, { receiveId: senderId, answer });
});
/* answer ๋ฐ๊ธฐ */
socket.on(RTC_MESSAGE.ANSWER, async ({ senderId, answer }) => {
const peerConnection = peerConnectionRef.get(senderId);
if (!peerConnection) {
return console.log('Peer Connection does not exist');
}
await peerConnection.setRemoteDescription(answer);
});
/* ice candidate */
socket.on(RTC_MESSAGE.ICE_CANDIDATE, ({ senderId, candidate }) => {
const peerConnection = peerConnectionRef.get(senderId);
if (!peerConnection) {
return console.log('Peer Connection does not exist');
}
peerConnection.addIceCandidate(candidate);
});
/* disconnected */
socket.on(RTC_MESSAGE.DISCONNECTED, (senderId) => {
peerConnectionRef.delete(senderId);
setParticipants((prev) => {
const newState = new Map(prev);
newState.delete(senderId);
return newState;
});
});
return () => {
socket.off(RTC_MESSAGE.JOIN);
socket.off(RTC_MESSAGE.OFFER);
socket.off(RTC_MESSAGE.ANSWER);
socket.off(RTC_MESSAGE.ICE_CANDIDATE);
socket.off(RTC_MESSAGE.DISCONNECTED);
};
}, []);
const streams = Array.from(participants.values());
return (
<ul>
{streams.map((stream) => (
<li key={stream.id}>
<Video stream={stream} />
</li>
))}
</ul>
);
๋ชจ๋ํ๋ ์ ์๋ผ์๋๋ฐ ๋ฆฌํฉํ ๋ง์ ๋ค์ ํด์ผํ๋ค ใ
ใ
..
์ฌ์ค ์ด ์ฝ๋๋ ๋ฒ๊ทธ๊ฐ ์ข ์๋ค.. ํ์ฌ ๋ฒ๊ทธ๊ฐ ๋ฐ์ํ ์์น๋ ์ฐพ์์ผ๋ ์์ธ์ ์ฐพ์ง ๋ชปํ ์ํฉ์ด๋ค. offer, answer, candidate ๊ณผ์ ์ ํตํด SDP๋ฅผ ๋ชจ๋ ์ค์ ์ ํด์ฃผ์์ผ๋ peerConnection.addEventListener('track', (e) => {}) ์ด ๋ถ๋ถ์ด ํธ์ถ๋์ง ์๋ ๊ฒ์ ๋ฐ๊ฒฌํ๋ค..
๋ด๊ฐ ์ดํดํ ๋ฐ๋ก๋ track ์ด๋ฒคํธ๋ peerConnection.addTrack ๊ฐ ์คํ๋ ๋ ๋ฐ์ํ๋ ๊ฒ์ด๋ค. ์๋๋ฐฉ์ด peerConnection ์ ๋ณธ์ธ์ ์๋ก์ด track์ ์ถ๊ฐํ๊ฒ ๋๋ฉด track ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๊ฒ ๋๊ณ , ํธ๋ค๋ฌ๊ฐ ์คํ๋๋ฉด์ ์๋์ stream์ ๊ฐ์ ธ์ฌ ์ ์๋ ๊ฒ์ด๋ค.
ํ์ง๋ง track์ด ์ฌ๋ฐ๋ฅด๊ฒ ์ถ๊ฐ๋ ๊ฒ์ ํ์ธํ์ผ๋ ๊ฐ๋ ํธ๋ค๋ฌ์ ๋ค์ด๊ฐ์ง ์๋ ๊ฒฝ์ฐ๊ฐ ์๋๋ผ.. ๋ง์ฝ ๋ฒ๊ทธ๋ฅผ ๋ฐ๊ฒฌํ์ ๋ถ์ด ์๋ค๋ฉด ๋๊ธ๋ก ๊ผญ๊ผญ ์๋ ค์ฃผ์ธ์ ใ
ใ
๐
์๋ฌดํผ ๋ฒ๊ทธ๊ฐ ์๋๋ผ๋ ํ๋ฆ์ ๋ง์ผ๋๊น ๋ค์ ์ค๋ช
์ผ๋ก ๋์๊ฐ์..
setMyStream
๋ด ๋น๋์ค ์คํธ๋ฆผ์ ์ค์ ํ๋ ํจ์๋ค. myStreamRef ์๋ MediaStream ํ์
์ธ ์คํธ๋ฆผ์ ๊ฑธ์ด์ฃผ๊ณ
myVideoRef ์๋ myVideoRef.current.srcObject ์ myStreamRef์ ์คํธ๋ฆผ์ ๋ฃ์ด์ฃผ๋ฉด, video ํ๊ทธ์ ์๋์ฒ๋ผ ๊ฑธ์ด์ฃผ๋ฉด ๋ด ๋น๋์ค๋ฅผ ๋ณผ ์ ์๋ค.
<video ref={myVideoRef} />
setPeerConnection
Peer Connection์ ์ค์ ํด์ฃผ๋ ํจ์๋ค.
- ๋จผ์ , new RTCPeerConnection ๋ก ์๋ก์ด ํผ์ด ๊ฐ์ฒด๋ฅผ ์์ฑํด์ฃผ๊ณ , ์คํด ์๋ฒ๋ฅผ ์ค์ ํด์ค๋ค. ๊ตฌ๊ธ์์ ์ ๊ณตํด์ฃผ๋ ์๋ฒ๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ ๊ฒ ๊ฐ๋ค.
- icecandidate ์ด๋ฒคํธ๋ฅผ ๋ฑ๋กํด์ค๋ค. ์ด ์ด๋ฒคํธ๋ฆฌ์ค๋๋ peer connection์ด candidate๋ฅผ ์ ๋ฌํด์ผ ํ ํ์๊ฐ ์๋ค๊ณ ํ๋จ๋ ๋๋ง๋ค ๋ฐ์ํ๊ฒ ๋๋ค.
- e.candidate ๋ก candidate๋ฅผ ๋ฐ์์จ ํ, ์์ผ emit ์ด๋ฒคํธ๋ฅผ ๊ฑธ์ด์ค๋ค. ๋ด candidate๋ฅผ ์๋์๊ฒ ๋ณด๋ด๋ ๊ณผ์ ์ด๋ค.
- track ์ด๋ฒคํธ๋ฅผ ๋ฑ๋กํด์ค๋ค. ์ด ์ด๋ฒคํธ ๋ฆฌ์ค๋๋ ์์์ ๋งํ๋ ๊ฒ์ฒ๋ผ ์๋๋ฐฉ์ด peerConnection ์ ๋ณธ์ธ์ ์๋ก์ด track์ ์ถ๊ฐํ๊ฒ ๋๋ฉด ๋ฐ์ํ๊ฒ ๋๋ค.
- e.streams๋ก ์๋์ stream์ ๋ฐ์์จ ํ, UI์ ๋ฟ๋ ค์ฃผ๊ธฐ ์ํด useState๋ก ๊ด๋ฆฌ์ค์ธ participants์ ์๋ก์ด ์คํธ๋ฆผ์ผ๋ก ์ถ๊ฐํด์ค๋ค.
- getTracks() : ์ข
๋ฅ์ ๊ด๊ณ์์ด ์คํธ๋ฆผ์ ํธ๋ set์ ์๋ ๋ชจ๋ MediaStreamTrack ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ค.
- ๋ค๋ฅธ ์ ์ ์๊ฒ ๋ด ์คํฌ๋ฆผ์ ์ ๋ฌํด์ฃผ๊ธฐ ์ํด peerConnection์ ๋ด ์คํธ๋ฆผ์ addTrack์ผ๋ก ๋ด๋๋ค.
์ด์ useEffect ์์ผ๋ก ๋ค์ด์๋ณด์.
- ๋จผ์ setMyStream ์ ํธ์ถํด ๋ด ๋ฏธ๋์ด๋ฅผ ๋จผ์ ์ค์ ํด์ค๋ค.
- join ์ด๋ฒคํธ๋ก ๋ด๊ฐ ๋ค์ด์๋ค๋ ๊ฒ์ ์ด๋ฏธ ์ ์ํด ์๋ ๋ชจ๋ ์ ์ ๋ค์๊ฒ ์๋ฆฐ๋ค.
- socket.on(RTC_MESSAGE.JOIN) : ๊ทธ๋ผ ๋๊ตฐ๊ฐ๋ ์ด ์ด๋ฒคํธ๋ฅผ ๋ฐ๊ฒ ๋ ๊ฒ์ด๋ค.
- ๋๊ตฐ๊ฐ ๋ค์ด์์ผ๋ ์๋ก์ด peer connection์ ์์ฑํด์ฃผ์ด์ผ ํ๋ค.
- peerConnectionRef์ socketId๋ฅผ ํค ๊ฐ์ผ๋ก peer connection์ ์ ์ฅํด๋์. socketId์ ์๋์ ๋งบ์ connection์ด ์ด๊ฑฐ๋ค! ๋ผ๋ ๊ฒ์ ๊ตฌ๋ถํด์ฃผ๊ธฐ ์ํจ์ด๋ค.
- answer์ candidate๋ฅผ ๋ฐ์ ๋, ์๋์ ๋งบ์ peer connection์ ๋ฐ์ answer์ candidate๋ฅผ ์ถ๊ฐํด์ฃผ์ด์ผ ํ๊ธฐ ๋๋ฌธ์ peerConnection์ ์ ์ฅํด๋ ํ์๊ฐ ์๋ค.
- socket.on(RTC_MESSAGE.OFFER) : ์๋๋ฐฉ์ offer๋ฅผ ๋ฐ๋ ๋ถ๋ถ์ด๋ค.
- ์ฌ๊ธฐ์ ์ค์ํ ๊ฒ์, offer๋ฅผ ๋ฐ์ผ๋ฉด ์๋ก์ด peerConnection์ ์์ฑํด์ผ ํ๋ค๋ ๊ฒ์ด๋ค.
- Joinํ ๋ ์์ฑํ peer connection์ A์ ๊ฒ์ด๊ณ , offer๋ฅผ ๋ฐ๋ ์ด๋ฒคํธ๋ B๊ฐ ๋ฐ์ ๊ฒ์ด๋ฏ๋ก B๋ peerConnection์ ์์ฑํด์ผ ํ๋ค
- โ Peer๋ 1๋1๋ก ๋งบ๋ ๊ฑฐ๊ธฐ ๋๋ฌธ์ A,B,C๊ฐ ์์ ๋ A-B, A-C, B-C ์ด๋ ๊ฒ Peer Connection์ด ์์ฑ๋์ด์ผ ํ๋ค. Peer Connection ๊ฐ์ฒด๋ ์ฌ์ค ์ 6๊ฐ์ธ ์ ์ด๋ค.
- ๊ฐ์์ Peer Connection์ ์๋์ ์ ๋ณด๋ฅผ ์ ์ฅํด๋์ด์ผ ํ๋ฏ๋ก..
- ์ด์ , setRemoteDescription ๋ก ๋ฐ์ offer ๋ฅผ ๋ฆฌ๋ชจํธ์ ๋ฃ์ด๋๋ค.
- ์ด์ ์ ๋ฌํด์ค answer๋ฅผ ์์ฑํ๊ณ answer๋ ๋ด ๊ฒ์ด๋ฏ๋ก local description์ ์ค์ ํ socket์ answer ์ด๋ฒคํธ๋ก ๋ณด๋ด์ค๋ค.
- socket.on(RTC_MESSAGE.ANSWER) : ์๋๋ฐฉ์ answer๋ฅผ ๋ฐ๋ ๋ถ๋ถ์ด๋ค.
- ํ๋ผ๋ฏธํฐ๋ก ๋ณด๋ธ ์ฌ๋์ socketId๋ฅผ ๋ฐ์์ผ ํ๋ค. ๊ธฐ์กด์ on(โjoinโ) ์์ ์ ์ฅํด๋์๋ peer connection์์ socketId๋ฅผ ๊ธฐ์ค์ผ๋ก ๋๊ตฌ์ ๋งบ์ ์ปค๋ฅ์ ์ธ์ง ์ฐพ์ ํ, ๊ทธ ์ปค๋ฅ์ ์ ๋ฆฌ๋ชจํธ๋ก answer๋ฅผ ์ถ๊ฐํด์ค๋ค.
- socket.on(RTC_MESSAGE.ICE_CANDIDATE) : ์๋๋ฐฉ์ candidate๋ฅผ ๋ฐ๋ ๋ถ๋ถ์ด๋ค.
- ๋ง์ฐฌ๊ฐ์ง๋ก ํ๋ผ๋ฏธํฐ๋ก ๋ณด๋ธ ์ฌ๋์ socketId๋ฅผ ๋ฐ๊ณ , ๋๊ตฌ์ ๋งบ์ ์ปค๋ฅ์ ์ธ์ง ์ฐพ์ ํ addIceCandidate ๋ก candidate๋ฅผ ์ถ๊ฐํด์ค๋ค.
- socket.on(RTC_MESSAGE.DISCONNECTED) : ์๋๋ฐฉ์ด ๋๊ฐ์ ๊ฒฝ์ฐ peer connection๊ณผ participants์์ ์๋๋ฐฉ์ ์ญ์ ํ๋ค.
๋ด๊ฐ ์ดํดํ ๋ด์ฉ์ผ๋ก ์ฃผ์ ๋ฆฌ์ฃผ์ ๋ฆฌ ์ ๋ฆฌํด๋ณด์๋๋ฐ ๋๋ง ์๋ ์ธ์ด๋ก ์ ๋ฆฌํ ๊ฑฐ ๊ฐ๊ธฐ๋ ํ๊ณ .. ๋๋ฆ ๋์ค์ ๋ด๋ ์ฝ๊ฒ ์ดํดํ ์ ์๋๋ก ์์ธํ๊ฒ ์ ์ด๋จ๋ค ..!๐
๋
ธ์
์ ์ ๋ฆฌํ ๋งํฌ๋ ์ฒจ๋ถํ๋ค.
https://saber-ash-4ab.notion.site/React-TypeScript-WebRTC-1defea4ed489417fb4ce9c7a047291f7
React+TypeScript+WebRTC ๊ตฌํํ๊ธฐ
๋ถ์คํธ์บ ํ ์น๋ชจ๋ฐ์ผ ๋ฉค๋ฒ์ญ ๊ทธ๋ฃน ํ๋ก์ ํธ์์ WebRTC๋ฅผ ๋ค๋ฃจ๊ฒ ๋๋ค. ๋ณธ๊ฒฉ์ ์ธ ์์ ์ ๋ค์ด๊ฐ๊ธฐ ์์ WebRTC์ ๋ํด ์ ํ ๋ฌด์งํ ์ํ์๊ธฐ์ ๋ฏธ๋ฆฌ ํ์ต์ ํด๋ณด์๋ค
saber-ash-4ab.notion.site
๋ ํผ๋ฐ์ค
https://dareun.github.io/webRTCแ
แ
ณแฏ-แแ
ตแแ
ญแผแแ
กแซ-แแ
ชแแ
กแผแแ
ฌแแ
ด-แแ
ขแแ
กแฏ
https://velog.io/@gojaegaebal/210307-๊ฐ๋ฐ์ผ์ง90์ผ์ฐจ-์ ๊ธ-๋๋ง์-๋ฌด๊ธฐ-ํ๋ก์ ํธ-WebRTC๋-๋ฌด์์ธ๊ฐ2-ICE-SDP-Signalling
https://medium.com/@hyun.sang/webrtc-webrtc๋-43df68cbe511
https://kbs77.tistory.com/102
https://surprisecomputer.tistory.com/14
https://chuun92.tistory.com/39
https://web.dev/webrtc-infrastructure/