{"record":{"id":"3ff525c4919c22aa","repo":"moeru-ai/airi","slug":"3-browser-requires-user-gesture-ensure-micropho","errorCode":null,"errorMessage":"3. Browser requires user gesture - ensure microphone was enabled by user action","messagePattern":"3\\. Browser requires user gesture - ensure microphone was enabled by user action","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/stage-ui/src/libs/providers/providers/browser-web-speech-api/provider.ts","lineNumber":460,"sourceCode":"    console.info('Web Speech API sound ended')\n  }\n\n  recognition.onaudioend = () => {\n    console.info('Web Speech API audio capture ended')\n  }\n\n  recognition.onnomatch = () => {\n    console.info('Web Speech API: No speech match')\n  }\n\n  const started = startRecognition()\n  if (!started) {\n    // If immediate start failed, it might be a permission issue\n    // Web Speech API will prompt for permission automatically, so we just log\n    console.warn('Web Speech API recognition did not start immediately. This might be due to:')\n    console.warn('1. Microphone permission not granted - browser should prompt automatically')\n    console.warn('2. Recognition already running - this is normal if called multiple times')\n    console.warn('3. Browser requires user gesture - ensure microphone was enabled by user action')\n\n    // Don't retry immediately - wait for permission or user action\n    // The recognition instance is already created, so it can be started later if needed\n  }\n\n  return {\n    fullStream,\n    text: deferredText.promise,\n    textStream,\n    recognition: recognitionInstance,\n  }\n}\n","sourceCodeStart":442,"sourceCodeEnd":473,"githubUrl":"https://github.com/moeru-ai/airi/blob/677329427f32468c74b17f3ec47eeca4e05bec65/packages/stage-ui/src/libs/providers/providers/browser-web-speech-api/provider.ts#L442-L473","documentation":"Fourth line of the Web Speech API start-failure warn block: enumerated cause 3, 'Browser requires user gesture - ensure microphone was enabled by user action'. Some engines only allow SpeechRecognition.start() from a user-activation context; calling it from timers, autoplayed flows, or component mounts fails and startRecognition() returns false.","triggerScenarios":"Starting recognition from setTimeout, an animation frame, a WebSocket event handler, or onMounted without prior user interaction; browsers (notably Safari and some mobile WebViews) that gate audio capture and speech services behind transient user activation.","commonSituations":"App auto-enables hearing on page load; voice features triggered by server push events; automated tests that call start() without synthesizing a gesture.","solutions":["Bind the first recognition.start() to a click/tap handler that enables the feature","Keep a user-activated 'microphone enabled' toggle; only auto-restart after the user has activated once in the session","In tests, dispatch a real user event or grant and pre-warm the audio context before asserting recognition state","If hands-free startup is required, prefer a provider that does not need a gesture (local whisper worker)"],"exampleFix":"// before: started from a timer, gesture-gated browsers refuse\nsetTimeout(() => startRecognition(), 100)\n\n// after: first start from a user gesture, then auto-restart is allowed\nenableMicButton.addEventListener('click', () => {\n  startRecognition()\n})","handlingStrategy":"validation","validationCode":"function hasUserActivation(): boolean {\n  return typeof navigator !== 'undefined' && Boolean(navigator.userActivation?.isActive)\n}\nif (!hasUserActivation())\n  await waitForUserClick(enableButton)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always begin speech sessions from a click or tap handler","Persist an explicit 'microphone enabled by user' toggle before auto-restarting sessions","Prefer gesture-free providers (local whisper worker) for hands-free startup flows"],"tags":["web-speech-api","user-gesture","user-activation","autoplay-policy"],"backgroundTag":"user-gesture-required","analyzedSha":"677329427f32468c74b17f3ec47eeca4e05bec65","analyzedAt":"2026-08-18T17:29:58.153Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}