마지막 업데이트 2026-07-22
핑퐁이 AI 세션은 아동(게스트) 쪽에서만 돈다(세션 매니저 #1). 치료사(호스트)는 아동을 모니터링하고 제어해야 하므로, 게스트가 핑퐁이 음성 + 세션 상태를 호스트로 중계한다. 이게 세션 릴레이다.
useSessionManager(#1) + useWebRTC(#11 P2P)를 조합해 AI 음성 트랙을 호스트에 직접 보낸다. V2(SFU/client-guest)는 게스트가 ai-audio producer를 SFU에 올리고 호스트가 consume하는 방식(#2)이라 이 훅을 쓰지 않는다.
| 채널 | 운반 | 방향 |
|---|---|---|
P2P WebRTC (useWebRTC, connectionId="session-audio") | 핑퐁이 AI 음성 오디오 트랙 | 게스트 → 호스트 |
| Socket.io 이벤트 | 발화 상태·로그·자동종료·환경소음·증폭 상태 / 호스트 제어 명령 | 양방향 |
호스트가 먼저 P2P를 깔고(start3 → connect), 그 위에 실제 세션 파라미터(activity·VAD·끼어들기 등)를 실어 게스트의 OpenAI 세션을 띄운 뒤, 산출된 AI 음성 트랙을 P2P에 add하는 순서. P2P offer/answer 시그널링 자체는 useWebRTC(#11)가 소켓으로 처리.
const sessionAudioStream = result.outputAudioStream; // OpenAI 원격 스트림 const audioTrack = sessionAudioStream.getAudioTracks()[0]; sessionRelayWebRTC.addTrack(audioTrack, sessionAudioStream); // 재인코딩 없이 그대로
호스트 수신(host-session-relay.ts:99–105): onTrackReceived → sessionAudioStream 저장 → audioElement.srcObject(autoplay, setSinkId로 출력 장치 라우팅).
호스트가 emit하면 게스트가 받아 sessionManager의 해당 메서드를 호출한다.
| 이벤트 | 게스트 동작 |
|---|---|
host-session-start3 / -web-rtc-connect / -stop | P2P 연결 / 세션 시작 / 종료 |
host-session-microphone | toggleMicrophone |
host-session-interrupt-response | toggleInterruptResponse(끼어들기) |
host-session-noise-reduction | updateNoiseReduction(헤드셋/far·near) |
host-session-message | sendMessage ({{환경소음}} 포함 시 알림) |
host-session-response-cancel | cancelResponse |
host-session-auto-transition / -update-auto-finish-config / -update-start-ment | 자동 진행/종료/시작 멘트 설정 |
host-session-fallback-transcription | STT 폴백 토글 |
host-textarea-typing-status | updateTextareaTypingStatus |
| 이벤트 | 의미 |
|---|---|
guest-peer-talking-status / guest-user-talking-status | 핑퐁이/아동 발화 상태 → 호스트 UI |
guest-session-log | 전사/채팅 로그 미러링 |
guest-session-auto-finish | 자동 종료 조건 충족 |
guest-ambient-noise-alert | 환경소음 알림 시각 미러링 |
guest-volume-amplifier-status | 마이크 증폭 상태(#17) |
guest-session-web-rtc-disconnected/failed | OpenAI 세션 WebRTC 상태 |
guest-session-relay-web-rtc-disconnected/failed | 릴레이 P2P 상태 |
guest-session-cleanup-complete | 게스트 정리 완료(호스트 stop 동기화) |
위 이벤트들은 SFU 서버 session-handlers가 같은 room의 상대(게스트↔호스트)에게 중계한다. 서버는 페이로드를 거의 그대로 forward하며, 일부는 routerManager의 guestState/설정 갱신(#5)을 동반한다. (호스트는 room에 socket.join되어 있으므로 room broadcast 대상)
host-session-stop → 게스트 handleHostSessionStop: isNormalShutdownRef=true → disconnect()(sessionManager.stop + relay P2P disconnect + cleanUp) → guest-session-cleanup-complete emit.useWebRTC.onFailed — 정상 종료 플래그면 조용히 disconnect, 아니면 guest-session-relay-web-rtc-failed emit. 재시도(enableRetry, 지수 백오프)는 useWebRTC가 담당.shouldStopSessionRef로 세션 시작 도중/직후 stop 요청을 잡아 즉시 중단(시작과 종료가 겹치는 경쟁 방지).outputAudioStream이 나온 뒤에야 P2P에 add. 순서를 바꾸면 빈 트랙이 송출됨.guest-session-* vs guest-session-relay-*).shouldStopSessionRef/isNormalShutdownRef 가드를 건드리면 유령 세션 또는 조기 종료가 발생.| 파일 | 역할 |
|---|---|
| hooks/use-guest-session-relay.ts | 게스트측: 세션 매니저 + 릴레이 P2P, 호스트 제어 수신, 상태 emit(479L) |
| hooks/use-host-session-relay.ts | 호스트측: 릴레이 P2P 수신·재생, 제어 emit, 게스트 상태 수신(595L) |
| hooks/use-web-rtc.ts | P2P 시그널링·재시도(#11) |
| hooks/use-session-manager.ts | OpenAI 세션(#1) |
| sfu-socket/handlers/session-handlers.ts | 서버측 이벤트 중계(709L) |