2018년도에 webRTC 를 이용한 ‘코딩 실시간 화상 강의실 서비스’ 를 만들던 경험을 떠올리며, webRTC API 를 이용한 애플리케이션 작성법을 정리해봅니다. 🎉
WebRTC
?WebRTC 는 브라우저나 모바일 application 에서 Real Time Communication (RTC) 를 편리하게 할 수 있는 api 를 만들고자 하는 목적으로 시작된 프로젝트입니다. Open Project 이지만 (2018년 12월 기준) Google Chrome Team 의 주도로, Mozilla, Opera 등의 단체의 지원을 받으며 진행되고 있습니다.
프로젝트의 진행 방식은, WebRTC API 최신 명세 (SPEC) 를 WebRTC M{xx}
와 같은 이름으로 공개하고, 거의 동시에 Chrome (혹은 Chrome Beta) 에 구현하여 릴리즈하는 것 같습니다. Opera, Safari, Firefox 등의 브라우저들은 이 공개된 명세에 발맞춰 따라옵니다.
WebRTC 로 할 수 있는 일은 굉장히 많습니다. 그 중에서도 이 글에서는 서버를 통하지 않고 클라이언트와 클라이언트 간의 p2p 영상/음성/데이터 통신을 하는 활용법에 초점을 맞춰서 webRTC 의 API 를 정리해보겠습니다.
WebRTC
의 3가지 대표 API2018년 말인 현재, 대부분의 브라우저가 webRTC API 표준을 지원합니다. 대부분의 브라우저에서 지원하는 webRTC 의 대표적인 표준 API 3가지를 소개하겠습니다.
MediaStream
(getUserMedia
)RTCPeerConnection
RTCDataChannel
MediaStream
사용자의 카메라와 마이크 같은 곳의 데이터 스트림에 접근합니다. 우리의 애플리케이션이 사용자의 음성, 영상 데이터를 채집해 올 때 자주 사용하게 됩니다.
RTCPeerConnection
암호화 및 대역폭 관리를 하는 기능을 가지고 있고, 오디오 또는 비디오 연결을 담당합니다. 애플리케이션이 채집한 음성 및 영상 데이터를 서로 주고 받는 채널을 추상화하였다고 생각하면 됩니다.
RTCDataChannel
음성 및 영상 데이터가 아닌, json/text 데이터들을 주고받는 채널을 추상화한 API 입니다.
webRTC
application 이 수행하는 것여러분이 만약 p2p 영상 및 음성 통신을 하는 webRTC application 을 구성한다면, 다음의 주요한 4가지 작업을 수행해야 할 것입니다.
no | stage | 설명 |
---|---|---|
1 | Fetching | 상대 peer 에게 보낼 사용자의 음성 및 영상 데이터를 수집합니다. |
2 | Signaling | 이 세상 어딘가에 있는 상대 peer 와 연결을 맺기 위해서, 상대 peer 의 정보를 탐색합니다. |
3 | Connection | 발견한 peer 와 p2p connection 을 맺습니다. channel 을 개방해둡니다. |
4 | Communication | 개방해놓은 채널을 통해 음성/영상/텍스트 데이터를 주고 받습니다. |
위의 4가지 작업의 구체적인 방식과, 각각의 수행에 필요한 webRTC API 들을 알아보겠습니다.
webRTC API 인 MediaStream
, getUserMedia
를 이용해 사용자의 영상 및 음성 정보를 가져옵니다. 가져온 이후의 활용법은 4단계에서 자세히 다루겠습니다.
잠깐! Signlaing 단계는 피어와 피어가 서로를 찾을 수 있도록 돕는 중간 매개자 역할을 하는 서버인 Signaling Server 를 필요로 합니다. Signaling Sever 의 구현 방식에는 제약이 없습니다. 오롯히 애플리케이션을 만드는 개발자의 몫입니다. 개발 엔지니어 개인 역량에 따라 구현 형태도 다르고, 정답도 없습니다. webRTC 애플리케이션 개발을 하면서 가장 어려웠던 부분이기도 합니다.
Signaling 단계는 서로 다른 두 peer (WebRTC Client) 가 Communication 하기 위한 준비단계로, 3가지 종류의 정보를 교환해야 합니다.
offer
와 answer
를 주고 받으며 교환합니다.조금 더 자세히 알아보겠습니다.
세상 어딘가에 있는 상대 peer 를 찾아 연결을 맺기 위해선, 네트워크 정보를 교환해야합니다. 이 때, 중간 매개자 역할로서 별도의 서버인 Signaling Server 가 필요합니다. 순서는 다음과 같습니다.
step | do |
---|---|
1 | RTCPeerConnection Object 를 새롭게 생성하고 RTCPeerConnection.onicecandidate 핸들러를 통해 현재 내 client 의 Ice Candidate(Network 정보) 가 확보되면 실행될 callback 을 전달합니다. |
2 | Ice Candidate (내 네트워크 정보) 가 확보되면, 중간 매개자인 Signaling Server 을 통해 상대 peer 에게 serialized 된 ice candidate 정보를 전송합니다. (쌍방이 서로에게 합니다.) |
3 | 상대 peer 의 candidate (네트워크 정보) 가 도착하면, RTCPeerConnection.addIceCandidate 를 통해 상대 peer 의 네트워크 정보를 등록합니다. (쌍방이 모두 합니다.) |
상황을 가정해봅시다. A 와 B 가 webRTC 통신을 하려고합니다. 각자 브라우저에서 RTCPeerConnection
객체를 가지고 있고, 서로의 네트워크 정보 (ice candidate) 를 교환 후 각자의 RTCPeerConnection.addIceCandidate
를 통해 서로의 네트워크 정보를 등록하였습니다.
step | do |
---|---|
1 | B 가 RTCPeerConnection.createOffer 를 호출해 Offer SDP (Session Description Protocol) 을 생성합니다. 여기엔 내 브라우저에서 사용 가능한 코덱이나 해상도에 대한 정보가 들어있습니다. |
2 | B 가 Offer SDP 를 Signaling Server (매개자) 을 통해 전송합니다. |
3 | A 는 Signaling Channel 에서 Offer SDP 를 받아, RTCPeerConnection.setRemoteDescription 을 수행합니다. |
4 | A 의 RTCPeerConnection 객체는 상대 session 에 대한 정보를 알고 있게 되었고, RTCPeerConnection.createAnswer 를 호출하여 Answer SDP 를 생성하여 Signaling Channel 을 통해 B 에게 전달합니다. |
5 | B 도 마찬가지로 자신의 RTCPeerConnection.setRemoteDescription 을 호출해, 전달받은 Answer SDP 를 등록합니다. |
6 | A, B 각 측에서 setRemoteDescription 이 성공적으로 수행되었다면, 각 브라우저에서는 서로의 peer 에 대해 인지하고 있는 상태라고 할 수 있고, p2p 연결이 성공적으로 완료되었다고 할 수 있습니다. |
어려운 Signaling 을 통해 상대 피어의 정보가 잘 등록된 RTCPeerConnection
를 얻었다면, 연결이 성공적으로 이루어진 것입니다.
보통 webRTC 를 통해서 peer 와 peer 가 주고받는 데이터는 크게 아래의 두가지입니다.
교환의 양상은, 연결이 이루어지기 전에 데아터 stream 이나 채널을 미리 준비하고, 연결이 완료되면 데이터를 받았을 때의 callback 을 통해 받은 데이터를 처리합니다. 조금 더 자세한 내용은 아래와 같습니다.
1. video 나 audio 데이터 스트림
의 경우주는 입장 : 자신의 머신에서 (getUserMedia
등의 api 를 통해) video/audio 스트림 source 를 취득해 RTCPeerConnection
을 생성할 당시에 addTrack
(데이터 stream 채널을 연결) 해줍니다. Signaling 을 통해 connection 이 이루어지기 전에 미리 되어야합니다.
받는 입장 : RTCPeerConnection.ontrack
의 callback 을 커스텀하게 설정해서, connection 이 성공적으로 이루어진 후에 상대방의 Track (video/audio stream) 이 감지되면 어떤 동작을 할지 설정할 수 있습니다. 보통 받은 track 의 데이터 스트림을 DOM 의 <video srcObject={??}/>
element 에 연결해 보여줍니다.
2. 직렬화된 text 데이터
의 경우주는 입장 : RTCPeerConnection.createDataChannel
을 통해, 특정 이름의 data 전달 채널을 개설할 수 있습니다. 이 또한 Signaling 을 통해 connection 이 이루어지기 전에 미리 되어야합니다.
받는 입장 : RTCPeerConnection.ondatachannel
의 callback 을 커스텀하게 설정해서, connection 이 성공적으로 이루어진 후에 상대방이 data channel 을 통해 어떤 데이터를 보냈을 때의 동작을 설정할 수 있습니다.
인터넷의 문제나 예기치 못한 문제로 피어와 피어간에 맺어놓은 Connection 이 끊어지는 일이 굉장히 자주 발생합니다. 이 때 적절한 retry 로직으로 자연스럽게 재연결을 맺어주어야 합니다.
Signaling 단계가 성공하지 못하면 Connection 을 맺을 수 조차 없습니다. 따라서 안정적인 Signaling Server 를 구축하는 것이 무엇보다 중요했습니다.
Getting Started with webRTC : https://www.html5rocks.com/ko/tutorials/webrtc/basics/