Overview
The Rhombus video player integration enables you to:
Stream Live Video Display real-time camera feeds with adaptive bitrate streaming
Secure Authentication Use federated session tokens for time-limited, secure access
Optimized Playback Pre-configured settings optimized for security camera footage
Easy Integration Drop-in solution with minimal configuration required
Architecture
The video player implementation follows a three-tier architecture:
Frontend Requests Token
Your web application requests authentication from your backend server
Backend Proxies to Rhombus
Your server authenticates with Rhombus API using your API key and returns a federated token
Frontend Gets Media URI
Using the token, your frontend requests the camera’s streaming URI
Direct Stream Playback
DashJS player connects directly to Rhombus CDN using the authenticated URI
Prerequisites
Before implementing the video player, ensure you have:
Generate an API key from your Rhombus Console . This key will be used by your backend server to authenticate with the Rhombus API. Never expose your API key in frontend code. Always use a backend proxy server.
Obtain your camera’s UUID from the Rhombus Console or via the API: curl -X POST "https://api2.rhombussystems.com/api/camera/getCamerasWithFeatures" \
-H "x-auth-apikey: YOUR_API_KEY" \
-H "x-auth-scheme: api-token"
Set up a proxy server to handle API authentication. Your server must implement two endpoints:
Token generation endpoint (forwards to /org/generateFederatedSessionToken)
Media URI endpoint (forwards to /camera/getMediaUris)
Implementation Guide
Step 1: Set Up Your HTML Page
Create a basic HTML structure with the DashJS player:
<! DOCTYPE html >
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > Rhombus Camera Stream </ title >
< script src = "https://cdn.dashjs.org/latest/dash.all.min.js" ></ script >
< style >
body {
margin : 0 ;
padding : 20 px ;
font-family : Arial , sans-serif ;
background-color : #1a1a1a ;
}
#video-container {
max-width : 1280 px ;
margin : 0 auto ;
}
video {
width : 100 % ;
background-color : #000 ;
border-radius : 8 px ;
}
.controls {
margin-top : 20 px ;
text-align : center ;
}
button {
padding : 10 px 20 px ;
margin : 0 5 px ;
background-color : #2563EB ;
color : white ;
border : none ;
border-radius : 4 px ;
cursor : pointer ;
font-size : 14 px ;
}
button :hover {
background-color : #1D4ED8 ;
}
</ style >
</ head >
< body >
< div id = "video-container" >
< video id = "videoPlayer" controls ></ video >
< div class = "controls" >
< button onclick = " play ()" > Play </ button >
< button onclick = " pause ()" > Pause </ button >
</ div >
</ div >
< script src = "player.js" ></ script >
</ body >
</ html >
Create your player configuration file:
// Configuration - Replace with your values
const CAMERA_UUID = "YOUR_CAMERA_UUID_HERE" ;
const BASE_URL = "https://api2.rhombussystems.com/api" ;
const GET_FEDERATED_TOKEN_PATH = "/getFederatedToken" ;
const GET_MEDIA_URIS_PATH = "/getMediaUris" ;
// Global variables
let player ;
let federatedToken ;
// Initialize player on page load
document . addEventListener ( 'DOMContentLoaded' , async () => {
await initializePlayer ();
});
Step 3: Implement Authentication
Add token management functionality:
async function getFederatedSessionToken () {
try {
const response = await fetch ( ` ${ BASE_URL }${ GET_FEDERATED_TOKEN_PATH } ` , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({
durationSec: 86400 // 24 hours
})
});
if ( ! response . ok ) {
throw new Error ( `Token request failed: ${ response . status } ` );
}
const data = await response . json ();
federatedToken = data . federatedSessionToken ;
console . log ( 'Federated token obtained successfully' );
return federatedToken ;
} catch ( error ) {
console . error ( 'Error getting federated token:' , error );
throw error ;
}
}
function modifyRequestURL ( evt ) {
// Append authentication parameters to all stream requests
if ( ! federatedToken ) {
console . error ( 'No federated token available' );
return ;
}
const url = new URL ( evt . url );
url . searchParams . set ( 'x-auth-scheme' , 'federated-token' );
url . searchParams . set ( 'x-auth-ft' , federatedToken );
evt . url = url . toString ();
}
Implement media URI retrieval:
async function getMediaUri () {
try {
const response = await fetch ( ` ${ BASE_URL }${ GET_MEDIA_URIS_PATH } ` , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({
cameraUuid: CAMERA_UUID
})
});
if ( ! response . ok ) {
throw new Error ( `Media URI request failed: ${ response . status } ` );
}
const data = await response . json ();
return data . wanLiveMpd ; // DASH manifest URL
} catch ( error ) {
console . error ( 'Error getting media URI:' , error );
throw error ;
}
}
Step 5: Initialize DashJS Player
Set up the video player with optimized settings:
async function initializePlayer () {
try {
// Get authentication token
await getFederatedSessionToken ();
// Get media stream URL
const mediaUrl = await getMediaUri ();
// Initialize DashJS player
const video = document . querySelector ( "#videoPlayer" );
player = dashjs . MediaPlayer (). create ();
// Configure player settings optimized for security cameras
player . updateSettings ({
streaming: {
liveCatchup: {
enabled: true ,
mode: 'liveCatchupModeDefault' ,
maxDrift: 10 ,
playbackRate: {
min: - 0.5 ,
max: 0.5
}
},
buffer: {
fastSwitchEnabled: false ,
stableBufferTime: 12 ,
bufferTimeAtTopQuality: 30 ,
bufferTimeAtTopQualityLongForm: 60
},
gaps: {
jumpGaps: true ,
jumpLargeGaps: true ,
smallGapLimit: 1.5
},
stallThreshold: 0.5
},
streaming: {
scheduleWhilePaused: false
}
});
// Set up URL modifier for authentication
player . on ( dashjs . MediaPlayer . events . FRAGMENT_LOADING_STARTED ,
modifyRequestURL );
// Initialize and play
player . initialize ( video , mediaUrl , true );
console . log ( 'Player initialized successfully' );
} catch ( error ) {
console . error ( 'Error initializing player:' , error );
alert ( 'Failed to initialize video player. Check console for details.' );
}
}
Step 6: Add Playback Controls
Implement play/pause functionality:
function play () {
if ( player ) {
player . play ();
}
}
function pause () {
if ( player ) {
player . pause ();
}
}
// Optional: Handle player events
function setupPlayerEvents () {
player . on ( dashjs . MediaPlayer . events . PLAYBACK_STARTED , () => {
console . log ( 'Playback started' );
});
player . on ( dashjs . MediaPlayer . events . PLAYBACK_PAUSED , () => {
console . log ( 'Playback paused' );
});
player . on ( dashjs . MediaPlayer . events . ERROR , ( e ) => {
console . error ( 'Player error:' , e );
});
}
Backend Server Implementation
Your backend server must proxy requests to the Rhombus API. Here’s an example using Node.js/Express:
Node.js/Express
Python/Flask
const express = require ( 'express' );
const axios = require ( 'axios' );
const app = express ();
app . use ( express . json ());
const RHOMBUS_API_KEY = process . env . RHOMBUS_API_KEY ;
const RHOMBUS_BASE_URL = 'https://api2.rhombussystems.com/api' ;
// Federated token endpoint
app . post ( '/api/getFederatedToken' , async ( req , res ) => {
try {
const response = await axios . post (
` ${ RHOMBUS_BASE_URL } /org/generateFederatedSessionToken` ,
{ durationSec: req . body . durationSec || 86400 },
{
headers: {
'x-auth-apikey' : RHOMBUS_API_KEY ,
'x-auth-scheme' : 'api-token' ,
'Content-Type' : 'application/json'
}
}
);
res . json ( response . data );
} catch ( error ) {
console . error ( 'Error generating token:' , error );
res . status ( 500 ). json ({ error: 'Failed to generate token' });
}
});
// Media URI endpoint
app . post ( '/api/getMediaUris' , async ( req , res ) => {
try {
const response = await axios . post (
` ${ RHOMBUS_BASE_URL } /camera/getMediaUris` ,
{ cameraUuid: req . body . cameraUuid },
{
headers: {
'x-auth-apikey' : RHOMBUS_API_KEY ,
'x-auth-scheme' : 'api-token' ,
'Content-Type' : 'application/json'
}
}
);
res . json ( response . data );
} catch ( error ) {
console . error ( 'Error getting media URIs:' , error );
res . status ( 500 ). json ({ error: 'Failed to get media URIs' });
}
});
app . listen ( 3000 , () => {
console . log ( 'Server running on port 3000' );
});
from flask import Flask, request, jsonify
import requests
import os
app = Flask( __name__ )
RHOMBUS_API_KEY = os.environ.get( 'RHOMBUS_API_KEY' )
RHOMBUS_BASE_URL = 'https://api2.rhombussystems.com/api'
@app.route ( '/api/getFederatedToken' , methods = [ 'POST' ])
def get_federated_token ():
try :
data = request.get_json()
duration = data.get( 'durationSec' , 86400 )
response = requests.post(
f ' { RHOMBUS_BASE_URL } /org/generateFederatedSessionToken' ,
json = { 'durationSec' : duration},
headers = {
'x-auth-apikey' : RHOMBUS_API_KEY ,
'x-auth-scheme' : 'api-token' ,
'Content-Type' : 'application/json'
}
)
response.raise_for_status()
return jsonify(response.json())
except Exception as e:
return jsonify({ 'error' : str (e)}), 500
@app.route ( '/api/getMediaUris' , methods = [ 'POST' ])
def get_media_uris ():
try :
data = request.get_json()
camera_uuid = data.get( 'cameraUuid' )
response = requests.post(
f ' { RHOMBUS_BASE_URL } /camera/getMediaUris' ,
json = { 'cameraUuid' : camera_uuid},
headers = {
'x-auth-apikey' : RHOMBUS_API_KEY ,
'x-auth-scheme' : 'api-token' ,
'Content-Type' : 'application/json'
}
)
response.raise_for_status()
return jsonify(response.json())
except Exception as e:
return jsonify({ 'error' : str (e)}), 500
if __name__ == '__main__' :
app.run( port = 3000 )
Player Configuration Options
The DashJS player can be customized with various settings optimized for different use cases:
Enable automatic catch-up to live edge when playback falls behind
Maximum allowed drift from live edge in seconds before catch-up kicks in
Target buffer size for stable playback in seconds
Disable fast quality switching to maintain consistent video quality
Automatically skip small gaps in the stream
Time in seconds before considering playback stalled
Security Best Practices
Critical Security Requirements Follow these security practices to protect your API credentials and ensure secure streaming:
Store API keys only on your backend server
Use environment variables for sensitive credentials
Never commit API keys to version control
Rotate keys regularly
Implement Access Controls
Authenticate users before granting stream access
Implement rate limiting on your proxy endpoints
Log all access requests for audit trails
Use short-lived federated tokens (24 hours or less)
Sanitize camera UUID inputs
Validate token expiration
Check user permissions before proxying requests
Implement CSRF protection
Enforce HTTPS for all communications
Implement proper SSL/TLS certificates
Enable HSTS headers
Validate certificate chains
Troubleshooting
Check these common issues:
Verify your backend server is running and accessible
Confirm camera UUID is correct (check Rhombus Console)
Check browser console for error messages
Verify API key has proper permissions
Ensure CORS is properly configured on your server
Test your endpoints: # Test token generation
curl -X POST "https://api2.rhombussystems.com/api/getFederatedToken" \
-H "Content-Type: application/json" \
-d '{"durationSec": 86400}'
# Test media URI retrieval
curl -X POST "https://api2.rhombussystems.com/api/getMediaUris" \
-H "Content-Type: application/json" \
-d '{"cameraUuid": "YOUR_CAMERA_UUID"}'
Buffering or stuttering:
Reduce bufferTimeAtTopQuality for faster startup
Increase stableBufferTime for smoother playback
Check network bandwidth and latency
Verify CDN connectivity
Stream disconnects:
Check token expiration (default 24 hours)
Verify continuous network connectivity
Review firewall rules for CDN access
Check browser console for errors
Token generation fails:
Verify API key is valid and not expired
Check API key permissions in Rhombus Console
Ensure headers are correctly formatted
Verify account is active
Token rejected during playback:
Check token expiration time
Verify URL parameters are properly appended
Ensure token is correctly passed to all requests
Check for special characters in token
Advanced Features
Multi-Camera Grid
Display multiple camera feeds simultaneously:
class MultiCameraPlayer {
constructor () {
this . players = new Map ();
}
async addCamera ( cameraUuid , containerId ) {
const token = await getFederatedSessionToken ();
const mediaUrl = await getMediaUri ( cameraUuid );
const video = document . querySelector ( `# ${ containerId } ` );
const player = dashjs . MediaPlayer (). create ();
player . updateSettings ({
streaming: {
liveCatchup: { enabled: true , maxDrift: 10 },
buffer: { stableBufferTime: 8 }
}
});
player . on ( dashjs . MediaPlayer . events . FRAGMENT_LOADING_STARTED ,
( evt ) => this . modifyRequestURL ( evt , token ));
player . initialize ( video , mediaUrl , true );
this . players . set ( cameraUuid , player );
}
modifyRequestURL ( evt , token ) {
const url = new URL ( evt . url );
url . searchParams . set ( 'x-auth-scheme' , 'federated-token' );
url . searchParams . set ( 'x-auth-ft' , token );
evt . url = url . toString ();
}
}
// Usage
const multiPlayer = new MultiCameraPlayer ();
await multiPlayer . addCamera ( 'camera-uuid-1' , 'video1' );
await multiPlayer . addCamera ( 'camera-uuid-2' , 'video2' );
Custom Controls
Build custom video controls:
class VideoControls {
constructor ( player ) {
this . player = player ;
this . setupEventListeners ();
}
setupEventListeners () {
// Volume control
document . querySelector ( '#volume-slider' ). addEventListener ( 'input' , ( e ) => {
this . player . setVolume ( e . target . value / 100 );
});
// Quality selection
document . querySelector ( '#quality-select' ). addEventListener ( 'change' , ( e ) => {
const quality = parseInt ( e . target . value );
this . player . setQualityFor ( 'video' , quality );
});
// Fullscreen toggle
document . querySelector ( '#fullscreen-btn' ). addEventListener ( 'click' , () => {
const video = document . querySelector ( '#videoPlayer' );
if ( video . requestFullscreen ) {
video . requestFullscreen ();
}
});
}
updateQualityOptions () {
const bitrates = this . player . getBitrateInfoListFor ( 'video' );
const select = document . querySelector ( '#quality-select' );
bitrates . forEach (( bitrate , index ) => {
const option = document . createElement ( 'option' );
option . value = index ;
option . text = ` ${ bitrate . height } p ( ${ Math . round ( bitrate . bitrate / 1000 ) } kbps)` ;
select . appendChild ( option );
});
}
}
Next Steps
Support
Need help with your implementation?
This implementation guide is based on the official Rhombus player example and is regularly updated to reflect best practices.