siyuan-note/siyuan · error · Error

MP3 encoder is not initialized

Error message

MP3 encoder is not initialized

What it means

Inside the RecordMedia worker script, messages of type "encode" or "finish" require the MP3 encoder instance to exist. If encoder is undefined (worker not initialized, or an encode/finish arrives after finish already reset it), it throws 'MP3 encoder is not initialized'.

Solutions

  1. Ensure the worker receives a valid init message (with correct sample rate/channels) before any encode/finish message
  2. Track recording state in the UI and disable encode/finish posts after finish has been sent
  3. Guard the main thread: only post finish once, and await worker ready before sending audio chunks
  4. In the worker, log message.type and encoder state in the catch handler to identify the out-of-order message

Example fix

// before (main thread)
worker.postMessage({type: "finish"});
worker.postMessage({type: "finish"}); // second call throws
// after
let finished = false;
const finish = () => {
  if (finished) return;
  finished = true;
  worker.postMessage({type: "finish"});
};
Defensive patterns

Strategy: try-catch

Validate before calling

let encoderReady = false;
worker.addEventListener("message", e => { if (e.data.type === "ready") encoderReady = true; });
// before encode/finish:
if (!encoderReady) throw new Error("worker not initialized");

Type guard

const canPostToRecorderWorker = (state: {initialized: boolean, finished: boolean}): boolean =>
  state.initialized && !state.finished;

Try / catch

worker.onerror = e => {
  if (String(e.message).includes("MP3 encoder is not initialized")) {
    console.error("Recording worker used out of order; re-initializing");
    initWorker(worker);
  }
};

Prevention

When it happens

Trigger: Posting {type: "encode"} or {type: "finish"} to the recording worker without a prior successful init message, posting encode after finish reset encoder to undefined, or the init message failed silently.

Common situations: Recording stop (finish) races with a final queued encode message; worker reused across recordings without re-init; init payload (sample rate/channels) failed causing encoder creation to be skipped.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/6dfd1d9f69376943. Report an issue: GitHub.

Appendix: source

Thrown at app/src/protyle/util/RecordMediaWorker.ts:38

    }
    const buffer = chunk.slice().buffer;
    workerScope.postMessage({type: "chunk", buffer}, [buffer]);
};

workerScope.onmessage = (event: MessageEvent<WorkerMessage>) => {
    try {
        const message = event.data;
        if (message.type === "init") {
            encoder = new Mp3Encoder(1, message.sampleRate, message.bitRate);
            workerScope.postMessage({type: "ready"});
        } else if (message.type === "encode") {
            if (!encoder) {
                throw new Error("MP3 encoder is not initialized");
            }
            postChunk(encoder.encodeBuffer(new Int16Array(message.buffer)));
        } else if (message.type === "finish") {
            if (!encoder) {
                throw new Error("MP3 encoder is not initialized");
            }
            postChunk(encoder.flush());
            encoder = undefined;
            workerScope.postMessage({type: "finished"});
        }
    } catch (error) {
        workerScope.postMessage({
            type: "error",
            message: error instanceof Error ? error.message : String(error),
        });
    }
};

View on GitHub (pinned to 9f775e8a12)