Skip to content

React Hooks

The @moveris/react package provides 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 default capture flow, 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,
  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 {
  state,
  result,
  error,
  errorCode,
  errorMessage,
  progress,
  start,
  stop,
  reset,
  restartCapture,
} = useLiveness(options?);

Options

Option Type Default Description
model FastCheckModel DEFAULT_MODEL (mixed-30-v3_1_1) 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 Mixed V3.1.1 with client landmarks; fast-check-stream for streaming; fast-check for batch with full frames.
sequentialStreaming boolean false When endpoint is fast-check-stream, send one frame at a time instead of in parallel
frameCount number from model Frames to capture (30, 60, or 120)
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
state 'idle' \| 'capturing' \| 'uploading' \| 'processing' \| 'complete' \| 'error' Current state of the verification flow
result LivenessResult \| null Verification result when complete
error Error \| null Error if the flow failed
errorCode string \| null API error code when the error is a LivenessApiError
errorMessage string \| null User-facing error message
progress { current: number; total: number } Frames captured so far and frames required
start () => void Start the verification flow
stop () => void Stop capturing
reset () => void Reset to idle state
restartCapture () => void Drop the current sequence and start a new one (use with onSequenceReset)

Example

function CustomLivenessUI() {
  const {
    state,
    result,
    error,
    progress,
    start,
    reset,
  } = useLiveness({
    endpoint: 'fast-check-stream',
    sequentialStreaming: true,
    onResult: (r) => console.log('Done:', r.verdict),
  });

  return (
    <div>
      {state === 'idle' && <button onClick={start}>Begin</button>}
      {state === 'capturing' && <p>{progress.current}/{progress.total}</p>}
      {state === '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 upload-first HTTP pipeline (POST /api/v2/upload, then /api/v2/fast-check or /api/v2/live-check). The "V2" in the name is that API path, not Mixed V2 models. It still runs Mixed V3.1.1 (mixed-30-v3_1_1 by default). Completely separate from useLiveness.

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

Options

Option Type Default Description
model FastCheckModel from context Model alias (mixed-30-v3_1_1 default)
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 so capture does not start before the FaceLandmarker is ready.


useCamera

Manages camera permission and the MediaStream. Attach the stream to your own <video> element, or use LivenessCamera.

const {
  stream,
  error,
  permissionStatus,
  capabilities,
  validation,
  checkPermission,
  start,
  stop,
  applyConstraints,
  isActive,
} = useCamera(options?);

Options

Option Type Default Description
onCapabilities (capabilities, validation) => void -- Fires after the stream starts and capabilities are read

Return Value

Property Type Description
stream MediaStream \| null Active media stream
error Error \| null Camera initialization error
permissionStatus 'undetermined' \| 'granted' \| 'denied' \| 'unavailable' Camera permission
capabilities CameraCapabilities \| null Camera track capabilities when available (browser-dependent)
validation CameraValidationResult \| null Validation result against default requirements
checkPermission () => Promise<CameraPermissionStatus> Read permission without prompting
start () => Promise<void> Request permission and start the stream
stop () => void Stop and release the camera
applyConstraints (constraints: MediaTrackConstraints) => Promise<void> Apply runtime track constraints when supported
isActive boolean Whether the camera is currently running

Example

function CameraPreview() {
  const videoRef = useRef<HTMLVideoElement>(null);
  const { stream, error, permissionStatus, start, isActive } = useCamera();

  useEffect(() => {
    if (videoRef.current) {
      videoRef.current.srcObject = stream;
    }
  }, [stream]);

  if (permissionStatus !== 'granted' && !isActive) {
    return <button onClick={start}>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 frames from a video element on an interval, or on demand.

const { captureFrame } = useFrameCapture({
  videoRef,
  onFrame: (frame) => {},
  interval: 33,
  enabled: true,
});

Options

Option Type Default Description
videoRef RefObject<HTMLVideoElement \| null> required Video element to capture from
onFrame (frame: CapturedFrame) => void required Called for each captured frame
interval number 33 Capture interval in milliseconds (~30 FPS)
enabled boolean true Whether the interval is running
mirror boolean false Mirror captured frames. Leave false for API payloads

Return Value

Property Type Description
captureFrame () => CapturedFrame \| null Capture one frame now. Returns null if the video is not ready

Example

function ManualCapture() {
  const videoRef = useRef<HTMLVideoElement>(null);
  const [count, setCount] = useState(0);
  const { stream, permissionStatus, start, isActive } = useCamera();
  const { captureFrame } = useFrameCapture({
    videoRef,
    onFrame: () => setCount((n) => n + 1),
    enabled: false,
  });

  useEffect(() => {
    if (videoRef.current) {
      videoRef.current.srcObject = stream;
    }
  }, [stream]);

  if (permissionStatus !== 'granted' && !isActive) {
    return <button onClick={start}>Enable Camera</button>;
  }

  return (
    <div>
      <video ref={videoRef} autoPlay playsInline muted />
      <button onClick={() => captureFrame()}>Capture ({count}/30)</button>
    </div>
  );
}

useFaceDetection

Loads a face detector and exposes detect(video) for on-demand detection. Supports MediaPipe (web) and custom adapters.

const {
  isReady,
  isInitializing,
  error,
  initialize,
  detect,
  lastResult,
} = useFaceDetection(options?);

Options

Option Type Default Description
adapter FaceDetectorAdapter built-in MediaPipe Optional custom detector
autoInitialize boolean true Initialize on mount

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
isReady boolean Whether the detector is ready
isInitializing boolean Whether the detector is still loading
error Error \| null Initialization error
initialize () => Promise<void> Initialize manually when autoInitialize is false
detect (video: HTMLVideoElement) => Promise<FaceDetectionResult> Run detection on a video frame
lastResult FaceDetectionResult \| null Last detection result

Example

function FaceDetectionPreview() {
  const videoRef = useRef<HTMLVideoElement>(null);
  const { stream, permissionStatus, start, isActive } = useCamera();
  const { isReady, detect, lastResult } = useFaceDetection();

  useEffect(() => {
    if (videoRef.current) {
      videoRef.current.srcObject = stream;
    }
  }, [stream]);

  const handleDetect = async () => {
    if (!videoRef.current) return;
    await detect(videoRef.current);
  };

  if (permissionStatus !== 'granted' && !isActive) {
    return <button onClick={start}>Enable Camera</button>;
  }

  return (
    <div>
      <video ref={videoRef} autoPlay playsInline muted />
      <button onClick={handleDetect} disabled={!isReady}>Detect</button>
      {lastResult?.detected && <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' | 'capturing' | 'complete' | 'error'
  progress, // { captured, target, highQuality, goodQuality }
  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: 30,
  captureMode: 'full',
  onFrameCapture: (frame, index, total) => console.log('Captured:', index),
  onComplete: (frames) => console.log('All frames:', frames),
  onError: (error) => console.error(error),
  onSequenceReset: (reason) => liveness.restartCapture(),
});

Options

Option Type Default Description
videoRef RefObject<HTMLVideoElement \| null> required Reference to the live video element
targetFrames number 10 Pass 30, 60, or 120 to match the model. The hook default is 10, which the API rejects.
captureIntervalMs number 0 Optional tick cap in ms. Omit to run as fast as the browser allows.
minCaptureDelay number 0 Extra minimum gap between accepted frames. Leave 0 unless you need a hard spacing floor.
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, sdkQuality) => void - Called when all frames are captured
onError (error) => void - Error callback
onQualityUpdate (quality) => void - Called on each quality update (for UI feedback)
onSequenceReset (reason) => void - Fired when a partial sequence is discarded (blur, quality gate, detection gate). Wire this to restartCapture() on useLiveness / useLivenessV2.

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' \| 'capturing' \| 'complete' \| 'error' Current capture state
progress { captured: number; target: number; highQuality: number; goodQuality: number } Capture progress
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 = useRef<HTMLVideoElement>(null);
  const { stream, permissionStatus, start: startCamera, isActive } = useCamera();
  const {
    state,
    progress,
    feedback,
    ovalState,
    start,
    reset,
    restart,
  } = useSmartFrameCapture({
    videoRef,
    targetFrames: 30,
    captureMode: 'full',
    onComplete: (captured) => {
      console.log('All frames captured:', captured.length);
    },
  });

  useEffect(() => {
    if (videoRef.current) {
      videoRef.current.srcObject = stream;
    }
  }, [stream]);

  if (permissionStatus !== 'granted' && !isActive) {
    return <button onClick={startCamera}>Enable Camera</button>;
  }

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

      {feedback && <p>{feedback}</p>}
      <p>
        Frames: {progress.captured}/{progress.target}
      </p>
      <p>Guide: {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' PNG downscaled to fit 1280×720 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

captureIntervalMs and minCaptureDelay both default to 0. Pass values only when you need to cap the tick rate or enforce a minimum gap between accepted frames.

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: 30,
  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. Prefer mixed-30-v3_1_1 for most integrations. See Models overview.


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.