siyuan-note/siyuan · error
Recorder has been disposed
Error message
Recorder has been disposed
What it means
Thrown by RecordMedia.startRecording when the recorder instance has already been disposed. Once dispose() runs, its resources (AudioContext, tracks) are released, so starting a new recording on the dead instance is refused instead of failing obscurely later. A new RecordMedia instance must be created for the next recording.
Solutions
- Guard calls: check the instance is not disposed (or track disposal in your wrapper) before calling startRecording
- Create a fresh RecordMedia instance for each recording session instead of reusing a disposed one
- Remove/nullify references and event handlers to the recorder when disposing so stale callers cannot reach it
- Catch the error and restart the flow with a newly constructed recorder
Example fix
// before
await cachedRecorder.startRecording();
// after
if (cachedRecorder?.disposed) { cachedRecorder = new RecordMedia(stream); }
await cachedRecorder.startRecording(); Defensive patterns
Strategy: try-catch
Validate before calling
// caller-side bookkeeping let disposed = false; const isAlive = () => recorder != null && !disposed;
Try / catch
try {
await recorder.startRecording();
} catch (e) {
if (e.message === "Recorder has been disposed") {
recorder = new RecordMedia(stream); // restart with a fresh instance
await recorder.startRecording();
} else throw e;
} Prevention
- Create a new RecordMedia per recording session; never reuse after dispose
- Null out references and remove event listeners when disposing
- Disable record buttons once disposal has begun
- Await startRecording before allowing teardown
When it happens
Trigger: Calling await recorder.startRecording() after recorder.dispose() has been called — e.g. a stale UI handler or a queued promise resolving after the recording dialog was closed.
Common situations: User closes the recording dialog while a start action is in flight; components caching a recorder instance across sessions; event listeners not removed on teardown firing after dispose.
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
- agent runtime turn is not finalized
- AudioContext is not supported
- AudioWorklet is not supported
- capability handler unavailable
- capability is disabled or no longer available
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/de978832e38328dc.
Report an issue: GitHub.
Appendix: source
Thrown at app/src/protyle/util/RecordMedia.ts:114
}
const contextOptions = {} as AudioContextOptions & { sinkId?: { type: "none" } };
if ((isInAndroid() || isInHarmony()) && "setSinkId" in AudioContextConstructor.prototype) {
contextOptions.sinkId = {type: "none"};
}
this.context = new AudioContextConstructor(contextOptions);
if (!this.context.audioWorklet || typeof AudioWorkletNode === "undefined") {
throw new Error("AudioWorklet is not supported");
}
this.audioInput = this.context.createMediaStreamSource(mediaStream);
this.mediaStream.getAudioTracks().forEach((track) => {
track.addEventListener("ended", this.handleTrackEnded);
});
}
public async startRecording() {
if (this.disposed) {
throw new Error("Recorder has been disposed");
}
if (this.isRecording) {
return;
}
this.chunks = [];
this.readyPromise = new Promise<void>((resolve, reject) => {
this.resolveReady = resolve;
this.rejectReady = reject;
});
// Webpack 通过 import.meta.url 将录音编码器打包为按需加载的独立 Worker。
// @ts-ignore TypeScript 的 CommonJS 类型检查不识别由 Webpack 转换的 import.meta.url。
this.worker = new Worker(new URL("./RecordMediaWorker.ts", import.meta.url));
this.worker.onmessage = (event: MessageEvent<EncoderMessage>) => {
this.handleWorkerMessage(event.data);
};
this.worker.onerror = (event: ErrorEvent) => {
this.handleWorkerError(new Error(event.message || "MP3 encoder failed"));View on GitHub (pinned to 8641553a1f)