useViewerConnection
Combines every feature that needs ModelViewer.onViewerReady into one typed connection. Camera, runtime effects, screenshots, video export, and custom setup callbacks can share the same viewer without manual callback wiring.
Usage
import {
ModelViewer,
useViewerCamera,
useViewerConnection,
useViewerEffects,
} from "@liveroom-tech/react-immersive";
const camera = useViewerCamera({
initialTarget: [0, 1.2, 0],
});
const effects = useViewerEffects();
const connection = useViewerConnection(
camera,
effects,
(viewer) => {
console.log("Loaded scene", viewer.raw.scene);
},
);
<ModelViewer
modelUrl="/model.glb"
licenseKey={licenseKey}
preserveDrawingBuffer
onCameraChange={camera.handleCameraChange}
onViewerReady={connection.handleViewerReady}
/>;Consumers run in the order supplied. A consumer can be a controller with a handleViewerReady method, a callback, null, or undefined.
Screenshots and direct access
The connection keeps the latest viewer in a ref, so camera changes do not cause extra React renders.
const dataUrl = await connection.captureImage({
width: 1920,
height: 1080,
transparent: true,
});
connection.invalidate();
const viewer = connection.getViewer();captureImage requires preserveDrawingBuffer on the connected ModelViewer
and rejects with a clear error before the viewer is ready or when framebuffer
preservation is disabled. invalidate returns false before connection and
true after requesting a frame.
Video recording
captureVideo(options?: CaptureVideoOptions): Promise<Blob> records one
turntable revolution or cinematic pass from the connected viewer. It is also
available through useViewer().connection. Call it from a user action after
loading and initial framing, rather than from onViewerReady, which runs
repeatedly as the camera moves.
const abort = new AbortController();
const clip = await connection.captureVideo({
mode: "turntable",
format: "auto",
duration: 12,
fps: 30,
signal: abort.signal,
});
// Upload the Blob, or preview it with URL.createObjectURL(clip).
// abort.abort() cancels and discards an in-progress recording.Video recording does not require preserveDrawingBuffer. The browser must
support canvas.captureStream, and MediaRecorder for WebM/MP4; Auto prefers
WebM, then MP4. format: "gif" makes an animated GIF instead, gifWidth pixels
wide (default 480) at 15 fps by default. Explicit unsupported formats reject.
Video uses the canvas’s current pixel resolution. Recording runs in real time,
so keep the tab visible. It restores the previous camera and controls when it
ends. Audio and HTML overlays are not included. Calls before framing, during XR, or during another recording
reject. See Video export and
CaptureVideoOptions for all options.
WebGL recovery
After context loss, the viewer can rebuild automatically or through Reload
viewer. handleViewerReady receives the rebuilt viewer and replaces the
connection’s live controls and node references. Custom setup consumers run again;
use those fresh references instead of retaining objects from the old scene.
Returns
| Name | Description |
|---|---|
handleViewerReady | The single callback to pass to ModelViewer.onViewerReady |
getViewer | Return the latest ViewerReadyState, or null |
captureImage | Capture a PNG data URL from the connected viewer |
captureVideo | Record one cinematic pass or turntable revolution as a video Blob |
invalidate | Request a render frame from the connected viewer |