{"record":{"id":"5ca1ce2f1521cf0b","repo":"moeru-ai/airi","slug":"no-audio-stream-available","errorCode":null,"errorMessage":"No audio stream available","messagePattern":"No audio stream available","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/stage-pages/src/pages/settings/modules/hearing.vue","lineNumber":154,"sourceCode":"  if (useVADModel.value && loadedVAD.value) {\n    return isSpeechVAD.value\n  }\n\n  return isSpeechVolume.value\n})\n\nasync function setupAudioMonitoring() {\n  try {\n    if (!selectedAudioInput.value) {\n      console.warn('No audio input device selected')\n      return false\n    }\n\n    await stopAudioMonitoring()\n\n    await startStream()\n    if (!stream.value) {\n      console.warn('No audio stream available')\n      return false\n    }\n\n    if (supportsStreamInput.value) {\n      // The Hearing pipeline owns speech segmentation and the provider session.\n      // The page VAD below only drives the visualization for streaming providers.\n      await transcribeForMediaStream(stream.value, {\n        consumerId: hearingPlaygroundTranscriptionConsumerId,\n        onSpeechEnd: finishStreaming,\n        onTranscriptionUpdate: replaceStreamingText,\n      })\n    }\n\n    const source = audioContext.value.createMediaStreamSource(stream.value)\n\n    // Fallback speaking detection (when VAD model is not used)\n    const analyzer = startAnalyzer(audioContext.value)\n    onAnalyzerUpdate((volumeLevel) => {","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/moeru-ai/airi/blob/677329427f32468c74b17f3ec47eeca4e05bec65/packages/stage-pages/src/pages/settings/modules/hearing.vue#L136-L172","documentation":"Second guard in setupAudioMonitoring(): after the device check passes, the hearing store's startStream() is awaited and stream.value verified. If no MediaStream materialized - getUserMedia denied, no audio hardware, insecure context, or the store failed to acquire the mic - the function warns and returns false, so monitoring never starts.","triggerScenarios":"getUserMedia rejects (NotAllowedError after denial, NotFoundError without hardware); page served over plain http (not localhost) where getUserMedia is unavailable; stream acquired but immediately torn down by a concurrent stopAudioMonitoring; OS-level exclusive mic lock by another application.","commonSituations":"Permission denied then retry; dev server accessed over http on a non-localhost host; VMs without audio devices; race between toggling monitoring off and on rapidly.","solutions":["Grant microphone permission for the origin and retry setup","Serve over HTTPS or localhost - getUserMedia requires a secure context","Verify a microphone exists and is enabled at the OS level","Avoid racing setup with stopAudioMonitoring; serialize start/stop toggles","Look for the preceding console error from startStream/getUserMedia - the DOMException name (NotAllowedError, NotFoundError, NotReadableError) identifies the cause"],"exampleFix":"// before\nawait startStream()\nif (!stream.value) {\n  console.warn('No audio stream available')\n  return false\n}\n\n// after - surface the real acquisition failure and validate tracks\ntry {\n  await startStream()\n}\ncatch (err) {\n  console.error('getUserMedia failed:', (err as DOMException).name)\n  return false\n}\nif (!stream.value || stream.value.getAudioTracks().length === 0)\n  return false","handlingStrategy":"validation","validationCode":"if (!window.isSecureContext)\n  throw new Error('Microphone access requires HTTPS or localhost')\nconst perm = await navigator.permissions.query({ name: 'microphone' as PermissionName })\nif (perm.state === 'denied')\n  throw new Error('Microphone permission denied')\nawait startStream()\nif (!stream.value || stream.value.getAudioTracks().length === 0)\n  return false","typeGuard":null,"tryCatchPattern":"try {\n  await startStream()\n}\ncatch (err) {\n  const name = (err as DOMException).name\n  if (name === 'NotAllowedError') showPermissionUI()\n  else if (name === 'NotFoundError') showNoDeviceUI()\n  else throw err\n  return false\n}","preventionTips":["Serve over HTTPS or localhost for getUserMedia","Ask permission early (onboarding) so streams acquire on first try","Serialize start/stop toggles to avoid tearing down a stream mid-setup","Check stream.value.getAudioTracks().length, not just stream existence"],"tags":["getusermedia","media-stream","microphone","permissions","secure-context"],"backgroundTag":"getusermedia-failed","analyzedSha":"677329427f32468c74b17f3ec47eeca4e05bec65","analyzedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-08-23T16:17:53.355Z"}