1. 소개
WebRTC는 웹 및 네이티브 앱에서 오디오, 동영상, 데이터의 실시간 통신을 지원하는 오픈소스 프로젝트입니다.
WebRTC에는 여러 JavaScript API가 있습니다. 링크를 클릭하여 데모를 확인하세요.
getUserMedia(): 오디오 및 동영상을 캡처합니다.MediaRecorder: 오디오 및 동영상을 녹화합니다.RTCPeerConnection: 사용자 간에 오디오 및 동영상을 스트리밍합니다.RTCDataChannel: 사용자 간에 데이터를 스트리밍합니다.
WebRTC는 어디에서 사용할 수 있나요?
Firefox, Opera, 데스크톱 및 Android의 Chrome WebRTC는 iOS 및 Android의 네이티브 앱에서도 사용할 수 있습니다.
시그널링이란 무엇인가요?
WebRTC는 RTCPeerConnection을 사용하여 브라우저 간에 스트리밍 데이터를 통신하지만 통신을 조정하고 제어 메시지를 전송하는 메커니즘도 필요합니다. 이 프로세스를 시그널링이라고 합니다. 신호 메서드와 프로토콜은 WebRTC에 의해 지정되지 않습니다. 이 Codelab에서는 메시지 전송에 Socket.IO를 사용하지만 다양한 대안이 있습니다.
STUN과 TURN이란 무엇인가요?
WebRTC는 P2P로 작동하도록 설계되어 사용자가 가능한 가장 직접적인 경로로 연결할 수 있습니다. 하지만 WebRTC는 실제 네트워킹에 대처하도록 빌드됩니다. 클라이언트 애플리케이션은 NAT 게이트웨이와 방화벽을 통과해야 하며, 직접 연결이 실패할 경우를 대비해 피어 투 피어 네트워킹에 대체가 필요합니다. 이 프로세스의 일환으로 WebRTC API는 STUN 서버를 사용하여 컴퓨터의 IP 주소를 가져오고, 피어 투 피어 통신이 실패할 경우 TURN 서버를 중계 서버로 사용합니다. (실제 WebRTC에서 자세히 설명합니다.)
WebRTC는 안전한가요?
암호화는 모든 WebRTC 구성요소에 필수이며 JavaScript API는 보안 출처 (HTTPS 또는 localhost)에서만 사용할 수 있습니다. 신호 메커니즘은 WebRTC 표준에 의해 정의되지 않으므로 보안 프로토콜을 사용해야 합니다.
2. 개요
웹캠으로 동영상을 가져오고 스냅샷을 찍어 WebRTC를 통해 P2P로 공유하는 앱을 빌드합니다. 이 과정에서 핵심 WebRTC API를 사용하고 Node.js를 사용하여 메시지 서버를 설정하는 방법을 알아봅니다.
학습할 내용
- 웹캠에서 동영상 가져오기
- RTCPeerConnection으로 동영상 스트리밍
- RTCDataChannel을 사용한 데이터 스트리밍
- 메시지를 교환할 신호 서비스 설정
- 피어 연결과 시그널링 결합
- 사진을 찍어 데이터 채널을 통해 공유
필요한 항목
- Chrome 47 이상
- Chrome용 웹 서버를 사용하거나 원하는 웹 서버를 사용합니다.
- 샘플 코드
- 텍스트 편집기
- HTML, CSS, JavaScript에 대한 기본 지식
3. 샘플 코드 가져오기
코드 다운로드
git에 익숙하다면 GitHub에서 이 Codelab의 코드를 클론하여 다운로드할 수 있습니다.
git clone https://github.com/googlecodelabs/webrtc-web
또는 다음 버튼을 클릭하여 코드의 .zip 파일을 다운로드합니다.
다운로드한 zip 파일을 엽니다. 그러면 이 Codelab의 단계별로 폴더 하나씩 포함된 프로젝트 폴더 (adaptive-web-media)가 압축 해제되며 필요한 모든 리소스가 포함됩니다.
모든 코딩 작업은 work라는 디렉터리에서 진행합니다.
step-nn 폴더에는 이 Codelab의 각 단계에 대한 완성된 버전이 포함되어 있습니다. 참고용으로 제공됩니다.
웹 서버 설치 및 확인
자체 웹 서버를 사용할 수 있지만 이 Codelab은 Chrome 웹 서버와 잘 작동하도록 설계되었습니다. 아직 앱을 설치하지 않았다면 Chrome 웹 스토어에서 설치할 수 있습니다.

Chrome용 웹 서버 앱을 설치한 후 북마크 바, 새 탭 페이지 또는 앱 런처에서 Chrome 앱 바로가기를 클릭합니다.

웹 서버 아이콘을 클릭합니다.

다음으로 로컬 웹 서버를 구성할 수 있는 이 대화상자가 표시됩니다.

폴더 선택 버튼을 클릭하고 방금 만든 work 폴더를 선택합니다. 이렇게 하면 웹 서버 URL 섹션의 웹 서버 대화상자에 강조 표시된 URL을 통해 Chrome에서 진행 중인 작업을 볼 수 있습니다.
옵션에서 아래와 같이 index.html 자동 표시 옆에 있는 체크박스를 선택합니다.

그런 다음 웹 서버: 시작됨이라고 표시된 전환 버튼을 왼쪽으로 슬라이드한 다음 다시 오른쪽으로 슬라이드하여 서버를 중지했다가 다시 시작합니다.

이제 강조 표시된 웹 서버 URL을 클릭하여 웹브라우저에서 작업 사이트를 방문합니다. 다음과 같은 페이지가 표시됩니다. 이 페이지는 work/index.html에 해당합니다.

이 앱은 아직 흥미로운 작업을 수행하지 않습니다. 지금까지는 웹 서버가 제대로 작동하는지 확인하는 데 사용하는 최소한의 스켈레톤일 뿐입니다. 이후 단계에서 기능과 레이아웃 기능을 추가합니다.
4. 웹캠에서 동영상 스트리밍하기
학습할 내용
이 단계에서는 다음 방법을 알아봅니다.
- 웹캠에서 동영상 스트림을 가져옵니다.
- 스트림 재생을 조작합니다.
- CSS와 SVG를 사용하여 동영상을 조작합니다.
이 단계의 전체 버전은 step-01 폴더에 있습니다.
HTML을 조금만 추가하면...
work 디렉터리의 index.html에 video 요소와 script 요소를 추가합니다.
<!DOCTYPE html>
<html>
<head>
<title>Realtime communication with WebRTC</title>
<link rel="stylesheet" href="css/main.css" />
</head>
<body>
<h1>Realtime communication with WebRTC</h1>
<video autoplay playsinline></video>
<script src="js/main.js"></script>
</body>
</html>
...그리고 약간의 JavaScript
js 폴더의 main.js에 다음을 추가합니다.
'use strict';
// On this codelab, you will be streaming only video (video: true).
const mediaStreamConstraints = {
video: true,
};
// Video element where stream will be placed.
const localVideo = document.querySelector('video');
// Local stream that will be reproduced on the video.
let localStream;
// Handles success by adding the MediaStream to the video element.
function gotLocalMediaStream(mediaStream) {
localStream = mediaStream;
localVideo.srcObject = mediaStream;
}
// Handles error by logging a message to the console with the error message.
function handleLocalMediaStreamError(error) {
console.log('navigator.getUserMedia error: ', error);
}
// Initializes media stream.
navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
.then(gotLocalMediaStream).catch(handleLocalMediaStreamError);
사용해 보기
브라우저에서 index.html을 열면 다음과 같은 화면이 표시됩니다 (물론 웹캠의 뷰가 표시됨).

작동 방식
getUserMedia() 호출 후 브라우저는 사용자에게 카메라 액세스 권한을 요청합니다 (현재 출처에 대해 카메라 액세스가 처음 요청된 경우). 성공하면 srcObject 속성을 통해 미디어 요소에서 사용할 수 있는 MediaStream이 반환됩니다.
navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
.then(gotLocalMediaStream).catch(handleLocalMediaStreamError);
}
function gotLocalMediaStream(mediaStream) {
localVideo.srcObject = mediaStream;
}
constraints 인수를 사용하면 가져올 미디어를 지정할 수 있습니다. 이 예에서는 오디오가 기본적으로 사용 중지되어 있으므로 동영상만 표시됩니다.
const mediaStreamConstraints = {
video: true,
};
동영상 해상도와 같은 추가 요구사항에 제약 조건을 사용할 수 있습니다.
const hdConstraints = {
video: {
width: {
min: 1280
},
height: {
min: 720
}
}
}
MediaTrackConstraints 사양에는 모든 잠재적 제약 조건 유형이 나열되어 있지만 일부 옵션은 일부 브라우저에서 지원되지 않습니다. 요청된 해상도가 현재 선택된 카메라에서 지원되지 않으면 getUserMedia()가 OverconstrainedError로 거부되고 사용자에게 카메라 액세스 권한을 부여하라는 메시지가 표시되지 않습니다.
getUserMedia()가 성공하면 웹캠의 동영상 스트림이 동영상 요소의 소스로 설정됩니다.
function gotLocalMediaStream(mediaStream) {
localVideo.srcObject = mediaStream;
}
보너스 포인트
getUserMedia()에 전달된localStream객체는 전역 범위에 있으므로 브라우저 콘솔에서 검사할 수 있습니다. 콘솔을 열고 stream을 입력한 후 Return 키를 누릅니다. (Chrome에서 콘솔을 보려면 Ctrl-Shift-J를 누르거나 Mac을 사용하는 경우 Command-Option-J를 누르세요.)localStream.getVideoTracks()은 무엇을 반환하나요?localStream.getVideoTracks()[0].stop()에게 전화를 걸어 보세요.- 제약 조건 객체를 살펴보세요. 이를
{audio: true, video: true}로 변경하면 어떻게 되나요? - 동영상 요소의 크기는 얼마인가요? 디스플레이 크기가 아닌 JavaScript에서 동영상의 자연스러운 크기를 가져오려면 어떻게 해야 하나요? Chrome 개발자 도구를 사용하여 확인합니다.
- 동영상 요소에 CSS 필터를 추가해 보세요. 예를 들면 다음과 같습니다.
video {
filter: blur(4px) invert(1) opacity(0.5);
}
- SVG 필터를 추가해 보세요. 예를 들면 다음과 같습니다.
video {
filter: hue-rotate(180deg) saturate(200%);
}
학습한 내용
이 단계에서는 다음 방법을 알아봤습니다.
- 웹캠에서 동영상을 가져옵니다.
- 미디어 제약 조건 설정
- 동영상 요소를 조작합니다.
이 단계의 전체 버전은 step-01 폴더에 있습니다.
팁
video요소의autoplay속성을 잊지 마세요. 이러한 설정이 없으면 단일 프레임만 표시됩니다.getUserMedia()제약 조건에는 더 많은 옵션이 있습니다. webrtc.github.io/samples/src/content/peerconnection/constraints에서 데모를 살펴보세요. 이 사이트에는 흥미로운 WebRTC 샘플이 많이 있습니다.
권장사항
- 동영상 요소가 컨테이너를 오버플로하지 않도록 합니다. 동영상의 원하는 크기와 최대 크기를 설정할 수 있도록
width및max-width가 추가되었습니다. 브라우저에서 높이를 자동으로 계산합니다.
video {
max-width: 100%;
width: 320px;
}
다음 단계
동영상은 있는데 어떻게 스트리밍해야 할까요? 다음 단계에서 확인해 보세요.
5. RTCPeerConnection으로 동영상 스트리밍
학습할 내용
이 단계에서는 다음 방법을 알아봅니다.
- WebRTC shim인 adapter.js를 사용하여 브라우저 차이점을 추상화합니다.
- RTCPeerConnection API를 사용하여 동영상을 스트리밍합니다.
- 미디어 캡처 및 스트리밍을 제어합니다.
이 단계의 전체 버전은 step-2 폴더에 있습니다.
RTCPeerConnection이란 무엇인가요?
RTCPeerConnection은 동영상과 오디오를 스트리밍하고 데이터를 교환하기 위해 WebRTC 통화를 하는 API입니다.
이 예에서는 동일한 페이지에 있는 두 개의 RTCPeerConnection 객체 (피어라고 함) 간의 연결을 설정합니다.
실용적인 용도는 많지 않지만 RTCPeerConnection의 작동 방식을 이해하는 데는 유용합니다.
동영상 요소 및 컨트롤 버튼 추가
index.html에서 단일 동영상 요소를 동영상 요소 2개와 버튼 3개로 바꿉니다.
<video id="localVideo" autoplay playsinline></video>
<video id="remoteVideo" autoplay playsinline></video>
<div>
<button id="startButton">Start</button>
<button id="callButton">Call</button>
<button id="hangupButton">Hang Up</button>
</div>
한 동영상 요소에는 getUserMedia()의 스트림이 표시되고 다른 동영상 요소에는 RTCPeerconnection을 통해 스트리밍되는 동일한 동영상이 표시됩니다. (실제 애플리케이션에서는 한 동영상 요소가 로컬 스트림을 표시하고 다른 동영상 요소가 원격 스트림을 표시합니다.)
adapter.js shim 추가
main.js 링크 위에 현재 버전의 adapter.js 링크를 추가합니다.
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
이제 Index.html이 다음과 같이 표시됩니다.
<!DOCTYPE html>
<html>
<head>
<title>Realtime communication with WebRTC</title>
<link rel="stylesheet" href="css/main.css" />
</head>
<body>
<h1>Realtime communication with WebRTC</h1>
<video id="localVideo" autoplay playsinline></video>
<video id="remoteVideo" autoplay playsinline></video>
<div>
<button id="startButton">Start</button>
<button id="callButton">Call</button>
<button id="hangupButton">Hang Up</button>
</div>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script src="js/main.js"></script>
</body>
</html>
RTCPeerConnection 코드 설치
main.js를 step-02 폴더의 버전으로 바꿉니다.
전화 걸기
index.html을 열고 시작 버튼을 클릭하여 웹캠에서 동영상을 가져온 다음 통화를 클릭하여 피어 연결을 만듭니다. 두 동영상 요소에 동일한 동영상 (웹캠에서 가져온 동영상)이 표시됩니다. 브라우저 콘솔을 확인하여 WebRTC 로깅을 확인합니다.
작동 방식
이 단계에서는 많은 작업을 수행합니다.
WebRTC는 RTCPeerConnection API를 사용하여 피어라고 하는 WebRTC 클라이언트 간에 동영상을 스트리밍하는 연결을 설정합니다.
이 예에서 두 RTCPeerConnection 객체는 동일한 페이지에 있습니다(pc1 및 pc2). 실용적인 용도는 많지 않지만 API 작동 방식을 보여주기에는 좋습니다.
WebRTC 피어 간 통화 설정에는 다음 세 가지 작업이 포함됩니다.
- 통화의 각 끝에 RTCPeerConnection을 만들고 각 끝에서
getUserMedia()의 로컬 스트림을 추가합니다. - 네트워크 정보 가져오기 및 공유: 잠재적 연결 엔드포인트를 ICE 후보라고 합니다.
- 로컬 및 원격 설명 가져오기 및 공유: SDP 형식의 로컬 미디어에 관한 메타데이터입니다.
앨리스와 밥이 RTCPeerConnection을 사용하여 영상 채팅을 설정하려고 한다고 가정해 보겠습니다.
먼저 앨리스와 밥이 네트워크 정보를 교환합니다. '후보 찾기'라는 표현은 ICE 프레임워크를 사용하여 네트워크 인터페이스와 포트를 찾는 프로세스를 의미합니다.
- 앨리스는
onicecandidate (addEventListener('icecandidate'))핸들러를 사용하여 RTCPeerConnection 객체를 만듭니다. 이는 main.js의 다음 코드에 해당합니다.
let localPeerConnection;
localPeerConnection = new RTCPeerConnection(servers);
localPeerConnection.addEventListener('icecandidate', handleConnection);
localPeerConnection.addEventListener(
'iceconnectionstatechange', handleConnectionChange);
- 앨리스가
getUserMedia()를 호출하고 전달된 스트림을 추가합니다.
navigator.mediaDevices.getUserMedia(mediaStreamConstraints).
then(gotLocalMediaStream).
catch(handleLocalMediaStreamError);
function gotLocalMediaStream(mediaStream) {
localVideo.srcObject = mediaStream;
localStream = mediaStream;
trace('Received local stream.');
callButton.disabled = false; // Enable call button.
}
localPeerConnection.addStream(localStream);
trace('Added local stream to localPeerConnection.');
- 네트워크 후보를 사용할 수 있게 되면 1단계의
onicecandidate핸들러가 호출됩니다. - Alice가 직렬화된 후보자 데이터를 Bob에게 전송합니다. 실제 애플리케이션에서는 이 프로세스 (시그널링이라고 함)가 메시지 서비스를 통해 이루어집니다. 이 작업은 나중에 단계에서 자세히 알아봅니다. 물론 이 단계에서는 두 RTCPeerConnection 객체가 동일한 페이지에 있으므로 외부 메시지 없이 직접 통신할 수 있습니다.
- 밥이 앨리스로부터 후보 메시지를 받으면
addIceCandidate()를 호출하여 후보를 원격 피어 설명에 추가합니다.
function handleConnection(event) {
const peerConnection = event.target;
const iceCandidate = event.candidate;
if (iceCandidate) {
const newIceCandidate = new RTCIceCandidate(iceCandidate);
const otherPeer = getOtherPeer(peerConnection);
otherPeer.addIceCandidate(newIceCandidate)
.then(() => {
handleConnectionSuccess(peerConnection);
}).catch((error) => {
handleConnectionFailure(peerConnection, error);
});
trace(`${getPeerName(peerConnection)} ICE candidate:\n` +
`${event.candidate.candidate}.`);
}
}
WebRTC 피어는 해상도, 코덱 기능과 같은 로컬 및 원격 오디오와 동영상 미디어 정보도 파악하고 교환해야 합니다. 미디어 구성 정보를 교환하기 위한 신호는 SDP로 알려진 세션 설명 프로토콜 형식을 사용하여 제안 및 응답으로 알려진 메타데이터 블롭을 교환하여 진행됩니다.
- 앨리스가 RTCPeerConnection
createOffer()메서드를 실행합니다. 반환된 약속은 RTCSessionDescription(앨리스의 로컬 세션 설명)을 제공합니다.
trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
.then(createdOffer).catch(setSessionDescriptionError);
- 성공하면 Alice는
setLocalDescription()를 사용하여 로컬 설명을 설정한 다음 시그널링 채널을 통해 이 세션 설명을 Bob에게 전송합니다. - Bob은
setRemoteDescription()를 사용하여 Alice가 보낸 설명을 원격 설명으로 설정합니다. - 밥은 RTCPeerConnection
createAnswer()메서드를 실행하여 앨리스로부터 받은 원격 설명을 전달하므로 앨리스의 설명과 호환되는 로컬 세션을 생성할 수 있습니다.createAnswer()약속은 RTCSessionDescription을 전달합니다. Bob은 이를 로컬 설명으로 설정하고 Alice에게 전송합니다. - Alice는 Bob의 세션 설명을 가져오면
setRemoteDescription()를 사용하여 이를 원격 설명으로 설정합니다.
// Logs offer creation and sets peer connection session descriptions.
function createdOffer(description) {
trace(`Offer from localPeerConnection:\n${description.sdp}`);
trace('localPeerConnection setLocalDescription start.');
localPeerConnection.setLocalDescription(description)
.then(() => {
setLocalDescriptionSuccess(localPeerConnection);
}).catch(setSessionDescriptionError);
trace('remotePeerConnection setRemoteDescription start.');
remotePeerConnection.setRemoteDescription(description)
.then(() => {
setRemoteDescriptionSuccess(remotePeerConnection);
}).catch(setSessionDescriptionError);
trace('remotePeerConnection createAnswer start.');
remotePeerConnection.createAnswer()
.then(createdAnswer)
.catch(setSessionDescriptionError);
}
// Logs answer to offer creation and sets peer connection session descriptions.
function createdAnswer(description) {
trace(`Answer from remotePeerConnection:\n${description.sdp}.`);
trace('remotePeerConnection setLocalDescription start.');
remotePeerConnection.setLocalDescription(description)
.then(() => {
setLocalDescriptionSuccess(remotePeerConnection);
}).catch(setSessionDescriptionError);
trace('localPeerConnection setRemoteDescription start.');
localPeerConnection.setRemoteDescription(description)
.then(() => {
setRemoteDescriptionSuccess(localPeerConnection);
}).catch(setSessionDescriptionError);
}
- 핑!
보너스 포인트
- chrome://webrtc-internals를 확인하세요. 이를 통해 WebRTC 통계와 디버깅 데이터가 제공됩니다. (Chrome URL의 전체 목록은 chrome://about에 있습니다.)
- CSS로 페이지 스타일을 지정합니다.
- 동영상을 나란히 배치합니다.
- 버튼의 너비를 동일하게 하고 텍스트를 더 크게 만듭니다.
- 레이아웃이 모바일에서 작동하는지 확인합니다.
- Chrome 개발자 도구 콘솔에서
localStream,localPeerConnection,remotePeerConnection를 확인합니다. - 콘솔에서
localPeerConnectionpc1.localDescription을 확인합니다. SDP 형식은 어떤 모습인가요?
학습한 내용
이 단계에서는 다음 방법을 알아봤습니다.
- WebRTC shim인 adapter.js를 사용하여 브라우저 차이점을 추상화합니다.
- RTCPeerConnection API를 사용하여 동영상을 스트리밍합니다.
- 미디어 캡처 및 스트리밍을 제어합니다.
- 피어 간에 미디어 및 네트워크 정보를 공유하여 WebRTC 통화를 사용 설정합니다.
이 단계의 전체 버전은 step-2 폴더에 있습니다.
팁
- 이 단계에서는 배울 내용이 많습니다. RTCPeerConnection을 자세히 설명하는 다른 리소스를 찾으려면 webrtc.org를 참고하세요. 이 페이지에는 WebRTC를 사용하고 싶지만 API를 조작하고 싶지 않은 경우를 위한 JavaScript 프레임워크 제안이 포함되어 있습니다.
- adapter.js GitHub 저장소에서 adapter.js shim에 대해 자세히 알아보세요.
- 세계 최고의 영상 채팅 앱이 어떤 모습인지 확인하고 싶으신가요? WebRTC 통화를 위한 WebRTC 프로젝트의 표준 앱인 AppRTC를 살펴보세요. app, code 통화 설정 시간이 500ms 미만입니다.
권장사항
- 코드를 미래에 대비하려면 새 Promise 기반 API를 사용하고 adapter.js를 사용하여 이를 지원하지 않는 브라우저와의 호환성을 사용 설정하세요.
다음 단계
이 단계에서는 WebRTC를 사용하여 피어 간에 동영상을 스트리밍하는 방법을 보여줍니다. 하지만 이 Codelab은 데이터에 관한 내용도 다룹니다.
다음 단계에서는 RTCDataChannel을 사용하여 임의의 데이터를 스트리밍하는 방법을 알아봅니다.
6. RTCDataChannel을 사용하여 데이터 교환
학습할 내용
- WebRTC 엔드포인트 (피어) 간에 데이터를 교환하는 방법
이 단계의 전체 버전은 step-03 폴더에 있습니다.
HTML 업데이트
이 단계에서는 WebRTC 데이터 채널을 사용하여 동일한 페이지에 있는 두 textarea 요소 간에 텍스트를 전송합니다. 이 방법은 그다지 유용하지는 않지만 WebRTC를 사용하여 동영상을 스트리밍할 뿐만 아니라 데이터를 공유할 수 있는 방법을 보여줍니다.
index.html에서 동영상 및 버튼 요소를 삭제하고 다음 HTML로 바꿉니다.
<textarea id="dataChannelSend" disabled
placeholder="Press Start, enter some text, then press Send."></textarea>
<textarea id="dataChannelReceive" disabled></textarea>
<div id="buttons">
<button id="startButton">Start</button>
<button id="sendButton">Send</button>
<button id="closeButton">Stop</button>
</div>
하나의 textarea는 텍스트를 입력하는 데 사용되고 다른 textarea는 피어 간에 스트리밍되는 텍스트를 표시합니다.
이제 index.html이 다음과 같이 표시됩니다.
<!DOCTYPE html>
<html>
<head>
<title>Realtime communication with WebRTC</title>
<link rel="stylesheet" href="css/main.css" />
</head>
<body>
<h1>Realtime communication with WebRTC</h1>
<textarea id="dataChannelSend" disabled
placeholder="Press Start, enter some text, then press Send."></textarea>
<textarea id="dataChannelReceive" disabled></textarea>
<div id="buttons">
<button id="startButton">Start</button>
<button id="sendButton">Send</button>
<button id="closeButton">Stop</button>
</div>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script src="js/main.js"></script>
</body>
</html>
JavaScript 업데이트
main.js를 step-03/js/main.js의 내용으로 바꿉니다.
피어 간 스트리밍 데이터를 사용해 보세요. index.html을 열고 시작을 눌러 피어 연결을 설정하고 왼쪽의 textarea에 텍스트를 입력한 다음 전송을 클릭하여 WebRTC 데이터 채널을 사용하여 텍스트를 전송합니다.
작동 방식
이 코드는 RTCPeerConnection 및 RTCDataChannel을 사용하여 문자 메시지 교환을 지원합니다.
이 단계의 코드는 RTCPeerConnection 예와 거의 동일합니다.
sendData() 및 createConnection() 함수에는 대부분의 새 코드가 있습니다.
function createConnection() {
dataChannelSend.placeholder = '';
var servers = null;
pcConstraint = null;
dataConstraint = null;
trace('Using SCTP based data channels');
// For SCTP, reliable and ordered delivery is true by default.
// Add localConnection to global scope to make it visible
// from the browser console.
window.localConnection = localConnection =
new RTCPeerConnection(servers, pcConstraint);
trace('Created local peer connection object localConnection');
sendChannel = localConnection.createDataChannel('sendDataChannel',
dataConstraint);
trace('Created send data channel');
localConnection.onicecandidate = iceCallback1;
sendChannel.onopen = onSendChannelStateChange;
sendChannel.onclose = onSendChannelStateChange;
// Add remoteConnection to global scope to make it visible
// from the browser console.
window.remoteConnection = remoteConnection =
new RTCPeerConnection(servers, pcConstraint);
trace('Created remote peer connection object remoteConnection');
remoteConnection.onicecandidate = iceCallback2;
remoteConnection.ondatachannel = receiveChannelCallback;
localConnection.createOffer().then(
gotDescription1,
onCreateSessionDescriptionError
);
startButton.disabled = true;
closeButton.disabled = false;
}
function sendData() {
var data = dataChannelSend.value;
sendChannel.send(data);
trace('Sent Data: ' + data);
}
RTCDataChannel의 문법은 send() 메서드와 message 이벤트를 사용하여 WebSocket과 유사하도록 의도적으로 설계되었습니다.
dataConstraint의 사용에 유의하세요. 데이터 채널은 성능보다 안정적인 전송을 우선시하는 등 다양한 유형의 데이터 공유를 지원하도록 구성할 수 있습니다. 옵션에 관한 자세한 내용은 Mozilla 개발자 네트워크에서 확인할 수 있습니다.
보너스 포인트
- WebRTC 데이터 채널에서 사용하는 프로토콜인 SCTP를 사용하면 안정적이고 순서가 지정된 데이터 전송이 기본적으로 사용 설정됩니다. RTCDataChannel이 데이터의 안정적인 전송을 제공해야 하는 경우는 언제이며, 일부 데이터가 손실되더라도 성능이 더 중요한 경우는 언제인가요?
- CSS를 사용하여 페이지 레이아웃을 개선하고 'dataChannelReceive' textarea에 자리표시자 속성을 추가합니다.
- 휴대기기에서 페이지를 테스트합니다.
학습한 내용
이 단계에서는 다음 방법을 알아봤습니다.
- 두 WebRTC 피어 간에 연결을 설정합니다.
- 피어 간에 텍스트 데이터를 교환합니다.
이 단계의 전체 버전은 step-03 폴더에 있습니다.
자세히 알아보기
- WebRTC 데이터 채널 (몇 년이 지났지만 여전히 읽어볼 만함)
- WebRTC의 데이터 채널에 SCTP가 선택된 이유는 무엇인가요?
다음 단계
동일한 페이지의 피어 간에 데이터를 교환하는 방법을 배웠습니다. 하지만 서로 다른 머신 간에는 어떻게 해야 할까요? 먼저 메타데이터 메시지를 교환할 신호 채널을 설정해야 합니다. 다음 단계에서 방법을 알아보세요.
7. 메시지를 교환할 신호 서비스 설정
학습할 내용
이 단계에서는 다음 방법을 알아봅니다.
npm을 사용하여 package.json에 지정된 프로젝트 종속 항목을 설치합니다.- Node.js 서버를 실행하고 node-static을 사용하여 정적 파일을 제공합니다.
- Socket.IO를 사용하여 Node.js에서 메시지 서비스 설정
- 이를 사용하여 '채팅방'을 만들고 메시지를 교환합니다.
이 단계의 전체 버전은 step-04 폴더에 있습니다.
개념
WebRTC 통화를 설정하고 유지하려면 WebRTC 클라이언트 (피어)가 메타데이터를 교환해야 합니다.
- 후보자 (네트워크) 정보입니다.
- 해상도, 코덱 등 미디어에 관한 정보를 제공하는 offer 및 answer 메시지
즉, 오디오, 동영상 또는 데이터의 P2P 스트리밍이 이루어지기 전에 메타데이터 교환이 필요합니다. 이 프로세스를 시그널링이라고 합니다.
이전 단계에서는 발신자와 수신자 RTCPeerConnection 객체가 동일한 페이지에 있으므로 '시그널링'은 객체 간에 메타데이터를 전달하는 문제일 뿐입니다.
실제 애플리케이션에서 발신자와 수신자 RTCPeerConnection은 서로 다른 기기의 웹페이지에서 실행되므로 메타데이터를 통신할 방법이 필요합니다.
이를 위해 WebRTC 클라이언트 (피어) 간에 메시지를 전달할 수 있는 서버인 시그널링 서버를 사용합니다. 실제 메시지는 문자열화된 JavaScript 객체인 일반 텍스트입니다.
필수사항: Node.js 설치
이 Codelab의 다음 단계 (step-04~step-06 폴더)를 실행하려면 Node.js를 사용하여 localhost에서 서버를 실행해야 합니다.
이 링크에서 또는 원하는 패키지 관리자를 통해 Node.js를 다운로드하고 설치할 수 있습니다.
설치가 완료되면 다음 단계 (npm install 실행)에 필요한 종속 항목을 가져올 수 있을 뿐만 아니라 코드랩을 실행하기 위한 작은 localhost 서버 (node index.js 실행)를 실행할 수 있습니다. 이러한 명령어는 필요할 때 나중에 표시됩니다.
앱 정보
WebRTC는 클라이언트 측 JavaScript API를 사용하지만 실제 사용에는 신호 (메시지) 서버와 STUN 및 TURN 서버도 필요합니다. 자세한 내용은 여기를 참고하세요.
이 단계에서는 메시지 전송을 위해 Socket.IO Node.js 모듈과 JavaScript 라이브러리를 사용하여 간단한 Node.js 신호 서버를 빌드합니다. Node.js 및 Socket.IO 사용 경험이 있으면 도움이 되지만 필수는 아닙니다. 메시지 구성요소는 매우 간단합니다.
이 예에서 서버 (Node.js 애플리케이션)는 index.js에 구현되고 서버에서 실행되는 클라이언트 (웹 앱)는 index.html에 구현됩니다.
이 단계의 Node.js 애플리케이션에는 두 가지 작업이 있습니다.
첫째, 메시지 중계 역할을 합니다.
socket.on('message', function (message) {
log('Got message: ', message);
socket.broadcast.emit('message', message);
});
두 번째로 WebRTC 영상 채팅 '회의실'을 관리합니다.
if (numClients === 0) {
socket.join(room);
socket.emit('created', room, socket.id);
} else if (numClients === 1) {
socket.join(room);
socket.emit('joined', room, socket.id);
io.sockets.in(room).emit('ready');
} else { // max two clients
socket.emit('full', room);
}
간단한 WebRTC 애플리케이션을 사용하면 최대 2명의 피어가 방을 공유할 수 있습니다.
HTML 및 JavaScript
index.html을 다음과 같이 업데이트합니다.
<!DOCTYPE html>
<html>
<head>
<title>Realtime communication with WebRTC</title>
<link rel="stylesheet" href="css/main.css" />
</head>
<body>
<h1>Realtime communication with WebRTC</h1>
<script src="/socket.io/socket.io.js"></script>
<script src="js/main.js"></script>
</body>
</html>
이 단계에서는 페이지에 아무것도 표시되지 않습니다. 모든 로깅은 브라우저 콘솔에 실행됩니다. (Chrome에서 콘솔을 보려면 Ctrl-Shift-J를 누르거나 Mac을 사용하는 경우 Command-Option-J를 누르세요.)
js/main.js를 다음으로 바꿉니다.
'use strict';
var isInitiator;
window.room = prompt("Enter room name:");
var socket = io.connect();
if (room !== "") {
console.log('Message from client: Asking to join room ' + room);
socket.emit('create or join', room);
}
socket.on('created', function(room, clientId) {
isInitiator = true;
});
socket.on('full', function(room) {
console.log('Message from client: Room ' + room + ' is full :^(');
});
socket.on('ipaddr', function(ipaddr) {
console.log('Message from client: Server IP address is ' + ipaddr);
});
socket.on('joined', function(room, clientId) {
isInitiator = false;
});
socket.on('log', function(array) {
console.log.apply(console, array);
});
Node.js에서 실행되도록 Socket.IO 설정
HTML 파일에서 Socket.IO 파일을 사용하고 있는 것을 확인할 수 있습니다.
<script src="/socket.io/socket.io.js"></script>
work 디렉토리의 최상위 수준에서 다음 내용으로 package.json이라는 파일을 만듭니다.
{
"name": "webrtc-codelab",
"version": "0.0.1",
"description": "WebRTC codelab",
"dependencies": {
"node-static": "^0.7.10",
"socket.io": "^1.2.0"
}
}
Node Package Manager (npm)에 설치할 프로젝트 종속 항목을 알려주는 앱 매니페스트입니다.
종속 항목 (예: /socket.io/socket.io.js)을 설치하려면 work 디렉터리의 명령줄 터미널에서 다음을 실행합니다.
npm install
다음과 같이 끝나는 설치 로그가 표시됩니다.

npm가 package.json에 정의된 종속 항목을 설치한 것을 확인할 수 있습니다.
work 디렉터리의 최상위 수준 (js 디렉터리 아님)에 index.js라는 새 파일을 만들고 다음 코드를 추가합니다.
'use strict';
var os = require('os');
var nodeStatic = require('node-static');
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);
var io = socketIO.listen(app);
io.sockets.on('connection', function(socket) {
// convenience function to log server messages on the client
function log() {
var array = ['Message from server:'];
array.push.apply(array, arguments);
socket.emit('log', array);
}
socket.on('message', function(message) {
log('Client said: ', message);
// for a real app, would be room-only (not broadcast)
socket.broadcast.emit('message', message);
});
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);
} 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');
} else { // max two clients
socket.emit('full', room);
}
});
socket.on('ipaddr', function() {
var ifaces = os.networkInterfaces();
for (var dev in ifaces) {
ifaces[dev].forEach(function(details) {
if (details.family === 'IPv4' && details.address !== '127.0.0.1') {
socket.emit('ipaddr', details.address);
}
});
}
});
});
명령줄 터미널에서 work 디렉터리에 다음 명령어를 실행합니다.
node index.js
브라우저에서 localhost:8080을 엽니다.
이 URL을 열 때마다 채팅방 이름을 입력하라는 메시지가 표시됩니다. 동일한 방에 참여하려면 매번 동일한 방 이름(예: 'foo')을 선택하세요.
새 탭 페이지를 열고 localhost:8080을 다시 엽니다. 동일한 회의실 이름을 선택합니다.
세 번째 탭 또는 창에서 localhost:8080을 엽니다. 동일한 회의실 이름을 다시 선택합니다.
각 탭의 콘솔을 확인합니다. 위의 JavaScript에서 로깅이 표시되어야 합니다.
보너스 포인트
- 어떤 대체 메시지 메커니즘이 가능할까요? '순수' WebSocket을 사용하면 어떤 문제가 발생할 수 있나요?
- 이 애플리케이션을 확장하는 데 어떤 문제가 있을 수 있나요? 수천 또는 수백만 개의 동시 룸 요청을 테스트하는 방법을 개발할 수 있나요?
- 이 앱은 JavaScript 프롬프트를 사용하여 채팅방 이름을 가져옵니다. URL에서 채팅방 이름을 가져오는 방법을 알아냅니다. 예를 들어 localhost:8080/foo는 방 이름
foo를 제공합니다.
학습한 내용
이 단계에서는 다음을 수행하는 방법을 배웠습니다.
- npm을 사용하여 package.json에 지정된 프로젝트 종속 항목 설치
- Node.js 서버를 실행하여 정적 파일을 제공합니다.
- socket.io를 사용하여 Node.js에서 메시지 서비스 설정
- 이를 사용하여 '채팅방'을 만들고 메시지를 교환합니다.
이 단계의 전체 버전은 step-04 폴더에 있습니다.
자세히 알아보기
다음 단계
시그널링을 사용하여 두 사용자가 피어 연결을 설정하는 방법을 알아봅니다.
8. 피어 연결과 시그널링 결합
학습할 내용
이 단계에서는 다음 방법을 알아봅니다.
- Node.js에서 실행되는 Socket.IO를 사용하여 WebRTC 신호 서비스 실행
- 이 서비스를 사용하여 피어 간에 WebRTC 메타데이터를 교환합니다.
이 단계의 전체 버전은 step-05 폴더에 있습니다.
HTML 및 JavaScript 바꾸기
index.html의 내용을 다음으로 바꿉니다.
<!DOCTYPE html>
<html>
<head>
<title>Realtime communication with WebRTC</title>
<link rel="stylesheet" href="/css/main.css" />
</head>
<body>
<h1>Realtime communication with WebRTC</h1>
<div id="videos">
<video id="localVideo" autoplay muted></video>
<video id="remoteVideo" autoplay></video>
</div>
<script src="/socket.io/socket.io.js"></script>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script src="js/main.js"></script>
</body>
</html>
js/main.js를 step-05/js/main.js의 내용으로 바꿉니다.
Node.js 서버 실행
작업 디렉터리에서 이 Codelab을 따르지 않는 경우 step-05 폴더 또는 현재 작업 폴더의 종속 항목을 설치해야 할 수 있습니다. 작업 디렉터리에서 다음 명령어를 실행합니다.
npm install
설치가 완료되면 Node.js 서버가 실행되고 있지 않은 경우 work 디렉터리에서 다음 명령어를 호출하여 시작합니다.
node index.js
Socket.IO를 구현하는 이전 단계의 index.js 버전을 사용해야 합니다. Node 및 Socket IO에 대한 자세한 내용은 '메시지를 교환하는 신호 서비스 설정' 섹션을 참고하세요.
브라우저에서 localhost:8080을 엽니다.
새 탭 또는 창에서 localhost:8080을 다시 엽니다. 한 동영상 요소에는 getUserMedia()의 로컬 스트림이 표시되고 다른 동영상 요소에는 RTCPeerconnection을 통해 스트리밍된 '원격' 동영상이 표시됩니다.
브라우저 콘솔에서 로깅을 확인합니다.
보너스 포인트
- 이 애플리케이션은 일대일 영상 채팅만 지원합니다. 두 명 이상의 사용자가 동일한 영상 채팅방을 공유할 수 있도록 디자인을 어떻게 변경할 수 있을까요?
- 이 예시에서는 방 이름 foo가 하드 코딩되어 있습니다. 다른 방 이름을 사용 설정하는 가장 좋은 방법은 무엇인가요?
- 사용자는 회의실 이름을 어떻게 공유하나요? 회의실 이름을 공유하는 대신 다른 방법을 사용해 보세요.
- 앱을 어떻게 변경할 수 있나요?
학습한 내용
이 단계에서는 다음 방법을 알아봤습니다.
- Node.js에서 실행되는 Socket.IO를 사용하여 WebRTC 신호 서비스 실행
- 이 서비스를 사용하여 피어 간에 WebRTC 메타데이터를 교환합니다.
이 단계의 전체 버전은 step-05 폴더에 있습니다.
팁
- WebRTC 통계 및 디버그 데이터는 chrome://webrtc-internals에서 확인할 수 있습니다.
- test.webrtc.org를 사용하여 로컬 환경을 확인하고 카메라와 마이크를 테스트할 수 있습니다.
- 캐싱에 문제가 있는 경우 다음을 시도해 보세요.
- Ctrl 키를 누른 상태에서 새로고침 버튼을 클릭하여 강제 새로고침을 실행합니다.
- 브라우저 다시 시작
- 명령줄에서
npm cache clean를 실행합니다.
다음 단계
사진을 찍고, 이미지 데이터를 가져오고, 원격 피어 간에 공유하는 방법을 알아보세요.
9. 사진을 찍어 데이터 채널을 통해 공유
학습할 내용
이 단계에서는 다음 방법을 알아봅니다.
- 캔버스 요소를 사용하여 사진을 찍고 데이터를 가져옵니다.
- 원격 사용자와 이미지 데이터를 교환합니다.
이 단계의 전체 버전은 step-06 폴더에 있습니다.
작동 방식
이전에는 RTCDataChannel을 사용하여 텍스트 메시지를 교환하는 방법을 배웠습니다.
이 단계를 통해 전체 파일을 공유할 수 있습니다. 이 예에서는 getUserMedia()를 통해 캡처한 사진입니다.
이 단계의 핵심 부분은 다음과 같습니다.
- 데이터 채널을 설정합니다. 이 단계에서는 피어 연결에 미디어 스트림을 추가하지 않습니다.
getUserMedia()를 사용하여 사용자의 웹캠 동영상 스트림을 캡처합니다.
var video = document.getElementById('video');
function grabWebCamVideo() {
console.log('Getting user media (video) ...');
navigator.mediaDevices.getUserMedia({
video: true
})
.then(gotStream)
.catch(function(e) {
alert('getUserMedia() error: ' + e.name);
});
}
- 사용자가 스냅샷 버튼을 클릭하면 동영상 스트림에서 스냅샷 (동영상 프레임)을 가져와
canvas요소에 표시합니다.
var photo = document.getElementById('photo');
var photoContext = photo.getContext('2d');
function snapPhoto() {
photoContext.drawImage(video, 0, 0, photo.width, photo.height);
show(photo, sendBtn);
}
- 사용자가 보내기 버튼을 클릭하면 이미지를 바이트로 변환하고 데이터 채널을 통해 전송합니다.
function sendPhoto() {
// Split data channel message in chunks of this byte length.
var CHUNK_LEN = 64000;
var img = photoContext.getImageData(0, 0, photoContextW, photoContextH),
len = img.data.byteLength,
n = len / CHUNK_LEN | 0;
console.log('Sending a total of ' + len + ' byte(s)');
dataChannel.send(len);
// split the photo and send in chunks of about 64KB
for (var i = 0; i < n; i++) {
var start = i * CHUNK_LEN,
end = (i + 1) * CHUNK_LEN;
console.log(start + ' - ' + (end - 1));
dataChannel.send(img.data.subarray(start, end));
}
// send the reminder, if any
if (len % CHUNK_LEN) {
console.log('last ' + len % CHUNK_LEN + ' byte(s)');
dataChannel.send(img.data.subarray(n * CHUNK_LEN));
}
}
- 수신 측에서는 데이터 채널 메시지 바이트를 이미지로 다시 변환하고 사용자에게 이미지를 표시합니다.
function receiveDataChromeFactory() {
var buf, count;
return function onmessage(event) {
if (typeof event.data === 'string') {
buf = window.buf = new Uint8ClampedArray(parseInt(event.data));
count = 0;
console.log('Expecting a total of ' + buf.byteLength + ' bytes');
return;
}
var data = new Uint8ClampedArray(event.data);
buf.set(data, count);
count += data.byteLength;
console.log('count: ' + count);
if (count === buf.byteLength) {
// we're done: all data chunks have been received
console.log('Done. Rendering photo.');
renderPhoto(buf);
}
};
}
function renderPhoto(data) {
var canvas = document.createElement('canvas');
canvas.width = photoContextW;
canvas.height = photoContextH;
canvas.classList.add('incomingPhoto');
// trail is the element holding the incoming images
trail.insertBefore(canvas, trail.firstChild);
var context = canvas.getContext('2d');
var img = context.createImageData(photoContextW, photoContextH);
img.data.set(data);
context.putImageData(img, 0, 0);
}
코드 가져오기
work 폴더의 콘텐츠를 step-06의 콘텐츠로 바꿉니다. 이제 work의 index.html 파일이 다음과 같이 표시됩니다.
<!DOCTYPE html>
<html>
<head>
<title>Realtime communication with WebRTC</title>
<link rel="stylesheet" href="/css/main.css" />
</head>
<body>
<h1>Realtime communication with WebRTC</h1>
<h2>
<span>Room URL: </span><span id="url">...</span>
</h2>
<div id="videoCanvas">
<video id="camera" autoplay></video>
<canvas id="photo"></canvas>
</div>
<div id="buttons">
<button id="snap">Snap</button><span> then </span><button id="send">Send</button>
<span> or </span>
<button id="snapAndSend">Snap & Send</button>
</div>
<div id="incoming">
<h2>Incoming photos</h2>
<div id="trail"></div>
</div>
<script src="/socket.io/socket.io.js"></script>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script src="js/main.js"></script>
</body>
</html>
작업 디렉터리에서 이 Codelab을 따르지 않는 경우 step-06 폴더 또는 현재 작업 폴더의 종속 항목을 설치해야 할 수 있습니다. 작업 디렉터리에서 다음 명령어를 실행하기만 하면 됩니다.
npm install
설치가 완료되면 Node.js 서버가 실행되고 있지 않은 경우 work 디렉터리에서 다음 명령어를 호출하여 시작합니다.
node index.js
Socket.IO를 구현하는 index.js 버전을 사용하고 있는지 확인하고 변경사항이 있으면 Node.js 서버를 다시 시작해야 합니다. Node 및 Socket IO에 대한 자세한 내용은 '메시지를 교환하는 신호 서비스 설정' 섹션을 참고하세요.
필요한 경우 허용 버튼을 클릭하여 앱이 웹캠을 사용하도록 허용합니다.
앱은 무작위 방 ID를 생성하고 해당 ID를 URL에 추가합니다. 새 브라우저 탭 또는 창에서 주소 표시줄의 URL을 엽니다.
스냅 및 전송 버튼을 클릭한 다음 페이지 하단의 다른 탭에 있는 수신 영역을 확인합니다. 앱이 탭 간에 사진을 전송합니다.
다음과 같은 결과를 확인할 수 있습니다.

보너스 포인트
- 모든 파일 형식을 공유할 수 있도록 코드를 어떻게 변경해야 할까요?
자세히 알아보기
- MediaStream Image Capture API: 사진을 찍고 카메라를 제어하는 API로, 곧 가까운 브라우저에서 사용할 수 있습니다.
- 오디오 및 동영상 녹화를 위한 MediaRecorder API: 데모, 문서
학습한 내용
- 캔버스 요소를 사용하여 사진을 찍고 사진에서 데이터를 가져오는 방법
- 원격 사용자와 데이터를 교환하는 방법
이 단계의 전체 버전은 step-06 폴더에 있습니다.
10. 축하합니다
실시간 동영상 스트리밍 및 데이터 교환을 위한 앱을 빌드했습니다.
학습한 내용
이 Codelab을 통해 학습한 내용은 다음과 같습니다.
- 웹캠에서 동영상을 가져옵니다.
- RTCPeerConnection으로 동영상을 스트리밍합니다.
- RTCDataChannel로 데이터 스트리밍
- 메시지를 교환할 신호 서비스 설정
- 피어 연결과 신호 결합
- 사진을 찍고 데이터 채널을 통해 공유합니다.
다음 단계
- 표준 WebRTC 채팅 애플리케이션 AppRTC의 코드와 아키텍처를 살펴봅니다(app, code).
- github.com/webrtc/samples의 라이브 데모를 사용해 보세요.
자세히 알아보기
- webrtc.org에서 WebRTC를 시작하는 데 사용할 수 있는 다양한 리소스를 확인할 수 있습니다.