세션 릴레이 (게스트→호스트 핑퐁이 음성) — 코드레벨 동작 흐름 P1코드레벨

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

작성일: 2026-06-14 대상: 개발자 — 아동↔치료사 세션 중계 흐름 파악 핵심 파일: hooks/use-guest-session-relay.ts, use-host-session-relay.ts

개요 · 범위

핑퐁이 AI 세션은 아동(게스트) 쪽에서만 돈다(세션 매니저 #1). 치료사(호스트)는 아동을 모니터링하고 제어해야 하므로, 게스트가 핑퐁이 음성 + 세션 상태를 호스트로 중계한다. 이게 세션 릴레이다.

V1 경로 문서: 이 두 훅은 V1(P2P) 릴레이다 — 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 이벤트발화 상태·로그·자동종료·환경소음·증폭 상태 / 호스트 제어 명령양방향

세션 시작 시퀀스

[호스트] startSession() → emit host-session-start3 {avatarId} [게스트] handleHostSessionStart → sessionRelayWebRTC.connect() (P2P offer) → emit guest-session-web-rtc-connect [호스트] handleGuestSessionWebRTCConnect → P2P answer (useWebRTC 시그널링) → emit host-session-web-rtc-connect {activity, settings...} [게스트] handleHostSessionWebRTCConnect → sessionManager.startSession({activity, ...}) // OpenAI 세션 시작 → result.outputAudioStream 의 audioTrack → sessionRelayWebRTC.addTrack(audioTrack, stream) // P2P로 송출 [호스트] useWebRTC.onTrackReceived → audioElement.srcObject = stream // 치료사가 청취

호스트가 먼저 P2P를 깔고(start3 → connect), 그 위에 실제 세션 파라미터(activity·VAD·끼어들기 등)를 실어 게스트의 OpenAI 세션을 띄운 뒤, 산출된 AI 음성 트랙을 P2P에 add하는 순서. P2P offer/answer 시그널링 자체는 useWebRTC(#11)가 소켓으로 처리.

AI 음성 릴레이 guest-session-relay.ts:301–303

const sessionAudioStream = result.outputAudioStream;       // OpenAI 원격 스트림
const audioTrack = sessionAudioStream.getAudioTracks()[0];
sessionRelayWebRTC.addTrack(audioTrack, sessionAudioStream);    // 재인코딩 없이 그대로
핵심: OpenAI에서 받은 음성 트랙을 재인코딩/가공 없이 호스트 P2P에 그대로 add한다. 따라서 OpenAI 출력에 정적/버징이 섞이면 호스트도 동일하게 듣는다 → "정적을 아동/호스트 중 누가 들었나"가 원인 격리의 판별점(gpt-realtime 정적 분석).

호스트 수신(host-session-relay.ts:99–105): onTrackReceivedsessionAudioStream 저장 → audioElement.srcObject(autoplay, setSinkId로 출력 장치 라우팅).

호스트 → 게스트 제어 이벤트 guest-relay 등록 L433–452

호스트가 emit하면 게스트가 받아 sessionManager의 해당 메서드를 호출한다.

이벤트게스트 동작
host-session-start3 / -web-rtc-connect / -stopP2P 연결 / 세션 시작 / 종료
host-session-microphonetoggleMicrophone
host-session-interrupt-responsetoggleInterruptResponse(끼어들기)
host-session-noise-reductionupdateNoiseReduction(헤드셋/far·near)
host-session-messagesendMessage ({{환경소음}} 포함 시 알림)
host-session-response-cancelcancelResponse
host-session-auto-transition / -update-auto-finish-config / -update-start-ment자동 진행/종료/시작 멘트 설정
host-session-fallback-transcriptionSTT 폴백 토글
host-textarea-typing-statusupdateTextareaTypingStatus

게스트 → 호스트 상태 이벤트 host-relay 등록 L482–495 / guest emit

이벤트의미
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/failedOpenAI 세션 WebRTC 상태
guest-session-relay-web-rtc-disconnected/failed릴레이 P2P 상태
guest-session-cleanup-complete게스트 정리 완료(호스트 stop 동기화)

서버 중계 sfu-socket/handlers/session-handlers.ts

위 이벤트들은 SFU 서버 session-handlers가 같은 room의 상대(게스트↔호스트)에게 중계한다. 서버는 페이로드를 거의 그대로 forward하며, 일부는 routerManager의 guestState/설정 갱신(#5)을 동반한다. (호스트는 room에 socket.join되어 있으므로 room broadcast 대상)

종료 · 실패 처리

  • 정상 종료: 호스트 host-session-stop → 게스트 handleHostSessionStop: isNormalShutdownRef=truedisconnect()(sessionManager.stop + relay P2P disconnect + cleanUp) → guest-session-cleanup-complete emit.
  • P2P 실패: useWebRTC.onFailed — 정상 종료 플래그면 조용히 disconnect, 아니면 guest-session-relay-web-rtc-failed emit. 재시도(enableRetry, 지수 백오프)는 useWebRTC가 담당.
  • start race 가드: shouldStopSessionRef로 세션 시작 도중/직후 stop 요청을 잡아 즉시 중단(시작과 종료가 겹치는 경쟁 방지).

함정 · 주의

  • V1 전용: 이 훅은 V1 P2P 릴레이. V2는 SFU producer/consumer(#2). 동일 기능을 두 경로에 각각 반영해야 할 수 있음.
  • addTrack은 startSession 성공 후: outputAudioStream이 나온 뒤에야 P2P에 add. 순서를 바꾸면 빈 트랙이 송출됨.
  • 재인코딩 없음: AI 음성을 그대로 중계하므로 OpenAI측 음질 문제는 호스트에도 그대로 전파(원인 격리 시 활용).
  • 두 P2P 혼동 금지: 세션 매니저의 OpenAI WebRTC와 릴레이 P2P는 별개 연결. 둘 다 connectionId/상태가 따로 있고 실패 이벤트도 분리(guest-session-* vs guest-session-relay-*).
  • start/stop 경쟁: shouldStopSessionRef/isNormalShutdownRef 가드를 건드리면 유령 세션 또는 조기 종료가 발생.

파일 · 라인 레퍼런스

파일역할
hooks/use-guest-session-relay.ts게스트측: 세션 매니저 + 릴레이 P2P, 호스트 제어 수신, 상태 emit(479L)
hooks/use-host-session-relay.ts호스트측: 릴레이 P2P 수신·재생, 제어 emit, 게스트 상태 수신(595L)
hooks/use-web-rtc.tsP2P 시그널링·재시도(#11)
hooks/use-session-manager.tsOpenAI 세션(#1)
sfu-socket/handlers/session-handlers.ts서버측 이벤트 중계(709L)