Etc

[WebRTC] 웹 RTC 구현해보기 - 1 : 1 연결

천우산__ ㅣ 2023. 7. 15. 16:59

// 연결 후보 전달을 위한 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번)가 특정 방에 먼저 진입한다.

1번 사용자 입장

  - 방에 있는 사용자에게 입장을 알리지만 아무도 없었기 때문에 아무 반응이 없다.

 

2.  다음 사용자 (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' 카테고리의 다른 글

[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
REST? RESTful?  (0) 2023.04.20