@reactvision/react-viro 2.55.0 → 2.57.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 (94) hide show
  1. package/README.md +27 -0
  2. package/android/react_viro/react_viro-release.aar +0 -0
  3. package/android/viro_renderer/viro_renderer-release.aar +0 -0
  4. package/components/AR/ViroARScene.tsx +17 -0
  5. package/components/AR/ViroARSceneNavigator.tsx +33 -0
  6. package/components/Utilities/StreamingAudioManager.ts +73 -0
  7. package/components/Utilities/ViroVersion.ts +1 -1
  8. package/components/ViroCameraTexture.tsx +230 -0
  9. package/components/ViroGameLoop.tsx +74 -0
  10. package/components/ViroGameLoopUtils.ts +52 -0
  11. package/components/ViroObjectDetector.tsx +222 -0
  12. package/components/ViroVirtualButton.tsx +101 -0
  13. package/components/ViroVirtualJoystick.tsx +126 -0
  14. package/components/hooks/useGameLoop.ts +78 -0
  15. package/dist/components/AR/ViroARScene.d.ts +12 -0
  16. package/dist/components/AR/ViroARScene.js +8 -1
  17. package/dist/components/AR/ViroARSceneNavigator.d.ts +30 -0
  18. package/dist/components/Utilities/StreamingAudioManager.d.ts +22 -0
  19. package/dist/components/Utilities/StreamingAudioManager.js +45 -0
  20. package/dist/components/Utilities/ViroVersion.d.ts +1 -1
  21. package/dist/components/Utilities/ViroVersion.js +1 -1
  22. package/dist/components/ViroCameraTexture.d.ts +137 -0
  23. package/dist/components/ViroCameraTexture.js +170 -0
  24. package/dist/components/ViroGameLoop.d.ts +31 -0
  25. package/dist/components/ViroGameLoop.js +31 -0
  26. package/dist/components/ViroGameLoopUtils.d.ts +26 -0
  27. package/dist/components/ViroGameLoopUtils.js +44 -0
  28. package/dist/components/ViroObjectDetector.d.ts +138 -0
  29. package/dist/components/ViroObjectDetector.js +85 -0
  30. package/dist/components/ViroVirtualButton.d.ts +76 -0
  31. package/dist/components/ViroVirtualButton.js +62 -0
  32. package/dist/components/ViroVirtualJoystick.d.ts +85 -0
  33. package/dist/components/ViroVirtualJoystick.js +72 -0
  34. package/dist/components/hooks/useGameLoop.d.ts +34 -0
  35. package/dist/components/hooks/useGameLoop.js +63 -0
  36. package/dist/index.d.ts +11 -1
  37. package/dist/index.js +20 -2
  38. package/dist/plugins/withViro.js +2 -15
  39. package/dist/plugins/withViroAndroid.js +52 -15
  40. package/dist/plugins/withViroIos.js +0 -6
  41. package/index.ts +21 -0
  42. package/ios/ViroReact.podspec +25 -6
  43. package/ios/dist/ViroRenderer/ViroKit.framework/ARCoreCoreMLSemanticsResources.bundle/Info.plist +0 -0
  44. package/ios/dist/ViroRenderer/ViroKit.framework/ARCoreResources.bundle/Info.plist +0 -0
  45. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARDepthMesh.h +9 -1
  46. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARFrame.h +33 -0
  47. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARFrameiOS.h +26 -0
  48. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARScene.h +15 -1
  49. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARSceneDelegateiOS.h +8 -0
  50. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARSession.h +7 -0
  51. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARSessioniOS.h +18 -0
  52. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARWorldMesh.h +111 -8
  53. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROAudioPlayer.h +19 -0
  54. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROAudioPlayerStreamiOS.h +95 -0
  55. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROCameraTextureiOS.h +17 -1
  56. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VRODriver.h +20 -0
  57. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VRODynamicGeometry.h +170 -0
  58. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VRODynamicMeshNode.h +87 -0
  59. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROGLTFLoader.h +5 -1
  60. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROGameLoopListener.h +125 -0
  61. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROInputState.h +133 -0
  62. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROMonocularDepthEstimator.h +73 -0
  63. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROObjectDetector.h +134 -0
  64. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROPCMRingBuffer.h +143 -0
  65. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROPencil.h +9 -0
  66. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VRORenderContext.h +4 -0
  67. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VRORenderer.h +1 -0
  68. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROVertexBuffer.h +102 -0
  69. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROViewAR.h +9 -0
  70. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROVirtualControllerRegistry.h +106 -0
  71. package/ios/dist/ViroRenderer/ViroKit.framework/Info.plist +0 -0
  72. package/ios/dist/ViroRenderer/ViroKit.framework/Shaders.dat +1 -1
  73. package/ios/dist/ViroRenderer/ViroKit.framework/ViroKit +0 -0
  74. package/ios/dist/ViroRenderer/ViroKit.framework/constant_fsh.glsl +1 -0
  75. package/ios/dist/ViroRenderer/ViroKit.framework/constant_fsh_adreno.glsl +1 -0
  76. package/ios/dist/ViroRenderer/ViroKit.framework/standard_fsh.glsl +1 -0
  77. package/ios/dist/ViroRenderer/ViroKit.framework/standard_vsh.glsl +4 -0
  78. package/ios/dist/ViroRenderer/ViroKit.framework/standard_vsh_adreno.glsl +4 -0
  79. package/ios/dist/ViroRendererVisionOS/ViroKit.podspec +27 -0
  80. package/ios/dist/include/VRTARScene.h +1 -0
  81. package/ios/dist/include/VRTARSceneNavigator.h +3 -0
  82. package/ios/dist/include/VRTCameraTexture.h +62 -0
  83. package/ios/dist/include/VRTCameraTextureManager.h +30 -0
  84. package/ios/dist/include/VRTCameraTextureModule.h +38 -0
  85. package/ios/dist/include/VRTGameLoopView.h +19 -0
  86. package/ios/dist/include/VRTGameLoopViewManager.h +8 -0
  87. package/ios/dist/include/VRTManagedAnimation.h +67 -0
  88. package/ios/dist/include/VRTStreamingAudioModule.h +21 -0
  89. package/ios/dist/include/VRTVirtualButtonView.h +47 -0
  90. package/ios/dist/include/VRTVirtualButtonViewManager.h +21 -0
  91. package/ios/dist/include/VRTVirtualJoystickView.h +59 -0
  92. package/ios/dist/include/VRTVirtualJoystickViewManager.h +26 -0
  93. package/ios/dist/lib/libViroReact.a +0 -0
  94. package/package.json +17 -20
package/README.md CHANGED
@@ -45,6 +45,14 @@ For Expo projects, the easiest way to start is to clone the official starter kit
45
45
 
46
46
  For step-by-step setup instructions, including platform-specific permissions and build configuration, see the full installation guide in the docs: <https://viro-community.readme.io/docs/installation-instructions>
47
47
 
48
+ ## MCP Server
49
+
50
+ If you're building AR, VR or XR apps with ViroReact, our MCP server is the fastest way to get moving. Think of it as a living guide to ViroReact, always current, that plugs straight into the tools you already use.
51
+
52
+ Connect it to Claude, Codex, Cursor or any other coding agent, and your agent instantly understands how to work with ViroReact's cross-platform rendering. No hunting through docs, no guesswork. (Our documentation is still a great companion, but the MCP is the most up to date source there is.)
53
+
54
+ [MCP Setup Instructions](https://viro-community.readme.io/docs/viroreact-mcp-server)
55
+
48
56
  ## Features
49
57
 
50
58
  ViroReact ships with a complete spatial computing toolkit out of the box — no third-party plugins, no paid add-ons.
@@ -84,6 +92,25 @@ You can get a Studio account, which includes Platform access, for free at <https
84
92
  - Cloud Anchors guide: <https://viro-community.readme.io/docs/cloud-anchors>
85
93
  - Geospatial Anchors guide: <https://viro-community.readme.io/docs/geospatial-anchors>
86
94
 
95
+ ## Studio
96
+
97
+ [ReactVision Studio](https://studio.reactvision.xyz) is our browser-based visual scene editor for AR and VR. Build scenes visually in Studio, then embed them inside your own ViroReact app with a single component. Designers and developers can iterate on a scene in Studio and have it update in production without a new app build.
98
+
99
+ **Build scenes visually**
100
+
101
+ - **Placement** — drag-and-drop 3D objects, images, video, text, and primitives into a scene with visual gizmos for position, rotation, and scale. No JSX required to lay out a scene.
102
+ - **AI asset generation** — generate 3D models from a text prompt or reference image directly inside the editor, powered by ReactVision Platform.
103
+ - **Animations** — author keyframe and property animations on a timeline, chain them into sequences, and trigger them from interactions or scene events.
104
+ - **Physics** — assign dynamic, static, or kinematic bodies to objects, configure mass, friction, and restitution, and wire up collision callbacks — the same physics engine ViroReact ships with, exposed visually.
105
+
106
+ **Embed Studio scenes in your app**
107
+
108
+ Drop the `StudioSceneNavigator` component into your ViroReact app, point it at a Studio scene, and your app picks up whatever your team is editing in Studio without a new native build.
109
+
110
+ **Getting started**
111
+
112
+ Full walkthrough — including Studio setup, exporting a scene, and wiring `StudioSceneNavigator` into a React Native app — is in our blog: <https://updates.reactvision.xyz/how-to-build-your-first-ar-vr-app-with-studio-and-react-native-f2421ecce9ae>
113
+
87
114
  ## Documentation
88
115
 
89
116
  - Full API reference and guides: <https://viro-community.readme.io/docs/overview>
@@ -72,6 +72,14 @@ type Props = ViroCommonProps & {
72
72
  ) => void;
73
73
  onPlatformUpdate?: (platformInfoViro: ViroPlatformInfo) => void;
74
74
  onAmbientLightUpdate?: (update: ViroAmbientLightInfo) => void;
75
+ /**
76
+ * Fired once when depth data (LiDAR or monocular depth estimation) first becomes
77
+ * available for the session — i.e. hit tests can now return DepthPoints. Useful for
78
+ * hiding an "initializing depth…" state instead of acting on early, inaccurate
79
+ * feature points. On devices using monocular depth estimation, this can take a few
80
+ * seconds after the AR screen appears while the model warms up.
81
+ */
82
+ onDepthReady?: () => void;
75
83
  /**
76
84
  * Describes the acoustic properties of the room around the user
77
85
  */
@@ -237,6 +245,13 @@ export class ViroARScene extends ViroBase<Props> {
237
245
  this.props.onAmbientLightUpdate(event.nativeEvent.ambientLightInfo);
238
246
  };
239
247
 
248
+ /**
249
+ * Fired once when depth data first becomes available for hit testing.
250
+ */
251
+ _onDepthReady = (_event: NativeSyntheticEvent<{}>) => {
252
+ this.props.onDepthReady && this.props.onDepthReady();
253
+ };
254
+
240
255
  _onAnchorFound = (event: NativeSyntheticEvent<ViroARAnchorFoundEvent>) => {
241
256
  // TODO: this is in a different format than the other onAnchorFound methods
242
257
  this.props.onAnchorFound &&
@@ -527,6 +542,7 @@ export class ViroARScene extends ViroBase<Props> {
527
542
  onPlatformUpdateViro={this._onPlatformUpdate}
528
543
  onTrackingUpdatedViro={this._onTrackingUpdated}
529
544
  onAmbientLightUpdateViro={this._onAmbientLightUpdate}
545
+ onDepthReadyViro={this._onDepthReady}
530
546
  onAnchorFoundViro={this._onAnchorFound}
531
547
  onAnchorUpdatedViro={this._onAnchorUpdated}
532
548
  onAnchorRemovedViro={this._onAnchorRemoved}
@@ -573,6 +589,7 @@ var VRTARScene = requireNativeComponent<any>(
573
589
  onTrackingInitializedViro: true,
574
590
  onTrackingUpdatedViro: true,
575
591
  onAmbientLightUpdateViro: true,
592
+ onDepthReadyViro: true,
576
593
  onAnchorFoundViro: true,
577
594
  onAnchorUpdatedViro: true,
578
595
  onAnchorRemovedViro: true,
@@ -184,6 +184,39 @@ type Props = ViewProps & {
184
184
  */
185
185
  preferMonocularDepth?: boolean;
186
186
 
187
+ /**
188
+ * Calibration scale applied to monocular depth values before use in occlusion.
189
+ * 1.0 (default) = no change. Use < 1.0 if the model overestimates distances
190
+ * (virtual objects visible through real surfaces). Use > 1.0 if it underestimates.
191
+ * Typical tuning range: 0.7 – 1.3.
192
+ *
193
+ * @default 1.0
194
+ * @platform ios
195
+ */
196
+ monocularDepthScale?: number;
197
+
198
+ /**
199
+ * Maximum inference rate for monocular depth (default: 5).
200
+ * Lower values reduce device heat. Thermal state automatically
201
+ * overrides this downward: Fair→3fps, Serious→2fps, Critical→stopped.
202
+ * 3fps is barely perceptible for occlusion; 5fps is very smooth.
203
+ *
204
+ * @default 5
205
+ * @platform ios
206
+ */
207
+ monocularDepthTargetFPS?: number;
208
+
209
+ /**
210
+ * Use the front (selfie) camera as the AR session background.
211
+ * On iOS uses ARFaceTrackingConfiguration (requires TrueDepth camera, iPhone X+).
212
+ * On Android uses ARCore Augmented Faces mode (front camera).
213
+ * World tracking, plane detection, and LiDAR are unavailable in this mode.
214
+ *
215
+ * @default false
216
+ * @platform ios, android
217
+ */
218
+ frontCameraEnabled?: boolean;
219
+
187
220
  /**
188
221
  * Cloud and geospatial anchor provider.
189
222
  * Set to `"reactvision"` (default) for the ReactVision backend,
@@ -0,0 +1,73 @@
1
+ import { NativeModules } from "react-native";
2
+
3
+ interface StreamingAudioNative {
4
+ create(playerId: string): void;
5
+ beginStreaming(playerId: string, sampleRate: number, channels: number): void;
6
+ play(playerId: string): void;
7
+ pause(playerId: string): void;
8
+ setVolume(playerId: string, volume: number): void;
9
+ setMuted(playerId: string, muted: boolean): void;
10
+ /**
11
+ * Push interleaved float32 PCM samples encoded as base64 (little-endian IEEE 754).
12
+ *
13
+ * JS encoding:
14
+ * const bytes = new Uint8Array(float32Array.buffer);
15
+ * const b64 = btoa(String.fromCharCode(...bytes));
16
+ */
17
+ pushSamples(playerId: string, base64Samples: string): void;
18
+ destroy(playerId: string): void;
19
+ }
20
+
21
+ // RCT_EXPORT_MODULE() in newer RN (0.76+) keeps the "Module" suffix in NativeModules.
22
+ const native = (NativeModules.VRTStreamingAudioModule ?? NativeModules.VRTStreamingAudio) as
23
+ | StreamingAudioNative
24
+ | undefined;
25
+
26
+ function noop(..._args: unknown[]): void {}
27
+
28
+ /**
29
+ * StreamingAudioManager — imperative API for streaming PCM audio.
30
+ *
31
+ * Typical TTS flow:
32
+ * StreamingAudioManager.create('tts');
33
+ * StreamingAudioManager.beginStreaming('tts', 24000, 1);
34
+ * StreamingAudioManager.play('tts');
35
+ * // on each TTS audio chunk:
36
+ * StreamingAudioManager.pushSamples('tts', base64FloatPCM);
37
+ * // when done:
38
+ * StreamingAudioManager.destroy('tts');
39
+ */
40
+ export const StreamingAudioManager = {
41
+ create: native
42
+ ? (id: string) => native.create(id)
43
+ : (noop as (id: string) => void),
44
+
45
+ beginStreaming: native
46
+ ? (id: string, sampleRate: number, channels: number) =>
47
+ native.beginStreaming(id, sampleRate, channels)
48
+ : (noop as (id: string, sr: number, ch: number) => void),
49
+
50
+ play: native
51
+ ? (id: string) => native.play(id)
52
+ : (noop as (id: string) => void),
53
+
54
+ pause: native
55
+ ? (id: string) => native.pause(id)
56
+ : (noop as (id: string) => void),
57
+
58
+ setVolume: native
59
+ ? (id: string, volume: number) => native.setVolume(id, volume)
60
+ : (noop as (id: string, v: number) => void),
61
+
62
+ setMuted: native
63
+ ? (id: string, muted: boolean) => native.setMuted(id, muted)
64
+ : (noop as (id: string, m: boolean) => void),
65
+
66
+ pushSamples: native
67
+ ? (id: string, base64: string) => native.pushSamples(id, base64)
68
+ : (noop as (id: string, b64: string) => void),
69
+
70
+ destroy: native
71
+ ? (id: string) => native.destroy(id)
72
+ : (noop as (id: string) => void),
73
+ };
@@ -1 +1 @@
1
- export const VIRO_VERSION = "2.55.0";
1
+ export const VIRO_VERSION = "2.57.0";
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Copyright (c) 2026-present, ReactVision, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
9
+ import * as React from "react";
10
+ import {
11
+ findNodeHandle,
12
+ NativeModules,
13
+ NativeSyntheticEvent,
14
+ requireNativeComponent,
15
+ ViewProps,
16
+ } from "react-native";
17
+ import { ViroErrorEvent } from "./Types/ViroEvents";
18
+ import { ViroNativeRef } from "./Types/ViroUtils";
19
+
20
+ const { VRTCameraTextureModule } = NativeModules;
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Types
24
+ // ---------------------------------------------------------------------------
25
+
26
+ export type ViroCameraPosition = "front" | "back";
27
+
28
+ export type ViroCameraReadyEvent = Record<string, never>;
29
+
30
+ /**
31
+ * Result returned by capturePhoto / startRecording / stopRecording.
32
+ * Mirrors the response shape from both iOS and Android NativeModules.
33
+ */
34
+ export type ViroCaptureResult =
35
+ | { success: true; url: string }
36
+ | { success: false; error: string };
37
+
38
+ export type ViroCapturePhotoOptions = {
39
+ /** Absolute file path for the JPEG. Defaults to a cache-dir path. */
40
+ outputPath?: string;
41
+ };
42
+
43
+ export type ViroCaptureVideoOptions = {
44
+ /** Absolute file path for the MP4. Defaults to a cache-dir path. */
45
+ outputPath?: string;
46
+ };
47
+
48
+ type Props = ViewProps & {
49
+ /**
50
+ * Name of the material (created via ViroMaterials.createMaterials) to bind
51
+ * the live camera feed to as its diffuse texture. Unlike ViroMaterialVideo,
52
+ * this component creates the camera texture internally and sets it on the
53
+ * named material — the material only needs a lightingModel defined.
54
+ */
55
+ material: string;
56
+
57
+ /**
58
+ * Which device camera to use. Front camera feed is automatically mirrored
59
+ * for a natural selfie effect. Defaults to "front".
60
+ */
61
+ cameraPosition?: ViroCameraPosition;
62
+
63
+ /**
64
+ * Whether camera capture is paused. When true the last captured frame is
65
+ * held and battery usage drops to near zero. Defaults to false.
66
+ */
67
+ paused?: boolean;
68
+
69
+ /**
70
+ * Called once when the first camera frame is available and the texture is
71
+ * ready to be rendered. Fires from the native AVFoundation / Camera2 callback,
72
+ * not immediately on mount.
73
+ */
74
+ onCameraReady?: () => void;
75
+
76
+ /**
77
+ * Called when the camera fails to initialise (permission denied, hardware
78
+ * error, invalid material name, etc.).
79
+ */
80
+ onError?: (event: NativeSyntheticEvent<ViroErrorEvent>) => void;
81
+ };
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // Component
85
+ // ---------------------------------------------------------------------------
86
+
87
+ /**
88
+ * ViroCameraTexture binds a live device camera feed to a named material texture.
89
+ *
90
+ * The component creates the camera texture internally and sets it on the material,
91
+ * so the material only needs a lightingModel. The texture is updated every frame.
92
+ *
93
+ * **Usage:**
94
+ * ```tsx
95
+ * import { ViroMaterials, ViroQuad, ViroCameraTexture } from '@reactvision/react-viro';
96
+ *
97
+ * ViroMaterials.createMaterials({
98
+ * selfieMat: { lightingModel: 'Constant' },
99
+ * });
100
+ *
101
+ * <ViroARScene>
102
+ * <ViroQuad position={[0, 0, -2]} width={1.6} height={2.4} materials={["selfieMat"]} />
103
+ * <ViroCameraTexture
104
+ * material="selfieMat"
105
+ * cameraPosition="front"
106
+ * onCameraReady={() => console.log('Camera ready')}
107
+ * onError={(e) => console.error(e.nativeEvent.error)}
108
+ * />
109
+ * </ViroARScene>
110
+ * ```
111
+ */
112
+ export class ViroCameraTexture extends React.Component<Props> {
113
+ _component: ViroNativeRef = null;
114
+
115
+ _onCameraReady = () => {
116
+ this.props.onCameraReady && this.props.onCameraReady();
117
+ };
118
+
119
+ _onError = (event: NativeSyntheticEvent<ViroErrorEvent>) => {
120
+ this.props.onError && this.props.onError(event);
121
+ };
122
+
123
+ // ---------------------------------------------------------------------------
124
+ // Capture API
125
+ // ---------------------------------------------------------------------------
126
+
127
+ /**
128
+ * Capture a single JPEG still from the camera feed.
129
+ *
130
+ * @param options.outputPath Absolute path for the output JPEG.
131
+ * Omit to let the native layer choose a default
132
+ * cache-directory path.
133
+ * @returns Promise resolving to `{ success: true, url }` on success or
134
+ * `{ success: false, error }` on failure.
135
+ *
136
+ * @example
137
+ * ```ts
138
+ * const result = await cameraRef.current?.capturePhoto();
139
+ * if (result?.success) console.log('Saved to', result.url);
140
+ * ```
141
+ */
142
+ async capturePhoto(
143
+ options: ViroCapturePhotoOptions = {}
144
+ ): Promise<ViroCaptureResult> {
145
+ const tag = findNodeHandle(this._component);
146
+ if (tag == null) {
147
+ return { success: false, error: "ViroCameraTexture is not mounted" };
148
+ }
149
+ return VRTCameraTextureModule.capturePhoto(
150
+ tag,
151
+ options.outputPath ?? null
152
+ ) as Promise<ViroCaptureResult>;
153
+ }
154
+
155
+ /**
156
+ * Start recording the camera feed to an MP4 file.
157
+ *
158
+ * The promise resolves once the recording session has successfully started.
159
+ * Call {@link stopRecording} to finalise the file.
160
+ *
161
+ * @param options.outputPath Absolute path for the output MP4.
162
+ * Omit to let the native layer choose a default.
163
+ * @returns Promise resolving to `{ success: true, url }` (the path that
164
+ * will be written) or `{ success: false, error }`.
165
+ *
166
+ * @example
167
+ * ```ts
168
+ * await cameraRef.current?.startRecording();
169
+ * // … some time later …
170
+ * const result = await cameraRef.current?.stopRecording();
171
+ * if (result?.success) console.log('Video saved to', result.url);
172
+ * ```
173
+ */
174
+ async startRecording(
175
+ options: ViroCaptureVideoOptions = {}
176
+ ): Promise<ViroCaptureResult> {
177
+ const tag = findNodeHandle(this._component);
178
+ if (tag == null) {
179
+ return { success: false, error: "ViroCameraTexture is not mounted" };
180
+ }
181
+ return VRTCameraTextureModule.startRecording(
182
+ tag,
183
+ options.outputPath ?? null
184
+ ) as Promise<ViroCaptureResult>;
185
+ }
186
+
187
+ /**
188
+ * Stop an in-progress recording and finalise the MP4 file.
189
+ *
190
+ * @returns Promise resolving to `{ success: true, url }` with the path of
191
+ * the written file, or `{ success: false, error }`.
192
+ */
193
+ async stopRecording(): Promise<ViroCaptureResult> {
194
+ const tag = findNodeHandle(this._component);
195
+ if (tag == null) {
196
+ return { success: false, error: "ViroCameraTexture is not mounted" };
197
+ }
198
+ return VRTCameraTextureModule.stopRecording(
199
+ tag
200
+ ) as Promise<ViroCaptureResult>;
201
+ }
202
+
203
+ render() {
204
+ const nativeProps = Object.assign({} as any, this.props);
205
+ nativeProps.cameraPosition = this.props.cameraPosition ?? "front";
206
+ nativeProps.paused = this.props.paused ?? false;
207
+ nativeProps.onCameraReadyViro = this._onCameraReady;
208
+ nativeProps.onErrorViro = this._onError;
209
+ nativeProps.ref = (component: ViroNativeRef) => {
210
+ this._component = component;
211
+ };
212
+ return <VRTCameraTexture {...nativeProps} />;
213
+ }
214
+ }
215
+
216
+ // ---------------------------------------------------------------------------
217
+ // Native component binding
218
+ // ---------------------------------------------------------------------------
219
+
220
+ const VRTCameraTexture = requireNativeComponent<any>(
221
+ "VRTCameraTexture",
222
+ // @ts-ignore
223
+ ViroCameraTexture,
224
+ {
225
+ nativeOnly: {
226
+ onCameraReadyViro: true,
227
+ onErrorViro: true,
228
+ },
229
+ }
230
+ );
@@ -0,0 +1,74 @@
1
+ /**
2
+ * ViroGameLoop.tsx — headless component that fires per-frame JS callbacks.
3
+ *
4
+ * Mount anywhere inside a ViroARScene or ViroScene to start the loop.
5
+ * The loop automatically stops when the component unmounts.
6
+ *
7
+ * Usage:
8
+ * <ViroGameLoop onUpdate={(dt, elapsed) => { ... }} />
9
+ * <ViroGameLoop fixedHz={30} onFixedUpdate={(dt) => { ... }} />
10
+ *
11
+ * Copyright © 2026 ReactVision. All rights reserved.
12
+ */
13
+
14
+ import React from "react";
15
+ import { requireNativeComponent, ViewStyle } from "react-native";
16
+
17
+ const VRTGameLoopView = requireNativeComponent<ViroGameLoopNativeProps>(
18
+ "VRTGameLoopView"
19
+ );
20
+
21
+ // ── Types ─────────────────────────────────────────────────────────────────────
22
+
23
+ export type ViroGameLoopUpdateEvent = {
24
+ dt: number; // delta time in seconds since last frame
25
+ elapsed: number; // total elapsed seconds since component mounted
26
+ };
27
+
28
+ export type ViroGameLoopFixedEvent = {
29
+ dt: number; // fixed delta (1 / fixedHz)
30
+ };
31
+
32
+ export type ViroGameLoopProps = {
33
+ /** Called every rendered frame. dt = seconds since last frame. */
34
+ onUpdate?: (event: ViroGameLoopUpdateEvent) => void;
35
+ /** Called after physics + rendering each frame (useLateUpdate equivalent). */
36
+ onLateUpdate?: (event: ViroGameLoopUpdateEvent) => void;
37
+ /** Fixed simulation frequency in Hz. When set, onFixedUpdate fires at this rate. */
38
+ fixedHz?: number;
39
+ /** Called at a fixed rate determined by fixedHz. */
40
+ onFixedUpdate?: (event: ViroGameLoopFixedEvent) => void;
41
+ };
42
+
43
+ type ViroGameLoopNativeProps = {
44
+ onUpdate?: (e: { nativeEvent: ViroGameLoopUpdateEvent }) => void;
45
+ onLateUpdate?: (e: { nativeEvent: ViroGameLoopUpdateEvent }) => void;
46
+ onFixedUpdate?: (e: { nativeEvent: ViroGameLoopFixedEvent }) => void;
47
+ fixedHz?: number;
48
+ style?: ViewStyle;
49
+ };
50
+
51
+ // ── Component ─────────────────────────────────────────────────────────────────
52
+
53
+ export function ViroGameLoop({
54
+ onUpdate,
55
+ onLateUpdate,
56
+ onFixedUpdate,
57
+ fixedHz,
58
+ }: ViroGameLoopProps) {
59
+ // Native sends dt/elapsed as strings to avoid Fabric conversions.h type-check spam.
60
+ const parse = (e: any) => ({
61
+ dt: parseFloat(e.nativeEvent.dt),
62
+ elapsed: parseFloat(e.nativeEvent.elapsed ?? "0"),
63
+ });
64
+ const parseFixed = (e: any) => ({ dt: parseFloat(e.nativeEvent.dt) });
65
+
66
+ return (
67
+ <VRTGameLoopView
68
+ onUpdate={onUpdate ? (e) => onUpdate(parse(e)) : undefined}
69
+ onLateUpdate={onLateUpdate ? (e) => onLateUpdate(parse(e)) : undefined}
70
+ onFixedUpdate={onFixedUpdate ? (e) => onFixedUpdate(parseFixed(e)) : undefined}
71
+ fixedHz={fixedHz}
72
+ />
73
+ );
74
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * ViroGameLoopUtils — direct node manipulation that bypasses the React reconciler.
3
+ *
4
+ * Call these from inside useGameLoop / onUpdate callbacks for zero-setState
5
+ * positional updates. The native NodeModule methods dispatch directly to the
6
+ * render thread via the existing VRONode::setPositionAtomic() infrastructure.
7
+ *
8
+ * Copyright © 2026 ReactVision. All rights reserved.
9
+ */
10
+
11
+ import { findNodeHandle, UIManager } from "react-native";
12
+ import type React from "react";
13
+
14
+ export const ViroGameLoopUtils = {
15
+ /**
16
+ * Set the node's position without going through React state.
17
+ * Equivalent to <ViroNode position={[x,y,z]} /> but synchronous
18
+ * and reconciler-free — safe to call every frame.
19
+ */
20
+ setPosition(
21
+ nodeRef: React.RefObject<any>,
22
+ position: [number, number, number]
23
+ ): void {
24
+ const handle = findNodeHandle(nodeRef.current);
25
+ if (handle == null) return;
26
+ UIManager.dispatchViewManagerCommand(handle, "setPosition", position);
27
+ },
28
+
29
+ /**
30
+ * Set Euler rotation (degrees) without going through React state.
31
+ */
32
+ setRotation(
33
+ nodeRef: React.RefObject<any>,
34
+ rotation: [number, number, number]
35
+ ): void {
36
+ const handle = findNodeHandle(nodeRef.current);
37
+ if (handle == null) return;
38
+ UIManager.dispatchViewManagerCommand(handle, "setRotationEuler", rotation);
39
+ },
40
+
41
+ /**
42
+ * Set uniform or non-uniform scale without going through React state.
43
+ */
44
+ setScale(
45
+ nodeRef: React.RefObject<any>,
46
+ scale: [number, number, number]
47
+ ): void {
48
+ const handle = findNodeHandle(nodeRef.current);
49
+ if (handle == null) return;
50
+ UIManager.dispatchViewManagerCommand(handle, "setScale", scale);
51
+ },
52
+ };