WebRTC 연결 진단 페이지 구현 플랜
마지막 업데이트 2026-07-22
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 | 디바이스 진단(마이크 레벨 미터, 카메라 프리뷰, 스피커 테스트음) — 기존 디바이스 훅 래핑 | 디바이스 테스트 섹션 컴포넌트 |
| P3 | ICE 진단(STUN/TURN candidate 수집 + relay-only 변형) | use-ice-probe.ts |
| P4 | 네트워크 latency(기존 use-network-quality) + 루프백 throughput/RTT | use-loopback-probe.ts + 품질 판정 재사용 |
| P5 | 결과 요약 패널 + 로그 JSON export + 실패 시 안내 문구 | 결과 컴포넌트, export 유틸 |
| P6 | QA — 정상/마이크차단/카메라차단/방화벽(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. 판정 기준(초안)
| 항목 | PASS | WARN | FAIL |
|---|---|---|---|
| 마이크 | 레벨 감지됨 | — | 권한 거부 / 무신호 |
| 카메라 | 스트림 + 해상도 확보 | 저해상도 | 권한 거부 / 디바이스 없음 |
| STUN | srflx 획득 | — | candidate 없음 |
| TURN | relay 획득 | P2P만 가능 | relay 실패(방화벽) |
| RTT | < 150ms | 150~300ms | > 300ms |
| Packet loss | < 2% | 2~5% | > 5% |
임계치는 기존 performance-monitor.tsx의 품질 판정(excellent/good/fair/poor)과 정렬한다. ovice처럼 "일부 항목 실패해도 수업은 가능"하다는 안내 문구를 결과 패널에 둔다.
8. 결론 — 구현 가능성
- 현재 웹앱(
apps/web) 내부 구현 가능. 추가 서버/인프라 불필요 — 기존 Metered TURN,/api/ice-servers,/api/health재활용. - 핵심 진단 로직(getUserMedia / RTCPeerConnection / getStats)은 전부 클라이언트 사이드라 같은 오리진에서 권한·CORS 이슈 없음.
- 재사용 ~80%, 신규는 진단 UI 조합 + 3~4개 테스트 케이스. 난이도·리스크 모두 낮음.
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. 관련 문서
- 카드뷰 세션 변천사 — WebRTC 통계가 표시되는 모니터링 영역
- architecture — V1(P2P) / V2(mediasoup SFU) 전체 통신 구조