{"record":{"id":"9dd9b76e7f93d9f7","repo":"siyuan-note/siyuan","slug":"audioworklet-is-not-supported","errorCode":null,"errorMessage":"AudioWorklet is not supported","messagePattern":"AudioWorklet is not supported","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/protyle/util/RecordMedia.ts","lineNumber":104,"sourceCode":"    private failure: Error;\n    private readonly handleTrackEnded = () => {\n        this.handleWorkerError(new RecordMediaInputEndedError());\n    };\n\n    constructor(mediaStream: MediaStream) {\n        this.mediaStream = mediaStream;\n        const AudioContextConstructor = typeof AudioContext !== \"undefined\" ? AudioContext : webkitAudioContext;\n        if (!AudioContextConstructor) {\n            throw new Error(\"AudioContext is not supported\");\n        }\n\n        const contextOptions = {} as AudioContextOptions & { sinkId?: { type: \"none\" } };\n        if ((isInAndroid() || isInHarmony()) && \"setSinkId\" in AudioContextConstructor.prototype) {\n            contextOptions.sinkId = {type: \"none\"};\n        }\n        this.context = new AudioContextConstructor(contextOptions);\n        if (!this.context.audioWorklet || typeof AudioWorkletNode === \"undefined\") {\n            throw new Error(\"AudioWorklet is not supported\");\n        }\n        this.audioInput = this.context.createMediaStreamSource(mediaStream);\n        this.mediaStream.getAudioTracks().forEach((track) => {\n            track.addEventListener(\"ended\", this.handleTrackEnded);\n        });\n    }\n\n    public async startRecording() {\n        if (this.disposed) {\n            throw new Error(\"Recorder has been disposed\");\n        }\n        if (this.isRecording) {\n            return;\n        }\n\n        this.chunks = [];\n        this.readyPromise = new Promise<void>((resolve, reject) => {\n            this.resolveReady = resolve;","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/app/src/protyle/util/RecordMedia.ts#L86-L122","documentation":"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'.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst recorder = new RecordMedia(stream);\n// after\nconst Ctx = window.AudioContext || window.webkitAudioContext;\nif (!Ctx || !new Ctx().audioWorklet || typeof AudioWorkletNode === \"undefined\") {\n  showMessage(window.siyuan.languages.audioNotSupported);\n  return;\n}\nconst recorder = new RecordMedia(stream);","handlingStrategy":"fallback","validationCode":"function audioWorkletSupported(): boolean {\n  const Ctx = window.AudioContext || (window as any).webkitAudioContext;\n  if (!Ctx) return false;\n  const ctx = new Ctx();\n  const ok = !!ctx.audioWorklet && typeof AudioWorkletNode !== \"undefined\";\n  ctx.close();\n  return ok;\n}","typeGuard":"const supportsWorklet = (ctx: BaseAudioContext): boolean =>\n  \"audioWorklet\" in ctx && ctx.audioWorklet instanceof AudioWorklet &&\n  typeof AudioWorkletNode !== \"undefined\";","tryCatchPattern":"try {\n  recorder = new RecordMedia(stream);\n} catch (e) {\n  if (e.message === \"AudioWorklet is not supported\") {\n    showMessage(\"Audio recording requires AudioWorklet support\");\n    return;\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["audio","audioworklet","browser-support"],"backgroundTag":"unsupported-platform","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}