본문 바로가기

기술&정보

#WebRTC #RTCPeerConnection RealTime P2P Communication

반응형

WebRTC 소개

WebRTC는 브라우저 및 안드로이드, iOS에서 사용 가능한 비디오 채팅 및 P2P 데이터 공유 기능입니다.

이론을 공부하기에 앞서 먼저 체험해봅시다.

  1. 브라우저에서 appr.tc 페이지를 엽니다.
  2. 브라우저에서 Webcam을 사용하도록 허용합니다.
  3. 하단에 표시된 URL로 다른 디바이스의 브라우저에서 접속합니다.

간단한 방법으로 쌍방향 비디오 전송을 확인할 수 있습니다.

WebRTC의 기능

WebRTC API는 2010년 대 이전의 플러그인 방식 실시간 커뮤니케이션과 달리 오픈소스이며, 무료이고, 표준화되었으며, 웹브라우저에 내장되어 있어 효율적입니다.

WebRTC는 다음과 같은 기능을 제공합니다:

  • 로컬 디바이스에서 스트리밍 오디오, 비디오, 데이터 가져오기
  • IP 주소, 포트 등의 네트워크 정보를 가져와 다른 WebRTC 클라이언트와 교환 (이 과정에서 NAT, 방화벽을 통과해야 합니다.)
  • 에러 보고 및 세션의 초기화와 종료를 위한 시그널링 통신 관리
  • 해상도와 코덱 같은 미디어 클라이언트 수용 능력 정보 교환
  • 스트리밍 오디오, 비디오, 데이터 수신 및 전송

WebRTC에는 위 기능을 제공하기 위해 다음 세 가지 API가 구현되어 있습니다:

  • MediaStream: 사용자의 카메라와 마이크 같은 데이터 스트림에 접근합니다.
  • RTCPeerConnection: 암호화 및 대역폭 관리 기능을 갖춘 오디오  및 비디오 연결 기능입니다.
  • RTCDataChannel: 일반적인 데이터 P2P 통신입니다.

MediaStream

카메라와 마이크의 입력에서 받아온 스트림을 오디오, 비디오 트랙에 동기화 할 수 있습니다. 로컬 카메라에서 정보를 받아와 html 화면에 뿌려주는 예제를 만들어봅시다. HTML 파일을 하나 생성하여 간단히 화면을 구성한 후 video 태그와 video를 받아올 js 파일을 추가해줍니다.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <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>
'use strict';

const mediaStreamConstraints = {
  video: true,
};

const localVideo = document.querySelector('video');

function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
}

function handleLocalMediaStreamError(error) {
  console.log('navigator.getUserMedia Error: ', error);
}

navigator.mediaDevices.getUserMedia(mediaStreamConstants)
  .then(gotLocalMediaStream)
  .catch(handleLocalMediaStreamError);

getUserMedia() 함수는 3개의 매개 변수를 받습니다:

  • 어떤 스트림을 받을지 명시한 제약 사항 (mediaStreamConstraints)
  • 성공 시 실행하는, MediaStream을 매개 변수로 넘겨받는 콜백 함수 (gotLocalMediaStream)
  • 실패 시 실행하는, 에러 오브젝트를 매개 변수로 넘겨받는 콜백 함수 (handleLocalMediaStreamError)

변수를 바꿔가며 테스트 해봅시다. mediaStreamConstants에 audio: true를 추가하면 마이크 권한이 추가되며, 권한 허용 시 로컬 마이크 인풋을 피드백으로 들을 수 있습니다.

'use strict';

const mediaStreamConstants = {
  video: true,
  audio: false,
};

const localVideo = document.querySelector('video');
let localStream;

function gotLocalMediaStream(mediaStream) {
  localStream = mediaStream;
  localVideo.srcObject = mediaStream;
  console.log(localStream);
}

function handleLocalMediaStreamError(error) {
  console.log('navigator.getUserMedia Error: ', error);
}

navigator.mediaDevices.getUserMedia(mediaStreamConstants)
  .then(gotLocalMediaStream)
  .catch(handleLocalMediaStreamError);

MediaStream을 log를 찍어봅시다. 각 미디어 스트림은 "{bb577e74-e392-4044-8e51-78339fd6011f}"와 같은 고유한 id를 가집니다. MediaStream은 getAudioTracks(), getVideoTracks() 메소드를 통해 등록된 트랙을 가져올 수 있으며, 위 스크린샷에서는 video는 허용하고 audio는 허용하지 않았기 때문에 video track 하나 만이 표시됩니다.

다음과 같은 스크립트를 콘솔에 입력하여 비디오 입력을 멈출 수 있습니다.

localStream.getVideoTracks()[0].stop()

다음과 같이 비디오에 CSS 필터를 설정할 수 있습니다.

const cssFilterConstraints = {
  video {
    filter: blur(4px) invert(1) opacity(0.5);
  }
}

다음과 같이 비디오의 가로, 세로 크기를 결정할 수도 있습니다.

const hdConstraints = {
  video: {
    width: {
      min: 1280
    },
    height: {
      min: 720
    }
  }
}

자세한 명세는 다음을 확인해주세요.

 

Media Capture and Streams

Initial Author of this Specification was Ian Hickson, Google Inc., with the following copyright statement: © Copyright 2004-2011 Apple Computer, Inc., Mozilla Foundation, and Opera Software ASA. You are granted a license to use, reproduce and create deriv

w3c.github.io

주의: createObjectURL()

예전에는 <video> 태그에 미디어 스트림을 부착하려면 URL.createObjectURL() 메소드를 사용하여 MediaStream의 객체 URL을 생성했어야 했습니다. 이제 이런 과정은 필수가 아니며, 브라우저도 지원을 중단하고 있습니다. 대신 video 태그의 srcObject에 MediaStream을 직접 설정하도록 코드를 수정해야 합니다.

Signaling Server

WebRTC는 RTCPeerConnection을 사용하여 브라우저 간 스트리밍 데이터를 송수신합니다. 그러나 RTCPeerConnection을 시작하기에 앞서 통신을 조정하고, 제어 메시지를 받는 매커니즘이 필요합니다. 이를 시그널링이라고 하며, 시그널링 방법과 프로토콜에 대한 명세는 정해지지 않아 개발자가 익숙하고 자유로운 형태로 구현할 수 있습니다.

명세는 정해지지 않았다고 하지만, 몇 가지 장점 때문에 Socket.IO를 이용한 웹 소켓을 이용하는 방법이 일반적입니다. 여러 가지 시그널링 방법과 특징을 알아봅시다.

  • XHR API: 새 메시지를 확인하기 위해 주기적으로 XHR 요청을 보냅니다. (Polling)
    • 가장 단순한 방식
    • 별도의 도구 없이 구현 가능
    • Polling 이후 메시지 발생 -> 다음 Polling까지 메시지 송신 지연
    • 주고 받을 메시지가 없어도 Polling 사이클 반복 -> 대역폭 낭비
  • XHR Long-Polling: 요청이 들어올 시 전송 메시지가 없으면 네트워크 연결을 유지하고, 메시지가 발생할 때 응답을 보냅니다.
    • 전송할 메시지가 없으면 서버가 HTTP 1.1 keep-alive 매커니즘으로 네트워크 연결 유지
    • 새로운 정보가 발생하면 요청에 대한 응답으로 메시지 송신
    • 주기적인 Polling이 없으므로 네트워크 오버헤드가 감소
    • 지속적인 네트워크 트래픽이 발생
  • 서버 전송 이벤트: 서버에서 메시지를 직접 전송하는 방식입니다.
    • 서버에 새로운 연결 생성 -> 이벤트 리스너 추가하여 서버 전송 메시지 처리
    • 서버에서 메시지 직접 전송 -> 브라우저가 즉시 반응
    • 브라우저에서 서버로 정보를 전달하는 채널을 별도로 생성해야 함
    • 두 채널 간 동기화 필요
  • Web Socket: 제안/응답(Offer/Answer) 시그널링에 이상적입니다.
    • 양방향 통신이 가능
    • 메시지, 에러 수신 처리 함수만 설정하면 간단
    • 빠르면서 가벼움
    • 필요할 때 즉시 메시지 송수신 가능
    • 발송 지연, 지속적인 네트워크 트래픽이 없음

STUN/TURN 서버

시그널링 메시지에는 ICE 후보인 STUN/TURN 서버 주소가 포함됩니다. WebRTC는 P2P 방식으로 작동하도록 설계되었기 때문에 사용자는 가능한 한 직접적으로 연결되어야 합니다. 따라서 NAT 게이트웨이, 방화벽을 넘을 방법을 찾아야 하며, 이러한 시도가 실패할 경우 직접적인 연결에 실패한 것으로 간주하여 중개 서버로 연결됩니다.

WebSocket API는 STUN(Session Traversal Utilities for NAT) 서버를 사용하여 컴퓨터 IP를 얻으며, 직접적인 연결에 실패한 경우 TURN(Traversal Using Relay around NAT) 서버를 사용하여 통신을 중개합니다.

RTCPeerConnection

다음은 RTCPeerConnection에 대해 알아볼 수 있는 예제입니다. 다만, 현재 서버가 없는 환경임을 가정하여 한 페이지 안에서 local stream과 remote stream 모두 띄워보도록 하겠습니다.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <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="js/main.js"></script>
  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
</body>
</html>
'use strict';

// Set up media stream constant and parameters.

// In this codelab, you will be streaming video only: "video: true".
// Audio will not be streamed because it is set to "audio: false" by default.
const mediaStreamConstraints = {
  video: true,
};

// Set up to exchange only video.
const offerOptions = {
  offerToReceiveVideo: 1,
};

// Define initial start time of the call (defined as connection between peers).
let startTime = null;

// Define peer connections, streams and video elements.
const localVideo = document.getElementById('localVideo');
const remoteVideo = document.getElementById('remoteVideo');

let localStream;
let remoteStream;

let localPeerConnection;
let remotePeerConnection;


// Define MediaStreams callbacks.

// Sets the MediaStream as the video element src.
function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
  localStream = mediaStream;
  trace('Received local stream.');
  callButton.disabled = false;  // Enable call button.
}

// Handles error by logging a message to the console.
function handleLocalMediaStreamError(error) {
  trace(`navigator.getUserMedia error: ${error.toString()}.`);
}

// Handles remote MediaStream success by adding it as the remoteVideo src.
function gotRemoteMediaStream(event) {
  const mediaStream = event.stream;
  remoteVideo.srcObject = mediaStream;
  remoteStream = mediaStream;
  trace('Remote peer connection received remote stream.');
}


// Add behavior for video streams.

// Logs a message with the id and size of a video element.
function logVideoLoaded(event) {
  const video = event.target;
  trace(`${video.id} videoWidth: ${video.videoWidth}px, ` +
        `videoHeight: ${video.videoHeight}px.`);
}

// Logs a message with the id and size of a video element.
// This event is fired when video begins streaming.
function logResizedVideo(event) {
  logVideoLoaded(event);

  if (startTime) {
    const elapsedTime = window.performance.now() - startTime;
    startTime = null;
    trace(`Setup time: ${elapsedTime.toFixed(3)}ms.`);
  }
}

localVideo.addEventListener('loadedmetadata', logVideoLoaded);
remoteVideo.addEventListener('loadedmetadata', logVideoLoaded);
remoteVideo.addEventListener('onresize', logResizedVideo);


// Define RTC peer connection behavior.

// Connects with new peer candidate.
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}.`);
  }
}

// Logs that the connection succeeded.
function handleConnectionSuccess(peerConnection) {
  trace(`${getPeerName(peerConnection)} addIceCandidate success.`);
};

// Logs that the connection failed.
function handleConnectionFailure(peerConnection, error) {
  trace(`${getPeerName(peerConnection)} failed to add ICE Candidate:\n`+
        `${error.toString()}.`);
}

// Logs changes to the connection state.
function handleConnectionChange(event) {
  const peerConnection = event.target;
  console.log('ICE state change event: ', event);
  trace(`${getPeerName(peerConnection)} ICE state: ` +
        `${peerConnection.iceConnectionState}.`);
}

// Logs error when setting session description fails.
function setSessionDescriptionError(error) {
  trace(`Failed to create session description: ${error.toString()}.`);
}

// Logs success when setting session description.
function setDescriptionSuccess(peerConnection, functionName) {
  const peerName = getPeerName(peerConnection);
  trace(`${peerName} ${functionName} complete.`);
}

// Logs success when localDescription is set.
function setLocalDescriptionSuccess(peerConnection) {
  setDescriptionSuccess(peerConnection, 'setLocalDescription');
}

// Logs success when remoteDescription is set.
function setRemoteDescriptionSuccess(peerConnection) {
  setDescriptionSuccess(peerConnection, '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);
}


// Define and add behavior to buttons.

// Define action buttons.
const startButton = document.getElementById('startButton');
const callButton = document.getElementById('callButton');
const hangupButton = document.getElementById('hangupButton');

// Set up initial action buttons status: disable call and hangup.
callButton.disabled = true;
hangupButton.disabled = true;


// Handles start button action: creates local MediaStream.
function startAction() {
  startButton.disabled = true;
  navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
    .then(gotLocalMediaStream).catch(handleLocalMediaStreamError);
  trace('Requesting local stream.');
}

// Handles call button action: creates peer connection.
function callAction() {
  callButton.disabled = true;
  hangupButton.disabled = false;

  trace('Starting call.');
  startTime = window.performance.now();

  // Get local media stream tracks.
  const videoTracks = localStream.getVideoTracks();
  const audioTracks = localStream.getAudioTracks();
  if (videoTracks.length > 0) {
    trace(`Using video device: ${videoTracks[0].label}.`);
  }
  if (audioTracks.length > 0) {
    trace(`Using audio device: ${audioTracks[0].label}.`);
  }

  const servers = null;  // Allows for RTC server configuration.

  // Create peer connections and add behavior.
  localPeerConnection = new RTCPeerConnection(servers);
  trace('Created local peer connection object localPeerConnection.');

  localPeerConnection.addEventListener('icecandidate', handleConnection);
  localPeerConnection.addEventListener(
    'iceconnectionstatechange', handleConnectionChange);

  remotePeerConnection = new RTCPeerConnection(servers);
  trace('Created remote peer connection object remotePeerConnection.');

  remotePeerConnection.addEventListener('icecandidate', handleConnection);
  remotePeerConnection.addEventListener(
    'iceconnectionstatechange', handleConnectionChange);
  remotePeerConnection.addEventListener('addstream', gotRemoteMediaStream);

  // Add local stream to connection and create offer to connect.
  localPeerConnection.addStream(localStream);
  trace('Added local stream to localPeerConnection.');

  trace('localPeerConnection createOffer start.');
  localPeerConnection.createOffer(offerOptions)
    .then(createdOffer).catch(setSessionDescriptionError);
}

// Handles hangup action: ends up call, closes connections and resets peers.
function hangupAction() {
  localPeerConnection.close();
  remotePeerConnection.close();
  localPeerConnection = null;
  remotePeerConnection = null;
  hangupButton.disabled = true;
  callButton.disabled = false;
  trace('Ending call.');
}

// Add click event handlers for buttons.
startButton.addEventListener('click', startAction);
callButton.addEventListener('click', callAction);
hangupButton.addEventListener('click', hangupAction);


// Define helper functions.

// Gets the "other" peer connection.
function getOtherPeer(peerConnection) {
  return (peerConnection === localPeerConnection) ?
      remotePeerConnection : localPeerConnection;
}

// Gets the name of a certain peer connection.
function getPeerName(peerConnection) {
  return (peerConnection === localPeerConnection) ?
      'localPeerConnection' : 'remotePeerConnection';
}

// Logs an action (text) and the time when it happened on the console.
function trace(text) {
  text = text.trim();
  const now = (window.performance.now() / 1000).toFixed(3);

  console.log(now, text);
}

이제 다음과 같이 코드를 고친 뒤, 두 개의 브라우저에서 같은 페이지를 접속하고 두 브라우저를 연결해봅시다.

  • 각 브라우저에서 RTCPeerConnection을 생성한 후, 각 브라우저에 getUserMedia()로 획득한 local stream을 추가합니다.
  • 각 브라우저가 연결 가능한 End Point(네트워크 인터페이스와 포트)를 ICE 후보라고 합니다. 해당 네트워크 정보를 가져와 서로 공유합니다.
  • 로컬 미디어에 대한 메타 데이터를 SDP(Session Description Protocol) 형식으로 기술한 데이터를 Description이라고 합니다. 로컬 및 원격 Description을 가져와 서로 공유합니다.

위 과정은 코드 상에서 다음과 같은 단계로 나타납니다.

  1. RTCPeerConnection 오브젝트를 생성하고 icecandidate 핸들러와 iceconnectionstatechange 핸들러를 등록합니다. icecandidate 핸들러는 ICE 후보를 받아 다른 피어에 전송하는 핸들러이며, iceconnectionstatechange 핸들러는 변경된 ICE 상태를 보여줍니다.
let localPeerConnection;

/*
  ...
*/

localPeerConnection = new RTCPeerConnection(servers);
localPeerConnection.addEventListener('icecandidate', handleConnection);
localPeerConnection.addEventListener(
    'iceconnectionstatechange', handleConnectionChange);

2. getUserMedia()를 호출하고 전달된 local stream을 화면과 PeerConnection에 추가합니다.

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.');

3. onicecandidate 핸들러는 네트워크 후보가 유효해질 때 호출됩니다.

4. 한 브라우저가 시리얼화 된 후보 데이터를 다른 브라우저에 전달합니다. 이 과정을 시그널링이라고 하며 본래 서버를 통해서 전달됩니다. 이 예제에서는 같은 페이지 안에 remote, local이 존재하기 때문에 서버를 거치지 않습니다.

5. 다른 브라우저가 후보 메시지를 전달 받으면, addIceCandidate()를 호출하고 remote peer description에 후보를 추가합니다.

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}.`);
  }
}

이상 3, 4, 5의 과정입니다.

또한, WebRTC 피어 간에는 local과 remote가 사용할 수 있는 resolution과 codec capability 같은 정보를 교환하는 과정이 필요합니다. 이러한 미디어 구성 정보를 교환하는 시그널링에는 SDP(Session Description Protocol) 포맷을 적용한 제안/응답(Offer/Answer) 모델이 사용됩니다.

6. 브라우저에서 createOffer() 메소드를 실행하면, promise의 반환 값으로 (해당 브라우저의 local session description이 될 수 있는) RTCSessionDescription 형태가 반환됩니다.

trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
  .then(createdOffer).catch(setSessionDescriptionError);

7. 제안을 성공적으로 생성한 경우, 해당 description을 local session description으로 설정하고 이 정보를 시그널링 서버를 통해 다른 브라우저에 전달합니다.

8. 다른 브라우저는 해당 정보를 remote session description으로 설정합니다.

9. 다른 브라우저에서는 createAnswer() 메소드를 통해 제안에 상응하는 응답을 생성합니다. createAnswer() promise는 RTCSessionDescription 형태의 인스턴스를 반환합니다. 해당 정보는 다른 브라우저의 local description, 원래 브라우저의 remote description으로 각각 설정됩니다.

// 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);
}

Start 버튼만 눌렀을 때는 local stream이 표시되며, Call 버튼을 누를 경우 약간의 딜레이를 가지는 remote stream이 함께 표시됩니다. 네트워크 환경에 따라 remote stream의 해상도가 조정되는 모습을 볼 수 있으며, Hang Up 버튼을 누를 경우 remote stream이 송출 중단됩니다.

WebRTC 보안

모든 WebRTC 요소는 암호화가 필수입니다. 그리고 JS API는 HTTPS, localhost와 같은 secure origin에서만 사용될 수 있습니다. 다만, 시그널링 매커니즘은 WebRTC 표준에 포함되지 않으므로 이 과정에서 secure protocol을 사용하여야 합니다.

생각보다 글이 길어졌네요. 다음 시간에는 RTCDataChannel을 통해 데이터를 교환하는 방법과 Signaling Service를 통해 메시지를 교환하는 방법에 대해 알아보겠습니다.

 

codelabs.developers.google.com/codelabs/webrtc-web

 

Real time communication with WebRTC  |  Google Codelabs

Learn how to stream media and data between two browsers. Get to grips with the core APIs and technologies of WebRTC. Capture and manipulate images using getUserMedia, CSS, and the canvas element. Set up a peer connection and exchange data directly between

codelabs.developers.google.com

 

반응형