Commit 05b274e
authored
feat: add Fishjam background blur integration (#1080)
## Description
Adds react-native-executorch-webrtc package for real-time background
blur in Fishjam WebRTC video calls using on-device ExecuTorch
segmentation models.
Key features:
- useBackgroundBlur hook providing blurMiddleware for Fishjam's
useCamera
- blur compositing (OpenGL ES on Android, Core Image on iOS)
- Morphological mask cleaning + EMA temporal smoothing (C++/OpenCV)
Architecture:
- Reuses BaseSemanticSegmentation from react-native-executorch for
inference
- Registers custom VideoFrameProcessor with Fishjam's WebRTC pipeline
- All heavy processing in native (C++/Objective-C++) for performance
### Introduces a breaking change?
- [ ] Yes
- [x] No
### Type of change
- [ ] Bug fix (change which fixes an issue)
- [x] New feature (change which adds functionality)
- [ ] Documentation update (improves or adds clarity to existing
documentation)
- [ ] Other (chores, tests, code style improvements etc.)
### Tested on
- [x] iOS
- [x] Android
### Testing instructions
You'll need to setup your fishjam account, and verify this example works
properly:
```import { useEffect, useState } from 'react';
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View, TouchableOpacity } from 'react-native';
import {
FishjamProvider,
useConnection,
useCamera,
useInitializeDevices,
useSandbox,
RTCView,
} from '@fishjam-cloud/react-native-client';
import { useBackgroundBlur } from 'react-native-executorch-webrtc';
import { ResourceFetcher, SELFIE_SEGMENTATION, initExecutorch } from 'react-native-executorch';
import { ExpoResourceFetcher } from 'react-native-executorch-expo-resource-fetcher';
initExecutorch({ resourceFetcher: ExpoResourceFetcher });
const FISHJAM_ID = 'your-id';
function CameraScreen() {
const { initializeDevices } = useInitializeDevices();
const { cameraStream, cameraDevices, currentCamera, selectCamera, setCameraTrackMiddleware } = useCamera();
const { joinRoom, leaveRoom, peerStatus } = useConnection();
const { getSandboxPeerToken } = useSandbox();
const [isJoining, setIsJoining] = useState(false);
const [modelPath, setModelPath] = useState<string | null>(null);
const [downloadProgress, setDownloadProgress] = useState(0);
const [blurEnabled, setBlurEnabled] = useState(false);
const { blurMiddleware } = useBackgroundBlur({
modelUri: modelPath || '',
blurRadius: 12,
});
// Download the selfie segmentation model
useEffect(() => {
const downloadModel = async () => {
try {
const paths = await ResourceFetcher.fetch(
(progress) => setDownloadProgress(progress),
SELFIE_SEGMENTATION.modelSource
);
if (paths?.[0]) {
setModelPath(paths[0]);
}
} catch (error) {
console.error('Failed to download model:', error);
}
};
downloadModel();
}, []);
const handleFlipCamera = async () => {
if (cameraDevices.length < 2) return;
const currentIndex = cameraDevices.findIndex(
(device) => device.deviceId === currentCamera?.deviceId
);
const nextIndex = (currentIndex + 1) % cameraDevices.length;
await selectCamera(cameraDevices[nextIndex].deviceId);
};
const handleToggleBlur = async () => {
if (!modelPath) return;
if (blurEnabled) {
await setCameraTrackMiddleware(null);
setBlurEnabled(false);
} else {
await setCameraTrackMiddleware(blurMiddleware);
setBlurEnabled(true);
}
};
useEffect(() => {
initializeDevices();
}, []);
const handleJoinRoom = async () => {
setIsJoining(true);
try {
const roomName = 'demo-room';
const peerName = `user_${Date.now()}`;
const peerToken = await getSandboxPeerToken(roomName, peerName);
await joinRoom({ peerToken });
} catch (error) {
console.error('Failed to join room:', error);
} finally {
setIsJoining(false);
}
};
return (
<View style={styles.container}>
<StatusBar style="light" />
<View style={styles.videoContainer}>
{cameraStream ? (
<RTCView
mediaStream={cameraStream}
style={styles.video}
objectFit="cover"
mirror={true}
/>
) : (
<View style={styles.placeholder}>
<Text style={styles.placeholderText}>Starting camera...</Text>
</View>
)}
</View>
<View style={styles.controls}>
<Text style={styles.status}>Status: {peerStatus}</Text>
{downloadProgress > 0 && downloadProgress < 1 && (
<Text style={styles.status}>
Downloading model: {(downloadProgress * 100).toFixed(0)}%
</Text>
)}
<View style={styles.buttons}>
<TouchableOpacity style={styles.flipButton} onPress={handleFlipCamera}>
<Text style={styles.buttonText}>Flip</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.blurButton, blurEnabled && styles.blurButtonActive, !modelPath && styles.buttonDisabled]}
onPress={handleToggleBlur}
disabled={!modelPath}
>
<Text style={styles.buttonText}>{blurEnabled ? 'Blur On' : 'Blur'}</Text>
</TouchableOpacity>
{peerStatus === 'connected' ? (
<TouchableOpacity style={styles.leaveButton} onPress={leaveRoom}>
<Text style={styles.buttonText}>Leave Room</Text>
</TouchableOpacity>
) : (
<TouchableOpacity
style={[styles.button, isJoining && styles.buttonDisabled]}
onPress={handleJoinRoom}
disabled={isJoining}
>
<Text style={styles.buttonText}>
{isJoining ? 'Joining...' : 'Join Room'}
</Text>
</TouchableOpacity>
)}
</View>
</View>
</View>
);
}
export default function App() {
return (
<FishjamProvider fishjamId={FISHJAM_ID}>
<CameraScreen />
</FishjamProvider>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000',
},
videoContainer: {
flex: 1,
},
video: {
flex: 1,
},
placeholder: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#1a1a1a',
},
placeholderText: {
color: '#666',
fontSize: 18,
},
controls: {
padding: 20,
paddingBottom: 40,
alignItems: 'center',
gap: 12,
},
status: {
color: '#888',
fontSize: 14,
},
buttons: {
flexDirection: 'row',
gap: 12,
},
button: {
backgroundColor: '#007AFF',
paddingHorizontal: 32,
paddingVertical: 14,
borderRadius: 12,
},
flipButton: {
backgroundColor: '#333',
paddingHorizontal: 24,
paddingVertical: 14,
borderRadius: 12,
},
leaveButton: {
backgroundColor: '#FF3B30',
paddingHorizontal: 32,
paddingVertical: 14,
borderRadius: 12,
},
blurButton: {
backgroundColor: '#5856D6',
paddingHorizontal: 24,
paddingVertical: 14,
borderRadius: 12,
},
blurButtonActive: {
backgroundColor: '#34C759',
},
buttonDisabled: {
backgroundColor: '#444',
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: '600',
},
});
```
### Screenshots
<!-- Add screenshots here, if applicable -->
### Related issues
<!-- Link related issues here using #issue-number -->
### Checklist
- [ ] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have updated the documentation accordingly
- [ ] My changes generate no new warnings
### Additional notes
<!-- Include any additional information, assumptions, or context that
reviewers might need to understand this PR. -->1 parent 7992a10 commit 05b274e
33 files changed
Lines changed: 2901 additions & 0 deletions
File tree
- docs/docs/05-utilities
- packages/react-native-executorch-webrtc
- android
- gradle/wrapper
- src/main
- cpp
- java/com/executorch/webrtc
- gl
- ios
- src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
198 | 198 | | |
199 | 199 | | |
200 | 200 | | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
Lines changed: 78 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
Lines changed: 82 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
Binary file not shown.
Lines changed: 7 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
0 commit comments