-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
merge: [FE] 프론트엔드 WebRTC 구현
- Loading branch information
Showing
11 changed files
with
299 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
export const SOCKET_RECEIVE_EVENT = { | ||
ALL_USERS: 'all_users', | ||
OFFER: 'getOffer', | ||
ANSWER: 'getAnswer', | ||
CANDIDATE: 'getCandidate', | ||
USER_EXIT: 'user_exit', | ||
}; | ||
|
||
export const SOCKET_EMIT_EVENT = { | ||
OFFER: 'offer', | ||
ANSWER: 'answer', | ||
JOIN_ROOM: 'join_room', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export const SOCKET_URL = `ws://localhost:4000`; | ||
export const API_URL = `http://localhost:4000`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,48 @@ | ||
import { useEffect, useRef, useState } from 'react'; | ||
import { useParams } from 'react-router-dom'; | ||
import { createSocket } from '@/services/Socket'; | ||
import { StreamObject, streamModel } from '@/stores/StreamModel'; | ||
import { SOCKET_EMIT_EVENT } from '@/constants/socketEvents'; | ||
|
||
function StreamVideo({ stream }: { stream: MediaStream }) { | ||
const videoRef = useRef<HTMLVideoElement>(null); | ||
useEffect(() => { | ||
if (!videoRef.current) return; | ||
videoRef.current.srcObject = stream; | ||
}, []); | ||
|
||
return <video key={stream.id} ref={videoRef} muted autoPlay />; | ||
} | ||
|
||
export default function Room() { | ||
return <div>Room</div>; | ||
const videoRef = useRef<HTMLVideoElement>(null); | ||
const [streamList, setStreamList] = useState<StreamObject[]>([]); | ||
const { roomId } = useParams(); | ||
|
||
useEffect(() => { | ||
navigator.mediaDevices | ||
.getUserMedia({ | ||
video: true, | ||
}) | ||
.then((video) => { | ||
(videoRef.current as HTMLVideoElement).srcObject = video; | ||
streamModel.subscribe(() => { | ||
setStreamList(() => streamModel.getStream()); | ||
}); | ||
const socket = createSocket(video); | ||
socket.connect(); | ||
socket.emit(SOCKET_EMIT_EVENT.JOIN_ROOM, { | ||
room: roomId, | ||
}); | ||
}); | ||
}, []); | ||
|
||
return ( | ||
<div> | ||
<video ref={videoRef} muted autoPlay /> | ||
{streamList.map(({ id, stream }) => { | ||
return <StreamVideo key={id} stream={stream} />; | ||
})} | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { Socket } from 'socket.io-client/debug'; | ||
import { streamModel } from '@/stores/StreamModel'; | ||
|
||
const createPeerConnection = (socketId: string, socket: Socket, localStream: MediaStream) => { | ||
const RTCConnection = new RTCPeerConnection({ | ||
iceServers: [{ urls: 'stun:stun.1.google.com:19302' }], | ||
}); | ||
if (localStream) { | ||
localStream.getTracks().forEach((track) => { | ||
RTCConnection.addTrack(track, localStream); | ||
}); | ||
} | ||
RTCConnection.addEventListener('icecandidate', (e) => { | ||
if (e.candidate != null) | ||
socket.emit('candidate', { | ||
candidate: e.candidate, | ||
candidateSendId: socket.id, | ||
candidateReceiveId: socketId, | ||
}); | ||
}); | ||
|
||
RTCConnection.addEventListener('track', (e) => { | ||
streamModel.addStream({ id: socketId, stream: e.streams[0] }); | ||
}); | ||
|
||
return RTCConnection; | ||
}; | ||
|
||
export default createPeerConnection; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { io } from 'socket.io-client/debug'; | ||
import { SOCKET_URL } from '@/constants/urls'; | ||
import createPeerConnection from './RTC'; | ||
import { SOCKET_EMIT_EVENT, SOCKET_RECEIVE_EVENT } from '@/constants/socketEvents'; | ||
import { streamModel } from '@/stores/StreamModel'; | ||
|
||
export const RTCConnectionList: Record<string, RTCPeerConnection> = {}; | ||
|
||
export const createSocket = (localStream: MediaStream) => { | ||
const socket = io(SOCKET_URL); | ||
|
||
socket.on(SOCKET_RECEIVE_EVENT.ALL_USERS, async (data: { users: Array<{ id: string }> }) => { | ||
data.users.forEach((user) => { | ||
RTCConnectionList[user.id] = createPeerConnection(user.id, socket, localStream); | ||
}); | ||
|
||
Object.entries(RTCConnectionList).forEach(async ([key, value]) => { | ||
const offer = await value.createOffer({ | ||
offerToReceiveAudio: true, | ||
offerToReceiveVideo: true, | ||
}); | ||
|
||
await value.setLocalDescription(new RTCSessionDescription(offer)); | ||
socket.emit(SOCKET_EMIT_EVENT.OFFER, { | ||
sdp: offer, | ||
offerSendId: socket.id, | ||
offerReceiveId: key, | ||
}); | ||
}); | ||
}); | ||
|
||
socket.on(SOCKET_RECEIVE_EVENT.OFFER, async (data: { sdp: RTCSessionDescription; offerSendId: string }) => { | ||
RTCConnectionList[data.offerSendId] = createPeerConnection(data.offerSendId, socket, localStream); | ||
await RTCConnectionList[data.offerSendId].setRemoteDescription(new RTCSessionDescription(data.sdp)); | ||
const answer = await RTCConnectionList[data.offerSendId].createAnswer({ | ||
offerToReceiveAudio: true, | ||
offerToReceiveVideo: true, | ||
}); | ||
|
||
await RTCConnectionList[data.offerSendId].setLocalDescription(new RTCSessionDescription(answer)); | ||
socket.emit(SOCKET_EMIT_EVENT.ANSWER, { | ||
sdp: answer, | ||
answerSendId: socket.id, | ||
answerReceiveId: data.offerSendId, | ||
}); | ||
}); | ||
|
||
socket.on(SOCKET_RECEIVE_EVENT.ANSWER, (data: { sdp: RTCSessionDescription; answerSendId: string }) => { | ||
RTCConnectionList[data.answerSendId].setRemoteDescription(new RTCSessionDescription(data.sdp)); | ||
}); | ||
|
||
socket.on(SOCKET_RECEIVE_EVENT.CANDIDATE, (data: { candidate: RTCIceCandidateInit; candidateSendId: string }) => { | ||
if (RTCConnectionList[data.candidateSendId].remoteDescription) | ||
RTCConnectionList[data.candidateSendId].addIceCandidate(new RTCIceCandidate(data.candidate)); | ||
}); | ||
|
||
socket.on(SOCKET_RECEIVE_EVENT.USER_EXIT, (data: { id: string }) => { | ||
RTCConnectionList[data.id].close(); | ||
delete RTCConnectionList[data.id]; | ||
streamModel.removeStream(data.id); | ||
}); | ||
|
||
return socket; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import Observer from '@/utils/Observer'; | ||
|
||
export interface StreamObject { | ||
id: string; | ||
stream: MediaStream; | ||
} | ||
|
||
export class StreamModel extends Observer { | ||
streams: StreamObject[]; | ||
|
||
constructor() { | ||
super(); | ||
this.streams = []; | ||
} | ||
|
||
getStream() { | ||
return [...this.streams]; | ||
} | ||
|
||
addStream(stream: StreamObject) { | ||
this.streams.push(stream); | ||
this.notify(); | ||
} | ||
|
||
removeStream(id: string) { | ||
this.streams = this.streams.filter((stream) => stream.id !== id); | ||
this.notify(); | ||
} | ||
} | ||
|
||
export const streamModel = new StreamModel(); |
Oops, something went wrong.