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

QualityResolutionUse CaseFile Size (approx.)
low640x480Quick recording, small files~5-10 MB/min
medium1280x720Balanced quality/size (recommended)~15-25 MB/min
high1920x1080Best 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 options
    • quality (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

JS
// 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 from startRecording())

Returns: Promise<Object> - Recording completion information

JS
// 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

JS
// 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

JS
// 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

JS
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

JS
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 (from getRecordedFiles())

Returns: Promise<Object> - Deletion confirmation

JS
// 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:

JS
{ 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:

JS
// 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 maxDurationSeconds expires
  • 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

JS
// 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:

JS
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):

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:

JS
// 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:

JS
// 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:

JS
// 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:

JS
// 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'));