{"record":{"id":"3c99f0acd7ae533a","repo":"iflytek/astron-agent","slug":"recorder-com","errorCode":null,"errorMessage":"录音器未初始化","messagePattern":"录音器未初始化","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"console/frontend/src/pages/chat-page/components/recorder-com.tsx","lineNumber":72,"sourceCode":"          timer = null;\n        }\n        setStatus('end');\n      } catch (error) {\n        console.warn('停止录音失败:', error);\n      }\n    }, [changeStatus]);\n\n    // 开始录音事件处理\n    const handleStartRecord = useCallback(async (): Promise<void> => {\n      if (disabled || (status !== 'ready' && status !== 'end')) {\n        return;\n      }\n\n      try {\n        const tokenResponse = await getRtasrToken();\n\n        if (!record.current) {\n          throw new Error('录音器未初始化');\n        }\n\n        await record.current.recStart(tokenResponse);\n        setStatus('start');\n        changeStatus && changeStatus('play');\n        // 设置60秒超时\n        timer = setTimeout(() => {\n          stopAudio();\n          changeStatus && changeStatus('end');\n        }, 60 * 1000);\n      } catch (error) {\n        console.warn('录音启动失败:', error);\n\n        // 类型安全的错误处理\n        if (error && typeof error === 'object' && 'detail' in error) {\n          const errorDetail = error.detail as { code?: number };\n          if (errorDetail.code && [80000, 90000].includes(errorDetail.code)) {\n            return;","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/iflytek/astron-agent/blob/5e758547a83371a5a4b29dadf4ac03e8dd527635/console/frontend/src/pages/chat-page/components/recorder-com.tsx#L54-L90","documentation":"The recorder component obtains an RTASR (real-time speech) token, then verifies that the lazily-created recorder instance stored in `record.current` exists before calling `recStart`. If the ref is still null at start time, it throws '录音器未初始化' (recorder not initialized). This is an internal lifecycle guard: the recorder object is constructed asynchronously (often after user permission is granted), so recording can start before initialization completes.","triggerScenarios":"Clicking the record button (which calls getRtasrToken() then record.current.recStart()) before the recorder instance has finished asynchronous construction/assignment into the `record` ref — e.g. microphone permission prompt still pending, recorder script not yet loaded, or rapid double-click starting twice.","commonSituations":"Slow network delaying the recorder library load, first-ever mic permission grant (user clicks record before the permission callback resolves), React StrictMode double-effect causing the ref to be cleared, or a race between unmount cleanup nulling the ref and the in-flight async start.","solutions":["Initialize the recorder instance before requesting the token: construct RecordApp/the recorder first, await its init, then call getRtasrToken() and recStart.","Disable the record button until the recorder reports ready (state flag set after async init resolves).","Check `navigator.mediaDevices.getUserMedia`/permission first so the user grants mic access before clicking record.","Guard against double-click: ignore or debounce the start handler while a start is in flight."],"exampleFix":"// before\nconst tokenResponse = await getRtasrToken();\nif (!record.current) {\n  throw new Error('录音器未初始化');\n}\nawait record.current.recStart(tokenResponse);\n\n// after\n// ensure recorder is created before requesting token\nif (!record.current) {\n  record.current = await createRecorder(); // awaited init\n}\nconst tokenResponse = await getRtasrToken();\nawait record.current.recStart(tokenResponse);","handlingStrategy":"try-catch","validationCode":"if (typeof RecordApp === 'undefined' || !record.current) {\n  setStatus('idle');\n  return; // don't attempt recStart\n}","typeGuard":"function isRecorderReady(r: unknown): r is { recStart: (t: unknown) => Promise<void> } {\n  return !!r && typeof (r as any)?.recStart === 'function';\n}","tryCatchPattern":"try {\n  await startRecording();\n} catch (e) {\n  if (e.message === '录音器未初始化') {\n    setStatus('idle');\n    message.warning('录音组件尚未就绪，请稍后重试');\n  } else { throw e; }\n}","preventionTips":["Await recorder async init before enabling the record button.","Track a `recorderReady` state flag set after initialization resolves.","Debounce the start handler to prevent double-click races.","Handle microphone permission request before constructing the token flow."],"tags":["webrtc","async-lifecycle","null-reference","react"],"backgroundTag":"internal-invariant-violation","analyzedSha":"5e758547a83371a5a4b29dadf4ac03e8dd527635","analyzedAt":"2026-09-12T08:03:51.356Z","contentChangedAt":"2026-09-12T08:03:51.356Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}