WorkletNode
Requires react-native-audio-worklets, react-native-worklets >= 0.10.0, and react-native-audio-api >= 1.0.0. See the Worklets introduction for installation.
WorkletNode is a pass-through analysis node. It accumulates incoming audio frames until bufferLength is reached, then invokes your worklet on the UI runtime with a read-only snapshot. Audio output is unchanged — modifications inside the callback do not affect the signal.
For background on runtimes and performance tips, see How to use audio worklets mindfully.
Constructor
import { AudioContext } from 'react-native-audio-api';
import { WorkletNode } from 'react-native-audio-worklets';
const node = new WorkletNode(context, callback, 1024);
// or with explicit options:
const timeDomainNode = new WorkletNode(context, callback, {
domain: 'time-domain',
bufferLength: 1024,
});
const frequencyDomainNode = new WorkletNode(context, callback, {
domain: 'frequency-domain',
bufferLength: 1024,
});
After construction, tune frequency-domain analysis at runtime:
frequencyDomainNode.smoothingTimeConstant = 0.5;
Parameters
| Parameter | Type | Description |
|---|---|---|
context | BaseAudioContext | The audio context that owns this node. |
callback | WorkletNodeCallback | Worklet invoked on the UI runtime when a snapshot is ready. Must include the 'worklet' directive. |
options | number | WorkletNodeOptions | Optional. Pass bufferLength directly, { domain?, bufferLength? }, or omit for defaults (time-domain, bufferLength: 1024). |
WorkletNodeOptions
type WorkletNodeDomain = 'time-domain' | 'frequency-domain';
interface WorkletNodeOptions {
domain?: WorkletNodeDomain; // default 'time-domain'
bufferLength?: number; // power of 2, 32..32768, default 1024
smoothingTimeConstant?: number; // default 0.8, frequency-domain only
}
| Option | Default | Description |
|---|---|---|
domain | 'time-domain' | Snapshot type passed to the callback. |
bufferLength | 1024 | Snapshot size: PCM samples (time-domain) or magnitude bins (frequency-domain). In frequency-domain mode the internal FFT size is bufferLength * 2. |
smoothingTimeConstant | 0.8 | Exponential smoothing of linear magnitude bins (0..1). |
WorkletNodeCallback
type WorkletNodeCallback = (audioData: Float32Array) => void;
| Argument | Description |
|---|---|
audioData | Stable Float32Array reused every callback. Time-domain: down-mixed mono PCM (bufferLength samples). Frequency-domain: linear magnitude spectrum (bufferLength bins; FFT size bufferLength * 2). |
Do not assign audioData to a Reanimated shared value (or any state read later on the UI thread). The native buffer pool is reused for the next snapshot and may be written from the audio thread after your callback returns.
- Safe: compute a scalar inside the callback and assign that (e.g.
amplitude.value = rms). - Safe: copy samples if you need waveform data later —
Float32Array.from(audioData). - Unsafe:
waveform.value = audioData— you keep a live alias, not a frozen snapshot.
Errors
| Error type | Condition |
|---|---|
NotSupportedError | react-native-audio-worklets native module not installed, worklet extensions are not linked, or New Architecture is disabled. |
NotSupportedError | react-native-worklets is missing or below the supported version (>= 0.10.0). |
IndexSizeError | bufferLength or smoothingTimeConstant is out of range. |
How buffering works
- Each render quantum appends down-mixed mono frames into an internal buffer.
- Time-domain: when
bufferLengthframes are collected, the callback is scheduled. - Frequency-domain: when
bufferLength * 2PCM samples are collected, native code runs window + FFT + magnitude smoothing, then schedules the callback withbufferLengthbins. - While the callback is running, new frames are skipped (audio still passes through).
- After the callback completes, accumulation starts again from the beginning.
If a single quantum contains more frames than needed to fill the buffer, only the frames required to reach bufferLength are copied — the rest are discarded.
Example
Oscillator → WorkletNode (RMS on UI) → destination, driving Reanimated bars:
import { AudioContext } from 'react-native-audio-api';
import { WorkletNode } from 'react-native-audio-worklets';
import { useSharedValue, withSpring } from 'react-native-reanimated';
function Visualizer() {
const amplitude = useSharedValue(0);
const start = () => {
const ctx = new AudioContext();
const workletNode = new WorkletNode(
ctx,
(audioData) => {
'worklet';
let sum = 0;
for (let i = 0; i < audioData.length; i++) {
sum += audioData[i] * audioData[i];
}
const rms = Math.sqrt(sum / audioData.length);
amplitude.value = withSpring(Math.min(rms * 4, 1));
},
1024
);
const oscillator = ctx.createOscillator();
oscillator.frequency.value = 440;
oscillator.connect(workletNode);
workletNode.connect(ctx.destination);
oscillator.start();
ctx.resume();
};
// ...
}
Properties
Inherits all properties from AudioNode.
AudioNodeproperties| Property | Type | Description |
|---|---|---|
bufferLength Read only | number | Snapshot size passed to the callback (set at construction). In frequency-domain mode, internal FFT size is bufferLength * 2. |
smoothingTimeConstant | number | Exponential smoothing of linear magnitude bins (0..1, default 0.8). Meaningful in frequency-domain mode. |
Methods
Inherits all methods from AudioNode.
AudioNodemethodsWorkletNode does not define any additional methods.
Known issue
When using Reanimated shared values, UI updates from the worklet may not appear immediately because the microtask queue is not always flushed on the UI runtime.
Add this at the end of your callback if you see missing updates:
requestAnimationFrame(() => {});
Use only after confirming the issue — it adds a small scheduling cost.