๐ป Code Examples
Practical code examples for common use cases and integration patterns.
Table of Contents
- Basic Video Analysis
- Error Handling
- Progress Tracking
- Batch Processing
- React Integration
- Vue Integration
- Node.js Backend
- Python Backend
- File Upload UI
- Real-time Camera
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
- Webhooks โ - Set up event notifications
- Testing & Sandbox โ - Test your integration
- Rate Limits โ - Understand API quotas