Skip to Content
HooksuseViewerConnection

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

NameDescription
handleViewerReadyThe single callback to pass to ModelViewer.onViewerReady
getViewerReturn the latest ViewerReadyState, or null
captureImageCapture a PNG data URL from the connected viewer
captureVideoRecord one cinematic pass or turntable revolution as a video Blob
invalidateRequest a render frame from the connected viewer