WebRTC는 브라우저 간 서버를 거치지 않고 직접 연결하여 영상, 음성, 데이터를 주고받는 기술입니다. 화상회의, 파일 공유, 실시간 게임에 사용됩니다.

WebRTC 연결 흐름

PLAINTEXT
1. Signaling — ICE 후보와 SDP를 교환 (서버 필요)
2. STUN/TURN — NAT 뒤의 IP 주소를 발견
3. P2P 연결 — 직접 연결 수립
4. 미디어/데이터 전송

미디어 스트림 가져오기

JS
// 카메라와 마이크 접근
async function getMedia() {
  try {
    const stream = await navigator.mediaDevices.getUserMedia({
      video: { width: 1280, height: 720 },
      audio: true,
    });

    // 로컬 비디오에 표시
    const video = document.getElementById("localVideo");
    video.srcObject = stream;
    return stream;
  } catch (err) {
    console.error("미디어 접근 실패:", err);
  }
}

// 화면 공유
async function getScreenShare() {
  return navigator.mediaDevices.getDisplayMedia({
    video: { cursor: "always" },
    audio: false,
  });
}

P2P 연결 설정

JS
// ICE 서버 설정
const config = {
  iceServers: [
    { urls: "stun:stun.l.google.com:19302" }, // 무료 STUN 서버
  ],
};

// Peer A (Caller)
const peerA = new RTCPeerConnection(config);

// Peer B (Callee)
const peerB = new RTCPeerConnection(config);

// ICE 후보 교환 (실제로는 시그널링 서버를 통해)
peerA.onicecandidate = (e) => {
  if (e.candidate) {
    // 시그널링 서버를 통해 peerB에 전달
    peerB.addIceCandidate(e.candidate);
  }
};

peerB.onicecandidate = (e) => {
  if (e.candidate) {
    peerA.addIceCandidate(e.candidate);
  }
};

SDP 교환 (Offer/Answer)

JS
// 1. Caller가 Offer 생성
const offer = await peerA.createOffer();
await peerA.setLocalDescription(offer);

// 2. 시그널링으로 Offer를 Callee에 전달
// signalingServer.send(offer)

// 3. Callee가 Offer를 받고 Answer 생성
await peerB.setRemoteDescription(offer);
const answer = await peerB.createAnswer();
await peerB.setLocalDescription(answer);

// 4. 시그널링으로 Answer를 Caller에 전달
await peerA.setRemoteDescription(answer);

// 5. P2P 연결 완료!

미디어 트랙 전송

JS
// 미디어 스트림의 트랙을 피어 연결에 추가
const localStream = await getMedia();
localStream.getTracks().forEach((track) => {
  peerA.addTrack(track, localStream);
});

// 상대방의 미디어 수신
peerB.ontrack = (event) => {
  const remoteVideo = document.getElementById("remoteVideo");
  remoteVideo.srcObject = event.streams[0];
};

데이터 채널 — 임의 데이터 전송

JS
// Caller 측에서 데이터 채널 생성
const dataChannel = peerA.createDataChannel("chat", {
  ordered: true, // 순서 보장
});

dataChannel.onopen = () => {
  console.log("데이터 채널 열림");
  dataChannel.send("안녕하세요!");
};

dataChannel.onmessage = (e) => {
  console.log("받음:", e.data);
};

// Callee 측에서 데이터 채널 수신
peerB.ondatachannel = (event) => {
  const channel = event.channel;
  channel.onmessage = (e) => {
    console.log("받음:", e.data);
    channel.send("반갑습니다!");
  };
};

연결 상태 모니터링

JS
peerA.onconnectionstatechange = () => {
  console.log("연결 상태:", peerA.connectionState);
  // "new", "connecting", "connected", "disconnected", "failed", "closed"

  if (peerA.connectionState === "failed") {
    // 재연결 시도
    peerA.restartIce();
  }
};

peerA.oniceconnectionstatechange = () => {
  console.log("ICE 상태:", peerA.iceConnectionState);
};

미디어 제어

JS
// 비디오 끄기/켜기
function toggleVideo(stream) {
  const videoTrack = stream.getVideoTracks()[0];
  videoTrack.enabled = !videoTrack.enabled;
}

// 오디오 끄기/켜기 (뮤트)
function toggleAudio(stream) {
  const audioTrack = stream.getAudioTracks()[0];
  audioTrack.enabled = !audioTrack.enabled;
}

// 카메라 전환
async function switchCamera(peerConnection) {
  const newStream = await navigator.mediaDevices.getUserMedia({
    video: { facingMode: "environment" }, // 후면 카메라
  });
  const newTrack = newStream.getVideoTracks()[0];
  const sender = peerConnection.getSenders()
    .find((s) => s.track?.kind === "video");
  await sender.replaceTrack(newTrack);
}

WebRTC vs WebSocket

항목WebRTCWebSocket
연결 방식P2P (직접)클라이언트-서버
서버 부하시그널링만모든 데이터 중계
미디어 지원내장직접 구현
지연시간매우 낮음낮음
NAT 통과STUN/TURN 필요불필요
적합한 용도화상통화, P2P 게임채팅, 알림

** 기억하기 **: WebRTC는 브라우저 간 직접 연결이지만, 초기 연결 설정에는 시그널링 서버가 필요합니다. Offer → Answer → ICE Candidate 교환이 핵심 흐름이고, 연결이 수립되면 미디어와 데이터를 서버 없이 P2P로 주고받습니다.

댓글 로딩 중...