๐Ÿ’ป Code Examples

Practical code examples for common use cases and integration patterns.

๐Ÿ’ป Code Examples

Practical code examples for common use cases and integration patterns.

Table of Contents


Basic Video Analysis

Simple One-Shot Analysis

import { RPPGClient } from 'rppg-api-client';

const client = new RPPGClient({
  apiKey: process.env.RPPG_API_KEY,
  baseUrl: 'https://api.yourdomain.com/api'
});

async function analyzeVideo(videoFile) {
  try {
    const vitals = await client.analyzeVideo(videoFile);
    
    console.log('Results:');
    console.log('- Heart Rate:', vitals.heart_rate, 'bpm');
    console.log('- Respiratory Rate:', vitals.respiratory_rate, 'breaths/min');
    console.log('- HRV:', vitals.hrv);
    console.log('- Confidence:', (vitals.confidence * 100).toFixed(1) + '%');
    
    return vitals;
  } catch (error) {
    console.error('Analysis failed:', error.message);
    throw error;
  }
}

Manual Step-by-Step Process

async function manualAnalysis(videoFile) {
  // Step 1: Create session
  const session = await client.createSession();
  console.log('Created session:', session.session_id);
  
  // Step 2: Upload video
  const uploadResult = await client.uploadVideo(session.session_id, videoFile);
  console.log('Upload status:', uploadResult.status);
  
  // Step 3: Poll for results
  const vitals = await client.waitForVitals(session.session_id, {
    pollingInterval: 3000,
    maxPollingTime: 120000
  });
  
  return vitals;
}

Error Handling

Comprehensive Error Handling

import {
  RPPGClient,
  AuthenticationError,
  ValidationError,
  RateLimitError,
  NotFoundError,
  ServerError,
  NetworkError
} from 'rppg-api-client';

async function robustAnalysis(videoFile) {
  try {
    const vitals = await client.analyzeVideo(videoFile);
    return { success: true, data: vitals };
    
  } catch (error) {
    // Authentication issues
    if (error instanceof AuthenticationError) {
      return {
        success: false,
        error: 'Invalid API key. Please check your credentials.',
        code: 'AUTH_ERROR'
      };
    }
    
    // Validation errors (bad file, size, format)
    if (error instanceof ValidationError) {
      return {
        success: false,
        error: `Invalid video: ${error.message}`,
        code: 'VALIDATION_ERROR'
      };
    }
    
    // Rate limiting
    if (error instanceof RateLimitError) {
      return {
        success: false,
        error: `Rate limit exceeded. Retry after ${error.retryAfter} seconds.`,
        code: 'RATE_LIMIT',
        retryAfter: error.retryAfter
      };
    }
    
    // Session not found
    if (error instanceof NotFoundError) {
      return {
        success: false,
        error: 'Session not found or expired.',
        code: 'NOT_FOUND'
      };
    }
    
    // Server errors
    if (error instanceof ServerError) {
      return {
        success: false,
        error: 'Server error during processing. Please try again.',
        code: 'SERVER_ERROR'
      };
    }
    
    // Network issues
    if (error instanceof NetworkError) {
      return {
        success: false,
        error: 'Network connection failed. Check your internet connection.',
        code: 'NETWORK_ERROR'
      };
    }
    
    // Unknown errors
    return {
      success: false,
      error: 'An unexpected error occurred.',
      code: 'UNKNOWN_ERROR',
      details: error.message
    };
  }
}

Retry Logic

async function analyzeWithRetry(videoFile, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const vitals = await client.analyzeVideo(videoFile);
      return vitals;
      
    } catch (error) {
      console.log(`Attempt ${attempt} failed:`, error.message);
      
      // Don't retry validation errors
      if (error instanceof ValidationError) {
        throw error;
      }
      
      // Don't retry auth errors
      if (error instanceof AuthenticationError) {
        throw error;
      }
      
      // Wait before retrying (exponential backoff)
      if (attempt < maxRetries) {
        const delay = Math.pow(2, attempt) * 1000; // 2s, 4s, 8s
        await new Promise(resolve => setTimeout(resolve, delay));
      } else {
        throw error; // Final attempt failed
      }
    }
  }
}

Progress Tracking

With Progress Callback

async function analyzeWithProgress(videoFile, onProgress) {
  const startTime = Date.now();
  let lastUpdate = 0;
  
  const vitals = await client.analyzeVideo(videoFile, {
    pollingInterval: 3000,
    onProgress: (attempt) => {
      const elapsed = Date.now() - startTime;
      const estimatedTotal = 40000; // ~40 seconds typical
      const progress = Math.min((elapsed / estimatedTotal) * 100, 95);
      
      // Update UI every 3 seconds
      if (elapsed - lastUpdate >= 3000) {
        onProgress({
          attempt,
          progress: Math.round(progress),
          elapsed: Math.round(elapsed / 1000),
          message: `Processing video... ${Math.round(progress)}%`
        });
        lastUpdate = elapsed;
      }
    }
  });
  
  // 100% complete
  onProgress({
    progress: 100,
    elapsed: Math.round((Date.now() - startTime) / 1000),
    message: 'Analysis complete!'
  });
  
  return vitals;
}

// Usage
analyzeWithProgress(videoFile, (status) => {
  console.log(status.message);
  updateProgressBar(status.progress);
});

Batch Processing

Process Multiple Videos

async function processBatch(videoFiles) {
  const results = [];
  const errors = [];
  
  for (const [index, file] of videoFiles.entries()) {
    console.log(`Processing ${index + 1}/${videoFiles.length}: ${file.name}`);
    
    try {
      const vitals = await client.analyzeVideo(file);
      results.push({
        filename: file.name,
        success: true,
        vitals
      });
    } catch (error) {
      errors.push({
        filename: file.name,
        success: false,
        error: error.message
      });
    }
  }
  
  return {
    total: videoFiles.length,
    successful: results.length,
    failed: errors.length,
    results,
    errors
  };
}

Parallel Processing (with concurrency limit)

async function processBatchParallel(videoFiles, concurrency = 3) {
  const results = [];
  
  // Process in chunks
  for (let i = 0; i < videoFiles.length; i += concurrency) {
    const chunk = videoFiles.slice(i, i + concurrency);
    
    const chunkResults = await Promise.allSettled(
      chunk.map(file => client.analyzeVideo(file))
    );
    
    results.push(...chunkResults.map((result, idx) => ({
      filename: chunk[idx].name,
      success: result.status === 'fulfilled',
      vitals: result.status === 'fulfilled' ? result.value : null,
      error: result.status === 'rejected' ? result.reason.message : null
    })));
  }
  
  return results;
}

React Integration

Complete React Component

import React, { useState } from 'react';
import { RPPGClient } from 'rppg-api-client';

const client = new RPPGClient({
  apiKey: process.env.REACT_APP_RPPG_API_KEY,
  baseUrl: '/api' // Proxy through your backend
});

function VideoAnalyzer() {
  const [file, setFile] = useState(null);
  const [loading, setLoading] = useState(false);
  const [progress, setProgress] = useState(0);
  const [vitals, setVitals] = useState(null);
  const [error, setError] = useState(null);
  
  const handleFileChange = (e) => {
    const selectedFile = e.target.files[0];
    
    // Validate file
    if (!selectedFile) return;
    
    if (selectedFile.size > 100 * 1024 * 1024) {
      setError('File too large. Maximum size is 100MB.');
      return;
    }
    
    if (!selectedFile.type.startsWith('video/')) {
      setError('Please select a video file.');
      return;
    }
    
    setFile(selectedFile);
    setError(null);
  };
  
  const handleAnalyze = async () => {
    if (!file) return;
    
    setLoading(true);
    setError(null);
    setProgress(0);
    
    try {
      const results = await client.analyzeVideo(file, {
        pollingInterval: 3000,
        onProgress: (attempt) => {
          setProgress(Math.min(attempt * 10, 90));
        }
      });
      
      setProgress(100);
      setVitals(results);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };
  
  return (
    <div className="video-analyzer">
      <h2>rPPG Video Analysis</h2>
      
      <input
        type="file"
        accept="video/*"
        onChange={handleFileChange}
        disabled={loading}
      />
      
      {file && (
        <button onClick={handleAnalyze} disabled={loading}>
          {loading ? 'Analyzing...' : 'Analyze Video'}
        </button>
      )}
      
      {loading && (
        <div className="progress">
          <div className="progress-bar" style={{ width: `${progress}%` }}>
            {progress}%
          </div>
        </div>
      )}
      
      {error && (
        <div className="error">
          <strong>Error:</strong> {error}
        </div>
      )}
      
      {vitals && (
        <div className="results">
          <h3>Results</h3>
          <ul>
            <li>Heart Rate: {vitals.heart_rate.toFixed(1)} bpm</li>
            <li>Respiratory Rate: {vitals.respiratory_rate.toFixed(1)} breaths/min</li>
            <li>HRV: {vitals.hrv?.toFixed(1) || 'N/A'}</li>
            <li>Confidence: {(vitals.confidence * 100).toFixed(1)}%</li>
          </ul>
        </div>
      )}
    </div>
  );
}

export default VideoAnalyzer;

React Hook

import { useState, useCallback } from 'react';
import { RPPGClient } from 'rppg-api-client';

function useRPPGAnalysis(apiKey, baseUrl) {
  const [loading, setLoading] = useState(false);
  const [progress, setProgress] = useState(0);
  const [vitals, setVitals] = useState(null);
  const [error, setError] = useState(null);
  
  const client = new RPPGClient({ apiKey, baseUrl });
  
  const analyze = useCallback(async (videoFile) => {
    setLoading(true);
    setError(null);
    setProgress(0);
    setVitals(null);
    
    try {
      const results = await client.analyzeVideo(videoFile, {
        onProgress: (attempt) => {
          setProgress(Math.min(attempt * 10, 90));
        }
      });
      
      setProgress(100);
      setVitals(results);
      return results;
    } catch (err) {
      setError(err.message);
      throw err;
    } finally {
      setLoading(false);
    }
  }, [client]);
  
  const reset = useCallback(() => {
    setLoading(false);
    setProgress(0);
    setVitals(null);
    setError(null);
  }, []);
  
  return { analyze, loading, progress, vitals, error, reset };
}

// Usage
function MyComponent() {
  const { analyze, loading, vitals, error } = useRPPGAnalysis(
    process.env.REACT_APP_API_KEY,
    '/api'
  );
  
  const handleFile = async (file) => {
    await analyze(file);
  };
  
  return <div>{/* ... */}</div>;
}

Vue Integration

Vue 3 Composition API

<template>
  <div class="video-analyzer">
    <h2>rPPG Video Analysis</h2>
    
    <input
      type="file"
      accept="video/*"
      @change="handleFileChange"
      :disabled="loading"
    />
    
    <button
      v-if="file"
      @click="analyze"
      :disabled="loading"
    >
      {{ loading ? 'Analyzing...' : 'Analyze Video' }}
    </button>
    
    <div v-if="loading" class="progress">
      <div class="progress-bar" :style="{ width: progress + '%' }">
        {{ progress }}%
      </div>
    </div>
    
    <div v-if="error" class="error">
      <strong>Error:</strong> {{ error }}
    </div>
    
    <div v-if="vitals" class="results">
      <h3>Results</h3>
      <ul>
        <li>Heart Rate: {{ vitals.heart_rate.toFixed(1) }} bpm</li>
        <li>Respiratory Rate: {{ vitals.respiratory_rate.toFixed(1) }} breaths/min</li>
        <li>HRV: {{ vitals.hrv?.toFixed(1) || 'N/A' }}</li>
        <li>Confidence: {{ (vitals.confidence * 100).toFixed(1) }}%</li>
      </ul>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { RPPGClient } from 'rppg-api-client';

const client = new RPPGClient({
  apiKey: import.meta.env.VITE_RPPG_API_KEY,
  baseUrl: '/api'
});

const file = ref(null);
const loading = ref(false);
const progress = ref(0);
const vitals = ref(null);
const error = ref(null);

const handleFileChange = (event) => {
  const selectedFile = event.target.files[0];
  
  if (!selectedFile) return;
  
  if (selectedFile.size > 100 * 1024 * 1024) {
    error.value = 'File too large. Maximum size is 100MB.';
    return;
  }
  
  file.value = selectedFile;
  error.value = null;
};

const analyze = async () => {
  if (!file.value) return;
  
  loading.value = true;
  error.value = null;
  progress.value = 0;
  
  try {
    const results = await client.analyzeVideo(file.value, {
      onProgress: (attempt) => {
        progress.value = Math.min(attempt * 10, 90);
      }
    });
    
    progress.value = 100;
    vitals.value = results;
  } catch (err) {
    error.value = err.message;
  } finally {
    loading.value = false;
  }
};
</script>

Node.js Backend

Express.js Server

const express = require('express');
const multer = require('multer');
const { RPPGClient } = require('rppg-api-client');

const app = express();
const upload = multer({ dest: 'uploads/', limits: { fileSize: 100 * 1024 * 1024 } });

const client = new RPPGClient({
  apiKey: process.env.RPPG_API_KEY,
  baseUrl: 'https://api.yourdomain.com/api'
});

// Analyze video endpoint
app.post('/api/analyze', upload.single('video'), async (req, res) => {
  try {
    if (!req.file) {
      return res.status(400).json({ error: 'No video file provided' });
    }
    
    const vitals = await client.analyzeVideo(req.file);
    
    res.json({
      success: true,
      data: vitals
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      error: error.message
    });
  }
});

// Get status endpoint
app.get('/api/status/:sessionId', async (req, res) => {
  try {
    const result = await client.getVitals(req.params.sessionId);
    res.json(result);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Python Backend

Flask Server

from flask import Flask, request, jsonify
import requests

app = Flask(__name__)

API_KEY = 'your-api-key'
API_BASE_URL = 'https://api.yourdomain.com/api'

@app.route('/api/analyze', methods=['POST'])
def analyze_video():
    if 'video' not in request.files:
        return jsonify({'error': 'No video file provided'}), 400
    
    video_file = request.files['video']
    
    try:
        # Create session
        session_response = requests.post(
            f'{API_BASE_URL}/v1/scan-session',
            headers={'Authorization': f'Bearer {API_KEY}'}
        )
        session_response.raise_for_status()
        session_id = session_response.json()['session_id']
        
        # Upload video
        upload_response = requests.post(
            f'{API_BASE_URL}/v1/scan-session/{session_id}/video',
            headers={'Authorization': f'Bearer {API_KEY}'},
            files={'file': video_file}
        )
        upload_response.raise_for_status()
        
        # Poll for results
        import time
        max_attempts = 40
        for attempt in range(max_attempts):
            vitals_response = requests.get(
                f'{API_BASE_URL}/v1/scan-session/{session_id}/vitals',
                headers={'Authorization': f'Bearer {API_KEY}'}
            )
            vitals_response.raise_for_status()
            result = vitals_response.json()
            
            if result['status'] == 'completed':
                return jsonify({'success': True, 'data': result['vitals']})
            elif result['status'] == 'failed':
                return jsonify({'success': False, 'error': result.get('error')}), 500
            
            time.sleep(3)
        
        return jsonify({'success': False, 'error': 'Timeout'}), 408
        
    except Exception as e:
        return jsonify({'success': False, 'error': str(e)}), 500

if __name__ == '__main__':
    app.run(port=3000)

File Upload UI

HTML + Vanilla JavaScript

<!DOCTYPE html>
<html>
<head>
  <title>rPPG Analysis</title>
  <style>
    .container { max-width: 600px; margin: 50px auto; }
    .progress { width: 100%; height: 30px; background: #f0f0f0; }
    .progress-bar { height: 100%; background: #4CAF50; transition: width 0.3s; }
    .results { margin-top: 20px; padding: 20px; background: #f9f9f9; }
  </style>
</head>
<body>
  <div class="container">
    <h1>rPPG Video Analysis</h1>
    
    <input type="file" id="videoInput" accept="video/*" />
    <button id="analyzeBtn">Analyze Video</button>
    
    <div id="progress" class="progress" style="display:none;">
      <div id="progressBar" class="progress-bar"></div>
    </div>
    
    <div id="results" class="results" style="display:none;"></div>
    <div id="error" style="color:red; margin-top:10px;"></div>
  </div>
  
  <script type="module">
    import { RPPGClient } from 'rppg-api-client';
    
    const client = new RPPGClient({
      apiKey: 'your-api-key',
      baseUrl: '/api'
    });
    
    document.getElementById('analyzeBtn').addEventListener('click', async () => {
      const fileInput = document.getElementById('videoInput');
      const file = fileInput.files[0];
      
      if (!file) {
        alert('Please select a video file');
        return;
      }
      
      const progressDiv = document.getElementById('progress');
      const progressBar = document.getElementById('progressBar');
      const resultsDiv = document.getElementById('results');
      const errorDiv = document.getElementById('error');
      
      progressDiv.style.display = 'block';
      resultsDiv.style.display = 'none';
      errorDiv.textContent = '';
      
      try {
        const vitals = await client.analyzeVideo(file, {
          onProgress: (attempt) => {
            const progress = Math.min(attempt * 10, 90);
            progressBar.style.width = progress + '%';
            progressBar.textContent = progress + '%';
          }
        });
        
        progressBar.style.width = '100%';
        progressBar.textContent = '100%';
        
        resultsDiv.innerHTML = `
          <h3>Results</h3>
          <p><strong>Heart Rate:</strong> ${vitals.heart_rate.toFixed(1)} bpm</p>
          <p><strong>Respiratory Rate:</strong> ${vitals.respiratory_rate.toFixed(1)} breaths/min</p>
          <p><strong>HRV:</strong> ${vitals.hrv?.toFixed(1) || 'N/A'}</p>
          <p><strong>Confidence:</strong> ${(vitals.confidence * 100).toFixed(1)}%</p>
        `;
        resultsDiv.style.display = 'block';
        
      } catch (error) {
        errorDiv.textContent = 'Error: ' + error.message;
      } finally {
        progressDiv.style.display = 'none';
      }
    });
  </script>
</body>
</html>

Real-time Camera

Capture and Analyze from Webcam

class WebcamAnalyzer {
  constructor(apiKey, baseUrl) {
    this.client = new RPPGClient({ apiKey, baseUrl });
    this.mediaRecorder = null;
    this.chunks = [];
  }
  
  async startCamera(videoElement) {
    const stream = await navigator.mediaDevices.getUserMedia({
      video: { facingMode: 'user' },
      audio: false
    });
    
    videoElement.srcObject = stream;
    return stream;
  }
  
  startRecording(stream, duration = 10000) {
    return new Promise((resolve, reject) => {
      this.chunks = [];
      this.mediaRecorder = new MediaRecorder(stream);
      
      this.mediaRecorder.ondataavailable = (e) => {
        if (e.data.size > 0) {
          this.chunks.push(e.data);
        }
      };
      
      this.mediaRecorder.onstop = () => {
        const blob = new Blob(this.chunks, { type: 'video/webm' });
        resolve(blob);
      };
      
      this.mediaRecorder.onerror = reject;
      
      this.mediaRecorder.start();
      
      // Auto-stop after duration
      setTimeout(() => {
        if (this.mediaRecorder.state === 'recording') {
          this.mediaRecorder.stop();
        }
      }, duration);
    });
  }
  
  stopRecording() {
    if (this.mediaRecorder && this.mediaRecorder.state === 'recording') {
      this.mediaRecorder.stop();
    }
  }
  
  async analyze(videoBlob) {
    const file = new File([videoBlob], 'recording.webm', { type: 'video/webm' });
    return await this.client.analyzeVideo(file);
  }
}

// Usage
const analyzer = new WebcamAnalyzer('your-api-key', '/api');

// Start camera
const videoElement = document.querySelector('video');
await analyzer.startCamera(videoElement);

// Record for 10 seconds
const recordingBlob = await analyzer.startRecording(stream, 10000);

// Analyze
const vitals = await analyzer.analyze(recordingBlob);
console.log('Heart Rate:', vitals.heart_rate);

Next Steps

Have questions? Contact us at support@circadify.com