{"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":"Error","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/251596fc0de2f9528c00c224252fd073a99973f4/app/src/protyle/util/RecordMedia.ts#L86-L122","documentation":"After successfully creating an AudioContext, the constructor verifies both context.audioWorklet and the global AudioWorkletNode exist; if either is missing it throws 'AudioWorklet is not supported'. AudioWorklet is the modern (post-2018) replacement for ScriptProcessorNode and is required for the recorder's chunked Int16 capture path.","triggerScenarios":"Running on a browser/webview that has Web Audio but predates AudioWorklet (Chrome <66, Safari <14.1, old WebViews), or where the user disabled the worklet feature flag, or in a context where AudioWorkletNode global is not exposed.","commonSituations":"Old iOS Safari (<14.1); legacy Electron build; embedded webview shipped with an old Chromium; privacy extension stripping AudioWorklet globals.","solutions":["Feature-detect context.audioWorklet and typeof AudioWorkletNode before constructing RecordMedia; hide the record button if unsupported.","Upgrade the host browser/webview to a version with AudioWorklet (Chrome 66+, Firefox 76+, Safari 14.1+).","If you must support older engines, fall back to a ScriptProcessorNode-based recorder (not implemented here).","In automated tests, use standardized-audio-context which provides an audioWorklet mock."],"exampleFix":"// before\nconst rec = new RecordMedia(stream);\n// after\nconst ctx = new (window.AudioContext || window.webkitAudioContext)();\nif (!ctx.audioWorklet || typeof AudioWorkletNode === 'undefined') {\n    showMessage('Voice recording needs a newer browser (AudioWorklet unsupported).');\n    ctx.close(); return;\n}","handlingStrategy":"type-guard","validationCode":"const Ctor = typeof AudioContext !== 'undefined' ? AudioContext : (window as any).webkitAudioContext;\nconst tmp = new Ctor();\nconst ok = !!tmp.audioWorklet && typeof AudioWorkletNode !== 'undefined';\ntmp.close();\nif (!ok) { disableRecordButton(); }","typeGuard":"function audioWorkletSupported(): boolean {\n    const Ctor = typeof AudioContext !== 'undefined' ? AudioContext : (window as any).webkitAudioContext;\n    if (!Ctor) return false;\n    const c = new Ctor();\n    const ok = !!c.audioWorklet && typeof AudioWorkletNode !== 'undefined';\n    c.close();\n    return ok;\n}","tryCatchPattern":"try { recorder = new RecordMedia(stream); }\ncatch (e) { if (/AudioWorklet/i.test(e.message)) showMessage('Update your browser to use voice recording'); }","preventionTips":["Detect audioWorklet + AudioWorkletNode before constructing RecordMedia.","Require Chrome 66+/Safari 14.1+/Firefox 76+ for recording.","Disable the record button rather than throwing on unsupported clients.","Polyfill AudioWorklet in tests with standardized-audio-context mock."],"tags":["web-audio","recording","audio-worklet","feature-detection","typescript"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}