siyuan-note/siyuan · error
AudioWorklet is not supported
Error message
AudioWorklet is not supported
What it means
RecordMedia relies on AudioWorklet to process audio chunks for MP3 encoding. After creating the AudioContext, the constructor checks context.audioWorklet and the AudioWorkletNode global; if either is missing it throws 'AudioWorklet is not supported'.
Solutions
- Update the webview/browser to a version supporting AudioWorklet (Chrome 66+, Safari 14.1+)
- Catch the error and fall back to a non-worklet recording path or disable recording with a clear message
- Check Content-Security-Policy so worker/worklet loading is not blocked (script-src allows blob: or the worklet URL)
- Feature-detect ctx.audioWorklet before constructing RecordMedia and hide/gate the record button
Example fix
// before
const recorder = new RecordMedia(stream);
// after
const Ctx = window.AudioContext || window.webkitAudioContext;
if (!Ctx || !new Ctx().audioWorklet || typeof AudioWorkletNode === "undefined") {
showMessage(window.siyuan.languages.audioNotSupported);
return;
}
const recorder = new RecordMedia(stream); Defensive patterns
Strategy: fallback
Validate before calling
function audioWorkletSupported(): boolean {
const Ctx = window.AudioContext || (window as any).webkitAudioContext;
if (!Ctx) return false;
const ctx = new Ctx();
const ok = !!ctx.audioWorklet && typeof AudioWorkletNode !== "undefined";
ctx.close();
return ok;
} Type guard
const supportsWorklet = (ctx: BaseAudioContext): boolean => "audioWorklet" in ctx && ctx.audioWorklet instanceof AudioWorklet && typeof AudioWorkletNode !== "undefined";
Try / catch
try {
recorder = new RecordMedia(stream);
} catch (e) {
if (e.message === "AudioWorklet is not supported") {
showMessage("Audio recording requires AudioWorklet support");
return;
}
throw e;
} Prevention
- Require Chrome 66+/Safari 14.1+ for recording features
- Verify CSP allows loading the AudioWorklet module (blob:/worker-src)
- Feature-detect ctx.audioWorklet before constructing RecordMedia
- Create the AudioContext after a user gesture to avoid suspended/restricted contexts
When it happens
Trigger: Creating RecordMedia in a browser/webview whose AudioContext lacks audioWorklet support, or where AudioWorkletNode is undefined — typically older WebViews (older Android System WebView, old Safari) or an AudioContext created before user gesture in restrictive environments.
Common situations: Old Android WebView versions lacking AudioWorklet; older iOS Safari; embedded browser controls (e.g. Harmony/Android shells) with partial Web Audio support; recording attempted in an environment where AudioWorklet module loading is blocked by CSP.
Understand the failure class
Background: "unsupported platform" / "not supported on this platform" errors: what they mean and how to fix them — this error's family across 47 libraries.
Related errors
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/9dd9b76e7f93d9f7.
Report an issue: GitHub.
Appendix: source
Thrown at app/src/protyle/util/RecordMedia.ts:104
private failure: Error;
private readonly handleTrackEnded = () => {
this.handleWorkerError(new RecordMediaInputEndedError());
};
constructor(mediaStream: MediaStream) {
this.mediaStream = mediaStream;
const AudioContextConstructor = typeof AudioContext !== "undefined" ? AudioContext : webkitAudioContext;
if (!AudioContextConstructor) {
throw new Error("AudioContext is not supported");
}
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;View on GitHub (pinned to 9f775e8a12)