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 게이트웨이와 방화벽을 통과해야 하며, P2P 네트워킹은 직접 연결이 실패할 경우 대체해야 합니다. 이 프로세스의 일부로 WebRTC API는 STUN 서버를 사용하여 컴퓨터의 IP 주소를 가져오고 TURN 서버를 사용하여 P2P 통신이 실패할 경우 릴레이 서버 역할을 합니다. 자세한 내용은 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 앱 바로가기를 클릭합니다.
Web Server 아이콘을 클릭합니다.
그러면 다음 대화상자가 표시되면 로컬 웹 서버를 구성할 수 있습니다.
폴더 선택 버튼을 클릭하고 방금 만든 작업 폴더를 선택합니다. 이렇게 하면 웹 서버 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}
로 변경하면 어떻게 되는지 살펴보겠습니다. - 동영상 요소의 크기는 얼마인가요? 디스플레이 크기가 아닌 자바스크립트에서 동영상의 기본 크기를 가져오려면 어떻게 해야 하나요? 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을 통해 스트리밍된 동일한 동영상이 표시됩니다. (실제 애플리케이션에서는 한 동영상 요소가 로컬 스트림을 표시하고 다른 동영상 요소는 원격 스트림을 표시합니다.)
어댑터.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을 열고 Start(시작) 버튼을 클릭하여 웹캠에서 동영상을 가져온 다음 Call(호출)을 클릭하여 피어 연결을 설정합니다. 두 동영상 요소에 웹캠의 동일한 동영상이 표시되어야 합니다. 브라우저 콘솔에서 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
핸들러가 호출됩니다. - 앨리스는 직렬화된 후보 데이터를 밥에게 전송합니다. 실제 애플리케이션에서는 이 프로세스 (신호라고 함)가 메시지 서비스를 통해 진행됩니다. 이 프로세스의 방법은 이후 단계에서 알아봅니다. 물론 이 단계에서는 두 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라고 하는 세션 설명 프로토콜 형식을 사용하여 offer 및 answer로 알려진 메타데이터의 blob을 교환하는 방식으로 진행됩니다.
- 앨리스는 RTCPeerConnection
createOffer()
메서드를 실행합니다. 반환된 프로미스는 RTCSessionDescription: Alice의 로컬 세션 설명을 제공합니다.
trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
.then(createdOffer).catch(setSessionDescriptionError);
- 성공하면 앨리스는
setLocalDescription()
를 사용하여 지역 설명을 설정한 다음 신호 채널을 통해 이 세션 설명을 밥에게 전송합니다. - 밥은
setRemoteDescription()
을 사용하여 윤아가 보낸 설명을 원격 설명으로 설정합니다. - 밥은 RTCPeerConnection
createAnswer()
메서드를 실행하고 앨리스로부터 얻은 원격 설명을 앨리스에 전달하므로 앨리스와 호환되는 로컬 세션을 생성할 수 있습니다.createAnswer()
프로미스가 RTCSessionDescription을 전달함: Bob은 이를 로컬 설명으로 설정하여 Alice에게 전송합니다. - 윤아는 밥의 세션 설명을 받으면 이를
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를 살펴봅니다. 이 API는 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를 살펴보세요. 이 페이지에는 JavaScript 프레임워크에 대한 제안이 나와 있습니다. WebRTC를 사용하고 싶지만 API를 랭글링하고 싶지 않은 경우에 적합한 방법입니다.
- adapter.js GitHub 저장소에서 어댑터.js shim에 대해 자세히 알아보세요.
- 세계 최고의 영상 채팅 앱이 무엇인지 알고 싶으신가요? WebRTC 호출용 WebRTC 프로젝트의 표준 앱인 AppRTC(app, code)를 살펴보세요. 통화 설정 시간이 500ms 미만입니다.
권장사항
- 코드 미래 경쟁력을 확보하려면 새로운 프라미스 기반 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>
한 텍스트 영역은 텍스트를 입력하기 위한 것이고, 다른 하나는 동종 앱 사이에 스트리밍된 텍스트를 표시합니다.
이제 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
에 텍스트를 입력한 다음 Send를 클릭하여 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의 문법은 의도적으로 WebSocket과 유사하며, send()
메서드와 message
이벤트가 있습니다.
dataConstraint
의 사용에 주목하세요. 다양한 유형의 데이터 공유를 지원하도록 데이터 채널을 구성할 수 있습니다. 예를 들어 성능보다 안정적인 제공을 우선시합니다. Mozilla 개발자 네트워크에서 옵션에 대해 자세히 알아보세요.
보너스 점수
- SCTP를 사용하면 WebRTC 데이터 채널에서 사용하는 프로토콜로 안정적이고 순서가 지정된 데이터 전송이 기본적으로 사용 설정됩니다. RTCDataChannel이 안정적인 데이터 전송을 제공해야 하는 경우는 언제이며, 일부 데이터가 손실되더라도 성능이 더 중요할 수 있는 경우는 언제인가요?
- CSS를 사용하여 페이지 레이아웃을 개선하고 자리표시자 속성을 'dataChannelReceived'에 추가 텍스트 영역으로 설정됩니다.
- 휴대기기에서 페이지를 테스트합니다.
학습한 내용
이 단계에서는 다음을 수행하는 방법을 배웠습니다.
- 두 WebRTC 피어 간에 연결을 설정합니다.
- 피어 간에 텍스트 데이터를 교환합니다.
이 단계의 전체 버전은 step-03 폴더에 있습니다.
자세히 알아보기
- WebRTC 데이터 채널 (몇 년 전이지만 읽어볼 만한 가치가 있음)
- WebRTC의 데이터 채널에 SCTP가 선택된 이유는 무엇인가요?
다음 단계
같은 페이지에서 피어 간에 데이터를 교환하는 방법을 배웠습니다. 그런데 다른 머신 간에 데이터를 교환하려면 어떻게 해야 할까요? 먼저 메타데이터 메시지를 교환할 신호 채널을 설정해야 합니다. 다음 단계에서 방법을 알아보세요.
7. 메시지 교환을 위한 신호 서비스 설정하기
학습할 내용
이 단계에서는 다음 작업을 수행하는 방법을 알아봅니다.
npm
를 사용하여 package.json에 지정된 대로 프로젝트 종속 항목을 설치합니다.- Node.js 서버를 실행하고 node-static을 사용하여 정적 파일을 제공합니다.
- Socket.IO를 사용하여 Node.js에서 메시징 서비스를 설정합니다.
- 이를 사용하여 '방'을 만듭니다. 메시지를 교환할 수 있습니다
이 단계의 전체 버전은 step-04 폴더에 있습니다.
개념
WebRTC 통화를 설정하고 유지하려면 WebRTC 클라이언트 (피어)가 메타데이터를 교환해야 합니다.
- 후보자 (네트워크) 정보
- 해상도 및 코덱과 같은 미디어 관련 정보를 제공하는 제안 및 답변 메시지
즉, 오디오, 동영상 또는 데이터의 P2P 스트리밍이 이루어지기 전에 메타데이터 교환이 필요합니다. 이 프로세스를 신호라고 합니다.
이전 단계에서 발신자와 수신자 RTCPeerConnection 객체는 같은 페이지에 있으므로 '신호' 객체 간에 메타데이터를 전달하는 것입니다.
실제 애플리케이션에서 발신자 및 수신자 RTCPeerConnections는 서로 다른 기기의 웹페이지에서 실행되므로 이들이 메타데이터를 전달할 방법이 필요합니다.
이를 위해 WebRTC 클라이언트 (피어) 간에 메시지를 전달할 수 있는 서버인 신호 서버를 사용합니다. 실제 메시지는 문자열화된 JavaScript 객체인 일반 텍스트입니다.
기본 요건: Node.js 설치
이 Codelab의 다음 단계 (폴더 step-04에서 step-06까지)를 실행하려면 Node.js를 사용하여 localhost에서 서버를 실행해야 합니다.
이 링크 또는 원하는 패키지 관리자를 통해 Node.js를 다운로드하고 설치할 수 있습니다.
설치가 완료되면 다음 단계 (npm install
실행)에 필요한 종속 항목을 가져올 수 있을 뿐만 아니라 작은 localhost 서버를 실행하여 Codelab을 실행할 수 있습니다 (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 애플리케이션에서는 최대 두 명의 피어가 방을 공유할 수 있습니다.
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" } }
노드 패키지 관리자 (npm
)에 설치할 프로젝트 종속 항목을 알려주는 앱 매니페스트입니다.
종속 항목 (예: /socket.io/socket.io.js
)을 설치하려면 work 디렉터리의 명령줄 터미널에서 다음을 실행합니다.
npm install
다음과 같이 끝나는 설치 로그가 표시됩니다.
보시다시피 npm
는 package.json에 정의된 종속 항목을 설치했습니다.
js 디렉터리가 아닌 work 디렉터리의 최상위 수준에 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에서 로깅이 표시되어야 합니다.
보너스 점수
- 어떤 대체 메시지 메커니즘을 사용할 수 있을까요? 'pure'를 사용할 때 발생할 수 있는 문제는 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 및 자바스크립트 대체
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 서버 실행
work 디렉터리에서 이 Codelab을 따르지 않는 경우 step-05 폴더 또는 현재 작업 폴더에 종속 항목을 설치해야 할 수 있습니다. 작업 디렉터리에서 다음 명령어를 실행합니다.
npm install
설치된 후 Node.js 서버가 실행 중이 아니면 work 디렉터리에서 다음 명령어를 호출하여 Node.js를 시작합니다.
node index.js
Socket.IO를 구현하는 이전 단계의 index.js 버전을 사용 중인지 확인합니다. 노드 및 소켓 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);
}
- 사용자가 Send 버튼을 클릭하면 이미지를 바이트로 변환하고 데이터 채널을 통해 전송합니다.
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>
work 디렉터리에서 이 Codelab을 따르지 않는 경우 step-06 폴더 또는 현재 작업 폴더의 종속 항목을 설치해야 할 수 있습니다. 작업 디렉터리에서 다음 명령어를 실행하기만 하면 됩니다.
npm install
설치된 후 Node.js 서버가 실행 중이 아니면 work 디렉터리에서 다음 명령어를 호출하여 Node.js를 시작합니다.
node index.js
Socket.IO를 구현하는 index.js 버전을 사용하고 있는지 확인하고 변경사항이 있는 경우 Node.js 서버를 다시 시작해야 합니다. 노드 및 소켓 IO에 대한 자세한 내용은 '메시지 교환을 위한 신호 서비스 설정' 섹션을 참조하세요.
필요한 경우 허용 버튼을 클릭하여 앱에서 웹캠을 사용하도록 허용합니다.
앱에서 임의의 방 ID를 생성하고 이 ID를 URL에 추가합니다. 새 브라우저 탭 또는 창의 주소 표시줄에서 URL을 엽니다.
Snap & 보내기 버튼을 누른 다음 페이지 하단의 다른 탭에서 수신 영역을 확인합니다. 앱이 탭 간에 사진을 전송합니다.
다음과 같은 결과를 확인할 수 있습니다.
보너스 점수
- 모든 파일 형식을 공유할 수 있도록 코드를 변경하려면 어떻게 해야 하나요?
자세히 알아보기
- MediaStream Image Capture API: 사진을 찍고 카메라를 제어하는 API입니다. 이 API가 곧 사용자의 브라우저에 제공될 예정입니다.
- 오디오 및 동영상 녹음용 MediaRecorder API: 데모, 문서
학습한 내용
- 사진을 찍고 캔버스 요소를 사용하여 사진을 가져오는 방법
- 원격 사용자와 데이터를 교환하는 방법
이 단계의 전체 버전은 step-06 폴더에 있습니다.
10. 축하합니다
실시간 동영상 스트리밍 및 데이터 교환을 위한 앱을 빌드했습니다.
학습한 내용
이 Codelab을 통해 학습한 내용은 다음과 같습니다.
- 웹캠으로 동영상을 가져옵니다.
- RTCPeerConnection으로 동영상을 스트리밍하세요.
- RTCDataChannel을 사용하여 데이터를 스트리밍합니다.
- 메시지를 교환할 신호 서비스를 설정합니다.
- 피어 연결과 신호 결합
- 사진을 찍어 데이터 채널을 통해 공유합니다.
다음 단계
- 표준 WebRTC 채팅 애플리케이션 AppRTC의 코드와 아키텍처(app, code)를 살펴봅니다.
- github.com/webrtc/samples에서 라이브 데모를 사용해 보세요.
자세히 알아보기
- webrtc.org에서 WebRTC를 시작하는 데 필요한 다양한 리소스를 제공합니다.