V1 레거시 P2P WebRTC (use-web-rtc) — 코드레벨 동작 흐름 P1코드레벨

마지막 업데이트 2026-07-22

작성일: 2026-06-14 대상: 개발자 — V1 P2P 연결 시그널링·복구 파악 핵심 파일: hooks/use-web-rtc.ts

개요 · 범위

useWebRTC는 V1(레거시)의 P2P 1:1 연결을 캡슐화하는 기반 훅. offer/answer SDP 교환, ICE candidate buffering, 다층 복구(ICE restart → full reconnect → retry)를 담당한다. V2(SFU)는 이 훅 대신 mediasoup(#10)을 쓴다.

connectionId로 용도 분리: 같은 훅을 "session-audio"(릴레이 #6), "host-audio"(게스트↔호스트 미디어), "screen-share"(화면 공유) 세 갈래로 인스턴스화한다. 한 페이지에 여러 P2P가 공존하므로 모든 시그널링 이벤트가 connectionId로 필터링된다.

시그널링 모델 socket role-prefixed events

소켓 이벤트는 역할 접두사를 쓴다: 자신은 ${role}-sdp-offer/answer/ice-candidateemit하고, 상대(oppositeRole)의 같은 이벤트를 listen한다. 모든 핸들러는 connectionId !== props.connectionId면 무시.

[initiator] connect() → createOffer → setLocalDescription → emit ${role}-sdp-offer [responder] handleOffer(oppositeRole-sdp-offer) → setRemoteDescription → (큐 candidate flush) → createAnswer → setLocalDescription → emit ${role}-sdp-answer [initiator] handleAnswer(oppositeRole-sdp-answer) → setRemoteDescription → (큐 candidate flush) 양측: onicecandidate → emit ${role}-ice-candidate handleCandidate → addIceCandidate(또는 큐잉)

ICE candidate buffering handleCandidate L339–367

if (peerConnection.current.remoteDescription) {
  await peerConnection.current.addIceCandidate(parsed);   // 준비됨 → 즉시 추가
} else {
  candidateQueue.current.push(parsed);                // 아직 remote SDP 없음 → 큐잉
}
왜 필요한가: ICE candidate가 SDP answer/offer보다 먼저 도착할 수 있다. remoteDescription이 set되기 전 addIceCandidate하면 throw. 그래서 큐에 모아두었다가 setRemoteDescription 직후(handleOffer/handleAnswer)에 일괄 flush한다.

다층 복구 전략

연결 끊김에 대해 비용이 낮은 것부터 단계적으로 대응한다.

  1. 1ICE restart(attemptIceRestart L76): 기존 PeerConnection 재사용. createOffer({iceRestart:true}) → setLocalDescription → offer 재전송. 가드: PC가 closed거나 signalingState !== "stable"(협상 진행 중)이면 skip. iceState disconnected 시 1차 시도.
  2. 2disconnected 타임아웃(startDisconnectedTimeout L126): 일정 시간 disconnected 지속 시 onFailed() 호출(상위가 세션 종료/재시작 결정).
  3. 3full reconnection(attemptFullReconnection): ICE failed 시 즉시 전체 재연결(PC 폐기 후 재생성). ICE restart로 회복 안 되는 경우.
  4. 4retry(지수 백오프): enableRetry면 connect 실패/실패 콜백 시 onRetryAttempt(attempt, max)와 함께 재시도, 소진 시 onRetryFailed.
  • iceState가 connected/completed로 복귀하면 카운터·타이머 리셋.
  • onnegotiationneeded: 양측 모두 재협상 가능(트랙 add/replace 시). signalingState==="stable"+비협상 중일 때만 새 offer.
  • 게스트 역할은 useWebRTCMonitoring으로 getStats 통계를 소켓 송출(#19).

API · 사용처

반환역할
connect()PC 생성·offer 시작(initiator)
disconnect()PC close·리스너 해제·큐 정리
addTrack(track, stream)송신 트랙 추가(릴레이 AI 음성 등 → onnegotiationneeded 유발)
isConnected()연결 상태 조회

콜백: onTrackReceived(수신 트랙), onAnswered, onDisconnected, onFailed, onError, onRetryAttempt/Failed.

사용처connectionId
세션 릴레이(#6, guest/host)session-audio
게스트/호스트 미디어 매니저host-audio
화면 공유screen-share

함정 · 주의

  • connectionId 필터 필수: 한 페이지에 여러 P2P가 공존. 모든 시그널링 핸들러가 connectionId로 거른다. 새 P2P 추가 시 고유 connectionId 부여 안 하면 이벤트가 교차 오염.
  • candidate 큐 flush 시점: setRemoteDescription 직후에만 flush. handleOffer/handleAnswer 양쪽에서 모두 flush해야 한다(한쪽 빠뜨리면 ICE 누락).
  • ICE restart 가드: 협상 진행 중(signalingState≠stable) restart하면 glare(offer 충돌). 반드시 stable일 때만.
  • 복구 단계 중첩: ICE restart/full reconnect/retry가 동시 발동하지 않도록 플래그/카운터(iceRestartAttemptCount, isRetrying)로 가드. 임의 수정 시 재연결 폭주.
  • V1 전용: V2(SFU)는 mediasoup(#10). 이 훅은 릴레이·V1 미디어·화면공유에만 남아 있음.

파일 · 라인 레퍼런스

파일/심볼역할
use-web-rtc.tsP2P 연결·시그널링·복구 본체(969L)
handleOffer/Answer/Candidate (L268–367)SDP·candidate 처리
attemptIceRestart (L76) / startDisconnectedTimeout (L126)복구 1·2단계
use-guest-media-manager.ts / use-host-media-manager.tsV1 미디어 매니저(이 훅 래핑)
use-screen-share.ts화면 공유(이 훅 사용)