// 연결 후보 전달을 위한 server 코드
@SubscribeMessage("icecandidate")
handleIcecandidateMessage(client: Socket, { candidate, selectedRoom }){
client.broadcast.to(selectedRoom).emit("icecandidate", {
userId: client.id,
candidate });
}
목적
web Rtc 를 이용하여 클라이언트 간 1 : 1 영상 및 오디오 연결을 진행한다.
이용한 기능들
Front : html , javascript, socket.io
back : Nest js (gateway)
과정 및 코드
클라이언트 간 1 : 1 연결은 아래와 같은 순서대로 진행된다.
연결이 완성되기 전 까지의 소통은 socket 서버를 거쳐 통신한다.
- 로컬 사용자의 비디오 및 오디오 정보를 받아오기
async function start() {
try {
localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
localVideo.srcObject = localStream;
} catch (error) {
console.error('Error accessing media devices:', error);
}
}
사용자 연결 이전에 구현되어야 있어야 한다. 로컬 비디오 / 오디오 정보를 가져오는 것은 다른 코드에 비해서 느릴 수 있으므로
await 처리 하여 미디어 정보를 받아온 이후에 진행할 수 있도록 조치한다.
Peer 연결을 위한 기본 설정
let peerInfo;
const makePeerConnect = async(userId) => {
peerInfo.peerConnection = new RTCPeerConnection({
"iceServers": [{
urls: 'stun:stun.l.google.com:19302'
}]
});
// answer 연결이 끝난 후 후보 교환
peerInfo.peerConnection.addEventListener("icecandidate", icecandidate);
// 후보 교환이 끝난 후 비디오 정보 추가
peerInfo.peerConnection.addEventListener("addstream", addStream);
for (let track of localStream.getTracks()) {
await peerInfo[userId].peerConnection.addTrack(track, localStream);
}
};
// 연결 후보 교환 함수
const icecandidate = async(data) => {
try {
if (data.candidate) {
webRtcSocket.emit("icecandidate", {
candidate : data.candidate,
selectedRoom,
});
}
} catch (error) {
console.log("send candidate error : ", error);
}
};
// 상대 영상 & 비디오 추가
const addStream = (data) => {
let videoArea = document.createElement("video");
// 연결 시 자동 재생
videoArea.autoplay = true;
videoArea.srcObject = data.stream;
let container = document.getElementById("root");
container.appendChild(videoArea);
};
아래에서 설명하는 유저 간 offer & answer 교환이 완료된 이후에
서버를 거치지 않고 클라이언트 간 직접 연결을 위한 후보지를 교환하고, 비디오를 추가하는 코드를 먼저 만들어주어야 한다.
아래에서 설명하는 유저 간 answer 교환이 끝난 후 5번, 6번 과정은 위 코드에서 실행된다.
- 방 입장 하기 ( 기존 방 입장한 유저에게 알리기)
// frontend
const share = async() => {
webRtcSocket.emit('join', selectedRoom);
}
// backend
@SubscribeMessage("join")
async handleJoinMessage(client: Socket, roomId: any){
client.join(roomId);
client.broadcast.to(roomId).emit("enter", { userId: client.id });
}
클라이언트가 방 입장 (연결) 의사를 밝히면, 서버는 클라이언트를 소켓 룸으로 넣어주고,
기존 룸에 있던 유저에게 다른 유저가 입장했음을 알린다.
연결 시나리오
1. 최초 사용자 (1번)가 특정 방에 먼저 진입한다.
- 방에 있는 사용자에게 입장을 알리지만 아무도 없었기 때문에 아무 반응이 없다.
2. 다음 사용자 (2번)가 방에 입장한다.
- 2번 사용자 입장 시 : 서버 -> 1번 사용자에게 2번 사용자가 입장했다는 메세지를 보낸다.
3. 1번 사용자 -> 2번 사용자 : 연결을 위한 offer 를 보낸다.
- offer 작성 후 1번 사용자 : local 정보에 자신의 영상 정보를 넣음
// offer 전달을 위한 front 코드 : 1 -> server
webRtcSocket.on('enter', async({
userId
}) => {
try{
// 연결 정보 생성
await makePeerConnect();
// offer 생성
const offer = await peerInfo.peerConnection.createOffer();
// Local des에 offer 등록
await peerInfo.peerConnection.setLocalDescription(offer);
// offer 전달
webRtcSocket.emit("offer", { offer, selectedRoom });
}
} catch (error){
console.log("send offer error : ", error);
}
});
// 서버 -> 2 번 유저 전달을 위한 server 코드
@SubscribeMessage("offer")
handleOfferMessage(client: Socket, { offer, selectedRoom }){
client.broadcast.to(selectedRoom).emit("offer", { userId: client.id, offer });
}
4. 2번 사용자 -> 1번 사용자 : offer를 확인하고 answer를 보낸다.
- offer 수신 시 2번 사용자 : offer 로 받은 정보를 remote 정보에 넣음
- answer 작성 후 2번 사용자 : local에 자신의 영상 정보를 넣음
// answer 전달을 위한 fornt 코드
webRtcSocket.on("offer", async({
userId,
offer
}) => {
try{
// 연결 정보를 생성한다.
await makePeerConnect();
// 상대방의 offer를 내 remote 에 추가
await peerInfo.peerConnection.setRemoteDescription(offer);
// answer 생성
const answer = await peerInfo.peerConnection.createAnswer(offer);
// answer 를 내 local 로 저장
await peerInfo.peerConnection.setLocalDescription(answer);
// 서버를 통해 1로 전달
webRtcSocket.emit("answer", {
answer,
selectedRoom,
});
} catch (error) {
console.log("receive offer and send answer error : ",error);
}
});
// answer 전달을 위한 server 코드
@SubscribeMessage("answer")
handleAnswerMessage(client: Socket, {answer, selectedRoom}){
client.broadcast.to(selectedRoom).emit("answer", {
userId: client.id,
answer,
});
}
5. 1번 사용자 <-> 2번 사용자 : 클라이언트 간 연결을 위한 후보를 주고 받음
후보 교환을 위한 socket.emit 은 이 페이지 위쪽에서 peer 연결을 위한 함수 구성에 있음.
6. 1번 사용자 <-> 2번 사용자 : video 태그를 만든 후 주고 받은 영상 정보를 넣음
'Etc' 카테고리의 다른 글
[Vscode] 저장 시 Prettier 적용하기 (1) | 2024.11.19 |
---|---|
[WebRTC] 웹 RTC 구현해보기 - N : N 연결 (1) | 2023.08.07 |
[ETC] vscode 환경에서 ssh 이용하기 (0) | 2023.05.20 |
[DataBase] SQL ? NoSQL? 그리고 ORM (2) | 2023.05.11 |
[Nodejs] package.json 이란? (0) | 2023.05.02 |