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

  1. Update the webview/browser to a version supporting AudioWorklet (Chrome 66+, Safari 14.1+)
  2. Catch the error and fall back to a non-worklet recording path or disable recording with a clear message
  3. Check Content-Security-Policy so worker/worklet loading is not blocked (script-src allows blob: or the worklet URL)
  4. 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

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)