Skip to content

React Hooks

The @moveris/react package provides 8 hooks for building custom liveness verification UIs. Use these when you need more control than the pre-built components offer.

In plain terms

Hooks let you build your own UI while the SDK handles the logic: useLiveness for the v1 full flow, useLivenessV2 for the upload-first v2 pipeline, useWebRTCLiveness for WebRTC transport to an observer, useCamera for camera access, useFrameCapture for capturing frames, useFaceDetection for face detection, useSmartFrameCapture for quality-gated capture, useDetectionPipeline for gaze + eye-region gating, and useModels for dynamic model selection.

Optional parameters

Parameters followed by ? (for example, options?) are optional. You can omit them if you don't need to customize the call. For instance, useLiveness() works without passing options.

Import

import {
  useLiveness,
  useLivenessV2,
  useWebRTCLiveness,
  useCamera,
  useFrameCapture,
  useFaceDetection,
  useSmartFrameCapture,
  useDetectionPipeline,
  useModels,
} from '@moveris/react';

useLiveness

The main hook that manages the full liveness verification flow: camera access, frame capture, API submission, and result handling.

const {
  status,
  result,
  error,
  framesReceived,
  framesRequired,
  feedbackMessage,
  start,
  stop,
  reset,
} = useLiveness(options?);

Options

Option Type Default Description
model FastCheckModel '10' Model to use
source FrameSource 'live' Frame source
sessionId string auto-generated Optional. Session ID for API calls. When provided, the same ID is used for every request in the session.
endpoint 'fast-check-crops' \| 'fast-check-stream' \| 'fast-check' \| 'live-check' 'fast-check-crops' API endpoint. Use live-check for V3 models with client landmarks; fast-check-stream for streaming; fast-check for batch with full frames.
mode 'batch' \| 'stream' 'batch' Upload mode: batch or streaming (when endpoint is fast-check-stream)
autoStart boolean false Start capturing automatically
onResult (result: LivenessResult) => void -- Result callback
onError (error: Error) => void -- Error callback
onProgress (received: number, total: number) => void -- Progress callback

Return Value

Property Type Description
status 'idle' \| 'capturing' \| 'processing' \| 'complete' \| 'error' Current state of the verification flow
result LivenessResult \| null Verification result when complete
error Error \| null Error if the flow failed
framesReceived number Number of frames captured so far
framesRequired number Total frames needed
feedbackMessage string \| null User-facing feedback message
start () => void Start the verification flow
stop () => void Stop capturing
reset () => void Reset to idle state

Example

function CustomLivenessUI() {
  const {
    status,
    result,
    error,
    framesReceived,
    framesRequired,
    feedbackMessage,
    start,
    reset,
  } = useLiveness({
    mode: 'stream',
    onResult: (r) => console.log('Done:', r.verdict),
  });

  return (
    <div>
      {status === 'idle' && <button onClick={start}>Begin</button>}
      {status === 'capturing' && <p>{feedbackMessage}</p>}
      {status === 'capturing' && <p>{framesReceived}/{framesRequired}</p>}
      {status === 'processing' && <p>Analyzing...</p>}
      {result && <p>Verdict: {result.verdict}</p>}
      {error && <p>Error: {error.message}</p>}
      {(result || error) && <button onClick={reset}>Try Again</button>}
    </div>
  );
}

useLivenessV2

Self-contained hook for the v2 upload-first pipeline. Uploads frames in chunks during capture (POST /api/v2/upload), then runs a single POST /api/v2/fast-check or POST /api/v2/live-check when capture completes. Completely separate from useLiveness (v1).

const {
  state,
  result,
  error,
  progress,
  start,
  stop,
  reset,
  captureFrame,
} = useLivenessV2(options?);

Options

Option Type Default Description
model FastCheckModel from context Model alias (V3 recommended)
endpoint 'fast-check' \| 'live-check' 'fast-check' Final check endpoint. live-check requires landmarks on uploaded frames
uploadChunkSize number 5 Frames per upload request
source FrameSource 'live' Frame source
fps number 30 Capture FPS
sessionId string auto-generated Session ID shared across upload + check
onResult (result: LivenessResult) => void Result callback
onError (error: Error) => void Error callback
onProgress (received: number, total: number) => void Upload/capture progress

Landmarks for live-check

Pass the full CapturedFrame (including landmarks) to captureFrame() when endpoint is live-check. Pair with useSmartFrameCapture and set requireLandmarks: true (3.22.4+) so capture does not start before the FaceLandmarker is ready.

See V2 Upload-First Pipeline.


useCamera

Manages camera permissions and the video stream.

const {
  videoRef,
  stream,
  isReady,
  error,
  hasPermission,
  requestPermission,
  startStream,
  stopStream,
} = useCamera(options?);

Options

Option Type Default Description
facingMode 'user' \| 'environment' 'user' Camera to use
width number 640 Preferred video width
height number 480 Preferred video height
autoStart boolean true Start stream on mount
requirements CameraRequirements SDK defaults Minimum camera capability targets used for validation

Return Value

Property Type Description
videoRef RefObject<HTMLVideoElement> Ref to attach to a <video> element
stream MediaStream \| null Active media stream
isReady boolean Whether the camera is ready to capture
error Error \| null Camera initialization error
hasPermission boolean Whether camera permission is granted
capabilities CameraCapabilities \| null Camera track capabilities when available (browser-dependent)
validation CameraValidationResult \| null Validation result against requirements
requestPermission () => Promise<boolean> Request camera permission
startStream () => Promise<void> Start the camera stream
stopStream () => void Stop and release the camera
applyConstraints (constraints: MediaTrackConstraints) => Promise<void> Apply runtime track constraints when supported

Example

function CameraPreview() {
  const { videoRef, isReady, error, hasPermission, requestPermission } = useCamera();

  if (!hasPermission) {
    return <button onClick={requestPermission}>Enable Camera</button>;
  }

  if (error) return <p>Camera error: {error.message}</p>;

  return (
    <video
      ref={videoRef}
      autoPlay
      playsInline
      muted
      style={{ width: '100%' }}
    />
  );
}

Capability support

capabilities depends on browser support for MediaStreamTrack.getCapabilities(). Some browsers (for example, older Firefox versions) may return limited or no capability data.


useFrameCapture

Captures individual frames from a video element.

const {
  captureFrame,
  capturedFrames,
  frameCount,
  clearFrames,
} = useFrameCapture(videoRef, options?);

Options

Option Type Default Description
width number 640 Output frame width
height number 480 Output frame height
format 'image/png' \| 'image/jpeg' 'image/png' Image encoding format

Return Value

Property Type Description
captureFrame () => CapturedFrame Capture a frame from the video element
capturedFrames CapturedFrame[] All frames captured so far
frameCount number Number of captured frames
clearFrames () => void Clear all captured frames

Example

function ManualCapture() {
  const { videoRef } = useCamera();
  const { captureFrame, capturedFrames, frameCount } = useFrameCapture(videoRef);

  const handleCapture = () => {
    const frame = captureFrame();
    console.log(`Captured frame ${frameCount}:`, frame.index);
  };

  return (
    <div>
      <video ref={videoRef} autoPlay playsInline muted />
      <button onClick={handleCapture}>Capture ({frameCount}/10)</button>
    </div>
  );
}

useFaceDetection

Real-time face detection using an adapter pattern. Supports MediaPipe (web) and custom detectors.

const {
  detectionResult,
  isInitialized,
  error,
} = useFaceDetection(videoRef, options);

Options

Option Type Default Description
adapter FaceDetectorAdapter -- Required. Face detector adapter
enabled boolean true Enable/disable detection
interval number 100 Detection interval in milliseconds

Adapters

MediaPipe (Web)

import { createMediaPipeAdapter } from '@moveris/react';

const adapter = createMediaPipeAdapter({
  modelPath: 'https://storage.googleapis.com/mediapipe-models/face_detector/blaze_face_short_range/float16/1/blaze_face_short_range.tflite',
  delegate: 'GPU',
});

Return Value

Property Type Description
detectionResult DetectionResult \| null Latest detection result
isInitialized boolean Whether the detector is ready
error Error \| null Initialization error

Example

function FaceDetectionPreview() {
  const { videoRef } = useCamera();
  const { detectionResult, isInitialized } = useFaceDetection(videoRef, {
    adapter: createMediaPipeAdapter(),
  });

  return (
    <div>
      <video ref={videoRef} autoPlay playsInline muted />
      {isInitialized && detectionResult?.faceDetected && (
        <p>Face detected!</p>
      )}
    </div>
  );
}

useSmartFrameCapture

Intelligent frame capture that only counts frames when quality and alignment checks pass. Rejects faces below 4% of frame area. Gates capture based on external detectors and supports restart() when a blocking condition occurs (e.g. hidden/shadowed eyes).

const {
  state, // 'idle' | 'detecting' | 'capturing' | 'complete'
  progress, // { current: number, total: number, quality: string }
  feedback, // string - User guidance message
  ovalState, // 'no_face' | 'poor' | 'good' | 'perfect'
  frames, // CapturedFrame[]
  start, // () => void
  stop, // () => void
  reset, // () => void
  restart, // () => void - reset all state and immediately resume capturing
} = useSmartFrameCapture({
  videoRef,
  targetFrames: 10,
  captureMode: 'full',
  onFrameCapture: (frame, index, total) => console.log('Captured:', index),
  onComplete: (frames) => console.log('All frames:', frames),
  onError: (error) => console.error(error),
});

Options

Option Type Default Description
videoRef RefObject<HTMLVideoElement \| null> required Reference to the live video element
targetFrames number 10 Number of frames to capture
captureIntervalMs number 100 Minimum ms between captures (e.g. 50 for ~20 FPS)
minCaptureDelay number 300 Absolute lower bound between captures. Set this closer to captureIntervalMs to allow higher effective FPS.
blurThreshold number auto Floor for adaptive blur threshold (defaults: 100 desktop, 150 mobile). The hook may raise the effective threshold from a rolling window; it will not drop below MIN_BLUR_FLOOR (20). From 3.22+, the adaptive curve is platform-aware (iOS / Android / HarmonyOS / desktop).
brightnessThreshold number 60 (center) / 50 (face) Floor for adaptive brightness threshold. The hook calibrates to recent luminance samples; it will not drop below MIN_BRIGHTNESS_FLOOR (30).
captureMode 'crop' \| 'full' 'crop' Capture mode (crop for fast-check-crops, full for fast-check / fast-check-stream)
detectionGate () => boolean - External gate: only capture when it returns true
detectFace (video) => Promise<FaceBoundingBox \| null> built-in Optional custom face detection function
requireLandmarks boolean false When true, isDetectorReady also waits for the FaceLandmarker before capture. Set for live-check so short sessions do not finish without landmarks. Leave false for fast-check / fast-check-crops.
onFrameCapture (frame, index, total) => void - Called for each captured frame
onComplete (frames) => void - Called when all frames are captured
onError (error) => void - Error callback
onQualityUpdate (quality) => void - Called on each quality update (for UI feedback)

Adaptive thresholds (3.19+ / platform-aware 3.22+)

By default, blur and brightness gates adapt to each user's camera and lighting instead of using fixed cutoffs. From 3.22.0, tuning (window size, top percentile, fraction) follows the detected platform via detectPlatform() and PLATFORM_BLUR_PARAMS. After five consecutive blur or brightness rejections, the hook surfaces persistent advisory feedback that takes priority over generic "hold still" copy. A blur rejection resets the frame buffer so V3+ models always receive fully consecutive frames. See the Changelog entries for 2.9.0 and 2.13.0.

Return Value

Property Type Description
state 'idle' \| 'detecting' \| 'capturing' \| 'complete' Current capture state
progress { current: number; total: number; quality: string } Capture progress and current quality label
feedback string User guidance message
ovalState OvalGuideState Guide visual state (no_face / poor / good / perfect)
frames CapturedFrame[] Captured frames
isDetectorReady boolean true when the primary face detector is ready. With requireLandmarks: true, also waits for the FaceLandmarker
start () => void Start capture loop
stop () => void Stop capture loop
reset () => void Reset to initial state
restart () => void Reset state and immediately resume capturing

Example

function SmartCapture() {
  const { videoRef } = useCamera();
  const {
    state,
    progress,
    feedback,
    ovalState,
    start,
    stop,
    reset,
    restart,
  } = useSmartFrameCapture({
    videoRef,
    targetFrames: 10,
    captureMode: 'full',
    onComplete: (captured) => {
      console.log('All frames captured:', captured.length);
      // Send frames to API...
    },
  });

  return (
    <div>
      <video ref={videoRef} autoPlay playsInline muted />

      {feedback && <p>{feedback}</p>}
      <p>
        Frames: {progress.current}/{progress.total}
      </p>
      <p>Oval: {ovalState}</p>

      {state === 'idle' && <button onClick={start}>Start</button>}
      {state === 'complete' && <button onClick={reset}>Try Again</button>}

      {state !== 'idle' && <button onClick={() => restart()}>Restart</button>}
    </div>
  );
}

Capture Modes

Mode Output Use With Description
'crop' 224x224 PNG fast-check-crops Face-cropped frame (client-side detection)
'full' 640x480 JPEG fast-check, fast-check-stream Full video frame (server-side detection)

Important: fast-check and fast-check-stream expect full video frames because the server performs its own face detection. Only fast-check-crops expects pre-cropped 224x224 images.

Capture pacing

The actual capture cadence is controlled by both captureIntervalMs and minCaptureDelay. To run faster capture loops, lower both values together (for example, captureIntervalMs: 50 and minCaptureDelay: 100).

Detection Gate

Use detectionGate to integrate external detectors (e.g. gaze, eye-region gating):

const detectionPassedRef = useRef(true);

useSmartFrameCapture({
  videoRef,
  detectionGate: () => detectionPassedRef.current,
  onFrameCapture: (frame) => {
    console.log('Captured frame:', frame.index);
  },
});

When the gate returns false, the frame is silently skipped and the internal counter does NOT increment.

Uploading stage behavior

In recent SDK releases, camera and detection loops stop once frame collection is complete (uploading stage). This keeps camera usage lower while the API request is in flight.


useDetectionPipeline

Encapsulates the gaze + eye-region detection pipeline and provides: - detectionGate for useSmartFrameCapture - getWarnings() to collect non-blocking warnings at submission time (e.g. glasses glare) - onEyeWarning callback for real-time eye quality feedback (e.g. "Eyes are in shadow", "Glare detected") just before onRestartNeeded fires

const restartRef = useRef<() => void>(() => {});
const onRestartNeeded = useCallback(() => restartRef.current(), []);

const { detectionGate, getWarnings } = useDetectionPipeline({
  videoRef,
  enabled: isSessionOpen,
  onRestartNeeded,
  onGazeFeedback: (message) => setGazeFeedback(message), // optional
});

const { start, stop, restart } = useSmartFrameCapture({
  videoRef,
  targetFrames: 10,
  captureMode: 'full',
  detectionGate,
  onComplete: (frames) => {
    const warnings = getWarnings();
    // Submit frames + warnings to your API...
    submitFrames(frames, warnings);
  },
});

// Wire restart after the hook call.
restartRef.current = restart;

Options

Option Type Default Description
videoRef RefObject<HTMLVideoElement \| null> required Video element ref used by the detectors
enabled boolean required Activates/deactivates the detection loop
onRestartNeeded () => void required Called when hidden/shadowed eyes are detected (blocking)
intervalMs number 200 Detection interval in milliseconds
onGazeFeedback (message: string) => void - Called with each gaze feedback update (empty string = clear)
onEyeWarning (message: string) => void - Called with eye failure reason ("Eyes are in shadow", "Glare detected", etc.) just before onRestartNeeded

Return Value

Property Type Description
detectionGate () => boolean Pass to useSmartFrameCapture.detectionGate
getWarnings () => string[] Returns accumulated session warnings; call at submit time

Detection Behaviour

Condition Behaviour
Gaze off-camera detectionGate returns false; onGazeFeedback provides a hint
Gaze restored detectionGate returns true; onGazeFeedback('') clears the hint
Glasses glare Non-blocking; adds "User was wearing glasses" to warnings
Hidden/shadowed eyes Calls onRestartNeeded(); you should wire this to useSmartFrameCapture.restart()

useModels

Fetches the live model registry from the API for dynamic model selection. Returns active and deprecated models, with local fallback while loading or on error. Use this when you want users to choose a model from the current API configuration (e.g. a model selector dropdown).

const {
  models,
  activeModels,
  deprecatedModels,
  loading,
  error,
  refetch,
} = useModels();

Return Value

Property Type Description
models ModelEntry[] Full model list (active + deprecated)
activeModels ModelEntry[] Non-deprecated models only
deprecatedModels ModelEntry[] Deprecated models only
loading boolean Whether the fetch is in progress
error Error \| null Error if the fetch failed
refetch () => void Manually re-fetch the model list

Example

function ModelPicker() {
  const { activeModels, deprecatedModels, loading } = useModels();

  if (loading) return <span>Loading models</span>;

  return (
    <select>
      {activeModels.map((m) => (
        <option key={m.id} value={m.id}>
          {m.label} ({m.min_frames} frames)
        </option>
      ))}
      {deprecatedModels.length > 0 && (
        <optgroup label="Deprecated">
          {deprecatedModels.map((m) => (
            <option key={m.id} value={m.id}>
              {m.label} (deprecated)
            </option>
          ))}
        </optgroup>
      )}
    </select>
  );
}

Recommended models

Use activeModels to show only non-deprecated models. The API prefers mixed-30-v2 (Balanced) for most integrations. See Models overview.


useWebRTCLiveness

WebRTC transport hook that wraps WebRTCLivenessClient from @moveris/webrtc. Connects to a Moveris observer over AWS KVS, buffers captured frames, and auto-submits when frameCount is reached.

In plain terms

Use this when your deployment includes a WebRTC observer (/kvs/config) instead of sending frames with REST. The hook manages connection state, frame buffering, and progress—pair it with your camera capture (for example useSmartFrameCapture).

const {
  state,
  result,
  error,
  progress,
  start,
  captureFrame,
  reset,
} = useWebRTCLiveness(config);

Config

Option Type Default Description
observerConfigUrl string required Observer /kvs/config URL
model string required Model alias (e.g. mixed-30-v3_1)
apiKey string Moveris API key forwarded to the observer
frameCount number 30 Frames to buffer before auto-submit
onResult (result: WebRTCLivenessResult) => void Verdict callback
onError (error: Error) => void Error callback
onProgress (received: number, required: number) => void Observer frame receipt progress
channelOpenTimeoutMs number 10000 Max ms to wait for DataChannel open
resultTimeoutMs number 30000 Max ms to wait for verdict after submit

Return Value

Property Type Description
state WebRTCLivenessState idleconnectingcapturingsendingdone (or error)
result WebRTCLivenessResult \| null Verdict once complete
error Error \| null Error when state === 'error'
progress { current: number; total: number } Buffered frames vs target
start() () => void Connect and begin capturing
captureFrame() (pixels: string, timestampMs?: number) => void Buffer a PNG base64 frame
reset() () => void Disconnect and return to idle

Example

function WebRTCFlow() {
  const capture = useSmartFrameCapture({ targetFrames: 30 });
  const webrtc = useWebRTCLiveness({
    observerConfigUrl: 'https://your-observer.example.com/kvs/config',
    model: 'mixed-30-v3_1',
    apiKey: process.env.MOVERIS_API_KEY,
    frameCount: 30,
  });

  useEffect(() => {
    if (capture.frames.length > webrtc.progress.current) {
      const frame = capture.frames[capture.frames.length - 1];
      webrtc.captureFrame(frame.pixels, frame.timestampMs);
    }
  }, [capture.frames]);

  return (
    <div>
      <button onClick={webrtc.start}>Start WebRTC check</button>
      <p>{webrtc.state}  {webrtc.progress.current}/{webrtc.progress.total}</p>
      {webrtc.result && <p>{webrtc.result.verdict}</p>}
    </div>
  );
}

See WebRTC Quick Start for observer setup and WebRTC Client Reference for WebRTCLivenessClient details.


Utility Classes

The @moveris/react package also exports utility classes for advanced use cases:

DetectionManager

Orchestrates multiple detectors (face, gaze, hand occlusion):

import { DetectionManager } from '@moveris/react';

Frame Utilities

import {
  analyzeBlur,
  analyzeBlurFromVideo,
  analyzeLighting,
  captureVideoFrame,
  captureFaceCroppedFrame,
  checkFrameQuality,
  FrameCollector,
  VideoFrameSync,
  CameraStabilizer,
} from '@moveris/react';
  • analyzeBlur(imageData) -- Returns a blur score for the image
  • analyzeLighting(imageData) -- Evaluates lighting conditions
  • captureVideoFrame(video, index, startTime, options?) -- Capture a single frame from a video element. Returns a CapturedFrame with PNG base64 pixels; honors width/height/mirror/blurThreshold.
  • captureFaceCroppedFrame(video, index, startTime, faceBoundingBox, options?) -- Capture and crop a 224×224 face region from video
  • checkFrameQuality(frame) -- Combined quality check (blur + lighting)
  • FrameCollector -- Collects frames at a configured interval
  • VideoFrameSync -- Synchronizes frame capture with video playback
  • CameraStabilizer -- Detects when the camera feed has stabilized. From 3.22.3, retries once by default after a failed cold-start window (StabilizerConfig.maxRetries, default 1).

Capture defaults in 3.10+

captureVideoFrame always encodes to PNG base64 and pins the canvas to sRGB (willReadFrequently: true), so blur and brightness checks stay consistent on Display P3 / wide-gamut displays. The default full-frame size cap is 1280×720 to match the V3.1 training resolution path. See the Changelog for 2.7.0.