react-native-webrtc-kaleidoscope 2.1.1 → 2.2.1

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 (166) hide show
  1. package/README.md +8 -0
  2. package/dist/catalog/composites/clouds/clouds.controls.d.ts.map +1 -1
  3. package/dist/catalog/composites/clouds/clouds.d.ts.map +1 -1
  4. package/dist/catalog/composites/corporate-blobs/corporate-blobs.controls.d.ts.map +1 -1
  5. package/dist/catalog/composites/corporate-blobs/corporate-blobs.d.ts.map +1 -1
  6. package/dist/catalog/composites/corporate-blobs/corporate-blobs.web.d.ts.map +1 -1
  7. package/dist/catalog/composites/fairy-cave/fairy-cave.controls.d.ts.map +1 -1
  8. package/dist/catalog/composites/fairy-cave/fairy-cave.d.ts.map +1 -1
  9. package/dist/catalog/composites/fairy-cave/fairy-cave.web.d.ts.map +1 -1
  10. package/dist/catalog/composites/fairy-grotto/fairy-grotto.controls.d.ts.map +1 -1
  11. package/dist/catalog/composites/fairy-grotto/fairy-grotto.d.ts.map +1 -1
  12. package/dist/catalog/composites/fairy-grotto/fairy-grotto.web.d.ts.map +1 -1
  13. package/dist/catalog/composites/fairy-hollow/fairy-hollow.controls.d.ts.map +1 -1
  14. package/dist/catalog/composites/fairy-hollow/fairy-hollow.d.ts.map +1 -1
  15. package/dist/catalog/composites/fairy-hollow/fairy-hollow.web.d.ts.map +1 -1
  16. package/dist/catalog/composites/nebula/nebula.controls.d.ts.map +1 -1
  17. package/dist/catalog/composites/nebula/nebula.d.ts.map +1 -1
  18. package/dist/catalog/composites/nebula/nebula.web.d.ts.map +1 -1
  19. package/dist/catalog/composites/observation-deck/observation-deck.controls.d.ts.map +1 -1
  20. package/dist/catalog/composites/observation-deck/observation-deck.d.ts.map +1 -1
  21. package/dist/catalog/composites/observation-deck/observation-deck.web.d.ts.map +1 -1
  22. package/dist/catalog/composites/simianlights/simianlights.controls.d.ts.map +1 -1
  23. package/dist/catalog/composites/simianlights/simianlights.d.ts.map +1 -1
  24. package/dist/catalog/composites/simianlights/simianlights.web.d.ts.map +1 -1
  25. package/dist/catalog/composites/underwater/underwater.controls.d.ts.map +1 -1
  26. package/dist/catalog/composites/underwater/underwater.d.ts.map +1 -1
  27. package/dist/catalog/composites/underwater/underwater.web.d.ts.map +1 -1
  28. package/dist/catalog/composites/wizard-tower/wizard-tower.controls.d.ts.map +1 -1
  29. package/dist/catalog/composites/wizard-tower/wizard-tower.d.ts.map +1 -1
  30. package/dist/catalog/composites/wizard-tower/wizard-tower.web.d.ts.map +1 -1
  31. package/dist/catalog/composites/wizard-tower-night/wizard-tower-night.controls.d.ts.map +1 -1
  32. package/dist/catalog/composites/wizard-tower-night/wizard-tower-night.d.ts.map +1 -1
  33. package/dist/catalog/composites/wizard-tower-night/wizard-tower-night.web.d.ts.map +1 -1
  34. package/dist/catalog/images/corporate/corporate-logo.d.ts.map +1 -1
  35. package/dist/catalog/images/corporate/corporate-logo.web.d.ts.map +1 -1
  36. package/dist/catalog/images/debug/debug-resolutions.d.ts.map +1 -1
  37. package/dist/catalog/images/debug/debug-resolutions.web.d.ts.map +1 -1
  38. package/dist/catalog/images/fairy-caves/grotto.d.ts.map +1 -1
  39. package/dist/catalog/images/fairy-caves/grotto.web.d.ts.map +1 -1
  40. package/dist/catalog/images/fairy-caves/hollow.d.ts.map +1 -1
  41. package/dist/catalog/images/fairy-caves/hollow.web.d.ts.map +1 -1
  42. package/dist/catalog/images/fairy-caves/treehouse-2.d.ts.map +1 -1
  43. package/dist/catalog/images/fairy-caves/treehouse-2.web.d.ts.map +1 -1
  44. package/dist/catalog/images/fairy-caves/treehouse-3.d.ts.map +1 -1
  45. package/dist/catalog/images/fairy-caves/treehouse-3.web.d.ts.map +1 -1
  46. package/dist/catalog/images/fairy-caves/treehouse.d.ts.map +1 -1
  47. package/dist/catalog/images/fairy-caves/treehouse.web.d.ts.map +1 -1
  48. package/dist/catalog/images/home/home-dark.d.ts.map +1 -1
  49. package/dist/catalog/images/home/home-dark.web.d.ts.map +1 -1
  50. package/dist/catalog/images/home/home-light.d.ts.map +1 -1
  51. package/dist/catalog/images/home/home-light.web.d.ts.map +1 -1
  52. package/dist/catalog/images/image-ids.d.ts.map +1 -1
  53. package/dist/catalog/images/image.types.d.ts.map +1 -1
  54. package/dist/catalog/images/index.d.ts.map +1 -1
  55. package/dist/catalog/images/nature/landscape-dark.d.ts.map +1 -1
  56. package/dist/catalog/images/nature/landscape-dark.web.d.ts.map +1 -1
  57. package/dist/catalog/images/nature/landscape-light.d.ts.map +1 -1
  58. package/dist/catalog/images/nature/landscape-light.web.d.ts.map +1 -1
  59. package/dist/catalog/images/office/office-dark.d.ts.map +1 -1
  60. package/dist/catalog/images/office/office-dark.web.d.ts.map +1 -1
  61. package/dist/catalog/images/office/office-light.d.ts.map +1 -1
  62. package/dist/catalog/images/office/office-light.web.d.ts.map +1 -1
  63. package/dist/catalog/images/sci-fi/sci-fi-light.d.ts.map +1 -1
  64. package/dist/catalog/images/sci-fi/sci-fi-light.web.d.ts.map +1 -1
  65. package/dist/catalog/images/simiancraft/simiancraft-dark-transparency.d.ts.map +1 -1
  66. package/dist/catalog/images/simiancraft/simiancraft-dark-transparency.web.d.ts.map +1 -1
  67. package/dist/catalog/images/simiancraft/simiancraft-dark.d.ts.map +1 -1
  68. package/dist/catalog/images/simiancraft/simiancraft-dark.web.d.ts.map +1 -1
  69. package/dist/catalog/images/simiancraft/simiancraft-light-transparency.d.ts.map +1 -1
  70. package/dist/catalog/images/simiancraft/simiancraft-light-transparency.web.d.ts.map +1 -1
  71. package/dist/catalog/images/simiancraft/simiancraft-light.d.ts.map +1 -1
  72. package/dist/catalog/images/simiancraft/simiancraft-light.web.d.ts.map +1 -1
  73. package/dist/catalog/images/spaceship/observation-deck.d.ts.map +1 -1
  74. package/dist/catalog/images/spaceship/observation-deck.web.d.ts.map +1 -1
  75. package/dist/catalog/images/underwater/oceanscape-dark.d.ts.map +1 -1
  76. package/dist/catalog/images/underwater/oceanscape-dark.web.d.ts.map +1 -1
  77. package/dist/catalog/images/wizard-tower/wizard-tower-1.d.ts.map +1 -1
  78. package/dist/catalog/images/wizard-tower/wizard-tower-1.web.d.ts.map +1 -1
  79. package/dist/catalog/images/wizard-tower/wizard-tower-2.d.ts.map +1 -1
  80. package/dist/catalog/images/wizard-tower/wizard-tower-2.web.d.ts.map +1 -1
  81. package/dist/catalog/images/wizard-tower/wizard-tower-night.d.ts.map +1 -1
  82. package/dist/catalog/images/wizard-tower/wizard-tower-night.web.d.ts.map +1 -1
  83. package/dist/catalog/shaders/_shared/types.d.ts.map +1 -1
  84. package/dist/catalog/shaders/anamorphic-lensflare/anamorphic-lensflare.d.ts.map +1 -1
  85. package/dist/catalog/shaders/anamorphic-lensflare/anamorphic-lensflare.form.d.ts.map +1 -1
  86. package/dist/catalog/shaders/blur/blur.d.ts.map +1 -1
  87. package/dist/catalog/shaders/blur/blur.form.d.ts.map +1 -1
  88. package/dist/catalog/shaders/clouds/clouds.d.ts.map +1 -1
  89. package/dist/catalog/shaders/clouds/clouds.form.d.ts.map +1 -1
  90. package/dist/catalog/shaders/corporate-blobs/corporate-blobs.d.ts.map +1 -1
  91. package/dist/catalog/shaders/corporate-blobs/corporate-blobs.form.d.ts.map +1 -1
  92. package/dist/catalog/shaders/fireflies/fireflies.d.ts.map +1 -1
  93. package/dist/catalog/shaders/fireflies/fireflies.form.d.ts.map +1 -1
  94. package/dist/catalog/shaders/godrays/godrays.d.ts.map +1 -1
  95. package/dist/catalog/shaders/godrays/godrays.form.d.ts.map +1 -1
  96. package/dist/catalog/shaders/index.d.ts.map +1 -1
  97. package/dist/catalog/shaders/light-beams-and-motes/light-beams-and-motes.d.ts.map +1 -1
  98. package/dist/catalog/shaders/light-beams-and-motes/light-beams-and-motes.form.d.ts.map +1 -1
  99. package/dist/catalog/shaders/nebula/nebula.d.ts.map +1 -1
  100. package/dist/catalog/shaders/nebula/nebula.form.d.ts.map +1 -1
  101. package/dist/catalog/shaders/plasma/plasma.d.ts.map +1 -1
  102. package/dist/catalog/shaders/plasma/plasma.form.d.ts.map +1 -1
  103. package/dist/catalog/shaders/simianlights/simianlights.d.ts.map +1 -1
  104. package/dist/catalog/shaders/simianlights/simianlights.form.d.ts.map +1 -1
  105. package/dist/src/components/form/control-form.d.ts.map +1 -1
  106. package/dist/src/components/form/make-controls.d.ts.map +1 -1
  107. package/dist/src/components/form/scope.d.ts.map +1 -1
  108. package/dist/src/components/form/use-field.d.ts.map +1 -1
  109. package/dist/src/components/preset-book-menu/index.d.ts.map +1 -1
  110. package/dist/src/components/preset-book-menu/layout.d.ts.map +1 -1
  111. package/dist/src/components/preset-book-menu/preset-book-menu.types.d.ts.map +1 -1
  112. package/dist/src/components/preset-book-menu/preset-grid.d.ts.map +1 -1
  113. package/dist/src/components/preset-book-menu/resolve-image-uri.d.ts.map +1 -1
  114. package/dist/src/components/preset-book-menu/resolve-image-uri.types.d.ts.map +1 -1
  115. package/dist/src/components/preset-book-menu/resolve-image-uri.web.d.ts.map +1 -1
  116. package/dist/src/components/preset-control-panel/composite-layer-control-panel.d.ts.map +1 -1
  117. package/dist/src/components/preset-control-panel/control-section.d.ts.map +1 -1
  118. package/dist/src/components/preset-control-panel/control.d.ts.map +1 -1
  119. package/dist/src/components/preset-control-panel/index.d.ts.map +1 -1
  120. package/dist/src/components/preset-control-panel/mask-control-panel.d.ts.map +1 -1
  121. package/dist/src/components/preset-control-panel/preset-control-panel.d.ts.map +1 -1
  122. package/dist/src/components/preset-control-panel/transform-control-panel.d.ts.map +1 -1
  123. package/dist/src/components/preset-tile/index.d.ts.map +1 -1
  124. package/dist/src/components/theme/provider.d.ts.map +1 -1
  125. package/dist/src/components/theme/slots.d.ts.map +1 -1
  126. package/dist/src/components/ui/button.d.ts.map +1 -1
  127. package/dist/src/components/ui/color-picker.d.ts.map +1 -1
  128. package/dist/src/components/ui/index.d.ts.map +1 -1
  129. package/dist/src/components/ui/label.d.ts.map +1 -1
  130. package/dist/src/components/ui/point.d.ts.map +1 -1
  131. package/dist/src/components/ui/polygon-field.d.ts.map +1 -1
  132. package/dist/src/components/ui/readout.d.ts.map +1 -1
  133. package/dist/src/components/ui/slider-value.d.ts.map +1 -1
  134. package/dist/src/components/ui/slider.d.ts.map +1 -1
  135. package/dist/src/components/ui/switch.d.ts.map +1 -1
  136. package/dist/src/index.d.ts.map +1 -1
  137. package/dist/src/index.web.d.ts.map +1 -1
  138. package/dist/src/kaleidoscope/controls.d.ts.map +1 -1
  139. package/dist/src/kaleidoscope/effect.d.ts.map +1 -1
  140. package/dist/src/kaleidoscope/effect.types.d.ts.map +1 -1
  141. package/dist/src/kaleidoscope/shader-to-spec.d.ts.map +1 -1
  142. package/dist/src/kaleidoscope/types.d.ts.map +1 -1
  143. package/dist/src/kaleidoscope.preset-book.types.d.ts.map +1 -1
  144. package/dist/src/lib/primitives.types.d.ts.map +1 -1
  145. package/dist/src/lib/test-id.d.ts.map +1 -1
  146. package/dist/src/livekit.d.ts +2 -0
  147. package/dist/src/livekit.d.ts.map +1 -1
  148. package/dist/src/livekit.js +7 -1
  149. package/dist/src/livekit.js.map +1 -1
  150. package/dist/src/nativewind.d.ts.map +1 -1
  151. package/dist/web-driver/effects/composite.d.ts.map +1 -1
  152. package/dist/web-driver/effects/layer-shaders.d.ts.map +1 -1
  153. package/dist/web-driver/effects/transform.d.ts.map +1 -1
  154. package/dist/web-driver/index.d.ts.map +1 -1
  155. package/dist/web-driver/insertable-streams.d.ts.map +1 -1
  156. package/dist/web-driver/segmenter.d.ts.map +1 -1
  157. package/dist/web-driver/shaders.d.ts.map +1 -1
  158. package/dist/web-driver/shaders.generated.d.ts.map +1 -1
  159. package/dist/web-driver/tuning.d.ts +14 -0
  160. package/dist/web-driver/tuning.d.ts.map +1 -1
  161. package/dist/web-driver/tuning.js +24 -6
  162. package/dist/web-driver/tuning.js.map +1 -1
  163. package/llms.txt +576 -0
  164. package/package.json +8 -7
  165. package/plugin/build/lib/preset-book.js +48 -15
  166. package/src/livekit.ts +7 -0
package/llms.txt ADDED
@@ -0,0 +1,576 @@
1
+ # react-native-webrtc-kaleidoscope
2
+
3
+ > Live, on-device video effects for `react-native-webrtc`, packaged as a managed-Expo-friendly Expo Module. Works on web, Android, and iOS behind one JS facade. Every effect is a **composite**: a back-to-front stack of layers (a bundled image, the masked person, a camera blur, or a generative GLSL shader) rendered into the output frame. You drive it with three verbs: `kaleidoscope` (the background/art), `transform` (flip/rotate), and `mask` (the segmentation edge).
4
+
5
+ This file is written for an LLM integrating the library into a consumer app. Follow it top to bottom and you can install it, configure the native build, provision a working preset book, and get an effect on screen. The repo's `demo/` directory is the canonical working example; every file in the [Starting fileset](#starting-fileset-six-files-that-run) below is a trimmed copy of a demo file that runs on all three platforms.
6
+
7
+ ## What this package is
8
+
9
+ A thin layer over `react-native-webrtc`'s public-but-non-standard `track._setVideoEffects([...])` API. On native (iOS, Android) it registers one frame-processor factory, `composite`, at app boot via an Expo config plugin, and delivers the layer stack out of band. On web it runs a WebGL2 layered compositor through `MediaStreamTrackProcessor` + `MediaStreamTrackGenerator` (Insertable Streams) behind the same JS facade.
10
+
11
+ It is **not** a fork of `react-native-webrtc`, **not** a managed-cloud SaaS, and **not** a face-filter/AR SDK. It is a small module you install alongside `react-native-webrtc` to get background blur, background replacement, generative backgrounds, and geometric transforms on a local video track. Effects run on-device; the track stays peer-to-peer.
12
+
13
+ **Workflow:** this ships native frame processors, so **Expo Go is not supported**. You need a development build (`expo-dev-client` via EAS Build, or `npx expo run:android` / `run:ios` with local toolchains). Web needs no native build at all. Tested against Expo SDK 51 / React Native 0.74; the peer ranges are open-ended above that. Package version 2.x.
14
+
15
+ ## Install
16
+
17
+ ```sh
18
+ bun add react-native-webrtc react-native-webrtc-kaleidoscope
19
+ bunx expo install expo-build-properties expo-dev-client
20
+ # or npm install / yarn add / pnpm add
21
+ ```
22
+
23
+ Peer dependencies: `expo` (>=51), `expo-asset` (>=10, already a dependency of `expo`), `react` (>=18.2), `react-native` (>=0.74). `react-native-webrtc` (>=124) is an **optional** peer you supply (or `@livekit/react-native-webrtc` + `livekit-client` if you use the `/livekit` adapter; pick ONE fork, never both). `nativewind` (>=4) and `@react-native-community/slider` (>=4.5) are **optional** peers, only needed for the styled UI / live-controls extras.
24
+
25
+ ## Configure (copy-paste; every entry here is load-bearing)
26
+
27
+ In `app.config.ts` / `app.config.js` / `app.json`:
28
+
29
+ ```ts
30
+ export default {
31
+ expo: {
32
+ plugins: [
33
+ [
34
+ 'expo-build-properties',
35
+ {
36
+ // react-native-webrtc requires Android API 24+; at Expo's default
37
+ // minSdkVersion (23) the manifest merger rejects the WebRTC AAR and
38
+ // the Android build fails.
39
+ android: { minSdkVersion: 24 },
40
+ // The kaleidoscope plugin raises the Pods platform to 15.0, but the
41
+ // app target's IPHONEOS_DEPLOYMENT_TARGET is set by Expo prebuild
42
+ // independently; without this entry the iOS link step fails with
43
+ // "compiling for iOS 13.4, but module 'Kaleidoscope' has a minimum
44
+ // deployment target of iOS 15.0".
45
+ ios: { deploymentTarget: '15.0' },
46
+ },
47
+ ],
48
+ 'react-native-webrtc-kaleidoscope',
49
+ ],
50
+ ios: {
51
+ infoPlist: {
52
+ // Without this key iOS kills the app at the first getUserMedia call.
53
+ NSCameraUsageDescription: 'The camera is used for video calls; effects are applied on-device.',
54
+ // Required by react-native-webrtc even if you never record audio.
55
+ NSMicrophoneUsageDescription: 'Required by react-native-webrtc for call audio.',
56
+ },
57
+ },
58
+ android: {
59
+ permissions: ['CAMERA', 'RECORD_AUDIO', 'MODIFY_AUDIO_SETTINGS'],
60
+ },
61
+ },
62
+ };
63
+ ```
64
+
65
+ Do **not** add `'react-native-webrtc'` to `plugins`: upstream 124.x ships no config plugin and prebuild fails to resolve it. (If you are on a fork that adds one, add it explicitly, before the kaleidoscope plugin.)
66
+
67
+ The kaleidoscope plugin itself handles the rest at prebuild: it patches the iOS Podfile so `react-native-webrtc` builds with modular headers, raises the Pods deployment target, and copies the images your preset book references into the native bundle.
68
+
69
+ Then generate native projects and build a dev client:
70
+
71
+ ```sh
72
+ bunx expo prebuild
73
+ bunx eas-cli build -p android --profile development # or: bunx expo run:android
74
+ bunx eas-cli build -p ios --profile development # or: bunx expo run:ios
75
+ ```
76
+
77
+ Web (Chromium only) runs with no prebuild: `bunx expo start --web`.
78
+
79
+ ## The preset-book contract (read this before creating the file)
80
+
81
+ The plugin statically analyzes ONE file to decide which image assets ship in your native bundle. The contract:
82
+
83
+ 1. **Exact filename, exact location:** `kaleidoscope.preset-book.ts` at the **project root** (the directory containing `package.json` and your app config). A book named or placed differently still typechecks and still works on web, but at prebuild NO assets are copied and every image background silently renders nothing on native. This is the most common integration mistake; the failure is silent.
84
+ 2. **The book is parsed as text, never executed.** Keep every `image` layer's `source` statically analyzable: a single named import from `react-native-webrtc-kaleidoscope/images/<category>/<leaf>`, a `require('./local.webp')` literal, or a `const X = Asset.fromModule(require('./local.webp')).uri` binding (the binding may span lines; a formatter wrapping it at a narrow print width is fine). Do not compute sources, build layers in helper functions, or re-export the book through a barrel; the parser will not follow them, and each skipped `image` layer warns at prebuild naming the layer id.
85
+ 3. **An `image` layer's `id` doubles as its native plate id**: keep it equal to the image file's basename (`office-dark`, `my-bg`). Your own background images must be `.webp` (native resolves `assets/images/<id>.webp`).
86
+ 4. **Re-run `bunx expo prebuild` after editing the book** (adding/deleting presets changes the native asset set). JS-only uniform tweaks hot-reload without it.
87
+
88
+ ## Starting fileset (six files that run)
89
+
90
+ These are trimmed copies of the demo's working files (`demo/kaleidoscope.preset-book.ts`, `demo/src/`, `demo/app/index.tsx` in the repo). Create all six, run a dev build once, and you have the full catalog on screen behind a picker. The end user then opts out by DELETING preset entries (and their now-unused imports) from the book; nothing else references them.
91
+
92
+ ### 1. `kaleidoscope.preset-book.ts` (project root; exact name; see contract above)
93
+
94
+ ```ts
95
+ import type { KaleidoscopePresetBook } from 'react-native-webrtc-kaleidoscope';
96
+ // Packaged multi-layer scenes. Each carries its own taxonomy and thumbnail;
97
+ // delete the import + the entry to opt out of one.
98
+ import { clouds } from 'react-native-webrtc-kaleidoscope/composites/clouds';
99
+ import { corporateBlobs } from 'react-native-webrtc-kaleidoscope/composites/corporate-blobs';
100
+ import { fairyCave } from 'react-native-webrtc-kaleidoscope/composites/fairy-cave';
101
+ import { fairyGrotto } from 'react-native-webrtc-kaleidoscope/composites/fairy-grotto';
102
+ import { fairyHollow } from 'react-native-webrtc-kaleidoscope/composites/fairy-hollow';
103
+ import { nebula } from 'react-native-webrtc-kaleidoscope/composites/nebula';
104
+ import { observationDeck } from 'react-native-webrtc-kaleidoscope/composites/observation-deck';
105
+ import { simianlights } from 'react-native-webrtc-kaleidoscope/composites/simianlights';
106
+ import { underwater } from 'react-native-webrtc-kaleidoscope/composites/underwater';
107
+ import { wizardTower } from 'react-native-webrtc-kaleidoscope/composites/wizard-tower';
108
+ import { wizardTowerNight } from 'react-native-webrtc-kaleidoscope/composites/wizard-tower-night';
109
+ // Bundled background images, one subpath per image. An unused import is dead
110
+ // weight at most: web tree-shakes it, native never copies it.
111
+ import { homeDark } from 'react-native-webrtc-kaleidoscope/images/home/home-dark';
112
+ import { homeLight } from 'react-native-webrtc-kaleidoscope/images/home/home-light';
113
+ import { landscapeDark } from 'react-native-webrtc-kaleidoscope/images/nature/landscape-dark';
114
+ import { landscapeLight } from 'react-native-webrtc-kaleidoscope/images/nature/landscape-light';
115
+ import { officeDark } from 'react-native-webrtc-kaleidoscope/images/office/office-dark';
116
+ import { officeLight } from 'react-native-webrtc-kaleidoscope/images/office/office-light';
117
+ import { sciFiLight } from 'react-native-webrtc-kaleidoscope/images/sci-fi/sci-fi-light';
118
+ import { oceanscapeDark } from 'react-native-webrtc-kaleidoscope/images/underwater/oceanscape-dark';
119
+
120
+ // To add YOUR OWN background, use an analyzable require literal on a local WebP
121
+ // and give the image layer an id equal to the file basename:
122
+ // import { Asset } from 'expo-asset';
123
+ // const myBg = Asset.fromModule(require('./assets/my-bg.webp')).uri;
124
+ // ...then an entry shaped exactly like the image presets below.
125
+
126
+ export const presets = {
127
+ // --- Effects / Blur: the camera blurred behind you, you sharp on top. ---
128
+ 'blur-low': {
129
+ name: 'Low',
130
+ taxonomy: ['Effects', 'Blur'],
131
+ layers: [
132
+ { id: 'blur', shader: 'blur', target: 'background', uniforms: { sigma: 1.5 } },
133
+ { id: 'you', shader: 'direct', target: 'subject' },
134
+ ],
135
+ },
136
+ 'blur-medium': {
137
+ name: 'Medium',
138
+ taxonomy: ['Effects', 'Blur'],
139
+ layers: [
140
+ { id: 'blur', shader: 'blur', target: 'background', uniforms: { sigma: 3.75 } },
141
+ { id: 'you', shader: 'direct', target: 'subject' },
142
+ ],
143
+ },
144
+ 'blur-high': {
145
+ name: 'High',
146
+ taxonomy: ['Effects', 'Blur'],
147
+ layers: [
148
+ { id: 'blur', shader: 'blur', target: 'background', uniforms: { sigma: 8 } },
149
+ { id: 'you', shader: 'direct', target: 'subject' },
150
+ ],
151
+ },
152
+
153
+ // --- Worlds: packaged scenes, spread in as-is. One line each; delete freely. ---
154
+ 'wizard-tower': wizardTower,
155
+ 'wizard-tower-night': wizardTowerNight,
156
+ 'observation-deck': observationDeck,
157
+ 'fairy-cave': fairyCave,
158
+ 'fairy-grotto': fairyGrotto,
159
+ 'fairy-hollow': fairyHollow,
160
+ underwater: underwater,
161
+ 'corporate-blobs': corporateBlobs,
162
+ clouds: clouds,
163
+ nebula: nebula,
164
+ simianlights: simianlights,
165
+
166
+ // --- Backgrounds: one image (cover-fit), you composited over it. The image
167
+ // layer's id MUST stay the image basename. ---
168
+ 'office-dark': {
169
+ name: 'Office (Dark)',
170
+ taxonomy: ['Backgrounds', 'Office'],
171
+ thumbnail: officeDark,
172
+ layers: [
173
+ { id: 'office-dark', shader: 'image', source: officeDark },
174
+ { id: 'you', shader: 'direct', target: 'subject' },
175
+ ],
176
+ },
177
+ 'office-light': {
178
+ name: 'Office (Light)',
179
+ taxonomy: ['Backgrounds', 'Office'],
180
+ thumbnail: officeLight,
181
+ layers: [
182
+ { id: 'office-light', shader: 'image', source: officeLight },
183
+ { id: 'you', shader: 'direct', target: 'subject' },
184
+ ],
185
+ },
186
+ 'home-dark': {
187
+ name: 'Home (Dark)',
188
+ taxonomy: ['Backgrounds', 'Home'],
189
+ thumbnail: homeDark,
190
+ layers: [
191
+ { id: 'home-dark', shader: 'image', source: homeDark },
192
+ { id: 'you', shader: 'direct', target: 'subject' },
193
+ ],
194
+ },
195
+ 'home-light': {
196
+ name: 'Home (Light)',
197
+ taxonomy: ['Backgrounds', 'Home'],
198
+ thumbnail: homeLight,
199
+ layers: [
200
+ { id: 'home-light', shader: 'image', source: homeLight },
201
+ { id: 'you', shader: 'direct', target: 'subject' },
202
+ ],
203
+ },
204
+ 'landscape-dark': {
205
+ name: 'Landscape (Dark)',
206
+ taxonomy: ['Backgrounds', 'Nature'],
207
+ thumbnail: landscapeDark,
208
+ layers: [
209
+ { id: 'landscape-dark', shader: 'image', source: landscapeDark },
210
+ { id: 'you', shader: 'direct', target: 'subject' },
211
+ ],
212
+ },
213
+ 'landscape-light': {
214
+ name: 'Landscape (Light)',
215
+ taxonomy: ['Backgrounds', 'Nature'],
216
+ thumbnail: landscapeLight,
217
+ layers: [
218
+ { id: 'landscape-light', shader: 'image', source: landscapeLight },
219
+ { id: 'you', shader: 'direct', target: 'subject' },
220
+ ],
221
+ },
222
+ 'sci-fi-light': {
223
+ name: 'Sci-Fi',
224
+ taxonomy: ['Backgrounds', 'Sci-Fi'],
225
+ thumbnail: sciFiLight,
226
+ layers: [
227
+ { id: 'sci-fi-light', shader: 'image', source: sciFiLight },
228
+ { id: 'you', shader: 'direct', target: 'subject' },
229
+ ],
230
+ },
231
+ 'oceanscape-dark': {
232
+ name: 'Oceanscape',
233
+ taxonomy: ['Backgrounds', 'Underwater'],
234
+ thumbnail: oceanscapeDark,
235
+ layers: [
236
+ { id: 'oceanscape-dark', shader: 'image', source: oceanscapeDark },
237
+ { id: 'you', shader: 'direct', target: 'subject' },
238
+ ],
239
+ },
240
+
241
+ // --- Shaders: a generative GLSL field behind you; the inline-authoring
242
+ // pattern for any shader in the catalog. ---
243
+ 'plasma-ocean': {
244
+ name: 'Ocean',
245
+ taxonomy: ['Shaders', 'Plasma'],
246
+ layers: [
247
+ {
248
+ id: 'plasma',
249
+ shader: 'plasma',
250
+ uniforms: { uColorA: [0.0, 0.3, 0.6], uColorB: [0.0, 0.6, 0.6], uSpeed: 0.3, uScale: 8 },
251
+ },
252
+ { id: 'you', shader: 'direct', target: 'subject' },
253
+ ],
254
+ },
255
+ 'plasma-sunset': {
256
+ name: 'Sunset',
257
+ taxonomy: ['Shaders', 'Plasma'],
258
+ layers: [
259
+ {
260
+ id: 'plasma',
261
+ shader: 'plasma',
262
+ uniforms: { uColorA: [0.9, 0.3, 0.1], uColorB: [0.8, 0.1, 0.5], uSpeed: 0.3, uScale: 8 },
263
+ },
264
+ { id: 'you', shader: 'direct', target: 'subject' },
265
+ ],
266
+ },
267
+ } as const satisfies KaleidoscopePresetBook;
268
+
269
+ export type PresetId = keyof typeof presets;
270
+ ```
271
+
272
+ ### 2 + 3. `src/use-loopback-stream.ts` and `src/use-loopback-stream.web.ts`
273
+
274
+ Platform split by file extension (Metro picks `.web.ts` for web); the pair shares one shape. Native:
275
+
276
+ ```ts
277
+ // src/use-loopback-stream.ts
278
+ import { useEffect, useState } from 'react';
279
+ import { type MediaStream, mediaDevices } from 'react-native-webrtc';
280
+
281
+ type StreamState =
282
+ | { status: 'idle' }
283
+ | { status: 'pending' }
284
+ | { status: 'ready'; stream: MediaStream }
285
+ | { status: 'error'; error: Error };
286
+
287
+ export const useLoopbackStream = (): StreamState => {
288
+ const [state, setState] = useState<StreamState>({ status: 'idle' });
289
+ useEffect(() => {
290
+ let cancelled = false;
291
+ let acquired: MediaStream | null = null;
292
+ setState({ status: 'pending' });
293
+ mediaDevices
294
+ .getUserMedia({ video: { facingMode: 'user' }, audio: false })
295
+ .then((raw) => {
296
+ const stream = raw as unknown as MediaStream;
297
+ if (cancelled) {
298
+ for (const t of stream.getTracks()) t.stop();
299
+ return;
300
+ }
301
+ acquired = stream;
302
+ setState({ status: 'ready', stream });
303
+ })
304
+ .catch((err: unknown) => {
305
+ if (cancelled) return;
306
+ setState({ status: 'error', error: err instanceof Error ? err : new Error(String(err)) });
307
+ });
308
+ return () => {
309
+ cancelled = true;
310
+ if (acquired) for (const t of acquired.getTracks()) t.stop();
311
+ };
312
+ }, []);
313
+ return state;
314
+ };
315
+ ```
316
+
317
+ Web is identical except the import line goes away and the call is `navigator.mediaDevices.getUserMedia({ video: true, audio: false })` on the browser's own `MediaStream` type (no cast needed).
318
+
319
+ ### 4 + 5. `src/video-preview.tsx` and `src/video-preview.web.tsx`
320
+
321
+ ```tsx
322
+ // src/video-preview.tsx (native): RTCView over a wrapping MediaStream.
323
+ import { useMemo } from 'react';
324
+ import { StyleSheet, View } from 'react-native';
325
+ import {
326
+ MediaStream,
327
+ type MediaStreamTrack as RNWebRTCMediaStreamTrack,
328
+ RTCView,
329
+ } from 'react-native-webrtc';
330
+
331
+ export const VideoPreview = ({ track }: { track: MediaStreamTrack | null }) => {
332
+ const streamUrl = useMemo(() => {
333
+ if (!track) return null;
334
+ return new MediaStream([track as unknown as RNWebRTCMediaStreamTrack]).toURL();
335
+ }, [track]);
336
+ if (!streamUrl) return <View style={styles.box} />;
337
+ return <RTCView streamURL={streamUrl} objectFit="contain" style={styles.box} />;
338
+ };
339
+
340
+ const styles = StyleSheet.create({
341
+ box: { aspectRatio: 4 / 3, backgroundColor: '#1a1a1a', borderRadius: 8 },
342
+ });
343
+ ```
344
+
345
+ ```tsx
346
+ // src/video-preview.web.tsx: an HTML <video> with srcObject.
347
+ import { useEffect, useMemo, useRef } from 'react';
348
+ import { StyleSheet, View } from 'react-native';
349
+
350
+ export const VideoPreview = ({ track }: { track: MediaStreamTrack | null }) => {
351
+ const videoRef = useRef<HTMLVideoElement | null>(null);
352
+ const previewStream = useMemo(() => (track ? new MediaStream([track]) : null), [track]);
353
+ useEffect(() => {
354
+ if (videoRef.current) videoRef.current.srcObject = previewStream;
355
+ }, [previewStream]);
356
+ return (
357
+ <View style={styles.box}>
358
+ <video
359
+ ref={videoRef}
360
+ autoPlay
361
+ muted
362
+ playsInline
363
+ style={{ width: '100%', height: '100%', objectFit: 'cover', borderRadius: 8 }}
364
+ />
365
+ </View>
366
+ );
367
+ };
368
+
369
+ const styles = StyleSheet.create({
370
+ box: { aspectRatio: 4 / 3, backgroundColor: '#1a1a1a', borderRadius: 8, overflow: 'hidden' },
371
+ });
372
+ ```
373
+
374
+ ### 6. The screen (e.g. `app/index.tsx` under expo-router, or any screen component)
375
+
376
+ ```tsx
377
+ import { useEffect, useMemo, useState } from 'react';
378
+ import { SafeAreaView, StyleSheet } from 'react-native';
379
+ import { bindKaleidoscope, type KaleidoscopeBinding } from 'react-native-webrtc-kaleidoscope';
380
+ import { PresetBookMenu } from 'react-native-webrtc-kaleidoscope/preset-book-menu';
381
+ import { type PresetId, presets } from '../kaleidoscope.preset-book';
382
+ import { useLoopbackStream } from '../src/use-loopback-stream';
383
+ import { VideoPreview } from '../src/video-preview';
384
+
385
+ export default function KaleidoscopeScreen() {
386
+ const stream = useLoopbackStream();
387
+ const [controls, setControls] = useState<KaleidoscopeBinding<typeof presets> | null>(null);
388
+ const [displayTrack, setDisplayTrack] = useState<MediaStreamTrack | null>(null);
389
+ const [art, setArt] = useState<PresetId | null>(null);
390
+
391
+ const sourceTrack = useMemo<MediaStreamTrack | null>(() => {
392
+ if (stream.status !== 'ready') return null;
393
+ return (stream.stream.getVideoTracks()[0] ?? null) as unknown as MediaStreamTrack | null;
394
+ }, [stream]);
395
+
396
+ // Bind once per source track; dispose on teardown. onTrack matters on web,
397
+ // where each command yields a NEW track; native mutates the track in place.
398
+ useEffect(() => {
399
+ if (!sourceTrack) {
400
+ setControls(null);
401
+ return;
402
+ }
403
+ const c = bindKaleidoscope(sourceTrack, { presets, onTrack: setDisplayTrack });
404
+ setControls(c);
405
+ return () => {
406
+ c.dispose();
407
+ setDisplayTrack(null);
408
+ };
409
+ }, [sourceTrack]);
410
+
411
+ // Re-issue the art command on selection change. Split the null case so each
412
+ // call matches a kaleidoscope() overload.
413
+ useEffect(() => {
414
+ if (!controls) return;
415
+ if (art) controls.kaleidoscope(art);
416
+ else controls.kaleidoscope(null);
417
+ }, [controls, art]);
418
+
419
+ return (
420
+ <SafeAreaView style={styles.root}>
421
+ <VideoPreview track={displayTrack ?? sourceTrack} />
422
+ <PresetBookMenu presets={presets} value={art} onSelect={setArt} />
423
+ </SafeAreaView>
424
+ );
425
+ }
426
+
427
+ const styles = StyleSheet.create({
428
+ root: { flex: 1, padding: 12, gap: 12 },
429
+ });
430
+ ```
431
+
432
+ That is the whole starting state: camera in, composite out, every bundled preset selectable. In a real call you'd hand `displayTrack ?? sourceTrack` to your `RTCPeerConnection` / LiveKit publish path instead of (or in addition to) the local preview.
433
+
434
+ ## Deleting presets (how an end user opts out)
435
+
436
+ A preset is one entry in `kaleidoscope.preset-book.ts` plus its imports; nothing else references it.
437
+
438
+ 1. Delete the entry from the `presets` object.
439
+ 2. Delete the now-unused imports (your linter/TS will flag them).
440
+ 3. Re-run `bunx expo prebuild` so the native bundle drops the entry's assets. On web the bundler tree-shakes them on the next build automatically.
441
+
442
+ The picker, the verbs, and the types all derive from the book, so nothing else needs updating; group tabs and category menus disappear automatically when their last preset goes.
443
+
444
+ ## Use (bind a track, drive three verbs)
445
+
446
+ ```ts
447
+ import { mediaDevices } from 'react-native-webrtc';
448
+ import { bindKaleidoscope } from 'react-native-webrtc-kaleidoscope';
449
+ import { presets } from './kaleidoscope.preset-book';
450
+
451
+ const stream = await mediaDevices.getUserMedia({ video: true });
452
+ const [track] = stream.getVideoTracks();
453
+
454
+ const { kaleidoscope, transform, mask, dispose } = bindKaleidoscope(track, {
455
+ presets,
456
+ // Web rebuilds the pipeline per command and yields a NEW track; read it here
457
+ // (attach to <video> or RTCRtpSender.replaceTrack). Native mutates in place.
458
+ onTrack: (out) => {
459
+ /* setPreviewTrack(out) */
460
+ },
461
+ });
462
+
463
+ // kaleidoscope = the art/background axis. Pass a preset id (autocompletes from your book):
464
+ kaleidoscope('wizard-tower');
465
+ // Override a layer's uniforms live (addressed by layer id) while that preset is active,
466
+ // merged with no pipeline rebuild. `shader` types the uniforms:
467
+ kaleidoscope('blur-high', [{ id: 'blur', shader: 'blur', uniforms: { sigma: 5 } }]);
468
+ kaleidoscope(null); // clear the art
469
+
470
+ // transform = absolute geometry. Every call is the full state from identity; re-pass
471
+ // what you want to keep. rotate snaps to the nearest 90 degrees.
472
+ transform({ flip: { x: true }, rotate: 90 });
473
+ transform(); // reset to identity
474
+
475
+ // mask = the segmentation edge shared by every art effect. Both 0..1.
476
+ mask({ hardness: 0.5, threshold: 0.5 }); // defaults 0.5 / 0.5; nudge per camera/lighting
477
+ ```
478
+
479
+ That is the whole runtime surface: `kaleidoscope`, `transform`, `mask`, and `dispose` (tear the binding down when the track goes away). The key exported types: `KaleidoscopePresetBook` (the book), `KaleidoscopePreset` (one entry), `KaleidoscopeBinding<P>` (what `bindKaleidoscope` returns), `PatchesFor<P>` (live uniform patches).
480
+
481
+ ## Preset-book vocabulary
482
+
483
+ Each book entry is a `KaleidoscopePreset`:
484
+
485
+ - `name`: human label shown in the picker.
486
+ - `taxonomy`: the picker's grouping path, root first: `[group]` or `[group, category]` (e.g. `['Backgrounds', 'Office']`). Group is the tab; category is the second-level menu. (This field is named `taxonomy`, NOT `category`.)
487
+ - `thumbnail?`: optional preview source for the picker tile.
488
+ - `controls?`: optional editor component for the live-controls panel (see below).
489
+ - `layers`: the painter's stack, rendered back to front. A layer is `{ id, shader, target?, blend? }` plus the shader's own fields:
490
+ - `shader: 'image'` takes a `source` (a bundled image id on native; a URL/data-URI also allowed on web).
491
+ - `shader: 'direct'` samples the ingest-normalized (upright, non-mirrored) camera frame. `target: 'subject'` = the masked person; `target: 'background'` = the full frame.
492
+ - `shader: 'blur'` (camera-sampling) and the generative shaders (`plasma`, `clouds`, `nebula`, `godrays`, `fireflies`, `simianlights`, `anamorphic-lensflare`, `light-beams-and-motes`, `corporate-blobs`) take `uniforms`.
493
+ - `target` defaults to `'background'` (fullscreen); `'subject'` stencils to the segmented person. `blend` is opaque base / `'normal'` / `'additive'`.
494
+
495
+ Each layer's `id` is unique within its composite. Declare the book `as const satisfies KaleidoscopePresetBook` for per-layer type-checking and autocomplete.
496
+
497
+ ## Drop-in UI (optional): the picker
498
+
499
+ A headless, preset-driven picker that reads your book directly. Import from the `/preset-book-menu` subpath (shown wired in the starting screen above).
500
+
501
+ `PresetBookMenu` is a two-level browser: a tab row across the top, one tab per **group** (`taxonomy[0]`, e.g. Effects, Worlds, Backgrounds, Shaders), and a left-hand menu of **categories** (`taxonomy[1]`) under the active group; the tile grid is filtered by both. A flat (depth-1) group shows no category menu. Selection is controlled (`value` + `onSelect(id)`, narrowed to your book's keys); the component is presentational, it emits the chosen id and you apply it via `kaleidoscope`. Standalone primitives (`PresetGrid`, `PresetTile`, the `usePresetBookMenu` hook, `PresetBookMenuLayout`, a `renderTile` slot) are exported for BYO layouts. Style three ways: defaults, an RN `style` prop, or a `className` prop.
502
+
503
+ **NativeWind (optional).** Components accept `className`. Turn it on by importing the opt-in registration once in your interop setup (`nativewind` stays out of the core import):
504
+
505
+ ```ts
506
+ import { registerKaleidoscopeNativeWind } from 'react-native-webrtc-kaleidoscope/nativewind';
507
+ registerKaleidoscopeNativeWind();
508
+ ```
509
+
510
+ ## Live controls (optional): the editor
511
+
512
+ `/preset-control-panel` ships a headless editor that reads the active preset and renders one control per tunable uniform, plus mask and transform panels. Controlled and presentational: it emits patches, you apply them.
513
+
514
+ ```tsx
515
+ import {
516
+ KaleidoscopeThemeProvider,
517
+ PresetControlPanel,
518
+ MaskControlPanel,
519
+ TransformControlPanel,
520
+ } from 'react-native-webrtc-kaleidoscope/preset-control-panel';
521
+
522
+ <KaleidoscopeThemeProvider>
523
+ <PresetControlPanel presets={presets} value={art} onPatch={(p) => kaleidoscope(art, [p])} />
524
+ <MaskControlPanel hardness={h} threshold={t} onChange={setMask} />
525
+ <TransformControlPanel flip={flip} rotate={rotate} onChange={setTransform} />
526
+ </KaleidoscopeThemeProvider>
527
+ ```
528
+
529
+ Each preset supplies its editor as a `controls` component on the book entry: packaged composites export theirs at `react-native-webrtc-kaleidoscope/composites/<name>/controls`, the catalog shaders at `react-native-webrtc-kaleidoscope/shaders/<name>/form` (e.g. `BlurForm`, `PlasmaForm`); attach them like `'blur-high': { ..., controls: BlurForm }`. For custom widgets, compose `CompositeLayerControlPanel` over a shader's control descriptor (or `makeControls`). The sliders need `@react-native-community/slider` (optional peer; a native module, so installing it needs a dev-client rebuild; the starting fileset above deliberately omits `controls` so no slider is required). Live per-layer tuning runs on web today; on native the editor renders but the live per-layer uniform channel is in progress. Mask and transform are live on every platform.
530
+
531
+ ## Bundled assets you can reference
532
+
533
+ - **Packaged composites** (multi-layer scenes), each behind `react-native-webrtc-kaleidoscope/composites/<name>`: `wizard-tower`, `wizard-tower-night`, `observation-deck`, `fairy-cave`, `fairy-grotto`, `fairy-hollow`, `underwater`, `nebula`, `simianlights`, `corporate-blobs`, `clouds`. Import the export and spread it into your book.
534
+ - **Background images**, each behind `react-native-webrtc-kaleidoscope/images/<category>/<leaf>`: `office/office-dark`, `office/office-light`, `home/home-dark`, `home/home-light`, `nature/landscape-dark`, `nature/landscape-light`, `sci-fi/sci-fi-light`, `underwater/oceanscape-dark`, `simiancraft/simiancraft-light`, `simiancraft/simiancraft-dark`, `simiancraft/simiancraft-light-transparency`, `simiancraft/simiancraft-dark-transparency`, `corporate/corporate-logo`, `fairy-caves/grotto`, `fairy-caves/hollow`, `fairy-caves/treehouse`, `fairy-caves/treehouse-2`, `fairy-caves/treehouse-3`, `spaceship/observation-deck`, `wizard-tower/wizard-tower-1`, `wizard-tower/wizard-tower-2`, `wizard-tower/wizard-tower-night`, plus `debug/debug-resolutions` (a viewport calibration grid for verifying cover-fit). On web an image `source` can also be any image URL or data URI; native resolves bundled image ids only.
535
+
536
+ ## Web vs native differences
537
+
538
+ - **Output track.** Web rebuilds the pipeline per `kaleidoscope`/`transform` command and yields a NEW `MediaStreamTrack` (read it via `onTrack`); native mutates the bound track in place. `mask` never rebuilds.
539
+ - **Image source.** Native accepts a bundled image id only (the upstream `_setVideoEffects` registry is keyed by flat strings); web accepts an image id, a URL, or a data URI.
540
+ - **Web model fetch.** The web compositor loads MediaPipe Selfie Segmentation from `cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation` on first use; a strict CSP must allow that origin (`script-src`/`connect-src`/WASM), and effects do not work offline. `transform` needs no model.
541
+ - **Web browser support.** Insertable Streams ship in Chromium (Chrome, Edge); Safari and Firefox lack the API and throw a clear capability error, so feature-detect before binding.
542
+ - **Tree-shaking.** Images are per-file subpath exports and the package is `sideEffects: false`, so web bundlers drop unused ones; Metro (native) simply never imports them, and the prebuild plugin only copies what the preset book references.
543
+
544
+ ## LiveKit
545
+
546
+ `@livekit/react-native` pulls in `@livekit/react-native-webrtc`, a fork that preserves the same `videoEffects` native classes; kaleidoscope works against either fork (pick one, never both). On native, bind to the local track's underlying `MediaStreamTrack`:
547
+
548
+ ```ts
549
+ const { kaleidoscope } = bindKaleidoscope(localCameraTrack.mediaStreamTrack, { presets });
550
+ ```
551
+
552
+ On web, LiveKit owns the `RTCRtpSender`, so go through its processor API via the opt-in `/livekit` subpath (needs `livekit-client`):
553
+
554
+ ```ts
555
+ import { KaleidoscopeProcessor, setMaskTuning } from 'react-native-webrtc-kaleidoscope/livekit';
556
+ await localVideoTrack.setProcessor(new KaleidoscopeProcessor(['blur']), true);
557
+ setMaskTuning({ hardness: 0.2, threshold: 0.85 }); // the mask verb's twin on the processor path; page-shared, applies next frame, no rebuild
558
+ ```
559
+
560
+ ## Platform support
561
+
562
+ - **iOS** native >= 15.0; Metal pipeline; MediaPipe Tasks segmentation.
563
+ - **Android** native >= API 24; OpenGL ES 3.0 pipeline; MediaPipe Tasks segmentation.
564
+ - **Web** Chromium browsers with `MediaStreamTrackProcessor`; WebGL2 compositor; MediaPipe Selfie Segmentation (WASM).
565
+
566
+ ## The demo is the canonical working example
567
+
568
+ The repo's `demo/` is a complete consuming app exercising everything above on all three platforms: `demo/app.config.js` (the config-plugin + build-properties + permissions wiring, with comments explaining each entry), `demo/kaleidoscope.preset-book.ts` (a full book including a consumer-owned `wolf-cave.webp` background and per-preset `controls`), `demo/src/use-loopback-stream{,.web}.ts` and `demo/src/video-preview{,.web}.tsx` (the platform-split wiring), and `demo/app/index.tsx` (picker + editor + verbs on one screen). When this file and the demo disagree, trust the demo and file an issue: https://github.com/simiancraft/react-native-webrtc-kaleidoscope
569
+
570
+ ## Reference
571
+
572
+ - README.md: the same integration story with previews of the bundled composites and images.
573
+ - PATTERNS.md: file-layout conventions, the texture-orientation contract, and recipes for adding effects/shaders/presets/tunable parameters.
574
+ - catalog/shaders/README.md: authoring and extending shaders and their control descriptors.
575
+ - catalog/images/README.md: the image folder layout and how to add one.
576
+ - Sibling projects [chromonym](https://github.com/simiancraft/chromonym) and [unitforge](https://github.com/simiancraft/unitforge) share this project's OSS-hygiene template.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-webrtc-kaleidoscope",
3
- "version": "2.1.1",
3
+ "version": "2.2.1",
4
4
  "description": "Live video effects (blur, background replacement, generative backgrounds, flip/rotate) for react-native-webrtc, packaged as a managed-Expo-friendly Expo Module. Working on web, Android, and iOS. Active development.",
5
5
  "keywords": [
6
6
  "react-native",
@@ -502,7 +502,8 @@
502
502
  "expo-module.config.json",
503
503
  "README.md",
504
504
  "LICENSE",
505
- "NOTICE.md"
505
+ "NOTICE.md",
506
+ "llms.txt"
506
507
  ],
507
508
  "scripts": {
508
509
  "clean": "rm -rf dist",
@@ -576,23 +577,23 @@
576
577
  },
577
578
  "devDependencies": {
578
579
  "@biomejs/biome": "^2.4.13",
579
- "@expo/config-plugins": "~8.0.8",
580
- "@react-native-community/slider": "4.5.2",
580
+ "@expo/config-plugins": "~56.0.8",
581
+ "@react-native-community/slider": "5.2.0",
581
582
  "@semantic-release/changelog": "^6.0.3",
582
583
  "@semantic-release/git": "^10.0.1",
583
584
  "@semantic-release/npm": "^13.1.5",
584
585
  "@types/bun": "^1.3.12",
585
586
  "@types/react": "~18.2.79",
586
587
  "@typescript-eslint/parser": "^8.60.1",
587
- "@typescript/native-preview": "7.0.0-dev.20260508.1",
588
+ "@typescript/native-preview": "7.0.0-dev.20260609.1",
588
589
  "eslint": "^10.4.1",
589
590
  "eslint-plugin-react-compiler": "^19.1.0-rc.2",
590
591
  "expo": "^51.0.0",
591
- "expo-modules-core": "^1.12.0",
592
+ "expo-modules-core": "^56.0.15",
592
593
  "knip": "^6.5.0",
593
594
  "lefthook": "^2.1.9",
594
595
  "livekit-client": "^2.0.0",
595
- "nativewind": "4.1.x",
596
+ "nativewind": "4.2.x",
596
597
  "playwright": "^1.60.0",
597
598
  "publint": "^0.3.18",
598
599
  "react": "18.2.0",