Skip to content

fix(realtime): 오디오 스트림의 contentType 을 AI 에 전달 - #192

Merged
i3months merged 1 commit into
devfrom
fix/rt3-forward-content-type
Aug 20, 2026
Merged

fix(realtime): 오디오 스트림의 contentType 을 AI 에 전달#192
i3months merged 1 commit into
devfrom
fix/rt3-forward-content-type

Conversation

@i3months

Copy link
Copy Markdown
Member

배경

RT3(실시간 스트리밍 음성)를 프론트에 연결하려고 양쪽 와이어 규약을 읽다가 발견했다.

AI 의 /internal/voice/streamcontentType 쿼리로 STT 세션의 디코더를 고른다:

content_type: str = Query("audio/webm", alias="contentType"),
...
session = provider.open_session(content_type=content_type, language=None)

그런데 RealTime 이 업스트림 URL 을 만들 때 그 값을 붙이지 않았다:

aiURL := fmt.Sprintf("%s?sessionId=%d&messageId=%d", h.AIBaseURL, sid, mid)

결과적으로 브라우저가 무엇을 보내든 AI 는 기본값 audio/webm 으로 가정한다. MediaRecorderSafari 에서 audio/mp4 를 만들므로 그 경우 잘못된 디코더로 세션이 열린다.

아직 프론트가 RT3 를 쓰지 않아 드러나지 않았을 뿐, 연결하는 순간 webm 이 아닌 브라우저에서 깨진다. 지금 고쳐 두면 나중에 배선할 때 이 함정을 밟지 않는다.

수정

브라우저의 ?contentType= 을 전달하되, 사용자 제어 값이므로 그대로 붙이지 않는다:

  • "audio/webm;codecs=opus" 에서 base MIME 만 뽑고 소문자 정규화
  • 화이트리스트(webm/ogg/mp4/mpeg/wav — Core 가 저장을 허용하는 것과 동일)를 통과한 값만 사용
  • 그 외에는 기존 동작과 같은 audio/webm 으로 폴백 (동작 회귀 없음)
  • url.QueryEscape — 슬래시가 이스케이프되지 않으면 AI 쪽 쿼리 파싱이 어긋난다

임의 문자열을 업스트림 URL 에 붙이지 않는 건 방금 고친 #191(셸 주입)과 같은 원칙이다.

테스트 (10)

코덱 파라미터 제거·대소문자·공백 처리, 허용 목록 밖/빈 값의 기본값 폴백, 쿼리 주입 시도(audio/webm&apiKey=...)가 기본값으로 떨어지는지, URL 조립과 이스케이프.

로컬에 Go 툴체인이 없어 CI 로 검증한다.

RT3 프론트 배선은 이번에 하지 않았다 — 이유

전송 클라이언트(WS 프로토콜)까지는 어렵지 않은데, 컴포저 통합에 라이브 검증이 필요한 지점이 있다:

  • 마이크 소유권 — 지금 useVoiceRecorder 가 스트림과 MediaRecorder 를 소유한다. 스트리밍은 timeslice 기반 청크가 필요해 recorder 를 하나 더 붙이거나(이중 인코딩) 훅 구조를 바꿔야 한다
  • 첫 마디 잘림stream-begin + 토큰 발급(HTTP 2회)이 끝나야 소켓이 열린다. 그 사이 녹음을 미루면 첫 단어가 잘리고, 먼저 녹음하면 소켓 오픈 전 청크를 버퍼링·플러시해야 한다
  • 중도 실패 복구 — 스트리밍이 끊겼을 때 모아둔 오디오를 같은 Idempotency-Key 로 배치 업로드하면 같은 메시지에 이어붙는다(설계는 확인했다). 다만 이 경로는 실제 네트워크 단절로 시험해 봐야 한다

전부 마이크와 실행 중인 스택이 있어야 확인 가능한 것들이라, 검증 없이 기존의 동작하는 배치 경로를 대체하는 건 위험하다고 판단했다. 팀에서 5분이면 확인할 수 있는 환경이니 함께 하는 편이 낫겠다.

영향 범위

  • DB 마이그레이션 / API contract / 환경변수: 없음
  • 동작: contentType 미지정 시 기존과 동일(audio/webm)

AI 의 `/internal/voice/stream` 은 `contentType` 쿼리로 STT 세션의 디코더를 고른다
(`provider.open_session(content_type=...)`). 그런데 RealTime 이 업스트림 URL 을 만들 때
`sessionId`·`messageId` 만 붙이고 있었다:

    aiURL := fmt.Sprintf("%s?sessionId=%d&messageId=%d", h.AIBaseURL, sid, mid)

결과적으로 브라우저가 무엇을 보내든 AI 는 기본값 `audio/webm` 으로 가정한다. MediaRecorder 는
Safari 에서 `audio/mp4` 를 만들므로 그 경우 잘못된 디코더로 세션이 열린다. 아직 프론트가
RT3 를 쓰지 않아 드러나지 않았을 뿐, 연결하는 순간 webm 이 아닌 브라우저에서 깨진다.

브라우저의 `?contentType=` 을 그대로 전달한다. 다만 사용자 제어 값이므로:

- `"audio/webm;codecs=opus"` 같은 값에서 base MIME 만 뽑고 소문자로 정규화
- 화이트리스트(webm/ogg/mp4/mpeg/wav — Core 가 저장을 허용하는 것과 동일)를 통과한 값만 사용
- 그 외에는 기존 동작과 같은 `audio/webm` 으로 폴백
- URL 에 붙일 때 `url.QueryEscape` (슬래시가 이스케이프되지 않으면 AI 쪽 파싱이 어긋난다)

테스트 (10) — 코덱 파라미터 제거·대소문자·공백, 허용 목록 밖/빈 값의 기본값 폴백,
쿼리 주입 시도(`audio/webm&apiKey=...`)가 기본값으로 떨어지는지, URL 조립과 이스케이프.
@i3months
i3months merged commit c57ccb2 into dev Aug 20, 2026
5 checks passed
@i3months
i3months deleted the fix/rt3-forward-content-type branch August 20, 2026 07:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant