{"version":3,"file":"audio-recorder.cjs","names":[],"sources":["../../src/audio/audio-recorder.ts"],"sourcesContent":["import {\n    createMediaRecorder,\n    pickRecordingMimeType,\n    type MediaRecorderStatus,\n} from \"@/capture/media-recorder\";\n\n/** Lifecycle of a recording. Shared with the video recorder — the states are the same. */\nexport type AudioRecorderStatus = MediaRecorderStatus;\n\n/**\n * Container/codec candidates, best first.\n *\n * No browser supports all of these, and none supports MP3 or WAV from\n * `MediaRecorder` — see the note on {@link pickAudioMimeType}. Opus in WebM is the\n * first choice because it is the smallest at speech bitrates and is what Chromium\n * and Firefox produce natively; `audio/mp4` is here for Safari, which produces AAC\n * and nothing else.\n *\n * Ported from the intersection of what `MediaRecorder.isTypeSupported` accepts in\n * Chrome 120+, Firefox 115+ and Safari 17. Re-check against\n * <https://developer.mozilla.org/docs/Web/API/MediaRecorder/isTypeSupported> before\n * adding to the list.\n */\nexport const AUDIO_MIME_CANDIDATES: readonly string[] = [\n    \"audio/webm;codecs=opus\",\n    \"audio/webm\",\n    \"audio/ogg;codecs=opus\",\n    \"audio/mp4;codecs=mp4a.40.2\",\n    \"audio/mp4\",\n];\n\n/** A finished recording. */\nexport interface AudioRecording {\n    /** The audio. Wrap with `useObjectUrl` to play it, or POST it as-is. */\n    blob: Blob;\n    /** What the browser actually produced — not necessarily what you asked for. */\n    mimeType: string;\n    /** Recorded length, excluding time spent paused. */\n    durationMs: number;\n}\n\n/** Options for {@link createAudioRecorder}. */\nexport interface AudioRecorderOptions {\n    /**\n     * Force a container. Throws when the browser cannot produce it.\n     *\n     * Leave it out. The default negotiates from {@link AUDIO_MIME_CANDIDATES}, which\n     * is the only way one call site works on both Chromium and Safari.\n     */\n    mimeType?: string;\n    /** Target bitrate. 32000–64000 is plenty for speech in Opus. */\n    audioBitsPerSecond?: number;\n    /**\n     * Emit a chunk every N ms through `onChunk`, for streaming upload.\n     *\n     * Without it the whole recording is buffered in memory until `stop()` — fine for\n     * a voice note, not fine for an hour-long meeting.\n     */\n    timesliceMs?: number;\n    /** Receives each chunk when `timesliceMs` is set. Chunks are **not** independently playable. */\n    onChunk?: (chunk: Blob) => void;\n    /** Recorder-level failure (device unplugged mid-recording, encoder error). */\n    onError?: (error: unknown) => void;\n}\n\n/** Imperative recorder. */\nexport interface AudioRecorderHandle {\n    /** Begin recording. No-op when already recording or paused. */\n    start: () => void;\n    /** Pause. The clock stops; `durationMs` freezes. */\n    pause: () => void;\n    /** Resume after `pause()`. */\n    resume: () => void;\n    /** Stop and resolve with the assembled recording. */\n    stop: () => Promise<AudioRecording>;\n    /** Stop and throw the audio away. */\n    cancel: () => void;\n    status: () => AudioRecorderStatus;\n    /** Recorded length so far, excluding paused time. */\n    durationMs: () => number;\n    /** The negotiated container. */\n    mimeType: string;\n}\n\n/** Whether `MediaRecorder` exists and can produce at least one audio container. */\nexport function isAudioRecordingSupported(): boolean {\n    return typeof MediaRecorder !== \"undefined\" && pickAudioMimeType() !== null;\n}\n\n/**\n * First container in `preferred` the browser can actually produce, or `null`.\n *\n * **There is no MP3 or WAV here, and that is not an omission.** `MediaRecorder`\n * emits Opus (in WebM or Ogg) on Chromium and Firefox and AAC (in MP4) on Safari —\n * no engine implements an MP3 or WAV encoder for it. If a backend needs WAV,\n * {@link blobToWav} converts one client-side with no dependency; if it needs MP3,\n * transcode on the server. Shipping an MP3 encoder would mean a WASM build of the\n * order of 150 KB in every consumer's bundle to serve one format, which is the\n * trade this SDK does not make.\n *\n * @param preferred - Candidates, best first. Defaults to {@link AUDIO_MIME_CANDIDATES}.\n * @returns A supported MIME type, or `null` when none is.\n */\nexport function pickAudioMimeType(\n    preferred: readonly string[] = AUDIO_MIME_CANDIDATES,\n): string | null {\n    return pickRecordingMimeType(preferred);\n}\n\n/**\n * Wrap a `MediaStream` in a recorder with a real state machine and an honest clock.\n *\n * The engine is shared with the video recorder (`createMediaRecorder`), because the\n * subtle parts are not audio-specific: `MediaRecorder` reports **no duration** (WebM\n * it writes carries none in its header, which is why `<audio>` shows `Infinity` for a\n * fresh recording) so the clock is kept by hand and subtracts paused time; and\n * `stop()` returns *before* the last `dataavailable` event, so the blob can only be\n * assembled in `onstop`. What stays here is the part that genuinely differs — the\n * container list.\n *\n * The stream is **not** owned here: `stop()` leaves the microphone open so a retake\n * does not need a second permission round-trip. Release it with the owning\n * `useMicrophone().stop()`.\n *\n * @param stream - A live audio stream, usually from {@link useMicrophone}.\n * @param options - See {@link AudioRecorderOptions}.\n * @returns The imperative recorder.\n * @throws When `MediaRecorder` is unavailable, or an explicit `mimeType` is unsupported.\n */\nexport function createAudioRecorder(\n    stream: MediaStream,\n    options: AudioRecorderOptions = {},\n): AudioRecorderHandle {\n    return createMediaRecorder(stream, {\n        candidates: AUDIO_MIME_CANDIDATES,\n        kind: \"audio\",\n        mimeType: options.mimeType,\n        audioBitsPerSecond: options.audioBitsPerSecond,\n        timesliceMs: options.timesliceMs,\n        onChunk: options.onChunk,\n        onError: options.onError,\n    });\n}\n"],"mappings":"iDAuBA,IAAa,EAA2C,CACpD,yBACA,aACA,wBACA,6BACA,WACJ,EAwDA,SAAgB,GAAqC,CACjD,OAAO,OAAO,cAAkB,KAAe,EAAkB,IAAM,IAC3E,CAgBA,SAAgB,EACZ,EAA+B,EAClB,CACb,OAAO,EAAA,sBAAsB,CAAS,CAC1C,CAsBA,SAAgB,EACZ,EACA,EAAgC,CAAC,EACd,CACnB,OAAO,EAAA,oBAAoB,EAAQ,CAC/B,WAAY,EACZ,KAAM,QACN,SAAU,EAAQ,SAClB,mBAAoB,EAAQ,mBAC5B,YAAa,EAAQ,YACrB,QAAS,EAAQ,QACjB,QAAS,EAAQ,OACrB,CAAC,CACL"}