본문으로 건너뛰기

오디오 녹음

채팅 또는 생성 UI에서 사용자가 입력하는 대신 말할 수 있도록 하려는 경우를 살펴봅니다. 이 가이드를 마치면 브라우저에서 useAudioRecorder로 마이크 오디오를 캡처하고, 최신 녹음을 반응형으로 읽은 다음 트랜스코딩이나 추가 의존성 없이 채팅 메시지 또는 전사 요청으로 바로 전송할 수 있습니다.

useAudioRecorder 는 브라우저의 getUserMedia / MediaRecorder 를 감싸고 레코더의 네이티브 출력 (audio/webm 또는 audio/mp4) 을 반환합니다.

오디오 녹음

버튼에서 시작하여 캡처를 전환하고 결과를 전달하는 작동하는 레코더를 완성합니다.

import { useAudioRecorder } from '@tanstack/ai-react'

function RecordButton() {
const { isRecording, isSupported, start, stop } = useAudioRecorder({
onError: (error) => console.error(error),
})

if (!isSupported) return <p>Recording is not supported in this browser.</p>

return (
<button onClick={() => (isRecording ? void stop() : void start())}>
{isRecording ? 'Stop' : 'Record'}
</button>
)
}

stop()AudioRecording으로 확인됩니다.

필드타입설명
partAudioPart바로 사용할 수 있는 콘텐츠 파트: { type: 'audio', source: { type: 'data', value, mimeType } }
base64string녹음된 바이트의 원시 base64
blobBlob원시 녹음 Blob
mimeTypestring네이티브 레코더 타입(예: audio/webm;codecs=opus)
durationMsnumber녹음 길이(밀리초)

오류 처리

실패는 경로로 전달됩니다. 하나를 선택하고 둘 다 처리하지 마세요.

  • onError(error) 는 권한 거부 및 레코더 오류에 대해 발생합니다.
  • start()stop()거부합니다. start() 은 권한 거부 시, stop() 는 레코더 오류 또는 Recording cancelled 로 기록이 중지 중일 때 (예: 기록 중 컴포넌트가 언마운트되는 경우) 거부합니다.

따라서 await start() / await stop()을 사용한다면 void로 프로미스를 버리지 말고 try/catch로 감싸세요. 레코더의 네이티브 mimeType은 요청한 mimeType과 다를 수 있으므로(브라우저는 지원하지 않는 타입을 무시함), 이후 단계에 특정 형식이 필요하면 recording.mimeType을 읽으세요.

최신 녹음을 반응형으로 읽기

같은 값은 반응형 recording 필드로도 노출되므로 stop()의 반환값을 직접 저장하지 않고도 미리보기를 렌더링할 수 있습니다. 첫 stop() 전까지는 null입니다.

function Preview() {
const { recording, isRecording, start, stop } = useAudioRecorder()
// recording is AudioRecording | null
}

프레임워크 전반에서 recording은 다른 반응형 필드와 같은 형태를 따릅니다. Solid에서는 접근자(recording()), Vue에서는 읽기 전용 ref (recording.value), Svelte에서는 getter(recorder.recording), Angular에서는 Signal(recording())입니다.

녹음 변환

onComplete을 전달하여 원시 녹음을 애플리케이션에 필요한 값(업로드 후 URL, 인코딩된 Blob 또는 사용자 지정 객체)으로 변환합니다. 그러면 stop()과 반응형 recording 필드가 모두 변환된 값으로 확인되며, 변환은 async일 수 있습니다.

function Uploader() {
const { recording, stop } = useAudioRecorder({
onComplete: async (rec) => {
const res = await fetch('/api/upload', { method: 'POST', body: rec.blob })
const { url } = await res.json()
return url // `recording` and `stop()` now resolve to string
},
})
}

원시 AudioRecording을 유지하려면 아무것도 반환하지 마세요(undefined). 반환된 값(null 포함)은 그대로 사용되며 stop()recording의 타입을 다시 지정합니다. 이는 생성 훅onResult 변환과 유사하지만 async를 지원합니다. (null이 "이전 값을 유지"한다는 의미인 onResult와 달리, 여기서는 undefined만 원시 녹음을 유지합니다.)

채팅에서 녹음 전송

녹음의 part는 이미 채팅 콘텐츠 파트이므로 sendMessage에 바로 전달할 수 있습니다.

import {
useAudioRecorder,
useChat,
fetchServerSentEvents,
} from '@tanstack/ai-react'

function VoiceComposer() {
const { isRecording, start, stop } = useAudioRecorder()
const { sendMessage } = useChat({
connection: fetchServerSentEvents('/api/chat'),
})

const toggle = async () => {
try {
if (!isRecording) {
await start()
return
}
const rec = await stop()
await sendMessage({ content: [rec.part] })
} catch (error) {
// start()/stop() reject on permission denial, recorder error, or cancel.
console.error(error)
}
}

return (
<button onClick={() => void toggle()}>
{isRecording ? 'Send' : 'Record'}
</button>
)
}

녹음 전사

레코더의 네이티브 콘텐츠 타입을 프로바이더가 받도록 녹음을 data: URL로 감싸세요. 원시 base64를 전달하면 전사 어댑터가 audio/mpeg로 가정하여 webm/mp4 바이트의 라벨을 잘못 지정합니다. 대응하는 서버 라우트는 전사를 참고하세요.

import {
useAudioRecorder,
useTranscription,
fetchServerSentEvents,
} from '@tanstack/ai-react'

function Transcriber() {
const { isRecording, start, stop } = useAudioRecorder()
const { generate, result } = useTranscription({
connection: fetchServerSentEvents('/api/transcribe'),
})

const toggle = async () => {
try {
if (!isRecording) {
await start()
return
}
const rec = await stop()
// Wrap as a data URL so the provider gets the recorder's real content
// type. Passing raw base64 makes the transcription adapter assume
// `audio/mpeg`, which mislabels the native webm/mp4 bytes. Strip the
// `;codecs=...` parameter for a clean type.
const mimeType = rec.mimeType.split(';')[0]
await generate({ audio: `data:${mimeType};base64,${rec.base64}` })
} catch (error) {
console.error(error)
}
}

return (
<div>
<button onClick={() => void toggle()}>
{isRecording ? 'Stop' : 'Record'}
</button>
{result ? <p>{result.text}</p> : null}
</div>
)
}

다른 프레임워크

동일한 레코더가 모든 프레임워크에서 각 프레임워크에 맞는 반응성으로 제공됩니다. Svelte는 createAudioRecorder 팩토리를 사용합니다. Svelte 5 rune은 자동 정리를 등록할 수 없으므로 녹음이 아직 활성 상태일 수 있다면 컴포넌트 정리 시 cancel()을 호출하세요.

<script lang="ts">
import {
createAudioRecorder,
createChat,
fetchServerSentEvents,
} from '@tanstack/ai-svelte'

const recorder = createAudioRecorder()
const chat = createChat({ connection: fetchServerSentEvents('/api/chat') })

async function toggle() {
if (!recorder.isRecording) {
await recorder.start()
return
}
const rec = await recorder.stop()
await chat.sendMessage({ content: [rec.part] })
}
</script>

<button onclick={toggle}>{recorder.isRecording ? 'Send' : 'Record'}</button>
프레임워크가져오기함수반응형 필드
React@tanstack/ai-reactuseAudioRecorderisRecording, recording (값)
Solid@tanstack/ai-soliduseAudioRecorderisRecording(), recording() (접근자)
Vue@tanstack/ai-vueuseAudioRecorderisRecording.value, recording.value (읽기 전용 ref)
Svelte@tanstack/ai-sveltecreateAudioRecorderrecorder.isRecording, recorder.recording (getter 방식)
Angular@tanstack/ai-angularinjectAudioRecorderisRecording(), recording() (signal; 주입 컨텍스트에서 호출)

훅 API

useAudioRecorder(options?) 와 그 createAudioRecorder / injectAudioRecorder 동등체는 다음을 받습니다:

옵션타입설명
onComplete(recording: AudioRecording) => T | Promise<T>선택적 변환입니다. (await된) 반환값이 stop()recording의 타입을 다시 지정합니다. 원시 녹음을 유지하려면 아무것도 반환하지 않습니다.
onError(error: Error) => void권한 거부 또는 레코더 오류 시 호출됩니다.
audioMediaTrackConstraints | booleangetUserMedia({ audio })에 전달됩니다. 기본값은 true입니다.
mimeTypestring선호하는 레코더 mime 타입입니다. 지원되지 않으면 브라우저 기본값으로 대체됩니다.

그리고 다음을 반환합니다.

속성타입설명
recordingT | null최신 녹음(onComplete이 제공되면 변환됨), 반응형
isRecordingboolean현재 캡처가 활성 상태인지 여부
isSupportedboolean브라우저가 녹음을 지원하는지 여부
start() => Promise<void>마이크를 확보하고 녹음을 시작합니다.
stop() => Promise<T>중지하고 녹음으로 확인됩니다(해당하는 경우 변환됨).
cancel() => void진행 중인 녹음을 폐기하고 마이크를 해제합니다.

반응형 형태(recording, isRecording)는 프레임워크마다 다릅니다. 다른 프레임워크의 표를 참고하세요. onComplete 변환으로 변경되지 않는 한 TAudioRecording입니다.