1. 화상 채팅 예제로 익히는 WebRTC - 기본 예제
  2. 화상 채팅 예제로 익히는 WebRTC - 기능 추가
  3. WebRTC 외부에서 사용하기 – coturn

 

여기에서는 앞서 정리한 구글 WebRTC 예제에 다음 4가지 사항을 추가한다.

  1. SSL 적용
  2. 서버 기능 보강
  3. 오디오 추가
  4. 조금 나은 디자인

기존 예제에 추가된 소스는 GitHub에서 받을 수 있다.

 

먼저, NodeJS에 SSL을 적용하기 위해, SSL을 생성한다.

SSL은 HTTP를 암호화 해서 사용하기 위한 것으로 자세한 것은 찾아보길 바라고,

제대로 된 SSL은 유료로 구매해야 하지만

여기서는 개발을 위해 제공되는 OpenSSL로 간단하게 만들어서 사용한다.

OpenSSL을 설치하는 방법은 여기에서 정리하지 않으니 찾아보고 설치한다.



OpenSSL로 개인키(private.pem) 파일을 생성한다.

         openssl genrsa 1024 > private.pem

그리고, 개인키와 쌍이 되는 공개키를(public.pem) 다음과 같이 생성한다.

         openssl req -x509 -new -key private.pem > public.pem

탐색기로 개인키(private.pem)와 공개키(public.pem) 파일이 생성된 것을 확인 할 수 있다.

 

공개키를 생성하면, 국가, 도시 등의 여러 가지 값을 입력하라고 한다.

개발용 SSL이니 아무 값이나 입력하면 된다.

 참고: 여기서는 간단하게 키만 생성해서 사용하고, 제대로된 사설 인증서를 만드는 방법은 검색해서 익혀두면 유용하다.

 

생성한 인증서 파일을 구글 예제 폴더에 복사한다.

index.js 파일과 같은 폴더에 복사하고, index.js 파일을 적당한 편집기로 수정한다.

index.js는 다음과 같이 8080 포트의 http로 접속하도록 작성되어 있다.

var http = require('http');
var socketIO = require('socket.io');

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

이 내용을 다음과 같이 앞서 생성한 키 파일을 이용해서 https, 3000포트로 접속하도록 수정한다.

//var http = require('http');
var socketIO = require('socket.io');

const https = require('https');
const fs = require('fs');

const options = {
  key: fs.readFileSync('./private.pem'),
  cert: fs.readFileSync('./public.pem')
};

var fileServer = new(nodeStatic.Server)();
let app = https.createServer(options, (req,res)=>{
  fileServer.serve(req, res);
}).listen(3000);

console.log('Started chating server...');

기존에는 http://127.0.0.1:8080으로 접속했고,

이제는 https://127.0.0.1:3000으로 접속하면 된다.

그리고, "Started chating server..." 문장을 출력해서 서버가 가동된 사실을 알 수 있도록 한다.

주의: 실제로는 127.0.0.1로 접속하지 않고, 서버(PC)에 할당된 사설 IP(예: 192.168.35.154)나 공인 IP로 접속한다.

 

콘솔창에서 node index.js를 입력해서 실행하면, 다음과 같이 서버가 실행된 것을 알 수 있다.

PC나 휴대폰의 크롬 브라우저로 https://서버IP:3000으로 접속하면 다음과 같은 경고창이 실행된다.

정상 SSL이 아니라는 의미로, 웹 페이지 하단의 [고급] 버튼을 선택한다.

다음 페이지에서 "192.168.35.154(안전하지 않음)(으)로 이동"을 선택해서 서버 접속을 계속한다.

다음과 같이 PC와 휴대폰으로 접속하면, 비디오(만) 화상채팅이 실행된다.

 

두 번째 예제 보강 작업으로 서버 기능을 보강한다.

구글 예제에서 socket.io를 이용해 구현한 네트워크 연결 부분은 아주 기초적이고 단순한 구조로

두 대의 클라이언트에서 실행된 WebRTC가 서로 RTCPeerConnection로 연결할 수 있도록 도와 주는 역할을 한다(signaling).

 

현재의 예제는 채팅을 종료(웹브라우저를 닫거나 갱신-F5)하면 다시 채팅을 할 수 없다.

서버를 재가동하고 다시 접속해야만 한다.

주의: 서버 파일(index.js) 대부분과 클라이언트(main.js)의 네트워크 연결 관련 부분의 코드는 모두 socket.io로 작성되었다. socket.io에 대해서 잘 모른다면 socketio nodejs로 검색해서 관련 자료를 읽어보는 것이 좋다.

socketio nodejs 채팅방으로 검색 결과의 예제와 여기서 사용된 예제가 비슷한 것이 많으니 관련 내용을 읽어보고 구글 예제를 보강해 보는 것도 좋다.

 

다음 코드는 클라이언트의 각종 message를 받아서 전달(broadcast)하는 기존 코드(회색)에

message가 bye이면 채팅방(foo)을 비우는(leave) 코드를 추가한 것이다.

즉, 화상 채팅 중에 한 사람이라도 채팅방을 나가면(bye) 채팅방을 비우도록 한 것이다.

  socket.on('message', function(message) {
    log('Client said: ', message);
    
    if (message==="bye" && socket.rooms['foo']) {
        io.of('/').in('foo').clients((error, socketIds) => {
            if (error) throw error;

            socketIds.forEach(socketId => {
                io.sockets.sockets[socketId].leave('foo');
            });
        });
    }
    socket.broadcast.emit('message', message);
  });

다음 코드에서 확인 할 수 있지만,

다시 빈 채팅방에 입장한 순서로 연결해서 화상 채팅을 하게 된다.

참고: bye라는 메시지는 클라이언트(main.js 파일)에서 웹 브라우저를 닫거나 해당 채팅 웹 페이지를 떠날 때(onbeforeunload 이벤트시) 채팅 종료를 알리도록 작성된 메시지이다.

채팅방 이름인 foo는 클라이언트에 변수명으로 고정되어 있다 (구글 예제에서 그렇게 작성) .

제대로 된 채팅 프로그램이면 채팅방 이름이 bye란 메시지와 같이 전송되게 했을 것이다.

추가로 구현하면 예제를 이해하는데 도움이 될 것이다.

 

추가적으로 create or join 메시지의 기존 코드에 다음과 같이(빨간색),

첫 사용자가 입장하면 채팅방을 생성한다는 의미로 created,

두번째 사용자가 입장했다는 의미로 joined를 콘솔창에 출력하도록 한다.

  socket.on('create or join', function(room) {
    log('Received request to create or join room ' + room);

    var clientsInRoom = io.sockets.adapter.rooms[room];
    var numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0;
    log('Room ' + room + ' now has ' + numClients + ' client(s)');

    if (numClients === 0) {
      socket.join(room);
      log('Client ID ' + socket.id + ' created room ' + room);
      socket.emit('created', room, socket.id);
      console.log('created');
    } else if (numClients === 1) {
      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');
      console.log('joined');
    } else { // max two clients
      socket.emit('full', room);
    }
  });

위 코드는 이해를 하고 있는 것이 좋을 것 같아서 간단하게 정리한다.

create or join 메시지시 파라미터(room)에 채팅방 이름이(foo)이 같이 전송되어 온다.

해당 채팅방의 정보(clientsInRoom) 중에서 사용자 수가 0명이면 (numClients === 0),

해당 접속자(socket)를 지정된 채팅방(room)의 생성자로 참여(join)시키고,

해당 접속자에게 created란 메시지를 전달한다(emit).

해당 접속자는 메시지를 수신하면 방 생성자 또는 방장 등의 의미를 가지도록 설정한다.

다시 다른 사용자가 접속하면 접속자수는 현재 1이기 때문에 (numClients === 1),

해당 접속자에게 joined란 메시지와 이제 화상 채팅을 실행할 준비를(ready) 하라고 메시지를 전달한다(emit).

 

서버를 재가동하고, 다시 접속하면 화상 채팅을 할 수 있다.

이제 서버 재가동없이 한 사람이 채팅을 종료하고 다시 접속(둘다)하면 화상 채팅을 다시 할 수 있다.

양쪽에서 웹 페이지 갱신(F5)을 하면 확인 할 수 있다.

 

참고: 다소 어렵지만 한사람이 채팅방을 나가면, 채팅방을 비우기 보다는 남은 사람은 그대로 두고 새로 입장하는 사람과 채팅할 수 있도록 하는 것이 좋다.

 

세번째로 오디오가 전송 되도록 한다.

오디오가 전송되도록 하기 위해서

클라이언트에서(main.js) WebRTC가 비디오 정보를 가지고 오도록 하는 다음의 기존 코드를 수정한다.

audio가 false로 되어 있다.

navigator.mediaDevices.getUserMedia({
  audio: false,
  video: true
})
.then(gotStream)
.catch(function(e) {
  alert('getUserMedia() error: ' + e.name);
});

화상 채팅을 위해 비디오 정보를 가지고 오는 getUserMedia의 파라미터에 지정된 audio 옵션을 다음과 같이 true로 지정한다.

  const mediaOption = {
    audio: true,
    video: {
      mandatory: {
        maxWidth: 160,
        maxHeight: 120,
        maxFrameRate: 5,
      },
      optional: [
        { facingMode: 'user' },
      ],
    },
  };
  
navigator.mediaDevices.getUserMedia(mediaOption)

오디오 설정외에 인터넷에서 검색한 비디오의 다양한 옵션도 추가로 작성하였다.

추가한 옵션은 이름에서 어느 정도 의미를 알 수 있을 것이고, 자세한 내용은 여기서 정리하지 않는다.

 

다시 채팅을 시작하면, 오디오가 전송되는 것을 확인할 수 있다.

다만, 두 개의 클라이언트가 가까이 있는 경우 심한 잡음(?)을 들을 수 있다.

 

이제 마지막으로 조금더 그럴듯하게 디자인을 변경해 본다.

디자인을 변경하기 위해서는 CSS를 알아야 한다.

자세한 내용은 정리하지 않으니 따로 찾아보고,

index.html 파일의 head에 다음과 같이 메타 태그를(빨간색) 추가한다.

이 코드는 모바일 화면을 위해 추가하는 것이다.

<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
  <title>Realtime communication with WebRTC</title>
  <link rel="stylesheet" href="/css/main.css" />

</head>

css/main.css 파일의 내용을 다음과 같이 수정한다.

body {
  font-family: sans-serif;
}

#videos {
  width: 100%;
  max-width: 500px;
}

#localVideo {
  max-width: 100%;
  width: 100%;
  float:left;
  border: 1px solid;
}

#remoteVideo {
  max-width: 100%;
  width: 100%;
  float:left;
  border: 1px solid;
  display: none;
}

.localVideoInChatting {
  width: 100px !important;
  position: absolute;
}
.remoteVideoInChatting {
  display: inline-block !important;
}

위 CSS에서 localVideoInChattingremoteVideoInChatting 클래스(CSS의 클래스)를 기억해야 한다.

이 CSS의 내용은 채팅을 시작하기 전에는 내 화면(#localVideo)만 크게 보이고(width:100%),

상대 화면(#remoteVideo)은 보이지 않도록(display: none;)하는 것이다.

 

그러다 화상채팅이 시작되면(handleRemoteStreamAdded - 다음 코드)

내 화면(#localVideo)에 localVideoInChatting 클래스를 추가하여(add) 내 화면을 작게 만들고,

상대 화면(#remoteVideo)은 remoteVideoInChatting 클래스를 추가하여 크게 잘 보이도록 한다.

이 기능을 위해 다음 코드의 빨간색으로 표시된 부분을 추가하면 된다.

function handleRemoteStreamAdded(event) {
  console.log('Remote stream added.');
  remoteStream = event.stream;
  remoteVideo.srcObject = remoteStream;

  remoteVideo.classList.add("remoteVideoInChatting");
  localVideo.classList.add("localVideoInChatting");
}

function handleRemoteHangup() {
  remoteVideo.classList.remove("remoteVideoInChatting");
  localVideo.classList.remove("localVideoInChatting");
    
  console.log('Session terminated.');
  stop();
  isInitiator = false;
}

대화가 종료되면(handleRemoteHangup)

추가한 클래스들을 제거(remove)하여 원래데로 되돌린다.

 

이상으로 구글의 WebRTC 예제에 4가지를 추가하여 제법 그럴듯한 화상채팅 프로그램을 만들었다.

기존 예제에 추가된 소스는 GitHub에서 받을 수 있다.

 

제대로 화상채팅 프로그램을 만들기 위해서는 NodeJS Socket.IO, CSS를 알아야 하고,

WebRTCRTCPeerConnection를 알아야 하는 것을 알 수 있다.

그리고 signaling, STUN, TURN등의 용어를 사용하지 않았지만

예제 코드에 사용되어 있으니 확인하면서 알아 두어야 한다.

 

제대로된 화상채팅을 만들려면, 아직 많은 기능을 구현해야한다 (특히, 채팅방).

하나씩 기능을 추가하면서 제대로된 화상채팅 프로그램을 제작하다 보면 WebRTC에 대해서 잘 알게 되고

멋진 프로그램을 제작하게 될 것이다.

 

그리고 WebRTC를 이용하면,

화상채팅 외에 여러 명이 채팅 하는 컨퍼런스, 방송 등 미디어와 관련된 많은 것을 구현할 수 있다.

사용해보진 않았지만 WebRTC 기반의 Janus, Licode, Medooza, Jitsi 등을 이용하면 더 쉽게 구현할 수 있다고 한다.

 

 

+ Recent posts