Skip to main content

Streaming Video Implementation

This guide covers how to integrate Rhombus camera video streaming and media embedding capabilities into your applications. Whether you need real-time streaming, thumbnail previews, or recorded video playback, this implementation guide provides the tools and techniques you need.

Overview

Rhombus cameras provide multiple ways to access video content:
  • Live streaming for real-time monitoring
  • Thumbnail images for quick previews and dashboards
  • Recorded video clips for historical playback
  • Snapshot captures for specific moments

Getting Camera Thumbnails

All Rhombus cameras automatically upload thumbnail images that provide current snapshots of what the camera sees. These thumbnails are perfect for dashboard previews, monitoring interfaces, and quick status checks.

Thumbnail Endpoint

Retrieve camera thumbnails using a GET request to:
https://media.rhombussystems.com/media/{cameraUuid}/{mediaRegion}/snapshot.jpeg
All media.rhombussystems.com endpoints use the same authentication as the main API, so be sure to include the authentication headers!
To get the cameraUuid and mediaRegion for a camera, use the /camera/getMinimalCameraState endpoint to get a list of all cameras. In the response, you’ll find the uuid and mediaRegion fields.

Basic Implementation

async function getCameraThumbnail(cameraUuid, mediaRegion, apiKey) {
  try {
    const response = await fetch(`https://media.rhombussystems.com/media/${cameraUuid}/${mediaRegion}/snapshot.jpeg`, {
      method: 'GET',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json'
      }
    });
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const imageBlob = await response.blob();
    return URL.createObjectURL(imageBlob);
  } catch (error) {
    console.error('Error fetching thumbnail:', error);
    throw error;
  }
}

// First, get camera details
async function getCameraDetails(apiKey) {
  const response = await fetch('https://api.rhombussystems.com/camera/getMinimalCameraState', {
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json'
    }
  });
  return response.json();
}

// Usage example
const cameras = await getCameraDetails('your-api-key');
const camera = cameras[0]; // Get first camera
const thumbnailUrl = await getCameraThumbnail(camera.uuid, camera.mediaRegion, 'your-api-key');
document.getElementById('camera-preview').src = thumbnailUrl;

Getting Frames at Specific Times

To get a JPEG image at a specified time, use the /video/getExactFrameUri endpoint to generate a frame URI. Execute a GET request to the returned frame URI with the same authentication headers to get the requested frame directly from the camera.
async function getFrameAtTime(cameraUuid, timestamp, apiKey) {
  // Get frame URI
  const frameUriResponse = await fetch('https://api.rhombussystems.com/video/getExactFrameUri', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      cameraUuid: cameraUuid,
      timestamp: timestamp,
      crop: false // Set to true if you want a cropped image
    })
  });
  
  const frameData = await frameUriResponse.json();
  
  // Get the actual frame
  const frameResponse = await fetch(frameData.frameUri, {
    headers: {
      'Authorization': `Bearer ${apiKey}`
    }
  });
  
  return frameResponse.blob();
}

Live Video Streaming

Rhombus provides multiple ways to embed and stream live video content in your applications. The most common and recommended way is to use iframes to embed the Rhombus video player directly in your site. This comes with the added benefit that camera events and analytics are pulled automatically into the player. You will need to create shared streams for each camera that you want to embed. This can be done in the console, or from the API using the /api/camera/createSharedLiveVideoStream endpoint.
async function createSharedStream(cameraUuid, apiKey) {
  const response = await fetch('https://api.rhombussystems.com/api/camera/createSharedLiveVideoStream', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      cameraUuid: cameraUuid,
      // Additional configuration options
    })
  });
  
  const streamData = await response.json();
  return streamData.url; // This is your shared stream URL
}
The response includes a URL that you can add to an iframe and embed in your application:
<iframe 
  allow="fullscreen" 
  frameborder="0" 
  height="100%" 
  width="100%" 
  src="SHARED_STREAM_URL_GOES_HERE">
</iframe>

React Component Example

Here’s a React component for embedding shared streams:
import React, { useState, useEffect } from 'react';

const RhombusSharedStream = ({ 
  cameraUuid, 
  apiKey, 
  width = "100%", 
  height = "400px",
  options = {} 
}) => {
  const [streamUrl, setStreamUrl] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    createSharedStream();
  }, [cameraUuid]);

  const createSharedStream = async () => {
    try {
      setLoading(true);
      const response = await fetch('https://api.rhombussystems.com/api/camera/createSharedLiveVideoStream', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${apiKey}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          cameraUuid: cameraUuid
        })
      });

      if (!response.ok) {
        throw new Error('Failed to create shared stream');
      }

      const streamData = await response.json();
      
      // Add URL parameters if specified
      const url = new URL(streamData.url);
      Object.entries(options).forEach(([key, value]) => {
        url.searchParams.set(key, value);
      });
      
      setStreamUrl(url.toString());
      setError(null);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  if (loading) return <div>Loading stream...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <iframe
      src={streamUrl}
      width={width}
      height={height}
      frameBorder="0"
      allow="fullscreen"
      title={`Camera ${cameraUuid} Stream`}
    />
  );
};

export default RhombusSharedStream;

URL Parameters for Enhanced Control

You can customize the embedded player using URL parameters. All parameters can be set to true or false:
ParameterDescription
disableautoplayEnable or disable video autoplay upon loading
hideeventsEnable or disable the timeline and related events
realtimeEnable or disable real-time streaming by default
showheaderEnable or disable zoom & streaming buttons at the top
showfooterEnable or disable camera name and timestamp at bottom
lowResForce low resolution on the video stream
performanceModeForce performance mode allowing GPU usage
tTimestamp (epoch milliseconds) where video should start

Parameter Usage

Follow this structure: https://{{url}}/?{{variable}}=true&{{variable}}=false Example:
https://console.rhombussystems.com/share/live/ehrF58ABSj2VT8QzXh7GVA?disableautoplay=true&hideevents=true&realtime=true

React Usage with Parameters

// Usage with custom parameters
<RhombusSharedStream 
  cameraUuid="your-camera-uuid"
  apiKey="your-api-key"
  options={{
    disableautoplay: 'true',
    hideevents: 'true',
    realtime: 'true',
    showheader: 'false',
    lowRes: 'false'
  }}
/>

Accessing Raw MPEG-DASH Streams

With API authentication, you can also access the raw MPEG-DASH stream for use with any standard player. To keep the API token secure and resolve CORS issues, we recommend using a server middleman that acts as a proxy.
// Server-side proxy example (Node.js/Express)
app.get('/stream-proxy/:cameraUuid', async (req, res) => {
  const { cameraUuid } = req.params;
  const apiKey = process.env.RHOMBUS_API_KEY; // Keep API key server-side
  
  try {
    // Get DASH stream URL from Rhombus API
    const streamResponse = await fetch(`https://api.rhombussystems.com/api/camera/getDashStream`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ cameraUuid })
    });
    
    const streamData = await streamResponse.json();
    
    // Proxy the stream to client
    res.json({ streamUrl: streamData.dashUrl });
  } catch (error) {
    res.status(500).json({ error: 'Failed to get stream' });
  }
});
Client-side usage with dash.js:
import dashjs from 'dashjs';

async function setupDashStream(cameraUuid, videoElementId) {
  // Get stream URL from your proxy
  const response = await fetch(`/stream-proxy/${cameraUuid}`);
  const { streamUrl } = await response.json();
  
  const videoElement = document.getElementById(videoElementId);
  const player = dashjs.MediaPlayer().create();
  
  player.initialize(videoElement, streamUrl, true);
  
  return player;
}

Recorded Video Playback

Access recorded video clips for historical analysis and review.

Downloading VOD Clips as MP4s

You can download Video on Demand (VOD) footage directly from a camera by specifying the start time and duration to access footage.
async function downloadVODClip(cameraUuid, startTime, durationSeconds, apiKey) {
  const response = await fetch('https://api.rhombussystems.com/api/video/downloadClip', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      cameraUuid: cameraUuid,
      startTime: startTime, // Unix timestamp in milliseconds
      duration: durationSeconds * 1000, // Duration in milliseconds
      format: 'mp4'
    })
  });
  
  if (!response.ok) {
    throw new Error(`Failed to download clip: ${response.statusText}`);
  }
  
  // Return the blob for download
  return response.blob();
}

// Usage example
async function downloadAndSaveClip() {
  const startTime = new Date('2024-01-01T12:00:00Z').getTime();
  const duration = 300; // 5 minutes
  
  try {
    const clipBlob = await downloadVODClip('camera-uuid', startTime, duration, 'your-api-key');
    
    // Create download link
    const url = URL.createObjectURL(clipBlob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `camera-clip-${startTime}.mp4`;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
  } catch (error) {
    console.error('Error downloading clip:', error);
  }
}

Python Example for VOD Download

import requests
from datetime import datetime
import time

def download_vod_clip(camera_uuid, start_time, duration_seconds, api_key, output_filename):
    """
    Download a VOD clip from Rhombus camera
    
    Args:
        camera_uuid (str): Camera UUID
        start_time (datetime): Start time for the clip
        duration_seconds (int): Duration in seconds
        api_key (str): Rhombus API key
        output_filename (str): Output filename for the MP4
    """
    url = "https://api.rhombussystems.com/api/video/downloadClip"
    
    # Convert datetime to Unix timestamp in milliseconds
    start_timestamp = int(start_time.timestamp() * 1000)
    duration_ms = duration_seconds * 1000
    
    payload = {
        'cameraUuid': camera_uuid,
        'startTime': start_timestamp,
        'duration': duration_ms,
        'format': 'mp4'
    }
    
    headers = {
        'Authorization': f'Bearer {api_key}',
        'Content-Type': 'application/json'
    }
    
    response = requests.post(url, json=payload, headers=headers)
    response.raise_for_status()
    
    # Save the clip
    with open(output_filename, 'wb') as f:
        f.write(response.content)
    
    print(f"Clip saved as {output_filename}")

# Usage
start_time = datetime(2024, 1, 1, 12, 0, 0)  # January 1, 2024 at 12:00 PM
download_vod_clip('camera-uuid', start_time, 300, 'your-api-key', 'camera_clip.mp4')

Downloading Alert Clips

You can download clips corresponding to specific alerts. This is particularly useful when using webhooks to be triggered whenever an alert is created in the system.
async function downloadAlertClip(alertData, apiKey) {
  // alertData comes from webhook payload or API response
  const { cameraUuid, startTime, endTime, alertId } = alertData;
  
  const response = await fetch('https://api.rhombussystems.com/api/video/downloadAlertClip', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      cameraUuid: cameraUuid,
      alertId: alertId,
      startTime: startTime,
      endTime: endTime,
      format: 'mp4'
    })
  });
  
  if (!response.ok) {
    throw new Error(`Failed to download alert clip: ${response.statusText}`);
  }
  
  return response.blob();
}

// Webhook handler example (Express.js)
app.post('/webhook/alert', async (req, res) => {
  const alertData = req.body;
  
  try {
    // Download the clip associated with this alert
    const clipBlob = await downloadAlertClip(alertData, process.env.RHOMBUS_API_KEY);
    
    // Process or save the clip as needed
    console.log(`Downloaded clip for alert ${alertData.alertId}`);
    
    res.status(200).send('OK');
  } catch (error) {
    console.error('Error processing alert webhook:', error);
    res.status(500).send('Error');
  }
});

Getting Alert Details for Clip Download

If you’re using the API to get alert details (e.g., /api/event/getPolicyAlertsV2), you can use the response fields to construct the media request:
async function getAlertsAndDownloadClips(apiKey) {
  // Get recent alerts
  const alertsResponse = await fetch('https://api.rhombussystems.com/api/event/getPolicyAlertsV2', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      // Add your alert query parameters
      limit: 10,
      // startTime, endTime, etc.
    })
  });
  
  const alerts = await alertsResponse.json();
  
  // Download clips for each alert
  for (const alert of alerts) {
    try {
      const clipBlob = await downloadAlertClip({
        cameraUuid: alert.cameraUuid,
        alertId: alert.alertId,
        startTime: alert.startTime,
        endTime: alert.endTime
      }, apiKey);
      
      // Save or process the clip
      console.log(`Downloaded clip for alert ${alert.alertId}`);
    } catch (error) {
      console.error(`Failed to download clip for alert ${alert.alertId}:`, error);
    }
  }
}

Complete Implementation Examples

Multi-Purpose React Component

Here’s a comprehensive React component that supports thumbnails, shared streams, and video downloads:
import React, { useEffect, useState, useCallback } from 'react';

const RhombusCameraViewer = ({ 
  cameraUuid, 
  apiKey, 
  viewType = 'thumbnail', // 'thumbnail', 'stream', 'download'
  autoRefresh = true,
  refreshInterval = 5000,
  streamOptions = {},
  width = "100%",
  height = "400px"
}) => {
  const [thumbnailUrl, setThumbnailUrl] = useState(null);
  const [streamUrl, setStreamUrl] = useState(null);
  const [cameraDetails, setCameraDetails] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);
  
  // Get camera details (UUID and media region)
  const getCameraDetails = useCallback(async () => {
    try {
      const response = await fetch('https://api.rhombussystems.com/camera/getMinimalCameraState', {
        headers: {
          'Authorization': `Bearer ${apiKey}`,
          'Content-Type': 'application/json'
        }
      });
      
      if (!response.ok) throw new Error('Failed to get camera details');
      
      const cameras = await response.json();
      const camera = cameras.find(c => c.uuid === cameraUuid);
      
      if (!camera) throw new Error('Camera not found');
      
      setCameraDetails(camera);
      return camera;
    } catch (err) {
      setError(err.message);
      throw err;
    }
  }, [cameraUuid, apiKey]);
  
  // Load thumbnail
  const loadThumbnail = useCallback(async () => {
    if (!cameraDetails) return;
    
    try {
      setIsLoading(true);
      const response = await fetch(
        `https://media.rhombussystems.com/media/${cameraDetails.uuid}/${cameraDetails.mediaRegion}/snapshot.jpeg`,
        {
          headers: { 'Authorization': `Bearer ${apiKey}` }
        }
      );
      
      if (!response.ok) throw new Error('Failed to load thumbnail');
      
      const blob = await response.blob();
      const url = URL.createObjectURL(blob);
      
      // Clean up previous URL
      if (thumbnailUrl) URL.revokeObjectURL(thumbnailUrl);
      
      setThumbnailUrl(url);
      setError(null);
    } catch (err) {
      setError(err.message);
    } finally {
      setIsLoading(false);
    }
  }, [cameraDetails, apiKey, thumbnailUrl]);
  
  // Create shared stream
  const createSharedStream = useCallback(async () => {
    try {
      setIsLoading(true);
      const response = await fetch('https://api.rhombussystems.com/api/camera/createSharedLiveVideoStream', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${apiKey}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          cameraUuid: cameraUuid
        })
      });

      if (!response.ok) throw new Error('Failed to create shared stream');

      const streamData = await response.json();
      
      // Add URL parameters if specified
      const url = new URL(streamData.url);
      Object.entries(streamOptions).forEach(([key, value]) => {
        url.searchParams.set(key, value);
      });
      
      setStreamUrl(url.toString());
      setError(null);
    } catch (err) {
      setError(err.message);
    } finally {
      setIsLoading(false);
    }
  }, [cameraUuid, apiKey, streamOptions]);
  
  // Download VOD clip
  const downloadClip = async (startTime, duration) => {
    try {
      const response = await fetch('https://api.rhombussystems.com/api/video/downloadClip', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${apiKey}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          cameraUuid: cameraUuid,
          startTime: startTime,
          duration: duration * 1000,
          format: 'mp4'
        })
      });
      
      if (!response.ok) throw new Error('Failed to download clip');
      
      const blob = await response.blob();
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = `camera-${cameraUuid}-${startTime}.mp4`;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
    } catch (err) {
      setError(err.message);
    }
  };
  
  useEffect(() => {
    getCameraDetails().then(() => {
      if (viewType === 'thumbnail') {
        loadThumbnail();
        if (autoRefresh) {
          const interval = setInterval(loadThumbnail, refreshInterval);
          return () => clearInterval(interval);
        }
      } else if (viewType === 'stream') {
        createSharedStream();
      }
    });
  }, [viewType, autoRefresh, refreshInterval, getCameraDetails, loadThumbnail, createSharedStream]);
  
  if (error) {
    return (
      <div className="camera-error" style={{ padding: '20px', border: '1px solid #ff6b6b', borderRadius: '4px' }}>
        <p>Error: {error}</p>
        <button onClick={() => window.location.reload()}>Retry</button>
      </div>
    );
  }
  
  if (isLoading) {
    return <div className="loading" style={{ padding: '20px', textAlign: 'center' }}>Loading camera...</div>;
  }
  
  return (
    <div className="camera-viewer" style={{ width, height }}>
      {viewType === 'thumbnail' && thumbnailUrl && (
        <img 
          src={thumbnailUrl} 
          alt={`Camera ${cameraUuid}`}
          style={{ width: '100%', height: '100%', objectFit: 'cover' }}
        />
      )}
      
      {viewType === 'stream' && streamUrl && (
        <iframe
          src={streamUrl}
          width="100%"
          height="100%"
          frameBorder="0"
          allow="fullscreen"
          title={`Camera ${cameraUuid} Stream`}
        />
      )}
      
      {viewType === 'download' && (
        <div style={{ padding: '20px' }}>
          <h3>Download Video Clips</h3>
          <button 
            onClick={() => {
              const startTime = new Date().getTime() - (5 * 60 * 1000); // 5 minutes ago
              downloadClip(startTime, 300); // 5 minute duration
            }}
            style={{ padding: '10px 20px', margin: '5px' }}
          >
            Download Last 5 Minutes
          </button>
          <button 
            onClick={() => {
              const startTime = new Date().getTime() - (60 * 60 * 1000); // 1 hour ago
              downloadClip(startTime, 3600); // 1 hour duration
            }}
            style={{ padding: '10px 20px', margin: '5px' }}
          >
            Download Last Hour
          </button>
        </div>
      )}
    </div>
  );
};

export default RhombusCameraViewer;

Usage Examples

// Thumbnail view with auto-refresh
<RhombusCameraViewer 
  cameraUuid="your-camera-uuid"
  apiKey="your-api-key"
  viewType="thumbnail"
  autoRefresh={true}
  refreshInterval={3000}
  width="640px"
  height="480px"
/>

// Live stream with custom options
<RhombusCameraViewer 
  cameraUuid="your-camera-uuid"
  apiKey="your-api-key"
  viewType="stream"
  streamOptions={{
    disableautoplay: 'false',
    hideevents: 'false',
    realtime: 'true',
    showheader: 'true',
    showfooter: 'true'
  }}
  width="100%"
  height="600px"
/>

// Download interface
<RhombusCameraViewer 
  cameraUuid="your-camera-uuid"
  apiKey="your-api-key"
  viewType="download"
/>

Best Practices

Performance Optimization

  • Thumbnail Caching: Implement client-side caching for thumbnails to reduce API calls
  • Lazy Loading: Load video content only when needed or when in viewport
  • Quality Selection: Choose appropriate quality settings based on use case and bandwidth
  • Connection Pooling: Reuse connections for multiple API requests

Security Considerations

  • API Key Management: Store API keys securely and never expose them in client-side code
  • HTTPS Only: Always use HTTPS for all API communications
  • Token Rotation: Implement regular API key rotation
  • Access Control: Limit camera access based on user permissions

Error Handling

class RhombusVideoError extends Error {
  constructor(message, code, cameraUuid) {
    super(message);
    this.name = 'RhombusVideoError';
    this.code = code;
    this.cameraUuid = cameraUuid;
  }
}

async function robustThumbnailFetch(cameraUuid, apiKey, retries = 3) {
  for (let attempt = 1; attempt <= retries; attempt++) {
    try {
      const response = await fetch(`https://media.rhombus.com/thumbnail/${cameraUuid}`, {
        headers: { 'Authorization': `Bearer ${apiKey}` },
        timeout: 10000 // 10 second timeout
      });
      
      if (!response.ok) {
        throw new RhombusVideoError(
          `HTTP ${response.status}: ${response.statusText}`,
          response.status,
          cameraUuid
        );
      }
      
      return response.blob();
    } catch (error) {
      if (attempt === retries) {
        throw error;
      }
      
      // Exponential backoff
      await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
    }
  }
}

Accessibility

Ensure your video implementations are accessible to all users:
<video 
  controls
  aria-label={`Live feed from camera ${cameraName}`}
  poster={thumbnailUrl}
>
  <track kind="captions" src="captions.vtt" srcLang="en" label="English" />
  Your browser does not support the video tag.
</video>

Troubleshooting

Common Issues

Thumbnail Not Loading
  • Verify camera UUID is correct
  • Check API key permissions
  • Ensure camera is online and accessible
Stream Connection Failures
  • Check network connectivity
  • Verify WebRTC/HLS support in browser
  • Review firewall and proxy settings
Poor Video Quality
  • Adjust quality parameters
  • Check available bandwidth
  • Consider using adaptive bitrate streaming

Debug Mode

Enable debug logging for troubleshooting:
const DEBUG = process.env.NODE_ENV === 'development';

function debugLog(message, data = null) {
  if (DEBUG) {
    console.log(`[Rhombus Video] ${message}`, data);
  }
}

// Usage in your functions
debugLog('Fetching thumbnail', { cameraUuid, timestamp: new Date() });

Next Steps

Continue exploring Rhombus API documentation to build more advanced integrations and applications.