Skip to content

Model Loading Progress

INFO

Note: Model Loading Progress tracking is currently available for web SDKs (JavaScript, React, Vue). This feature provides real-time visibility into the AI model loading process, including download and caching stages.

When the Vitals™ SDK initializes for the first time, it downloads and loads various AI models (such as face detection models, facial landmark models, and real-time estimators) from the content delivery network (CDN). Depending on network conditions and device performance, this initialization process can take several seconds. The Model Loading Progress feature enables you to track this process and provide meaningful feedback to your users.

Overview

The Vitals™ SDK uses multiple AI modules working together to provide accurate vital sign measurements. During initialization, the SDK loads several model files from different modules:

AI Modules

The SDK loads the following AI modules:

  • Face Detection Module (face-mesh / mp-vision-face-mesh): Detects and tracks face landmarks with 468+ points
  • Age Estimation Module (face-api): Estimates user age for more accurate vital sign calculations
  • Face API Models: Additional models for facial analysis including gender detection and facial landmark detection

Each module consists of multiple model files that are downloaded and loaded during initialization. For example:

  • The face-api module loads: ssd_mobilenetv1_model, age_gender_model, face_landmark_68_model
  • The face-mesh module loads: face_landmarker.task, WASM runtime files
  • Each module may have 3-5+ model files totaling several megabytes

Loading Stages

Each model file progresses through the following stages:

  • Downloading: Model file is being fetched from the CDN to the user's device
  • Caching: Downloaded model is being saved to browser cache for faster subsequent loads
  • Ready: All model files from all modules are fully loaded and initialized, ready for scanning

The overall progress percentage aggregates the loading state across all modules and all files within those modules.

Key Components

The model loading feature consists of two main components:

ComponentPurpose
Built-in VisualizationOptional overlay that automatically displays model loading progress on top of the video element
Progress CallbackManual callback handler for custom UI implementations or monitoring

You can use either component independently or combine them based on your needs.

Key Benefits

BenefitDescription
User FeedbackShow loading bars or status text so users know the app is initializing
Network MonitoringUnderstand actual model loading times across different devices and networks
Custom UIImplement custom loading overlays matching your app's design
Module-Specific FeedbackDisplay different messages for face detection vs. age estimation loading
Error HandlingDetect and respond to download failures or timeout scenarios
Performance InsightsMonitor which models/modules take the longest to load

Implementation Approaches

The web SDKs provide two main mechanisms to handle model loading progress:

1. Built-in Visualization (Easiest)

Enable an automatic overlay that displays loading progress without writing code:

typescript
import { createVitalSignCamera } from 'ts-vital-sign-camera';

const video = document.querySelector('video')!;
const camera = createVitalSignCamera({
  isActive: true,
  userInfo: { age: 30, gender: 'male' }
});

camera.bind(video);

// Enable default model loading progress visualization
camera.visualizationOptions = {
  modelLoadingProgress: {
    enabled: true  // Shows default loading overlay
  }
};
typescript
import { VitalSignCamera, Gender } from 'react-vital-sign-camera';

export function CameraComponent() {
  return (
    <VitalSignCamera
      isActive={true}
      userInfo={{ age: 30, gender: Gender.Male }}
      visualizationOptions={{
        modelLoadingProgress: {
          enabled: true
        }
      }}
    />
  );
}
vue
<script setup lang="ts">
import { VitalSignCamera, Gender } from 'vue-vital-sign-camera'
</script>

<template>
  <div>
    <VitalSignCamera
      :is-active="true"
      :user-info="{ age: 30, gender: Gender.Male }"
      :visualization-options="{
        modelLoadingProgress: {
          enabled: true
        }
      }"
    />
  </div>
</template>

2. Manual Callback (Most Flexible)

Handle loading progress programmatically for custom UI:

typescript
import { createVitalSignCamera } from 'ts-vital-sign-camera';
import type { ModelLoadingProgressEvent } from 'ts-vital-sign-camera';

const video = document.querySelector('video')!;
const camera = createVitalSignCamera({
  isActive: true,
  userInfo: { age: 30, gender: 'male' },
  // Pass callback during creation (recommended approach)
  onModelLoadingProgress: (progress: ModelLoadingProgressEvent) => {
    console.log(`Loading: ${progress.percentage}%`);
    console.log(`Stage: ${progress.stage.type}`);
    
    // Update custom UI
    const progressBar = document.getElementById('loading-progress');
    if (progressBar) {
      progressBar.style.width = `${progress.percentage}%`;
    }
  }
});

camera.bind(video);
typescript
import { VitalSignCamera, Gender } from 'react-vital-sign-camera';
import { useState } from 'react';
import type { ModelLoadingProgressEvent } from 'react-vital-sign-camera';

export function CameraWithProgress() {
  const [progress, setProgress] = useState<ModelLoadingProgressEvent | null>(null);

  return (
    <>
      {progress && (
        <div>
          Loading: {progress.percentage}%
          {progress.stage.type === 'downloading' && (
            <span> ({Math.round(progress.loaded / 1024 / 1024)}MB / {Math.round(progress.total / 1024 / 1024)}MB)</span>
          )}
        </div>
      )}
      <VitalSignCamera
        isActive={true}
        userInfo={{ age: 30, gender: Gender.Male }}
        onModelLoadingProgress={setProgress}
      />
    </>
  );
}
vue
<script setup lang="ts">
import { ref } from 'vue'
import { VitalSignCamera, Gender } from 'vue-vital-sign-camera'
import type { ModelLoadingProgressEvent } from 'vue-vital-sign-camera'

const progress = ref<ModelLoadingProgressEvent | null>(null)

const handleModelLoadingProgress = (event: ModelLoadingProgressEvent) => {
  progress.value = event
}
</script>

<template>
  <div>
    <div v-if="progress">
      Loading: {{ progress.percentage }}%
      <span v-if="progress.stage.type === 'downloading'">
        ({{ Math.round(progress.loaded / 1024 / 1024) }}MB / {{ Math.round(progress.total / 1024 / 1024) }}MB)
      </span>
    </div>
    <VitalSignCamera
      :is-active="true"
      :user-info="{ age: 30, gender: Gender.Male }"
      @onModelLoadingProgress="handleModelLoadingProgress"
    />
  </div>
</template>

Progress Event Structure

The ModelLoadingProgressEvent provides detailed information about the current loading status across all AI modules:

typescript
interface ModelLoadingProgressEvent {
  /** Current progress percentage (0-100) */
  percentage: number;
  
  /** Bytes downloaded so far for the module that emitted this event (downloading stage) */
  loaded: number;
  
  /** Total bytes to download for that module (downloading stage) */
  total: number;
  
  /** Current stage with detailed information */
  stage: {
    type: 'downloading' | 'caching' | 'ready';
    fromCache: boolean;    // Whether model was loaded from cache
    filename?: string;     // Name of the specific file being processed
    error?: Error;         // Error object if loading failed
  };
  
  /** Name of the module being loaded (e.g., 'face-mesh', 'face-api', 'mp-vision-face-mesh') */
  module?: string;
}

How the Percentage Is Calculated

  • Events come from individual modules (face detection, age estimation, etc.); there is no cross-module aggregation.
  • Downloading stage (emitted by ModelFetchInterceptor): starts at ~30% and rises toward 100% based on bytes (or file-count fallback) for that module only.
  • Caching stage: 0–100% for the specific file being cached; values can drop relative to the previous downloading percentage.
  • Ready stage: always 100% for the emitting module and means downloads are finished for that module; it does not mean the camera is initialized—wait for onInitialized.
  • Modules may emit their own early progress values (e.g., a 10% kickoff event) before downloads begin.

Understanding the Module Property

The module property identifies which AI component is currently loading:

  • 'face-api': Age estimation and facial analysis models
  • 'mp-vision-face-mesh': MediaPipe face detection and landmark models
  • 'face-mesh': Alternative face detection implementation

You can use this to show more specific loading messages:

typescript
onModelLoadingProgress: (progress) => {
  let message = 'Loading AI models...';
  
  if (progress.module) {
    if (progress.module.includes('face-api') || progress.module.includes('age')) {
      message = 'Loading age estimation models...';
    } else if (progress.module.includes('face') || progress.module.includes('mesh')) {
      message = 'Loading face detection models...';
    }
  }
  
  console.log(message, `${progress.percentage}%`);
}
typescript
const getLoadingMessage = (progress: ModelLoadingProgressEvent) => {
  if (!progress.module) return 'Loading AI models...';
  
  if (progress.module.includes('face-api') || progress.module.includes('age')) {
    return 'Loading age estimation models...';
  } else if (progress.module.includes('face') || progress.module.includes('mesh')) {
    return 'Loading face detection models...';
  }
  
  return 'Loading AI models...';
};
vue
const getLoadingMessage = (progress: ModelLoadingProgressEvent) => {
  if (!progress.module) return 'Loading AI models...';
  
  if (progress.module.includes('face-api') || progress.module.includes('age')) {
    return 'Loading age estimation models...';
  } else if (progress.module.includes('face') || progress.module.includes('mesh')) {
    return 'Loading face detection models...';
  }
  
  return 'Loading AI models...';
};

Stage Types

  • downloading: Model file for that module is being downloaded from the CDN (the filename property shows which specific file)
  • caching: Model file for that module is being saved to browser cache for future use
  • ready: The emitting module has finished downloading (or fetched from cache) all of its model files; camera warm-up and onInitialized may still be pending

Multiple Files Per Module

Progress events are fired for each individual file within each module. For example, the face-api module may emit events for ssd_mobilenetv1_model-weights_manifest.json, age_gender_model-shard1, and other files sequentially. The percentage reflects progress for the emitting module across its files (not a cross-module aggregate).

Target SDK

JavaScript
React
Vue

The JavaScript SDK provides two ways to handle model loading progress: a built-in visualization overlay and a callback handler for custom implementations.

Using Built-in Visualization

The simplest approach is to enable the default model loading progress overlay:

typescript
import { createVitalSignCamera } from 'ts-vital-sign-camera';

window.onload = () => {
  const video = document.querySelector('video')!;
  
  const camera = createVitalSignCamera({
    isActive: true,
    userInfo: { age: 30, gender: 'male' }
  });
  
  camera.bind(video);
  
  // Enable default loading progress visualization
  camera.visualizationOptions = {
    modelLoadingProgress: {
      enabled: true
    }
  };
};
js
import { createVitalSignCamera } from 'ts-vital-sign-camera';

window.onload = () => {
  const video = document.querySelector('video');
  
  const camera = createVitalSignCamera({
    isActive: true,
    userInfo: { age: 30, gender: 'male' }
  });
  
  camera.bind(video);
  
  // Enable default loading progress visualization
  camera.visualizationOptions = {
    modelLoadingProgress: {
      enabled: true
    }
  };
};

The built-in overlay automatically displays:

  • A progress bar showing percentage complete
  • Current loading stage text (downloading/caching/ready)
  • Bytes loaded and total bytes (can be disabled with showBytes: false)

Using Progress Callback

For custom UI implementations, pass the onModelLoadingProgress callback during camera creation:

Important

The loading overlay should remain visible until the onInitialized callback is fired, not just when the progress reaches the 'ready' stage. The 'ready' stage indicates that models are loaded, but onInitialized confirms the camera is fully initialized and ready for use.

typescript
const video = document.querySelector('video')!;
let isLoading = true;
let progress = null;

const overlayEl = document.getElementById('loading-container');
const barEl = document.getElementById('loading-progress');
const textEl = document.getElementById('loading-text');

const camera = createVitalSignCamera({
  isActive: true,
  userInfo: { age: 30, gender: 'male' },
  onModelLoadingProgress: (evt) => {
    progress = evt;
    if (barEl) barEl.style.width = `${evt.percentage}%`;
    if (textEl) textEl.textContent = evt.stage.type === 'ready' ? 'Initializing...' : `${evt.percentage}%`;
    if (overlayEl) overlayEl.style.display = 'flex'; // ensure visible even for cached loads
  },
  onInitialized: () => {
    isLoading = false;
    if (overlayEl) overlayEl.style.display = 'none';
  }
});

camera.bind(video);
  • Start with the overlay visible (display: flex); cached loads can still need a quick state.
  • Update width/text in onModelLoadingProgress; when stage.type === 'ready', show “Initializing...”.
  • Dismiss only in onInitialized.
  • Provide fallback text when progress is null (before first event).

Customizing Built-in Visualization

The default overlay can be customized with colors and display options:

typescript
const video = document.querySelector('video')!;
const camera = createVitalSignCamera({
  isActive: true,
  userInfo: { age: 30, gender: 'male' }
});
camera.bind(video);

// Customize the built-in visualization
camera.visualizationOptions = {
  modelLoadingProgress: {
    enabled: true,
    backgroundColor: 'rgba(0, 0, 0, 0.7)',  // Overlay background
    progressColor: '#4CAF50',                 // Progress bar color
    textColor: '#4CAF50',                     // Text color
    showBytes: true                           // Show MB / MB
  }
};

Complete Example with Custom UI

Here's a more complete example showing both the callback and custom HTML:

html
<!DOCTYPE html>
<html>
<head>
  <style>
    #loading-container {
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: rgba(0, 0, 0, 0.7);
      display: none;
      align-items: center;
      justify-content: center;
      z-index: 1000;
    }
    
    .loading-content {
      background: white;
      padding: 40px;
      border-radius: 8px;
      max-width: 400px;
      text-align: center;
    }
    
    .progress-bar-container {
      width: 100%;
      height: 8px;
      background: #e0e0e0;
      border-radius: 4px;
      margin: 20px 0;
      overflow: hidden;
    }
    
    .progress-bar-fill {
      height: 100%;
      background: #4CAF50;
      width: 0%;
      transition: width 0.3s ease;
    }
  </style>
</head>
<body>
  <div id="loading-container">
    <div class="loading-content">
      <h2>Loading AI Models</h2>
      <div class="progress-bar-container">
        <div class="progress-bar-fill" id="progress-fill"></div>
      </div>
      <p id="progress-text">Initializing...</p>
    </div>
  </div>
  
  <video id="video" style="display: none;"></video>
  
  <script type="module">
    import { createVitalSignCamera } from 'ts-vital-sign-camera';
    
    window.onload = async () => {
      const video = document.querySelector('video');
      const loadingContainer = document.getElementById('loading-container');
      
      // Show loading container
      loadingContainer.style.display = 'flex';
      
      const camera = createVitalSignCamera({
        isActive: true,
        userInfo: { age: 30, gender: 'male' },
        // Handle progress updates via callback
        onModelLoadingProgress: (progress) => {
          const progressFill = document.getElementById('progress-fill');
          const progressText = document.getElementById('progress-text');
          
          if (progressFill) {
            progressFill.style.width = `${progress.percentage}%`;
          }
          
          // Format size info
          const sizeMB = (progress.total / 1024 / 1024).toFixed(1);
          const loadedMB = (progress.loaded / 1024 / 1024).toFixed(1);
          
          if (progressText) {
            if (progress.stage.type === 'downloading') {
              progressText.textContent = 
                `Downloading models: ${loadedMB}MB / ${sizeMB}MB (${progress.percentage}%)`;
            } else if (progress.stage.type === 'caching') {
              progressText.textContent = `Caching models: ${progress.percentage}%`;
            } else if (progress.stage.type === 'ready') {
              progressText.textContent = 'Initializing...';
            }
          }
        },
        // Hide loading overlay when fully initialized
        onInitialized: () => {
          console.log('Camera initialized and ready');
          setTimeout(() => {
            loadingContainer.style.display = 'none';
          }, 300);
        }
      });
      
      camera.bind(video);
    };
  </script>
</body>
</html>

Understanding Progress Events

The progress callback receives a ModelLoadingProgressEvent with the following properties:

PropertyTypeDescription
percentagenumberProgress from 0-100
loadednumberBytes downloaded
totalnumberTotal bytes to download
stage.typestringOne of: 'downloading', 'caching', 'ready'
stage.fromCachebooleanTrue if model came from browser cache
stage.filenamestringName of the file being processed
stage.errorErrorError object if loading failed
modulestringName of the module being loaded

Error Handling

The callback will still be invoked if an error occurs during loading:

typescript
const video = document.querySelector('video')!;
const camera = createVitalSignCamera({
  isActive: true,
  userInfo: { age: 30, gender: 'male' }
});
camera.bind(video);

// Set error handling after camera creation
camera.onModelLoadingProgress = (progress) => {
  if (progress.stage.error) {
    console.error('Model loading failed:', progress.stage.error.message);
    
    // Show error message to user
    const errorDiv = document.getElementById('error-message');
    if (errorDiv) {
      errorDiv.textContent = 'Failed to load AI models. Please refresh the page.';
      errorDiv.style.display = 'block';
    }
  } else {
    // Normal progress update
    console.log(`${progress.percentage}% complete`);
  }
};

Combining Both Approaches

You can use both the built-in visualization and a callback for monitoring:

typescript
const video = document.querySelector('video')!;

const camera = createVitalSignCamera({
  isActive: true,
  userInfo: { age: 30, gender: 'male' },
  // Set callback for custom monitoring and analytics
  onModelLoadingProgress: (progress) => {
    // Send analytics, log progress, etc.
    console.log('Model loading progress:', progress);
  },
  // Track when initialization completes
  onInitialized: () => {
    console.log('Camera ready for scanning');
  }
});

camera.bind(video);

// Enable built-in visualization after binding
camera.visualizationOptions = {
  modelLoadingProgress: {
    enabled: true
  }
};

Performance Tips

  • Throttle updates: The callback may fire many times per second. Only update the DOM at intervals
  • Cache DOM references: Store element references instead of querying each time
  • Use CSS transitions: Let CSS handle smooth progress bar animations
  • Avoid layout thrashing: Batch DOM reads and writes

Important Notes

  • The callback may be called rapidly during downloads. Consider throttling UI updates
  • On subsequent app loads, models will load from browser cache, making the process very fast
  • The fromCache property indicates whether the current file came from cache
  • Each module (face detection, landmarks, estimators) may report separate progress

Use this pattern to ensure the overlay stays up for both cold and warm loads:

  1. Start with isLoading = true so cached models still show overlay briefly.
  2. Update progress in onModelLoadingProgress.
  3. Dismiss overlay only in onInitialized (not at stage ready).
  4. Keep a graceful message when progress is null (before first event).
typescript
let isLoading = true;
let progress = null;

const loadingEl = document.getElementById('loading-overlay');
const barEl = document.getElementById('loading-bar');
const textEl = document.getElementById('loading-text');

const camera = createVitalSignCamera({
  isActive: true,
  userInfo: { age: 30, gender: 'male' },
  onModelLoadingProgress: (evt) => {
    progress = evt;
    if (barEl) barEl.style.width = `${evt.percentage}%`;
    if (textEl) textEl.textContent = evt.stage.type === 'ready' ? 'Initializing...' : `${evt.percentage}%`;
  },
  onInitialized: () => {
    isLoading = false;
    if (loadingEl) loadingEl.style.display = 'none';
  }
});

camera.bind(document.querySelector('video'));
typescript
import { useState, useCallback } from 'react';
import { VitalSignCamera, Gender } from 'react-vital-sign-camera';
import type { ModelLoadingProgressEvent } from 'react-vital-sign-camera';

export function CameraWithOverlay() {
  const [isLoading, setIsLoading] = useState(true);
  const [progress, setProgress] = useState<ModelLoadingProgressEvent | null>(null);

  const handleProgress = useCallback((evt: ModelLoadingProgressEvent) => {
    setProgress(evt);
  }, []);

  const handleInitialized = useCallback(() => {
    setIsLoading(false);
  }, []);

  return (
    <>
      {isLoading && (
        <div className="overlay">
          <div className="bar" style={{ width: `${progress?.percentage || 0}%` }} />
          <div className="text">
            {progress?.stage.type === 'ready' ? 'Initializing...' : `${progress?.percentage || 0}%`}
          </div>
        </div>
      )}
      <VitalSignCamera
        isActive={true}
        userInfo={{ age: 30, gender: Gender.Male }}
        onModelLoadingProgress={handleProgress}
        onInitialized={handleInitialized}
      />
    </>
  );
}
vue
<script setup lang="ts">
import { ref } from 'vue'
import { VitalSignCamera, Gender } from 'vue-vital-sign-camera'
import type { ModelLoadingProgressEvent } from 'vue-vital-sign-camera'

const isLoading = ref(true)
const progress = ref<ModelLoadingProgressEvent | null>(null)

const handleProgress = (evt: ModelLoadingProgressEvent) => {
  progress.value = evt
}

const handleInitialized = () => {
  isLoading.value = false
}
</script>

<template>
  <div>
    <div v-if="isLoading" class="overlay">
      <div class="bar" :style="{ width: `${progress?.percentage || 0}%` }"></div>
      <div class="text">
        <span v-if="progress?.stage.type === 'ready'">Initializing...</span>
        <span v-else>{{ progress?.percentage || 0 }}%</span>
      </div>
    </div>
    <VitalSignCamera
      :is-active="true"
      :user-info="{ age: 30, gender: Gender.Male }"
      @onModelLoadingProgress="handleProgress"
      @onInitialized="handleInitialized"
    />
  </div>
</template>

Understanding Multi-Module Loading

The SDK loads multiple AI modules in sequence or parallel, with each module containing multiple model files. Progress events are emitted for each file as it progresses through the downloading, caching, and ready stages.

Typical Loading Sequence

Progress events from different modules are interleaved; each module reports its own percentages:

  1. Face detection module begins downloading
    Example: { percentage: 30, stage: { type: 'downloading', filename: 'face_landmarker.task' }, module: 'mp-vision-face-mesh' }
  2. Age estimation (face-api) starts downloading in parallel
    Example: { percentage: 30, stage: { type: 'downloading', filename: 'ssd_mobilenetv1_model-weights_manifest.json' }, module: 'face-api' }
  3. Face detection finishes downloading its files
    Example: { percentage: 100, stage: { type: 'ready', fromCache: false }, module: 'mp-vision-face-mesh' }
  4. Age estimation finishes downloading its files
    Example: { percentage: 100, stage: { type: 'ready', fromCache: false }, module: 'face-api' }

Because each module reports independently, do not treat the percentages as a single global progress value.

Progress Calculation (per module)

  • Downloading stage (ModelFetchInterceptor): Uses a per-module baseline of ~30% plus byte-based progress for that module's downloads (70% range). If content-length is missing, it estimates using file counts. Values are per module, not aggregated.
  • Caching stage: Uses loaded/total × 100 for the specific file being cached (values can dip below the previous downloading percentage).
  • Custom kickoff events: Some modules emit a fixed percentage (e.g., 10%) before downloads start to show immediate feedback.
  • Ready stage: Always 100% for the emitting module; the camera may still be warming up. Use onInitialized to know when the camera is fully ready.

Because events are per module, displaying an "overall" bar should use the latest event or combine module-specific bars instead of assuming a single aggregated percentage.

Module-Specific Progress Tracking

To track progress for specific modules, you can filter events by the module property:

typescript
const moduleProgress = {
  'face-api': 0,
  'mp-vision-face-mesh': 0
};

camera = createVitalSignCamera({
  isActive: true,
  userInfo: { age: 30, gender: 'male' },
  onModelLoadingProgress: (progress) => {
    // Track module-specific progress
    if (progress.module) {
      moduleProgress[progress.module] = progress.percentage;
      console.log(`${progress.module}: ${progress.percentage}%`);
    }
    
    // Display overall progress
    console.log(`Overall: ${progress.percentage}%`);
  }
});
typescript
import { useState } from 'react';

function CameraWithModuleTracking() {
  const [moduleProgress, setModuleProgress] = useState<Record<string, number>>({});
  const [overallProgress, setOverallProgress] = useState(0);

  const handleProgress = (progress: ModelLoadingProgressEvent) => {
    setOverallProgress(progress.percentage);
    
    if (progress.module) {
      setModuleProgress(prev => ({
        ...prev,
        [progress.module!]: progress.percentage
      }));
    }
  };

  return (
    <div>
      <div>Overall: {overallProgress}%</div>
      {Object.entries(moduleProgress).map(([module, percent]) => (
        <div key={module}>{module}: {percent}%</div>
      ))}
      <VitalSignCamera
        isActive={true}
        userInfo={{ age: 30, gender: Gender.Male }}
        onModelLoadingProgress={handleProgress}
      />
    </div>
  );
}
vue
<script setup lang="ts">
import { ref } from 'vue'
import type { ModelLoadingProgressEvent } from 'vue-vital-sign-camera'

const moduleProgress = ref<Record<string, number>>({})
const overallProgress = ref(0)

const handleProgress = (progress: ModelLoadingProgressEvent) => {
  overallProgress.value = progress.percentage
  
  if (progress.module) {
    moduleProgress.value[progress.module] = progress.percentage
  }
}
</script>

<template>
  <div>
    <div>Overall: {{ overallProgress }}%</div>
    <div v-for="[module, percent] in Object.entries(moduleProgress)" :key="module">
      {{ module }}: {{ percent }}%
    </div>
    <VitalSignCamera
      :is-active="true"
      :user-info="{ age: 30, gender: Gender.Male }"
      @onModelLoadingProgress="handleProgress"
    />
  </div>
</template>

File-Level Progress Tracking

To track individual file downloads, use the stage.filename property:

typescript
onModelLoadingProgress: (progress) => {
  if (progress.stage.filename) {
    console.log(`Downloading: ${progress.stage.filename}`);
    console.log(`Module: ${progress.module || 'unknown'}`);
    console.log(`Progress: ${progress.loaded} / ${progress.total} bytes`);
  }
}
typescript
const [currentFile, setCurrentFile] = useState<string>('');

const handleProgress = (progress: ModelLoadingProgressEvent) => {
  if (progress.stage.filename) {
    setCurrentFile(progress.stage.filename);
  }
};

return (
  <div>
    {currentFile && <div>Loading: {currentFile}</div>}
    <VitalSignCamera
      isActive={true}
      userInfo={{ age: 30, gender: Gender.Male }}
      onModelLoadingProgress={handleProgress}
    />
  </div>
);
vue
<script setup lang="ts">
const currentFile = ref<string>('')

const handleProgress = (progress: ModelLoadingProgressEvent) => {
  if (progress.stage.filename) {
    currentFile.value = progress.stage.filename
  }
}
</script>

<template>
  <div>
    <div v-if="currentFile">Loading: {{ currentFile }}</div>
    <VitalSignCamera
      :is-active="true"
      :user-info="{ age: 30, gender: Gender.Male }"
      @onModelLoadingProgress="handleProgress"
    />
  </div>
</template>

Best Practices

User Experience

  • Show progress indication during initialization
  • Display estimated time remaining if available
  • Use smooth animations or transitions for progress updates
  • Provide clear messaging about what's being loaded

Performance

  • Don't update progress display too frequently (throttle to 100-200ms)
  • Cache DOM element references instead of querying repeatedly
  • Use efficient CSS transitions rather than JavaScript animations
  • Consider that progress may complete very quickly if models are cached

Error Handling

  • Handle network timeouts gracefully
  • Provide options for users to retry if loading fails
  • Check progress.stage.error for error information
  • Log errors for debugging purposes

Testing

  • Test with slow network connections using browser DevTools throttling
  • Verify behavior on low-end devices
  • Test with intermittent network failures
  • Check both cold load (first time) and warm load (from cache) scenarios