Published on

WebRTC Peer 구현

1. WebRTC Peer API

  • WebRTC는 표준화된 프로토콜과 Javascript API를 통해서, 3rd party 플러그인을 사용하지 않고 브라우저를 통해 peer간의 audio, video, data를 통신할 수 있게 해준다.

  • 브라우저는 WebRTC를 지원하기 위해 다음의 주요 API들을 제공한다.

    • getUserMedia : local audio와 video stream을 가져온다.
    • MediaStream : audio와 video stream을 객체화 한다.
    • MediaStreamTrack : MediaStream을 구성하는 각각의 track.
    • RTCPeerConnection : peer-to-peer연결과 audio와 video data의 통신을 지원.
    • RTCDataChannel : application data 통신을 지원.

2. getUserMedia API

  • getUserMedia를 통해서 application에서 필요한 audio와 video stream을 가져올 수 있다. (constraints를 통해 필요한 stream을 제한 또는 설정할 수 있다.)
<video autoplay></video>

<script>
  var constraints = {
    audio: true,
    video: {
      mandatory: {
        width: { min: 320 },
        height: { min: 180 }
      },
      optional: [
        { width: { max: 1280 }},
        { frameRate: 30 },
        { facingMode: "user" }
      ]
    }
  }

  navigator.getUserMedia(constraints, gotStream, logError);

  function gotStream(stream) {
    var video = document.querySelector('video');
    video.src = window.URL.createObjectURL(stream);
  }

  function logError(error) { ... }
</script>

3. RTCPeerConnection API

  • RTCPeerConnection API는 peer-to-peer 연결의 전체 life cycle을 담당하는 API이다.
object
  • Session Description Protocol (SDP) 을 통해, peer간의 교환할 미디어타입(video, audio, app data), 코덱정보, bandwidth정보, 그밖의 메타데이터 등을 교환한다.

  • SDP는 signalling 채널을 통해 offer, answer 로 peer간에 교환한다. (전송하는 SDP는 setLocalDescription, 전달받는 SDP는 setRemoteDescription 으로 설정한다.)

  • SDP 교환후에 RTCPeerConnection의 ICE agent는 peer 간의 연결과 연결상태 관리를 담당한다.

    • ICE agent는 os에 local IP 주소들을 물어봐서 candidate에 추가한다.

    • ICE agent는 STUN 서버로부터 peer의 public IP, port를 물어봐서 candidate에 추가한다.

    • ICE agent는 TURN서버를 last candidate로 추가하고, peer간의 직접 연결이 실패할 경우 TURN 서버를 통해 data가 relay된다.

  • offer, answer, candidate 을 교환하는 signalling 과정이 끝나면 ICE agent를 통해 peer-to-peer 연결이 이루어지고, peer간의 통신을 시작한다.

/**** Caller Code ****/
<video id="local_video" autoplay></video>
<video id="remote_video" autoplay></video>

<script>
  var ice = {"iceServers": [
    {"url": "stun:stunserver.com:12345"},
    {"url": "turn:turnserver.com", "username": "user", "credential": "pass"}
  ]};

  var signalingChannel = new SignalingChannel();
  var pc = new RTCPeerConnection(ice);

  navigator.getUserMedia({ "audio": true, "video": true }, gotStream, logError);

  function gotStream(evt) {
    pc.addStream(evt.stream);

    var local_video = document.getElementById('local_video');
    local_video.src = window.URL.createObjectURL(evt.stream);

    pc.createOffer(function(offer) {
      pc.setLocalDescription(offer);
      signalingChannel.send(offer.sdp);
    });
  }

  pc.onicecandidate = function(evt) {
    if (evt.candidate) {
      signalingChannel.send(evt.candidate);
    }
  }

  signalingChannel.onmessage = function(msg) {
    if (msg.answer) {
      pc.setRemoteDescription(msg.answer);
    } else if (msg.candidate) {
      pc.addIceCandidate(msg.candidate);
    }
  }

  pc.onaddstream = function (evt) {
    var remote_video = document.getElementById('remote_video');
    remote_video.src = window.URL.createObjectURL(evt.stream);
  }

  pc.oniceconnectionstatechange = function(evt) {
    logStatus("ICE connection state change: " + evt.target.iceConnectionState);
  }

  function logError() { ... }
</script>

/**** Callee Code ****/
<video id="local_video" autoplay></video>
<video id="remote_video" autoplay></video>

<script>
  var signalingChannel = new SignalingChannel();

  var pc = null;
  var ice = {"iceServers": [
    {"url": "stun:stunserver.com:12345"},
    {"url": "turn:turnserver.com", "username": "user", "credential": "pass"}
  ]};

  signalingChannel.onmessage = function(msg) {
    if (msg.offer) {
      pc = new RTCPeerConnection(ice);
      pc.setRemoteDescription(msg.offer);

      pc.onicecandidate = function(evt) {
        if (evt.candidate) {
          signalingChannel.send(evt.candidate);
        }
      }

      pc.onaddstream = function (evt) {
        var remote_video = document.getElementById('remote_video');
        remote_video.src = window.URL.createObjectURL(evt.stream);
      }

      navigator.getUserMedia({ "audio": true, "video": true },
        gotStream, logError);

    } else if (msg.candidate) {
      pc.addIceCandidate(msg.candidate);
    }
  }

  function gotStream(evt) {
    pc.addStream(evt.stream);

    var local_video = document.getElementById('local_video');
    local_video.src = window.URL.createObjectURL(evt.stream);

    pc.createAnswer(function(answer) {
      pc.setLocalDescription(answer);
      signalingChannel.send(answer.sdp);
    });
  }

  function logError() { ... }
</script>

참조