react-native-webrtc-kaleidoscope 0.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/README.md +122 -20
  2. package/android/build.gradle +67 -0
  3. package/android/src/main/AndroidManifest.xml +2 -0
  4. package/android/src/main/assets/backgrounds/office-1.png +0 -0
  5. package/android/src/main/assets/backgrounds/office-2.png +0 -0
  6. package/android/src/main/java/com/simiancraft/kaleidoscope/EffectTuning.kt +57 -0
  7. package/android/src/main/java/com/simiancraft/kaleidoscope/KaleidoscopeModule.kt +43 -0
  8. package/android/src/main/java/com/simiancraft/kaleidoscope/Registration.kt +39 -0
  9. package/android/src/main/java/com/simiancraft/kaleidoscope/effects/BackgroundImageFactory.kt +335 -0
  10. package/android/src/main/java/com/simiancraft/kaleidoscope/effects/BlurFactory.kt +336 -0
  11. package/android/src/main/java/com/simiancraft/kaleidoscope/effects/MirrorFactory.kt +57 -0
  12. package/android/src/main/java/com/simiancraft/kaleidoscope/gpu/Egl.kt +88 -0
  13. package/android/src/main/java/com/simiancraft/kaleidoscope/gpu/Fbo.kt +61 -0
  14. package/android/src/main/java/com/simiancraft/kaleidoscope/gpu/GlDebug.kt +41 -0
  15. package/android/src/main/java/com/simiancraft/kaleidoscope/gpu/GlProgram.kt +74 -0
  16. package/android/src/main/java/com/simiancraft/kaleidoscope/gpu/GpuEffectFactory.kt +14 -0
  17. package/android/src/main/java/com/simiancraft/kaleidoscope/gpu/GpuEffectProcessor.kt +198 -0
  18. package/android/src/main/java/com/simiancraft/kaleidoscope/gpu/Shaders.kt +91 -0
  19. package/android/src/main/java/com/simiancraft/kaleidoscope/gpu/ShadersGenerated.kt +64 -0
  20. package/android/src/main/java/com/simiancraft/kaleidoscope/segmentation/Mask.kt +298 -0
  21. package/android/src/main/java/com/simiancraft/kaleidoscope/segmentation/MaskTuning.kt +34 -0
  22. package/app.plugin.d.ts +4 -0
  23. package/app.plugin.js +255 -0
  24. package/dist/backgrounds/index.d.ts +3 -0
  25. package/dist/backgrounds/index.d.ts.map +1 -0
  26. package/dist/backgrounds/index.js +6 -0
  27. package/dist/backgrounds/index.js.map +1 -0
  28. package/dist/backgrounds/office-1.d.ts +3 -0
  29. package/dist/backgrounds/office-1.d.ts.map +1 -0
  30. package/dist/backgrounds/office-1.js +5 -0
  31. package/dist/backgrounds/office-1.js.map +1 -0
  32. package/dist/backgrounds/office-1.web.d.ts +3 -0
  33. package/dist/backgrounds/office-1.web.d.ts.map +1 -0
  34. package/dist/backgrounds/office-1.web.js +8 -0
  35. package/dist/backgrounds/office-1.web.js.map +1 -0
  36. package/dist/backgrounds/office-1.webp +0 -0
  37. package/dist/backgrounds/office-2.d.ts +3 -0
  38. package/dist/backgrounds/office-2.d.ts.map +1 -0
  39. package/dist/backgrounds/office-2.js +5 -0
  40. package/dist/backgrounds/office-2.js.map +1 -0
  41. package/dist/backgrounds/office-2.web.d.ts +3 -0
  42. package/dist/backgrounds/office-2.web.d.ts.map +1 -0
  43. package/dist/backgrounds/office-2.web.js +8 -0
  44. package/dist/backgrounds/office-2.web.js.map +1 -0
  45. package/dist/backgrounds/office-2.webp +0 -0
  46. package/dist/backgrounds/preset-source.types.d.ts +2 -0
  47. package/dist/backgrounds/preset-source.types.d.ts.map +1 -0
  48. package/dist/backgrounds/preset-source.types.js +2 -0
  49. package/dist/backgrounds/preset-source.types.js.map +1 -0
  50. package/dist/backgrounds/presets.d.ts +3 -0
  51. package/dist/backgrounds/presets.d.ts.map +1 -0
  52. package/dist/backgrounds/presets.js +21 -0
  53. package/dist/backgrounds/presets.js.map +1 -0
  54. package/dist/index.d.ts +28 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +128 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/index.web.d.ts +37 -0
  59. package/dist/index.web.d.ts.map +1 -0
  60. package/dist/index.web.js +97 -0
  61. package/dist/index.web.js.map +1 -0
  62. package/dist/livekit.d.ts +24 -0
  63. package/dist/livekit.d.ts.map +1 -0
  64. package/dist/livekit.js +57 -0
  65. package/dist/livekit.js.map +1 -0
  66. package/dist/types.d.ts +67 -0
  67. package/dist/types.d.ts.map +1 -0
  68. package/dist/types.js +13 -0
  69. package/dist/types.js.map +1 -0
  70. package/dist/web/effects/background-image.d.ts +3 -0
  71. package/dist/web/effects/background-image.d.ts.map +1 -0
  72. package/dist/web/effects/background-image.js +217 -0
  73. package/dist/web/effects/background-image.js.map +1 -0
  74. package/dist/web/effects/blur.d.ts +3 -0
  75. package/dist/web/effects/blur.d.ts.map +1 -0
  76. package/dist/web/effects/blur.js +241 -0
  77. package/dist/web/effects/blur.js.map +1 -0
  78. package/dist/web/effects/mirror.d.ts +3 -0
  79. package/dist/web/effects/mirror.d.ts.map +1 -0
  80. package/dist/web/effects/mirror.js +31 -0
  81. package/dist/web/effects/mirror.js.map +1 -0
  82. package/dist/web/effects/passthrough.d.ts +3 -0
  83. package/dist/web/effects/passthrough.d.ts.map +1 -0
  84. package/dist/web/effects/passthrough.js +15 -0
  85. package/dist/web/effects/passthrough.js.map +1 -0
  86. package/dist/web/insertable-streams.d.ts +36 -0
  87. package/dist/web/insertable-streams.d.ts.map +1 -0
  88. package/dist/web/insertable-streams.js +64 -0
  89. package/dist/web/insertable-streams.js.map +1 -0
  90. package/dist/web/segmenter.d.ts +20 -0
  91. package/dist/web/segmenter.d.ts.map +1 -0
  92. package/dist/web/segmenter.js +51 -0
  93. package/dist/web/segmenter.js.map +1 -0
  94. package/dist/web/shaders.d.ts +2 -0
  95. package/dist/web/shaders.d.ts.map +1 -0
  96. package/dist/web/shaders.generated.d.ts +4 -0
  97. package/dist/web/shaders.generated.d.ts.map +1 -0
  98. package/dist/web/shaders.generated.js +54 -0
  99. package/dist/web/shaders.generated.js.map +1 -0
  100. package/dist/web/shaders.js +32 -0
  101. package/dist/web/shaders.js.map +1 -0
  102. package/dist/web/tuning.d.ts +22 -0
  103. package/dist/web/tuning.d.ts.map +1 -0
  104. package/dist/web/tuning.js +43 -0
  105. package/dist/web/tuning.js.map +1 -0
  106. package/expo-module.config.json +9 -0
  107. package/ios/Kaleidoscope.podspec +100 -0
  108. package/ios/KaleidoscopeModule/EffectTuning.swift +73 -0
  109. package/ios/KaleidoscopeModule/KaleidoscopeModule.swift +32 -0
  110. package/ios/KaleidoscopeModule/Registration.swift +43 -0
  111. package/ios/KaleidoscopeModule/effects/BackgroundImageProcessor.swift +226 -0
  112. package/ios/KaleidoscopeModule/effects/BlurProcessor.swift +178 -0
  113. package/ios/KaleidoscopeModule/effects/FrameBridge.swift +40 -0
  114. package/ios/KaleidoscopeModule/effects/MirrorProcessor.swift +136 -0
  115. package/ios/KaleidoscopeModule/gpu/MetalRenderer.swift +423 -0
  116. package/ios/KaleidoscopeModule/gpu/ShaderLibrary.swift +100 -0
  117. package/ios/KaleidoscopeModule/gpu/TextureBridge.swift +120 -0
  118. package/ios/KaleidoscopeModule/resources/backgrounds/office-1.png +0 -0
  119. package/ios/KaleidoscopeModule/resources/backgrounds/office-2.png +0 -0
  120. package/ios/KaleidoscopeModule/segmentation/MaskTuning.swift +23 -0
  121. package/ios/KaleidoscopeModule/segmentation/Segmenter.swift +126 -0
  122. package/ios/KaleidoscopeModule/shaders/SHADERS.txt +5 -0
  123. package/ios/KaleidoscopeModule/shaders/blur.metalsrc +72 -0
  124. package/ios/KaleidoscopeModule/shaders/composite.metalsrc +22 -0
  125. package/ios/KaleidoscopeModule/shaders/nebula.metalsrc +72 -0
  126. package/ios/KaleidoscopeModule/shaders/passthrough.metalsrc +20 -0
  127. package/ios/KaleidoscopeModule/shaders/simianlights.metalsrc +72 -0
  128. package/package.json +83 -18
  129. package/src/backgrounds/README.md +78 -0
  130. package/src/backgrounds/assets.d.ts +7 -0
  131. package/src/backgrounds/index.ts +7 -0
  132. package/src/backgrounds/office-1.ts +6 -0
  133. package/src/backgrounds/office-1.web.ts +9 -0
  134. package/src/backgrounds/office-1.webp +0 -0
  135. package/src/backgrounds/office-2.ts +6 -0
  136. package/src/backgrounds/office-2.web.ts +9 -0
  137. package/src/backgrounds/office-2.webp +0 -0
  138. package/src/backgrounds/preset-source.types.ts +5 -0
  139. package/src/backgrounds/presets.ts +23 -0
  140. package/src/index.ts +177 -0
  141. package/src/index.web.ts +125 -0
  142. package/src/livekit.ts +65 -0
  143. package/src/types.ts +80 -0
  144. package/src/web/effects/background-image.ts +277 -0
  145. package/src/web/effects/blur.ts +291 -0
  146. package/src/web/effects/mirror.ts +37 -0
  147. package/src/web/effects/passthrough.ts +17 -0
  148. package/src/web/insertable-streams.ts +112 -0
  149. package/src/web/segmenter.ts +73 -0
  150. package/src/web/shaders.generated.ts +56 -0
  151. package/src/web/shaders.ts +31 -0
  152. package/src/web/tuning.ts +53 -0
package/README.md CHANGED
@@ -1,12 +1,39 @@
1
+ <p align="center">
2
+ <img src="./docs/kaleidoscope-logo.png" alt="react-native-webrtc-kaleidoscope logo" width="180" />
3
+ </p>
4
+
1
5
  # react-native-webrtc-kaleidoscope
2
6
 
3
- > Live video effects for `react-native-webrtc`, packaged as a managed-Expo-friendly Expo Module. v0.1 ships **mirror** and **blur** on iOS, Android, and web.
7
+ [![status: alpha](https://img.shields.io/badge/status-alpha-orange)](#status)
8
+ [![npm version](https://img.shields.io/npm/v/react-native-webrtc-kaleidoscope?color=cb3837&logo=npm)](https://www.npmjs.com/package/react-native-webrtc-kaleidoscope)
9
+ [![Types: included](https://img.shields.io/npm/types/react-native-webrtc-kaleidoscope?color=3178c6&logo=typescript)](https://www.npmjs.com/package/react-native-webrtc-kaleidoscope)
10
+ [![CI](https://github.com/simiancraft/react-native-webrtc-kaleidoscope/actions/workflows/ci.yml/badge.svg)](https://github.com/simiancraft/react-native-webrtc-kaleidoscope/actions/workflows/ci.yml)
11
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
12
+
13
+ > Live video effects for `react-native-webrtc`, packaged as a managed-Expo-friendly Expo Module.
4
14
 
5
15
  ## Status
6
16
 
7
- Pre-1.0. Active development. The bootstrap-and-ship plan in [`bootstrap-and-ship-v0-1.md`](./bootstrap-and-ship-v0-1.md) (root, while it exists) is the source of truth for v0.1.
17
+ **Active development; not yet production-ready.** Published to npm at `1.0.0` (semantic-release cut the first tag at 1.0.0; the number reflects release automation, not a maturity claim). The npm presentation, marketing, and release-quality polish will come in a later pass; right now the README's job is to tell the truth about what works.
18
+
19
+ ### What works today
20
+
21
+ - **Mirror** (horizontal flip).
22
+ - **Blur** (background blur, person stays sharp).
23
+ - **Background replacement** (composite a still PNG behind the segmented person; two bundled office presets, library-side asset pipeline).
24
+ - **Runtime tuning** of the GLSL effects; see the [Use](#use) section.
25
+
26
+ | Platform | Mirror | Blur | Background replacement | Notes |
27
+ |---|---|---|---|---|
28
+ | Web (Chrome / Edge) | ✓ | ✓ | ✓ | MediaStreamTrackProcessor + MediaPipe Selfie Segmentation (WASM, CDN) |
29
+ | Android (API 24+) | ✓ | ✓ | ✓ | OpenGL ES 3.0 + MLKit Selfie Segmentation |
30
+ | iOS (≥ 15) | ✓ | ✓ | ✓ | Metal + Vision (person segmentation), verified on device. Older A11 devices (iPhone X) run at a lower frame rate |
31
+ | Safari / Firefox | — | — | — | No Insertable Streams; `applyVideoEffects` throws a typed error |
8
32
 
9
- The first usable release will be `v0.1.0`, shipping `mirror` and `blur` on all three platforms. Until then, the package is published as `0.1.0-alpha.x` for name reservation and integration testing.
33
+ ### Coming soon
34
+
35
+ - **Procedural backgrounds** (animated shaders behind the person, not just still images). Same composite path; the only new piece is each effect's background producer.
36
+ - A careful pass over the npm presentation, install docs, and demo polish before any "we recommend you use this" framing.
10
37
 
11
38
  ## Install
12
39
 
@@ -16,6 +43,34 @@ bun add react-native-webrtc react-native-webrtc-kaleidoscope
16
43
 
17
44
  `react-native-webrtc` is a peer dependency. Install it explicitly.
18
45
 
46
+ ### Using LiveKit?
47
+
48
+ If your project uses `@livekit/react-native` it pulls in `@livekit/react-native-webrtc`, a fork of upstream `react-native-webrtc` that preserves the same `videoEffects` native classes and the `_setVideoEffects` JS API. Kaleidoscope works against either fork; the Android Gradle script picks whichever one your autolinking surfaced.
49
+
50
+ ```sh
51
+ bun add @livekit/react-native @livekit/react-native-webrtc react-native-webrtc-kaleidoscope
52
+ ```
53
+
54
+ Pick one fork. Installing both upstream `react-native-webrtc` and `@livekit/react-native-webrtc` in the same app will cause native class collisions; that's the consumer's problem to resolve.
55
+
56
+ **Native wiring.** `@livekit/react-native` hands you a `LocalVideoTrack`; apply effects to its underlying `MediaStreamTrack`:
57
+
58
+ ```ts
59
+ import { applyVideoEffects } from 'react-native-webrtc-kaleidoscope';
60
+
61
+ applyVideoEffects(localCameraTrack.mediaStreamTrack, ['blur']);
62
+ ```
63
+
64
+ **Web wiring.** On web, LiveKit owns the `RTCRtpSender`, so you cannot swap the track yourself; go through LiveKit's processor API instead. The opt-in `/livekit` subpath ships a ready-made processor (it needs `livekit-client`, which a LiveKit app already has):
65
+
66
+ ```ts
67
+ import { KaleidoscopeProcessor } from 'react-native-webrtc-kaleidoscope/livekit';
68
+
69
+ await localVideoTrack.setProcessor(new KaleidoscopeProcessor(['blur']), true);
70
+ ```
71
+
72
+ The second argument shows the processed stream in your local preview. The processor tears down its Insertable-Streams pipeline on camera flip (`restart`) and unpublish (`destroy`), so repeated flips do not leak generators.
73
+
19
74
  ## Configure
20
75
 
21
76
  Add the config plugin to `app.config.ts`:
@@ -23,11 +78,13 @@ Add the config plugin to `app.config.ts`:
23
78
  ```ts
24
79
  export default {
25
80
  expo: {
26
- plugins: ['react-native-webrtc', 'react-native-webrtc-kaleidoscope'],
81
+ plugins: ['react-native-webrtc-kaleidoscope'],
27
82
  },
28
83
  };
29
84
  ```
30
85
 
86
+ (`react-native-webrtc` 124.x does not ship a config plugin upstream; do not list it in `plugins`. If you are on a fork that adds one, add it explicitly.)
87
+
31
88
  Then rebuild native code:
32
89
 
33
90
  ```sh
@@ -38,32 +95,77 @@ bunx expo prebuild
38
95
 
39
96
  ```ts
40
97
  import { mediaDevices } from 'react-native-webrtc';
41
- import { applyVideoEffects } from 'react-native-webrtc-kaleidoscope';
98
+ import {
99
+ applyVideoEffects,
100
+ setBlurSigma,
101
+ setMaskHardness,
102
+ setMaskThreshold,
103
+ } from 'react-native-webrtc-kaleidoscope';
42
104
 
43
105
  const stream = await mediaDevices.getUserMedia({ video: true });
44
106
  const [track] = stream.getVideoTracks();
45
107
 
46
- applyVideoEffects(track, ['blur']); // background blur
47
- applyVideoEffects(track, ['mirror']); // horizontal flip
48
- applyVideoEffects(track, []); // clear all effects
108
+ applyVideoEffects(track, ['mirror']);
109
+ applyVideoEffects(track, ['blur']);
110
+ applyVideoEffects(track, [{ name: 'background-image', source: 'office-1' }]);
111
+ applyVideoEffects(track, []); // clear all effects
112
+
113
+ // Runtime tuning (effects pick up the new values on the next frame):
114
+ setBlurSigma(25); // Gaussian σ; clamped to [0.5, 64], default 8.
115
+ setMaskHardness(0.2); // smoothstep transition width; clamped to [0, 1]. 0 = soft halo, 1 = near-step. Default 0.5.
116
+ setMaskThreshold(0.7); // smoothstep center; clamped to [0.05, 0.95]. Higher rejects low-confidence pixels. Default 0.5.
49
117
  ```
50
118
 
51
119
  Effects chain in array order.
52
120
 
53
- ## Platform support
121
+ **Tuning note:** optimal values are platform-specific because each segmentation model (MediaPipe on web, MLKit on Android, Vision when iOS lands) produces a different confidence distribution. Working defaults on a typical well-lit scene:
54
122
 
55
- | Platform | Mirror | Blur | Backend |
123
+ | Platform | Blur sigma | Mask hardness | Mask threshold |
56
124
  |---|---|---|---|
57
- | iOS 15 | | | Apple Vision + Core Image |
58
- | Android API 24 | | | MLKit Selfie Segmentation + RenderScript / RenderEffect |
59
- | Chrome / Edge | ✓ | ✓ | MediaStreamTrackProcessor + MediaPipe Selfie Segmentation (WASM) |
60
- | Safari | | | No Insertable Streams; throws a typed error |
61
- | Firefox | ✓ | ⚠ | Same; capability-check before calling |
125
+ | Web (MediaPipe) | 25 | 0.2 | 0.85 |
126
+ | Android (MLKit) | 30 | 0.2 | 0.6 |
127
+
128
+ The library ships neutral defaults (8, 0.5, 0.5) and consumers tune at runtime via the API above; whether to ship the dialed-in values as platform-specific defaults is an open question waiting on iOS data.
129
+
130
+ ## Web and native differences
131
+
132
+ The API surface is the same across platforms, but the runtimes differ in ways worth knowing before you wire effects in:
133
+
134
+ - **Effect parameters.** Web reads tuning from the global setters (`setBlurSigma`, `setMaskHardness`, `setMaskThreshold`) on the next frame. Native currently ignores per-call `EffectSpec` parameters such as `{ name: 'blur', sigma: 12 }`; tuning is global through the same setters. Per-call uniforms through the native registry are a follow-up.
135
+ - **Background source.** `background-image.source` is a bundled preset name on native (the upstream `_setVideoEffects` registry is keyed by flat strings, not URIs), but on web it accepts either a preset name or an arbitrary image URL or data URI.
136
+ - **Background presets ship as tree-shakeable files.** Two example backgrounds (`office-1`, `office-2`) are importable per preset: `import { office1 } from 'react-native-webrtc-kaleidoscope/backgrounds/office-1'`. Each preset is its own file behind its own subpath export, and the package sets `sideEffects: false`, so an unused preset is dropped by web bundlers — and, since Metro doesn't tree-shake, simply never imported on native. Web resolves the bundled WebP to a URL; native loads its own bundled copy by name. Web also still accepts an arbitrary image URL or data URI. See [`src/backgrounds/README.md`](./src/backgrounds/README.md).
137
+ - **Segmentation model on web.** The web blur and background-image effects load MediaPipe Selfie Segmentation from the jsdelivr CDN (`cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation`) on first use. A strict Content-Security-Policy must allow that origin for `script-src`, `connect-src`, and the WASM fetch, and the effects do not work offline. Mirror needs no model.
138
+ - **Browser support on web.** Effects use Insertable Streams (`MediaStreamTrackProcessor` and `MediaStreamTrackGenerator`), which ship in Chromium-based browsers; Safari and Firefox throw a typed capability error.
139
+
140
+ ## What this isn't
141
+
142
+ - **Not a fork of `react-native-webrtc`.** A thin layer over its undocumented `_setVideoEffects` registry on native, and `MediaStreamTrackProcessor` on web. Install alongside `react-native-webrtc`.
143
+ - **Not a managed cloud SaaS.** Effects run locally on the device; the track stays peer-to-peer. No service, no API key, no per-minute billing.
144
+ - **Not a face-filter SDK.** Effects are background segmentation and frame transforms, not facial AR.
145
+ - **Not a streaming protocol replacement.** The transformed track plugs into the consumer's existing `RTCPeerConnection` pipeline.
146
+
147
+ ## Architecture
148
+
149
+ The codebase lives across four surfaces:
150
+
151
+ - `src/` — JS facade and shared types. `applyVideoEffects(track, effects)` plus runtime tuning setters.
152
+ - `src/web/` — WebGL2 pipeline. MediaPipe segmentation + GLSL composite. One shader file per stage in `src/web/shaders.ts`.
153
+ - `android/` — OpenGL ES 3.0 pipeline. MLKit segmentation (async, worker-thread, last-known-mask cache) + GLSL composite. Shaders inline in `gpu/Shaders.kt` as `const val` strings.
154
+ - `ios/` — Metal pipeline (Swift) with Vision person segmentation. The canonical GLSL in `shaders/` transpiles to Metal Shading Language via `scripts/build-shaders.ts`. Implemented and verified on device.
155
+
156
+ The composite shader (`shaders/composite.frag`) is the same GLSL source for every effect category (blur, background-image, future procedural backgrounds). Per-effect difference is upstream of the composite: how the `uBackground` texture gets produced.
157
+
158
+ See [`PATTERNS.md`](./PATTERNS.md) for the file-layout conventions, texture-orientation contract, and recipe for adding new effects, shaders, presets, or tunable parameters.
62
159
 
63
160
  ## Reference
64
161
 
65
- - [CONTRIBUTING.md](./CONTRIBUTING.md) setup, scripts, commit conventions
66
- - [AGENTS.md](./AGENTS.md) agent / contributor orientation
67
- - [SECURITY.md](./SECURITY.md) security policy and reporting
68
- - [NOTICE.md](./NOTICE.md) third-party attributions
69
- - Sibling projects: [chromonym](https://github.com/simiancraft/chromonym), [unitforge](https://github.com/simiancraft/unitforge) — same OSS-hygiene template
162
+ - [CONTRIBUTING.md](./CONTRIBUTING.md): setup, scripts, commit conventions.
163
+ - [AGENTS.md](./AGENTS.md): agent and contributor orientation.
164
+ - [PATTERNS.md](./PATTERNS.md): codebase conventions and how-to-extend.
165
+ - [SECURITY.md](./SECURITY.md): security policy and reporting.
166
+ - [NOTICE.md](./NOTICE.md): third-party attributions.
167
+ - Sibling projects: [chromonym](https://github.com/simiancraft/chromonym) and [unitforge](https://github.com/simiancraft/unitforge); same OSS-hygiene template.
168
+
169
+ ---
170
+
171
+ MIT licensed. © 2026 Jesse Harlin / [Simiancraft](https://github.com/simiancraft).
@@ -0,0 +1,67 @@
1
+ // Android build script for react-native-webrtc-kaleidoscope.
2
+
3
+ apply plugin: 'com.android.library'
4
+ apply plugin: 'kotlin-android'
5
+
6
+ group = 'com.simiancraft.kaleidoscope'
7
+ // Single source of truth for the version is package.json (the iOS podspec reads
8
+ // it the same way), so the three platforms cannot drift apart.
9
+ version = new groovy.json.JsonSlurper().parseText(file("../package.json").text).version
10
+
11
+ def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
12
+ if (expoModulesCorePlugin.exists()) {
13
+ apply from: expoModulesCorePlugin
14
+ applyKotlinExpoModulesCorePlugin()
15
+ }
16
+
17
+ android {
18
+ namespace 'com.simiancraft.kaleidoscope'
19
+ compileSdkVersion safeExtGet('compileSdkVersion', 34)
20
+
21
+ defaultConfig {
22
+ minSdkVersion safeExtGet('minSdkVersion', 24)
23
+ targetSdkVersion safeExtGet('targetSdkVersion', 34)
24
+ }
25
+
26
+ compileOptions {
27
+ sourceCompatibility JavaVersion.VERSION_17
28
+ targetCompatibility JavaVersion.VERSION_17
29
+ }
30
+
31
+ kotlinOptions {
32
+ jvmTarget = '17'
33
+ }
34
+ }
35
+
36
+ dependencies {
37
+ implementation project(':expo-modules-core')
38
+
39
+ // react-native-webrtc is a peer dependency: link against its types
40
+ // (ProcessorProvider, VideoFrameProcessor, VideoFrameProcessorFactoryInterface,
41
+ // and the org.webrtc.* surface it bundles) at compile time only; the consumer
42
+ // app provides the runtime via autolinking.
43
+ //
44
+ // Two forks ship the same classes under `com.oney.WebRTCModule.videoEffects.*`:
45
+ // - react-native-webrtc/react-native-webrtc (upstream)
46
+ // - livekit/react-native-webrtc (LiveKit fork, required by @livekit/react-native)
47
+ // Pick whichever the consumer's autolinking surfaced; throw if neither is present.
48
+ def rnWebrtcCandidates = [':react-native-webrtc', ':livekit_react-native-webrtc']
49
+ def rnWebrtcProject = rnWebrtcCandidates.find { rootProject.findProject(it) != null }
50
+ if (rnWebrtcProject == null) {
51
+ throw new GradleException(
52
+ 'react-native-webrtc-kaleidoscope: install either react-native-webrtc ' +
53
+ 'or @livekit/react-native-webrtc as a peer dependency.'
54
+ )
55
+ }
56
+ compileOnly project(rnWebrtcProject)
57
+
58
+ // Person/background mask for the blur effect. The Google artifact ID is
59
+ // `segmentation-selfie`, not `selfie-segmentation` (the plan and earlier
60
+ // notes had it inverted; that is why the pin would not resolve).
61
+ implementation 'com.google.mlkit:segmentation-selfie:16.0.0-beta6'
62
+ }
63
+
64
+ // Helper: gracefully read root project ext properties with a default.
65
+ def safeExtGet(prop, fallback) {
66
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
67
+ }
@@ -0,0 +1,2 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" />
@@ -0,0 +1,57 @@
1
+ // Mutable runtime parameters for the GLSL effects on Android. The Expo
2
+ // Module's setBlurSigma / setMaskHardness JS functions update these values;
3
+ // per-frame processors read them on every frame so changes take effect
4
+ // without re-registering processors.
5
+ //
6
+ // This is the parameter-passing side-channel that lets us tune effect
7
+ // uniforms without touching the upstream react-native-webrtc registry,
8
+ // which only accepts flat-string effect names. The web side mirrors this
9
+ // shape in src/web/tuning.ts; iOS mirrors it in
10
+ // ios/KaleidoscopeModule/EffectTuning.swift.
11
+
12
+ package com.simiancraft.kaleidoscope
13
+
14
+ internal object EffectTuning {
15
+ /**
16
+ * Gaussian sigma for the blur effect; higher = softer blur. Default
17
+ * matches the v0.1 hardcoded value. Custom setter clamps to a sane
18
+ * range; setting the property is the public mutation API.
19
+ */
20
+ @Volatile
21
+ var blurSigma: Float = 8f
22
+ set(value) {
23
+ field = value.coerceIn(0.5f, 64f)
24
+ }
25
+
26
+ /**
27
+ * Mask smoothstep hardness for blur and background-image composites,
28
+ * in [0, 1]; 0 = soft halo, 1 = near-step. Default reproduces the
29
+ * historical smoothstep(0.34, 0.66) edge.
30
+ */
31
+ @Volatile
32
+ var maskHardness: Float = 0.5f
33
+ set(value) {
34
+ field = value.coerceIn(0f, 1f)
35
+ }
36
+
37
+ /**
38
+ * Mask smoothstep center for blur and background-image composites,
39
+ * in [0, 1]; the threshold at which a pixel's raw confidence flips from
40
+ * "background" to "person". Default 0.5 reproduces the historical
41
+ * smoothstep centered on the confidence midpoint. Higher values reject
42
+ * low-confidence edges (chair backs, hair flyaway); lower values are
43
+ * more inclusive. Clamped to a workable range below to keep the
44
+ * smoothstep transition non-degenerate.
45
+ */
46
+ @Volatile
47
+ var maskThreshold: Float = 0.5f
48
+ set(value) {
49
+ field = value.coerceIn(0.05f, 0.95f)
50
+ }
51
+
52
+ fun reset() {
53
+ blurSigma = 8f
54
+ maskHardness = 0.5f
55
+ maskThreshold = 0.5f
56
+ }
57
+ }
@@ -0,0 +1,43 @@
1
+ // Expo Module entry point for react-native-webrtc-kaleidoscope on Android.
2
+ // Calls Registration.registerAll(context) at module init so frame-processor
3
+ // factories land in ProcessorProvider before any track requests them.
4
+ //
5
+ // Also exposes setBlurSigma / setMaskHardness / resetEffectTuning JS
6
+ // functions that mutate com.simiancraft.kaleidoscope.EffectTuning at
7
+ // runtime; the per-frame processors read those values each frame, so
8
+ // changes take effect without re-registering or rebuilding processors.
9
+ // This side-channels the upstream react-native-webrtc registry, which only
10
+ // accepts flat-string effect names; spec parameters flow through here.
11
+
12
+ package com.simiancraft.kaleidoscope
13
+
14
+ import expo.modules.kotlin.modules.Module
15
+ import expo.modules.kotlin.modules.ModuleDefinition
16
+
17
+ class KaleidoscopeModule : Module() {
18
+ override fun definition() = ModuleDefinition {
19
+ Name("RnWebrtcKaleidoscope")
20
+
21
+ OnCreate {
22
+ val ctx = appContext.reactContext
23
+ ?: error("Kaleidoscope: no react context at OnCreate; cannot register Android effects")
24
+ Registration.registerAll(ctx)
25
+ }
26
+
27
+ Function("setBlurSigma") { value: Float ->
28
+ EffectTuning.blurSigma = value
29
+ }
30
+
31
+ Function("setMaskHardness") { value: Float ->
32
+ EffectTuning.maskHardness = value
33
+ }
34
+
35
+ Function("setMaskThreshold") { value: Float ->
36
+ EffectTuning.maskThreshold = value
37
+ }
38
+
39
+ Function("resetEffectTuning") {
40
+ EffectTuning.reset()
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,39 @@
1
+ // Frame-processor registration for Android. Called from
2
+ // KaleidoscopeModule.OnCreate at Expo Module init time, before any track
3
+ // requests an effect by name. The Context is needed by GPU effects so they
4
+ // can read PNG assets for background-image and create RenderScript-style
5
+ // resources where applicable.
6
+
7
+ package com.simiancraft.kaleidoscope
8
+
9
+ import android.content.Context
10
+ import com.oney.WebRTCModule.videoEffects.ProcessorProvider
11
+ import com.simiancraft.kaleidoscope.effects.BackgroundImageFactory
12
+ import com.simiancraft.kaleidoscope.effects.BlurFactory
13
+ import com.simiancraft.kaleidoscope.effects.MirrorFactory
14
+ import com.simiancraft.kaleidoscope.gpu.GpuEffectFactory
15
+
16
+ object Registration {
17
+ @JvmStatic
18
+ fun registerAll(context: Context) {
19
+ ProcessorProvider.addProcessor("mirror", MirrorFactory())
20
+ ProcessorProvider.addProcessor("blur", BlurFactory(context))
21
+
22
+ // Background-image variants — one factory per source preset. JS side
23
+ // emits "background-image-{source}" so each preset gets its own
24
+ // ProcessorProvider entry. Parameterized dispatch via uniforms lands
25
+ // when we extend the upstream rn-webrtc API surface.
26
+ ProcessorProvider.addProcessor(
27
+ "background-image-office-1",
28
+ BackgroundImageFactory(context, "office-1"),
29
+ )
30
+ ProcessorProvider.addProcessor(
31
+ "background-image-office-2",
32
+ BackgroundImageFactory(context, "office-2"),
33
+ )
34
+
35
+ // Temporary architecture-proof passthrough hook from the GPU bring-up.
36
+ // Removed in the cleanup pass before v0.1 ships.
37
+ ProcessorProvider.addProcessor("gpu-passthrough", GpuEffectFactory())
38
+ }
39
+ }