moeru-ai/airi · error · Error
No microphone stream available for voice input
Error message
No microphone stream available for voice input
What it means
Thrown by startAutoSegmentation in the voice-input session composable when mediaRef.value is null/undefined at the moment segmentation starts. VAD (and the volume fallback) need the live MediaStream from getUserMedia; if microphone acquisition never completed, was stopped, or permission was denied, there is no stream to attach analyzers to.
Solutions
- Ensure the media stream is acquired and assigned before starting segmentation: await the session start (getUserMedia) first, then begin auto-segmentation
- Check navigator.mediaDevices and permissions beforehand: const perm = await navigator.mediaDevices.query({ name: 'microphone' as PermissionName }) and surface a permission prompt if state === 'prompt'
- Run only in secure contexts (https:// or localhost); in Electron, grant permission via session.setPermissionRequestHandler
- Guard the call: if (!mediaRef.value) await startMedia() / or report 'microphone unavailable' to the user instead of throwing
Example fix
// before
startAutoSegmentation() // mediaRef.value is null -> throws
// after
async function ensureVoiceInput() {
if (!mediaRef.value) {
if (!navigator.mediaDevices?.getUserMedia)
throw new Error('Microphone API unavailable (requires https or localhost)')
mediaRef.value = await navigator.mediaDevices.getUserMedia({ audio: true })
}
await startAutoSegmentation()
} Defensive patterns
Strategy: validation
Validate before calling
async function ensureMicStream(): Promise<MediaStream | null> {
if (!navigator.mediaDevices?.getUserMedia) return null // insecure context
try {
return await navigator.mediaDevices.getUserMedia({ audio: true })
} catch { return null }
}
const stream = await ensureMicStream()
if (!stream) return disableVoiceInput('Microphone unavailable or permission denied') Type guard
const hasMicStream = (mediaRef: Ref<MediaStream | null | undefined>): mediaRef is Ref<MediaStream> => !!mediaRef.value
Try / catch
catch (e) { if (e.message === 'No microphone stream available for voice input') { setVoiceInputUnavailable(); return } throw e } Prevention
- Await getUserMedia before starting VAD/segmentation
- Run only on https:// or localhost; configure Electron permission handlers
- Watch track 'ended' events and stop segmentation when the stream dies
- Prompt for mic permission in UI before enabling voice features
When it happens
Trigger: Calling the segmentation/start path before the session's start() resolved getUserMedia; microphone permission denied in the browser so mediaRef never populated; the stream was stopped/cleared (device unplugged, track ended) before auto-segmentation began; calling on a non-secure context where getUserMedia is unavailable.
Common situations: Race in UI wiring: a 'start recording' handler fires VAD setup in parallel with stream acquisition; Electron/Chromium permission handler not configured so permission queries auto-deny; page loaded over http:// (not localhost) so navigator.mediaDevices is undefined; OS-level mic privacy toggle blocking the device; calling start twice where the second call observes the torn-down state.
Related errors
- No audio stream available
- 1. Microphone permission not granted - browser should…
- 1. Microphone permission not granted - browser should…
- 2. Recognition already running - this is normal if called…
- Cannot declare permissions for unknown plugin
AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18).
Data as JSON: /api/errors/2cf374c22551ce4d.
Report an issue: GitHub.
Appendix: source
Thrown at packages/stage-ui/src/composables/audio/voice-input-session.ts:565
}
}
volumeFallbackAnimationFrame = requestAnimationFrame(analyze)
}
volumeFallbackAnimationFrame = requestAnimationFrame(analyze)
}
catch (error) {
stopVolumeFallback()
lastError.value = error
log('error', 'volume-fallback-start-failed', 'Failed to start volume-based recorder fallback.', { error })
}
}
async function startAutoSegmentation() {
const stream = mediaRef.value
if (!stream)
throw new Error('No microphone stream available for voice input')
await startVoiceInputVadDetectionSafely({
init: initVAD,
loaded: () => vadLoaded.value,
start: startVAD,
stream,
getError: () => vadError.value,
log,
})
await startVolumeFallback(stream)
}
async function stop(options: { flushActiveRecording?: boolean } = {}) {
stopVolumeFallback()
disposeVAD()
transcriptionChain.reset()
stoppedRecordingSegments.length = 0
vadRecordings.clear()View on GitHub (pinned to 677329427f)