Skip to content
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
08a1d81
feat: simple worklet callback (ios only)
poneciak57 Aug 22, 2025
3c603f1
fix: fixed worklets usage and dependencies
poneciak57 Aug 22, 2025
389dee3
feat: simple worklet callback (android)
poneciak57 Aug 26, 2025
585729d
chore: formatting and podfile.lock
poneciak57 Aug 26, 2025
e143434
feat: created example showcasing worklets usage
poneciak57 Aug 26, 2025
79946ca
fix: fixed issue with worklets sideeffects not being visible
poneciak57 Aug 27, 2025
da987ca
chore: formatting and package.json deps updt
poneciak57 Aug 27, 2025
45c5833
fix: fixed test and added feature flag
poneciak57 Aug 27, 2025
db5829c
chore: fixed yarn.lock and formatting
poneciak57 Aug 27, 2025
2bcdb29
chore: removed worklet part from recorder example
poneciak57 Aug 27, 2025
1a20444
feat: moved all worklets functionality into runner
poneciak57 Aug 28, 2025
e711fdb
feat: made worklets completly optional (android only)
poneciak57 Aug 28, 2025
e23e0a2
chore: minor changes
poneciak57 Aug 28, 2025
c0e5845
feat: made worklets completly optional (ios too)
poneciak57 Aug 29, 2025
490b2d3
chore: ios imports formatting
poneciak57 Aug 29, 2025
345f480
fix: added error for using worklets callback when worklets unavailable
poneciak57 Aug 29, 2025
11528ba
chore: neatpicks
poneciak57 Aug 29, 2025
470599c
docs: updated docs
poneciak57 Aug 29, 2025
6bf19fa
fix: performed requested changes
poneciak57 Aug 29, 2025
fcb4389
Merge branch 'main' into feat/worklets-support
poneciak57 Aug 29, 2025
4fee078
fix: removed unused kotlin import
poneciak57 Aug 29, 2025
211082c
feat: worklet node implementation
poneciak57 Sep 1, 2025
21dbc75
docs: small fixes
poneciak57 Sep 1, 2025
6c0bba8
fix: fixed tests
poneciak57 Sep 1, 2025
e62fcb7
docs: added documentation for worklet node
poneciak57 Sep 1, 2025
9f6772c
chore: fixed formatting
poneciak57 Sep 1, 2025
4c3c080
chore: updated podfile lock
poneciak57 Sep 1, 2025
fc6203b
Merge branch 'main' into feat/worklets-support
poneciak57 Sep 1, 2025
a755afd
chore: cmake redundancy fix
poneciak57 Sep 4, 2025
1ff0b4c
chore: removed worklets from recorder
poneciak57 Sep 5, 2025
854fdc0
feat: switched to array buffers for faster arguments preparation
poneciak57 Sep 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .yarn/releases/yarn-4.5.0.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -922,4 +922,4 @@ is-windows/index.js:
* Copyright © 2015-2018, Jon Schlinkert.
* Released under the MIT License.
*)
*/
*/
4 changes: 2 additions & 2 deletions apps/common-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
"react-native-audio-api": "workspace:*",
"react-native-background-timer": "2.4.1",
"react-native-gesture-handler": "2.28.0",
"react-native-reanimated": "4.0.2",
"react-native-reanimated": "^4.0.0",
"react-native-safe-area-context": "5.6.0",
"react-native-screens": "4.14.1",
"react-native-svg": "15.12.1",
"react-native-worklets": "0.4.2"
"react-native-worklets": "^0.4.1"
},
"devDependencies": {
"@babel/core": "^7.25.2",
Expand Down
200 changes: 200 additions & 0 deletions apps/common-app/src/examples/Worklets/Worklets.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import { useEffect, useRef } from "react";
import { Text, Button, View, StyleSheet } from 'react-native';
import {
AudioContext,
AudioManager,
AudioRecorder,
RecorderAdapterNode,
WorkletNode
} from 'react-native-audio-api';
import { Container } from "../../components";
import { Extrapolation, useSharedValue } from "react-native-reanimated";
import Animated, {
useAnimatedStyle,
withSpring,
interpolate,
} from "react-native-reanimated";


function Worklets() {
const SAMPLE_RATE = 16000;
const recorderRef = useRef<AudioRecorder | null>(null);
const aCtxRef = useRef<AudioContext | null>(null);
const recorderAdapterRef = useRef<RecorderAdapterNode | null>(null);
const workletNodeRef = useRef<WorkletNode | null>(null);

const bar0 = useSharedValue(0);
const bar1 = useSharedValue(0);
const bar2 = useSharedValue(0); // center bar
const bar3 = useSharedValue(0);
const bar4 = useSharedValue(0);

useEffect(() => {
AudioManager.setAudioSessionOptions({
iosCategory: 'playAndRecord',
iosMode: 'spokenAudio',
iosOptions: ['defaultToSpeaker', 'allowBluetoothA2DP'],
});

AudioManager.requestRecordingPermissions();
recorderRef.current = new AudioRecorder({
sampleRate: SAMPLE_RATE,
bufferLengthInSamples: 1024,
});
}, []);

const start = () => {
if (!recorderRef.current) {
console.error("Recorder is not initialized");
return;
}

const worklet = (audioData: Array<Float32Array>, timestamp: number) => {
'worklet';


// Calculates RMS amplitude
let sum = 0;
for (let i = 0; i < audioData.length; i++) {
sum += audioData[0][i] * audioData[0][i];
}
const rms = Math.sqrt(sum / audioData[0].length);
const scaledAmplitude = Math.min(rms * 500, 1);

console.log(`RMS: ${rms}, Scaled: ${scaledAmplitude}`);

bar0.value = bar1.value;
bar1.value = bar2.value;
bar3.value = bar2.value;
bar4.value = bar3.value;
bar2.value = scaledAmplitude;
};

aCtxRef.current = new AudioContext({ sampleRate: SAMPLE_RATE });
recorderAdapterRef.current = aCtxRef.current.createRecorderAdapter();
workletNodeRef.current = aCtxRef.current.createWorkletNode(worklet, 512, 1);
recorderAdapterRef.current.connect(workletNodeRef.current);
workletNodeRef.current.connect(aCtxRef.current.destination);

recorderRef.current.connect(recorderAdapterRef.current);
recorderRef.current.start();
console.log("Recording started");

if (aCtxRef.current.state === 'suspended') {
aCtxRef.current.resume();
}
}

const stop = () => {
if (!recorderRef.current) {
console.error("Recorder is not initialized");
return;
}
recorderRef.current.stop();
recorderAdapterRef.current = null;
aCtxRef.current = null;
console.log("Recording stopped");
bar0.value = 0;
bar1.value = 0;
bar2.value = 0;
bar3.value = 0;
bar4.value = 0;
}

const createBarStyle = (index: number) => {
return useAnimatedStyle(() => {
let amplitude = 0;

switch (index) {
case 0: amplitude = bar0.value; break;
case 1: amplitude = bar1.value; break;
case 2: amplitude = bar2.value; break;
case 3: amplitude = bar3.value; break;
case 4: amplitude = bar4.value; break;
}

const centerIndex = 2;
const distanceFromCenter = Math.abs(index - centerIndex);

const height = interpolate(
amplitude,
[0, 1],
[10, 200],
Extrapolation.CLAMP
);

const backgroundColor = interpolate(
amplitude,
[0, 0.5, 1],
[0, 0.5, 1],
Extrapolation.CLAMP
);

const barWidth = 40 - (distanceFromCenter * 5);
const opacity = 1 - (distanceFromCenter * 0.15);

return {
height: withSpring(height, { damping: 20, stiffness: 200 }),
width: barWidth,
backgroundColor: `rgba(${Math.floor(backgroundColor * 255)}, ${Math.floor((1 - backgroundColor) * 255)}, 100, ${opacity})`,
};
});
};

return (
<Container>
<Text style={styles.title}>Audio Worklets Visualizer</Text>
<Text style={styles.subtitle}>Speak into the microphone to see the animation</Text>

<View style={styles.visualizer}>
<View style={styles.barsContainer}>
{Array.from({ length: 5 }, (_, index) => (
<Animated.View
key={index}
style={[styles.bar, createBarStyle(index)]}
/>
))}
</View>
</View>

<Button onPress={start} title="Start Recording" />
<Button onPress={stop} title="Stop Recording" />
</Container>
);
}

const styles = StyleSheet.create({
title: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: 10,
textAlign: 'center',
},
subtitle: {
fontSize: 14,
color: '#666',
marginBottom: 30,
textAlign: 'center',
},
visualizer: {
height: 250,
justifyContent: 'flex-end',
alignItems: 'center',
marginVertical: 30,
backgroundColor: '#f0f0f0',
borderRadius: 10,
padding: 20,
},
barsContainer: {
flexDirection: 'row',
alignItems: 'flex-end',
justifyContent: 'center',
gap: 8,
},
bar: {
borderRadius: 20,
minHeight: 10,
},
});

export default Worklets;
8 changes: 8 additions & 0 deletions apps/common-app/src/examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import AudioVisualizer from './AudioVisualizer';
import OfflineRendering from './OfflineRendering';
import Record from './Record/Record';
import PlaybackSpeed from './PlaybackSpeed/PlaybackSpeed';
import Worklets from './Worklets/Worklets';

type NavigationParamList = {
Oscillator: undefined;
Expand All @@ -21,6 +22,7 @@ type NavigationParamList = {
AudioVisualizer: undefined;
OfflineRendering: undefined;
Record: undefined;
Worklets: undefined;
};

export type ExampleKey = keyof NavigationParamList;
Expand Down Expand Up @@ -88,4 +90,10 @@ export const Examples: Example[] = [
subtitle: 'Record audio',
screen: Record,
},
{
key: 'Worklets',
title: 'Worklets',
subtitle: 'Process audio on ui thread with worklet support',
screen: Worklets,
}
] as const;
Loading
Loading