WebRTC 연결 진단 페이지 구현 플랜

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

연결 진단 preflight getUserMedia STUN/TURN getStats diagnosis.ovice.com 대체 2026-06-05

TL;DR

diagnosis.ovice.com이 하는 WebRTC 연결 진단(마이크·카메라·스피커·네트워크·TURN/STUN)은 전부 브라우저 클라이언트 API로 동작한다. 우리 apps/web 안에 진단 전용 페이지 하나만 추가하면 추가 인프라 없이 구현 가능하다.

필요한 빌딩블록(디바이스 열거, ICE 서버 설정, getStats() 통계 수집, latency 측정)은 이미 프로덕션에서 돌고 있다. 신규 작업은 "진단 UI로 조합" + 3~4개 진단 전용 테스트 케이스 수준이다.

제안 위치: apps/web/app/main/diagnosis/ (라우트 패턴은 기존 /main/*와 동일). 게스트/호스트 입장 전 셀프 점검 용도로도 확장 가능.

1. 배경 — 무엇을 대체하는가

ovice 진단 사이트는 "수업에 필요한 통신이 성립하는지" 사전에 점검하는 도구다. 표준 WebRTC 진단 도구(webrtc/testrtc, Twilio Network Test)와 동일한 4가지를 본다.

진단 항목기술적 동작판정
마이크getUserMedia({audio})AudioContext/AnalyserNode로 입력 레벨 감지레벨 > 임계치면 정상
카메라getUserMedia({video}) → track settings로 해상도/프레임 확인스트림 획득 + 해상도 확보
스피커디바이스 열거 + 테스트음 재생(선택적 setSinkId)출력 디바이스 존재
네트워크(TURN/STUN)ICE 서버로 RTCPeerConnection 생성 → candidate 수집(host/srflx/relay)relay candidate 획득 = TURN 도달
연결성/대역폭로컬 PC 2개를 연결, DataChannel/미디어로 throughput·RTT·loss 측정RTT/loss/throughput 임계치

핵심: 전부 클라이언트 사이드다. 우리 STUN/TURN 서버(Metered.ca)와 /api/health에만 도달하면 되고, 별도 진단 백엔드가 필요 없다.

2. 재사용 가능한 기존 자산

코드베이스 탐색 결과, 진단 항목 대부분이 이미 있는 훅에 1:1로 대응된다. 약 80%는 신규 작성이 아니라 재사용이다.

진단 항목재사용할 기존 코드상태
마이크/카메라/스피커 열거·권한hooks/use-device-manager.ts, use-local-media-stream.ts, use-video-device-validator.ts그대로 사용
ICE 서버(STUN/TURN) 설정app/api/ice-servers/route.ts, hooks/use-ice-servers.ts그대로 사용
RTT / packet loss / jitter 수집hooks/use-webrtc-monitoring.ts (getStats())그대로 사용
통계 시각화 + 품질 판정components/sections/performance-monitor.tsx (excellent/good/fair/poor)거의 그대로
네트워크 latency 측정shared/lib/use-network-quality.ts (외부+/api/health, effectiveType)그대로 사용
연결 복구 참고 로직use-ice-restart.ts, use-network-recovery.ts참고용

현재 전용 진단/preflight 페이지는 없다. 위 훅들은 모두 "실제 수업 통화 중" 동작을 전제로 한다. 진단은 "통화 전 단독 점검"이라, 일부는 진단 맥락에 맞게 얇은 래퍼가 필요하다.

3. 신규로 만들어야 하는 것

① 진단 페이지/플로우

app/main/diagnosis/에 단계별 테스트 UI. 각 항목을 순차 실행하고 pass/fail/경고를 표시. 기존 훅을 import해 조합.

② TURN relay 강제 테스트

기존 ICE 설정을 iceTransportPolicy: "relay"로 바꾼 PC를 따로 만들어 relay candidate가 잡히는지 확인. 사실상 한 줄 변형.

③ 루프백 throughput 테스트

로컬 PC 2개 + DataChannel로 대역폭 추정. 현재는 실통화 stats만 측정하고 루프백 측정은 없음.

④ 마이크 입력 레벨 미터

AudioContext + AnalyserNode로 볼륨 시각화. 권한/열거는 있으나 레벨 미터는 없음. 라이브러리 불필요.

⑤ 결과 로그 export

ovice의 "Save log"처럼 진단 결과를 JSON/텍스트로 다운로드. 지원팀 전달용.

4. 진단 항목별 구현 상세

4-1. STUN/TURN 도달성

핵심 진단. ICE 서버로 PC를 만들고 onicecandidate로 수집되는 candidate type을 본다. srflx(서버 reflexive)면 STUN 도달, relay면 TURN 도달이다.

const config = await getIceServers(); // 기존 use-ice-servers 재사용
const pc = new RTCPeerConnection(config);
const found = { host: false, srflx: false, relay: false };
pc.onicecandidate = (e) => {
  if (!e.candidate) return; // gathering 완료
  found[e.candidate.type] = true; // host | srflx | relay
};
pc.createDataChannel("probe");
await pc.setLocalDescription(await pc.createOffer());
// 일정 시간(~5s) 후 found 평가: relay=true 면 TURN OK

TURN-only 변형: { ...config, iceTransportPolicy: "relay" }로 같은 절차를 한 번 더 돌리면, 방화벽이 P2P를 막아 relay만 가능한 환경에서도 통화가 되는지 확정 판정할 수 있다.

4-2. 마이크 / 카메라 / 스피커

디바이스 권한·열거는 use-device-manager를 그대로 쓰고, 마이크는 레벨 미터만 추가한다.

const ctx = new AudioContext();
const src = ctx.createMediaStreamSource(audioStream);
const analyser = ctx.createAnalyser();
src.connect(analyser);
// requestAnimationFrame 루프에서 getByteFrequencyData → 평균 레벨 막대 표시
// 카메라는 track.getSettings()로 width/height/frameRate 표시

4-3. 네트워크 latency / 품질

use-network-quality를 그대로 호출해 외부 + /api/health latency와 navigator.connection.effectiveType를 표시한다. 사용자 네트워크 문제인지 서버 문제인지 구분하는 기존 로직 재사용.

4-4. 루프백 대역폭/RTT

로컬에서 PC 2개를 서로 연결(같은 탭 내 offer/answer 교환)하고 DataChannel로 데이터를 흘려 throughput을, candidate-pair stats로 RTT를 측정한다. 그 후 use-webrtc-monitoring의 stats 파싱·품질 판정 로직을 재활용한다.

5. 단계별 작업 계획

단계작업산출물
P1라우트 스캐폴딩 + 진단 단계 상태 머신(각 항목 idle/running/pass/warn/fail)app/main/diagnosis/page.tsx, use-diagnosis-runner.ts
P2디바이스 진단(마이크 레벨 미터, 카메라 프리뷰, 스피커 테스트음) — 기존 디바이스 훅 래핑디바이스 테스트 섹션 컴포넌트
P3ICE 진단(STUN/TURN candidate 수집 + relay-only 변형)use-ice-probe.ts
P4네트워크 latency(기존 use-network-quality) + 루프백 throughput/RTTuse-loopback-probe.ts + 품질 판정 재사용
P5결과 요약 패널 + 로그 JSON export + 실패 시 안내 문구결과 컴포넌트, export 유틸
P6QA — 정상/마이크차단/카메라차단/방화벽(relay-only)/오프라인 시나리오 검증검증 리포트

6. 제안 파일 구조

apps/web/
├─ app/main/diagnosis/
│  └─ page.tsx                      # 진단 페이지 진입
├─ hooks/diagnosis/
│  ├─ use-diagnosis-runner.ts       # 단계 오케스트레이션/상태
│  ├─ use-ice-probe.ts              # STUN/TURN + relay-only 도달성
│  └─ use-loopback-probe.ts         # 로컬 PC 루프백 throughput/RTT
└─ components/sections/diagnosis/
   ├─ device-test-section.tsx       # 마이크 레벨/카메라/스피커
   ├─ network-test-section.tsx      # latency + ICE 결과
   └─ diagnosis-result.tsx          # 요약 + 로그 export

재사용(수정 거의 없음):
  hooks/use-ice-servers.ts, use-device-manager.ts,
  use-local-media-stream.ts, shared/lib/use-network-quality.ts
  components/sections/performance-monitor.tsx (품질 판정 로직)

7. 판정 기준(초안)

항목PASSWARNFAIL
마이크레벨 감지됨권한 거부 / 무신호
카메라스트림 + 해상도 확보저해상도권한 거부 / 디바이스 없음
STUNsrflx 획득candidate 없음
TURNrelay 획득P2P만 가능relay 실패(방화벽)
RTT< 150ms150~300ms> 300ms
Packet loss< 2%2~5%> 5%

임계치는 기존 performance-monitor.tsx의 품질 판정(excellent/good/fair/poor)과 정렬한다. ovice처럼 "일부 항목 실패해도 수업은 가능"하다는 안내 문구를 결과 패널에 둔다.

8. 결론 — 구현 가능성

9. 알려진 한계 / 주의

루프백 throughput은 "로컬 한계"를 잴 뿐, 실제 SFU(mediasoup) 경로의 상·하향 대역폭과 다르다. 정확한 end-to-end는 별도 echo 서버가 있어야 하므로 1차 범위에서는 근사 지표로 둔다.

candidate 수집 타임아웃: TURN 서버가 느리면 relay candidate가 늦게 잡힌다. 충분한 대기(~5s)와 타임아웃 후 WARN/FAIL 구분이 필요.

V1(P2P) vs V2(SFU) 경로 차이: 진단은 일반 RTCPeerConnection 기준이라 두 경로 공통의 "통신 가능 여부"를 본다. mediasoup 고유 이슈(Transport 연결 등)까지는 별도 확장 필요.

10. 관련 문서