iflytek/astron-agent · error · Error

录音器未初始化

Error message

录音器未初始化

What it means

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.

Solutions

  1. Initialize the recorder instance before requesting the token: construct RecordApp/the recorder first, await its init, then call getRtasrToken() and recStart.
  2. Disable the record button until the recorder reports ready (state flag set after async init resolves).
  3. Check `navigator.mediaDevices.getUserMedia`/permission first so the user grants mic access before clicking record.
  4. Guard against double-click: ignore or debounce the start handler while a start is in flight.

Example fix

// before
const tokenResponse = await getRtasrToken();
if (!record.current) {
  throw new Error('录音器未初始化');
}
await record.current.recStart(tokenResponse);

// after
// ensure recorder is created before requesting token
if (!record.current) {
  record.current = await createRecorder(); // awaited init
}
const tokenResponse = await getRtasrToken();
await record.current.recStart(tokenResponse);
Defensive patterns

Strategy: try-catch

Validate before calling

if (typeof RecordApp === 'undefined' || !record.current) {
  setStatus('idle');
  return; // don't attempt recStart
}

Type guard

function isRecorderReady(r: unknown): r is { recStart: (t: unknown) => Promise<void> } {
  return !!r && typeof (r as any)?.recStart === 'function';
}

Try / catch

try {
  await startRecording();
} catch (e) {
  if (e.message === '录音器未初始化') {
    setStatus('idle');
    message.warning('录音组件尚未就绪,请稍后重试');
  } else { throw e; }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of iflytek/astron-agent@5e758547a8 (2026-09-12). Data as JSON: /api/errors/3c99f0acd7ae533a. Report an issue: GitHub.

Appendix: source

Thrown at console/frontend/src/pages/chat-page/components/recorder-com.tsx:72

          timer = null;
        }
        setStatus('end');
      } catch (error) {
        console.warn('停止录音失败:', error);
      }
    }, [changeStatus]);

    // 开始录音事件处理
    const handleStartRecord = useCallback(async (): Promise<void> => {
      if (disabled || (status !== 'ready' && status !== 'end')) {
        return;
      }

      try {
        const tokenResponse = await getRtasrToken();

        if (!record.current) {
          throw new Error('录音器未初始化');
        }

        await record.current.recStart(tokenResponse);
        setStatus('start');
        changeStatus && changeStatus('play');
        // 设置60秒超时
        timer = setTimeout(() => {
          stopAudio();
          changeStatus && changeStatus('end');
        }, 60 * 1000);
      } catch (error) {
        console.warn('录音启动失败:', error);

        // 类型安全的错误处理
        if (error && typeof error === 'object' && 'detail' in error) {
          const errorDetail = error.detail as { code?: number };
          if (errorDetail.code && [80000, 90000].includes(errorDetail.code)) {
            return;

View on GitHub (pinned to 5e758547a8)