WebRTC 구현 예제1

dragonappear·2021년 11월 26일
0

WebRTC

목록 보기
2/2

rtc.html

<html>
<head>
    <meta charset="utf-8" />
    <title>WebRtc tutorial</title>
</head>

<body>
    <div>
        <video id="localVideo" autoplay width="480px"></video>
        <video id="remoteVideo" width="480px" autoplay></video>
    </div>

    <script src="/socket.io/socket.io.js"></script>
    <script src="./rtc.js"></script>
</body>

</html>
  • 화상통화를 구현하기 위해서 내 화면과 상대방의 화면 뷰 생성
  • 시그널링과정에 필요한 socket.io cdn 설정

rtc.js(내 영상 정보 가져오기)

let localVideo = document.getElementById("localVideo");
let remoteVideo = document.getElementById("remoteVideo");
let localStream;

navigator.mediaDevices
    .getUserMedia({
        video: true,
        audio: false,
    })
    .then(gotStream)
    .catch((error) => console.error(error));
    
function gotStream(stream) {
    console.log("Adding local Stream");
    localStream = stream;
    localVideo.srcObject = stream;
    sendMessage("got user media");
    if(isInitiator){
        maybeStart();
    }
}
  • mediaDevice 객체의 getUserMedia 함수를 통해 사용자의 미디어 데이터를 스트림으로 받아온다.
  • localStream과 localVideo에 출력할 영상을 본인 카메라로 지정.

sendMessage()

function sendMessage(message){
    console.log('Client sending message: ',message);
    socket.emit('message',message);
}
  • 시그널링 서버로 소켓정보를 전송하는 함수이다.
  • 시그널링 서버, 다른 Peer로 데이터를 전송하는 함수라고 보면 된다.

RTC Peer 연결하기

function createPeerConnection(){
    try{
        pc = new RTCPeerConnection(null);
        pc.onicecandidate = handleIceCandidate;
        pc.onaddstream = handleRemoteStreamAdded;
        console.log("Created RTCPeerConnection");
    }   catch (e) {
         alert("cannot create RTCPeerConnection object");
         return;
    }
}

function handleIceCandidate(event){
    console.log("iceCandidateEvent", event);
    if(event.candidate){
        sendMessage({
            type:"candidate",
            label: event.candidate.sdpMLineIndex,
            id: event.candidate.sdpMid,
            candidate: event.candidate.candidate,
        });
    } else{
        console.log("end of candidates");
    }
}

function handleRemoteStreamAdded(event){
    console.log("remote stream added");
    remoteStream = event.stream;
    remoteVideo.srcObject = remoteStream;
}

function handleCreateOfferError(event){
    console.log("createOffer() error: ", event);
}
  • createPeerConnection: RTCPeerConnection에 대한 객체를 형성
  • iceCandidate: 데이터 교환을 할 대상의 EndPoint 정보라고 생각하면 된다. 따라서 iceCandidat 할 대상이 생긴다면 handleIceCandidate를 실행한다. 이 부분은 시그널링 서버로 넘겨주어서 상대방 Peer가 내 Stream을 연결할 수 있도록 한다.
  • 연결된 Peer은 handleRemoteStreamAdded를 통해서 remoteVideo 뷰에 띄운다.

maybeStart

function maybeStart() {
    console.log(">>MaybeStart() : ", isStarted, localStream, isChannelReady);
    if (!isStarted && typeof localStream !== "undefined" && isChannelReady) {
        console.log(">>>>> creating peer connection");
        createPeerConnection();
        pc.addStream(localStream);
        isStarted = true;
        console.log("isInitiator : ", isInitiator);
        if (isInitiator) {
          doCall();
        }
     }else{
        console.error('maybeStart not Started!');
    }
}
  • maybeStart는 자신의 RTCPeerConnection을 초기화 하고 상대방의 RTCPeerConnection과 연결하는 함수이다.
  • 실제로 연결이 되면 doCall 함수를 실행시켜 데이터를 주고 받는다.
function doCall() {
    console.log("Sending offer to peer");
    pc.createOffer(setLocalAndSendMessage, handleCreateOfferError);
}

function doAnswer() {
    console.log("Sending answer to peer");
    pc.createAnswer().then(
    setLocalAndSendMessage,
    onCreateSessionDescriptionError
  );
}

function setLocalAndSendMessage(sessionDescription) {
    pc.setLocalDescription(sessionDescription);
    sendMessage(sessionDescription);
}
  • doCalldoAnswer를 통해서 Description을 교환한다.
  • 이 과정을 통해서 내 화상 정보가 상대방에게, 상대방의 화상정보가 내 뷰에 출력할 수 있게 되는 것이다.
let pcConfig = {
    'iceServers': [{
        'urls': 'stun:stun.l.google.com:19302'
      }]
}

socket.on('message', (message)=>{
  console.log('Client received message :',message);
  if(message === 'got user media'){
    maybeStart();
  }else if(message.type === 'offer'){
    if(!isInitiator && !isStarted){
      maybeStart();
    }
    pc.setRemoteDescription(new RTCSessionDescription(message));
    doAnswer();
  }else if(message.type ==='answer' && isStarted){
    pc.setRemoteDescription(new RTCSessionDescription(message));
  }else if(message.type ==='candidate' &&isStarted){
    const candidate = new RTCIceCandidate({
      sdpMLineIndex : message.label,
      candidate:message.candidate
    });

    pc.addIceCandidate(candidate);
  }
})
  • 위는 소켓통신에 대한 부분을 정의해서 데이터 교환을 올바르게 할 수 있게 해준다.
const http = require('http');
const os = require('os');
const socketIO = require('socket.io');
const nodeStatic = require('node-static');

let fileServer = new(nodeStatic.Server)();
let app = http.createServer((req,res)=>{
    fileServer.serve(req,res);
}).listen(8080);

let io = socketIO.listen(app);
io.sockets.on('connection',socket=>{
    function log() {
        let array = ['Message from server:'];
        array.push.apply(array,arguments);
        socket.emit('log',array);
    }

    socket.on('message',message=>{
        log('Client said : ' ,message);
        socket.broadcast.emit('message',message);
    });

    socket.on('create or join',room=>{
        let clientsInRoom = io.sockets.adapter.rooms[room];
        let numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0;
        log('Room ' + room + ' now has ' + numClients + ' client(s)');
        
        if(numClients === 0){
            console.log('create room!');
            socket.join(room);
            log('Client ID ' + socket.id + ' created room ' + room);
            socket.emit('created',room,socket.id);
        }
        else if(numClients===1){
            console.log('join room!');
            log('Client Id' + socket.id + 'joined room' + room);
            io.sockets.in(room).emit('join',room);
            socket.join(room);
            socket.emit('joined',room,socket.id);
            io.sockets.in(room).emit('ready');
        }else{
            socket.emit('full',room);
        }
    });
});
  • 위는 signaling 서버에 대한 구현으로 room이 없다면 생성하고, room이 이미 존재한다면 room에 참가하여 내 뷰를 상대방에게 중개해주는 그런 역할을 한다.

끝났다. 서버 실행를 실행시켜보자..


출처:

https://velog.io/@ehdrms2034/WebRTC-%EC%9B%B9%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EB%A1%9C-%ED%99%94%EC%83%81-%EC%B1%84%ED%8C%85%EC%9D%84-%EB%A7%8C%EB%93%A4-%EC%88%98-%EC%9E%88%EB%8B%A4%EA%B3%A0

0개의 댓글