Video Recording
The video recording module provides the ability to record video data from any camera topic and save it as MP4 files directly on the robot appliance. This feature is designed to work independently of live video streaming and supports multiple concurrent recordings from different camera topics.
Key Features
- Multi-topic recording - Record from multiple camera topics simultaneously
- Quality control - Choose from low, medium, or high quality presets
- Auto-stop protection - Configurable maximum recording duration to prevent runaway recordings
- Completion notifications - Get notified when recordings finish via callback
- File management - List, download, and delete recorded MP4 files
- Real-time control - Start and stop recordings dynamically
Quality Settings
| Quality | Resolution | Use Case | File Size (approx.) |
|---|---|---|---|
low | 640x480 | Quick recording, small files | ~5-10 MB/min |
medium | 1280x720 | Balanced quality/size (recommended) | ~15-25 MB/min |
high | 1920x1080 | Best quality, larger files | ~30-50 MB/min |
startRecording(topic, options)
Start recording video from a camera topic.
Parameters:
topic(string) - ROS camera topic to record from (e.g., '/camera/image_raw/compressed')options(object, optional) - Recording optionsquality(string) - Recording quality: 'low', 'medium', 'high' (default: 'medium')filename(string) - Custom filename (optional, auto-generated if not provided)maxDurationSeconds(number) - Maximum recording duration in seconds (optional, auto-stops recording)onCompleted(function) - Callback when recording completes (optional)
Returns: Promise<Object> - Recording session information
// Basic recording with auto-detected topic
const { bestTopic } = await oloClient.video.detectVideoTopics();
const recording = await oloClient.videoRecording.startRecording(bestTopic);
// Advanced recording with custom options and completion callback
const recording = await oloClient.videoRecording.startRecording('/camera/image_raw/compressed', {
quality: 'high',
filename: 'my_recording.mp4',
maxDurationSeconds: 60, // Auto-stop after 1 minute
onCompleted: (event) => {
console.log('🎬 Recording completed!');
console.log('File:', event.filename);
console.log('Duration:', event.duration);
console.log('Size:', Math.round(event.fileSize / 1024 / 1024) + ' MB');
console.log('Reason:', event.reason); // 'manual', 'auto-timeout', etc.
}
});
console.log('Recording started:', recording.recordingId);
console.log('Filename:', recording.filename);
console.log('Will auto-stop at:', new Date(Date.now() + 60000).toLocaleTimeString());stopRecording(recordingId)
Stop a specific recording by its ID.
Parameters:
recordingId(string) - ID of the recording to stop (obtained fromstartRecording())
Returns: Promise<Object> - Recording completion information
// Stop a specific recording
const result = await oloClient.videoRecording.stopRecording(recording.recordingId);
console.log('Recording stopped:', result.filename);
console.log('Duration:', result.duration);
console.log('Frame count:', result.frameCount);
console.log('File size:', Math.round(result.fileSize / 1024 / 1024) + ' MB');stopRecordingsByTopic(topic)
Stop all recordings for a specific camera topic.
Parameters:
topic(string) - ROS camera topic to stop recordings for
Returns: Promise<Array> - Array of stopped recording information
// Stop all recordings for a specific topic
const results = await oloClient.videoRecording.stopRecordingsByTopic('/camera/image_raw/compressed');
console.log(`Stopped ${results.length} recording(s):`);
results.forEach(result => {
if (result.success !== false) {
console.log(` ${result.filename} - ${result.frameCount} frames, ${result.duration}`);
} else {
console.log(` Failed: ${result.error}`);
}
});stopAllRecordings()
Stop all active recordings across all topics.
Returns: Promise<Array> - Array of stopped recording information
// Emergency stop - stop all recordings
const results = await oloClient.videoRecording.stopAllRecordings();
if (results.length === 0) {
console.log('No active recordings to stop');
} else {
console.log(`Stopped ${results.length} recording(s)`);
results.forEach(result => {
console.log(` ${result.filename} - ${Math.round(result.fileSize / 1024 / 1024)} MB`);
});
}getActiveRecordings()
Get list of currently active recordings.
Returns: Promise<Array> - Array of active recording information
const activeRecordings = await oloClient.videoRecording.getActiveRecordings();
console.log(`Active recordings: ${activeRecordings.length}`);
activeRecordings.forEach(rec => {
console.log(` ${rec.topic} - ${rec.duration} (${rec.frameCount} frames)`);
console.log(` File: ${rec.filename}`);
console.log(` Quality: ${rec.quality}`);
});getRecordedFiles()
Get list of recorded MP4 files stored on the appliance.
Returns: Promise<Array> - Array of recorded file information
const files = await oloClient.videoRecording.getRecordedFiles();
console.log(`Found ${files.length} recorded files:`);
files.forEach(file => {
const date = new Date(file.created).toLocaleString();
console.log(` ${file.filename} (${file.sizeFormatted}) - ${date}`);
});
// Calculate total storage used
const totalSize = files.reduce((sum, f) => sum + f.size, 0);
console.log(`Total storage used: ${Math.round(totalSize / 1024 / 1024)} MB`);deleteRecording(filename)
Delete a recorded MP4 file from the appliance.
Parameters:
filename(string) - Name of the file to delete (fromgetRecordedFiles())
Returns: Promise<Object> - Deletion confirmation
// Delete a specific recording
await oloClient.videoRecording.deleteRecording('old_recording.mp4');
console.log('Recording deleted successfully');
// Delete old recordings (older than 1 hour)
const files = await oloClient.videoRecording.getRecordedFiles();
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);
const oldFiles = files.filter(file => new Date(file.created) < oneHourAgo);
for (const file of oldFiles.slice(0, 5)) { // Delete up to 5 old files
try {
await oloClient.videoRecording.deleteRecording(file.filename);
console.log(`Deleted: ${file.filename}`);
} catch (error) {
console.error(`Failed to delete ${file.filename}:`, error.message);
}
}Recording Completion Callbacks
The video recording system supports completion callbacks that are triggered when recordings finish, whether by manual stop, auto-timeout, or system events.
Callback Event Object:
{
recordingId: "uuid-string",
topic: "/camera/image_raw/compressed",
filename: "recording_2024-01-15_10-30-00.mp4",
filePath: "/path/to/recording.mp4",
startTime: "2024-01-15T10:30:00.000Z",
endTime: "2024-01-15T10:31:30.000Z",
frameCount: 1800,
fileSize: 25600000, // bytes
duration: "90s",
reason: "auto-timeout" // or "manual", "stop-all"
}Usage Examples:
// Simple completion notification
await oloClient.videoRecording.startRecording(topic, {
maxDurationSeconds: 30,
onCompleted: (event) => {
console.log(`Recording saved: ${event.filename} (${event.duration})`);
}
});
// Advanced completion handling
await oloClient.videoRecording.startRecording(topic, {
maxDurationSeconds: 120,
onCompleted: (event) => {
// Log completion details
console.log('📹 Recording completed!');
console.log(` File: ${event.filename}`);
console.log(` Duration: ${event.duration}`);
console.log(` Size: ${Math.round(event.fileSize / 1024 / 1024)} MB`);
console.log(` Frames: ${event.frameCount}`);
// Handle different completion reasons
if (event.reason === 'auto-timeout') {
console.log('⏰ Recording stopped automatically after timeout');
} else if (event.reason === 'manual') {
console.log('🛑 Recording stopped manually');
}
// Trigger additional actions
// e.g., upload to cloud, send notification, etc.
}
});
// Error handling in callbacks
await oloClient.videoRecording.startRecording(topic, {
onCompleted: (event) => {
try {
// Your completion logic here
processRecording(event);
} catch (error) {
console.error('Error processing completed recording:', error);
}
}
});When Callbacks Are Triggered:
- ✅ Auto-timeout completion - When
maxDurationSecondsexpires - ✅ Manual stop - When
stopRecording(recordingId)is called - ✅ Topic stop - When
stopRecordingsByTopic(topic)is called - ✅ Bulk stop - When
stopAllRecordings()is called - ✅ System events - When appliance stops recordings due to errors or shutdown
Complete Video Recording Workflow
// Complete workflow: detect topic, record, and manage files
async function recordVideoWorkflow() {
try {
// Step 1: Auto-detect best camera topic
console.log('🔍 Detecting video topics...');
const { bestTopic, videoTopics } = await oloClient.video.detectVideoTopics();
if (!bestTopic) {
console.error('No video topics found');
return;
}
console.log(`Found ${videoTopics.length} video topics, using: ${bestTopic}`);
// Step 2: Start recording with auto-stop protection and completion callback
console.log('🎬 Starting recording...');
const recording = await oloClient.videoRecording.startRecording(bestTopic, {
quality: 'medium',
maxDurationSeconds: 30, // Auto-stop after 30 seconds
onCompleted: (event) => {
console.log(`🎉 Recording completed: ${event.filename}`);
console.log(`📊 Final stats: ${event.frameCount} frames, ${event.duration}`);
// Optional: Trigger additional processing
// uploadToCloud(event.filename);
// sendNotification(`Recording ready: ${event.filename}`);
}
});
console.log(`Recording started: ${recording.filename}`);
console.log(`Recording ID: ${recording.recordingId}`);
// Step 3: Recording will complete automatically and trigger callback
console.log('⏳ Recording in progress... (will auto-stop in 30s and trigger callback)');
// Optional: Stop early if needed
// await new Promise(resolve => setTimeout(resolve, 10000)); // Wait 10s
// await oloClient.videoRecording.stopRecording(recording.recordingId);
// The completion callback will handle final processing automatically
} catch (error) {
if (error.code === 'TOPIC_ALREADY_RECORDING') {
console.log('⚠️ Topic is already being recorded');
console.log('Existing recording:', error.existingRecording);
console.log('💡 Stop the existing recording first or choose a different topic');
} else {
console.error('Recording workflow failed:', error.message);
}
}
}
// Run the workflow
recordVideoWorkflow();Error Handling
The video recording system provides comprehensive error handling for common scenarios:
try {
const recording = await oloClient.videoRecording.startRecording('/camera/topic');
} catch (error) {
if (error.code === 'TOPIC_ALREADY_RECORDING') {
// Topic is already being recorded
console.log('Topic already recording:', error.existingRecording);
// Options:
// 1. Stop existing recording first
await oloClient.videoRecording.stopRecordingsByTopic('/camera/topic');
// 2. Or choose a different topic
const { videoTopics } = await oloClient.video.detectVideoTopics();
const alternateTopic = videoTopics.find(t => t.name !== '/camera/topic');
} else if (error.message.includes('Topic is required')) {
console.error('Must specify a valid camera topic');
} else if (error.message.includes('Not connected')) {
console.error('Robot not connected - connect first');
} else {
console.error('Recording failed:', error.message);
}
}Configuration and Storage
Video recordings are stored on the robot appliance with configurable settings:
Storage Location: /recordings/ directory on the appliance
File Format: MP4 with H.264 encoding for universal compatibility
Naming Convention: Auto-generated timestamps or custom filenames
Quality Control: Three presets optimized for different use cases
Appliance Configuration (in appliance config.js):
videoRecording: {
framerate: 15, // Recording framerate (fps)
defaultQuality: 'medium', // Default recording quality
maxDurationSeconds: 300, // Default max recording duration (5 minutes)
directory: 'recordings' // Recording storage directory
}Best Practices
1. Use Auto-Stop Protection:
// Always set maxDurationSeconds to prevent runaway recordings
const recording = await oloClient.videoRecording.startRecording(topic, {
maxDurationSeconds: 60 // Prevents accidental long recordings
});2. Check for Existing Recordings:
// Check active recordings before starting new ones
const active = await oloClient.videoRecording.getActiveRecordings();
const topicAlreadyRecording = active.some(rec => rec.topic === targetTopic);
if (topicAlreadyRecording) {
console.log('Topic already being recorded, stopping existing first...');
await oloClient.videoRecording.stopRecordingsByTopic(targetTopic);
}3. Manage Storage Space:
// Regular cleanup of old recordings
const files = await oloClient.videoRecording.getRecordedFiles();
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);
const oldFiles = files.filter(file => new Date(file.created) < oneHourAgo);
console.log(`Cleaning up ${oldFiles.length} old recordings...`);
for (const file of oldFiles) {
await oloClient.videoRecording.deleteRecording(file.filename);
}4. Quality Selection Guidelines:
- Low quality - Use for long-duration recordings or limited storage
- Medium quality - Recommended for most use cases (good balance)
- High quality - Use for detailed analysis or short, important recordings
5. Topic Selection:
// Use video topic detection for best results
const { bestTopic, videoTopics } = await oloClient.video.detectVideoTopics();
// Filter for specific camera types if needed
const frontCamera = videoTopics.find(t => t.name.includes('front'));
const depthCamera = videoTopics.find(t => t.name.includes('depth'));