Ir al contenido

Integración del reproductor de video

Crea un reproductor de video web personalizado para los streams de cámaras de Rhombus con reproducción en vivo HLS, video grabado VOD, desplazamiento y controles de línea de tiempo usando la API.

En esta página
Información

Ejemplo completo disponible

La implementación completa y funcional está disponible en GitHub: RhombusSystems/player-example

Descripción general

La integración del reproductor de video de Rhombus te permite:

Transmitir video en vivo

Muestra feeds de cámara en tiempo real con streaming de tasa de bits adaptativa

Autenticación segura

Usa tokens de sesión federados para un acceso seguro y por tiempo limitado

Reproducción optimizada

Ajustes preconfigurados y optimizados para grabaciones de cámaras de seguridad

Integración directa

Solución lista para usar con una configuración mínima

Arquitectura

La implementación del reproductor de video sigue una arquitectura de tres niveles:

Cargando el diagrama…
  1. El frontend solicita un token

    Tu aplicación web solicita la autenticación a tu servidor backend

  2. El backend hace de proxy hacia Rhombus

    Tu servidor se autentica con la API de Rhombus usando tu clave de API y devuelve un token federado

  3. El frontend obtiene la URI de medios

    Tu frontend le pide a tu backend la URI de streaming de la cámara; tu backend llama a getMediaUris con la clave de API

  4. Reproducción directa del stream

    DashJS se conecta directamente a Rhombus y agrega el token a cada solicitud: el manifiesto y cada segmento

Requisitos previos

Antes de implementar el reproductor de video, asegúrate de tener:

Credenciales de la API de Rhombus

Genera una clave de API desde tu Consola de Rhombus. Tu servidor backend usará esta clave para autenticarse con la API de Rhombus.

Advertencia

Nunca expongas tu clave de API en el código del frontend. Usa siempre un servidor proxy backend.

UUID de la cámara

Obtén el UUID de tu cámara desde la Consola de Rhombus o mediante la API:

bash
curl -X POST "https://api2.rhombussystems.com/api/camera/getMinimalCameraStateList" \
  -H "x-auth-apikey: YOUR_API_KEY" \
  -H "x-auth-scheme: api-token"
Servidor backend

Configura un servidor proxy para gestionar la autenticación de la API. Tu servidor debe implementar dos endpoints:

  • Endpoint de generación de token (reenvía a /org/generateFederatedSessionToken)
  • Endpoint de URI de medios (reenvía a /camera/getMediaUris)

Guía de implementación

Paso 1: Configura tu página HTML

Crea una estructura HTML básica con el reproductor DashJS:

index.htmlhtml
<!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: 20px;
            font-family: Arial, sans-serif;
            background-color: #1a1a1a;
        }
        #video-container {
            max-width: 1280px;
            margin: 0 auto;
        }
        video {
            width: 100%;
            background-color: #000;
            border-radius: 8px;
        }
        .controls {
            margin-top: 20px;
            text-align: center;
        }
        button {
            padding: 10px 20px;
            margin: 0 5px;
            background-color: #2563EB;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
        }
        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>

Paso 2: Configura los ajustes del reproductor

Crea tu archivo de configuración del reproductor:

player.jsjavascript
// Configuración - Reemplaza con tus valores
const CAMERA_UUID = "YOUR_CAMERA_UUID_HERE";
// Apunta BASE_URL a TU servidor proxy backend, nunca directamente a
// api2.rhombussystems.com. La clave API permanece en tu servidor; el navegador
// solo se comunica con tu proxy.
const BASE_URL = "http://localhost:3000";       // tu servidor backend
const GET_FEDERATED_TOKEN_PATH = "/api/federated-token";  // ruta de tu proxy
const GET_MEDIA_URIS_PATH = "/api/media-uris";            // ruta de tu proxy

// Variables globales
let player;
let federatedToken;

// Inicializa el reproductor al cargar la página
document.addEventListener('DOMContentLoaded', async () => {
    await initializePlayer();
});

Paso 3: Implementa la autenticación

Agrega la funcionalidad de gestión de tokens:

Token Managementjavascript
async function getFederatedSessionToken(cameraUuid = CAMERA_UUID) {
    try {
        const response = await fetch(`${BASE_URL}${GET_FEDERATED_TOKEN_PATH}`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                cameraUuid  // tu backend genera un token de corta duración solo para esta cámara
            })
        });

        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;
    }
}

// Agrega el token a cada solicitud del stream: el manifiesto y cada segmento
function withAuth(url) {
    if (url.includes('x-auth-ft=')) return url;
    const auth = `x-auth-scheme=federated-token&x-auth-ft=${encodeURIComponent(federatedToken)}`;
    return `${url}${url.includes('?') ? '&' : '?'}${auth}`;
}

Paso 4: Obtén la URI del stream de medios

Implementa la obtención de la URI de medios:

Media URI Requestjavascript
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.wanLiveMpdUri;  // URL del manifiesto DASH
    } catch (error) {
        console.error('Error getting media URI:', error);
        throw error;
    }
}

Paso 5: Inicializa el reproductor DashJS

Configura el reproductor de video con ajustes optimizados:

Player Initializationjavascript
async function initializePlayer() {
    try {
        // Obtén el token de autenticación
        await getFederatedSessionToken();

        // Obtén la URL del stream de medios
        const mediaUrl = await getMediaUri();

        // Inicializa el reproductor DashJS
        const video = document.querySelector("#videoPlayer");
        player = dashjs.MediaPlayer().create();

        // Configura los ajustes del reproductor optimizados para cámaras de seguridad
        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,
                scheduleWhilePaused: false
            }
        });

        // Agrega el token a cada solicitud de dash.js, incluido el manifiesto
        player.addRequestInterceptor((request) => {
            request.url = withAuth(request.url);
            return Promise.resolve(request);
        });

        // Inicializa y reproduce
        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.');
    }
}

Las URL de los segmentos DASH no llevan la cadena de consulta del manifiesto, así que el interceptor agrega el token a la solicitud del manifiesto y a cada segmento. addRequestInterceptor es la API de dash.js 5, la versión que sirve cdn.dashjs.org/latest. Consulta Medios y transmisiones.

Paso 6: Agrega controles de reproducción

Implementa la funcionalidad de reproducir/pausar:

Playback Controlsjavascript
function play() {
    if (player) {
        player.play();
    }
}

function pause() {
    if (player) {
        player.pause();
    }
}

// Opcional: Maneja los eventos del reproductor
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);
    });
}

Implementación del servidor backend

Tu servidor backend debe hacer de proxy de las solicitudes hacia la API de Rhombus. Aquí tienes un ejemplo usando Node.js/Express:

server.jsjavascript
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';

// Endpoint de token federado (el GET_FEDERATED_TOKEN_PATH del navegador)
app.post('/api/federated-token', async (req, res) => {
    try {
        const response = await axios.post(
            `${RHOMBUS_BASE_URL}/org/generateFederatedSessionToken`,
            {
                durationSec: 3600,               // 1 hora; no hay valor predeterminado
                deviceUUid: req.body.cameraUuid  // solo los medios de esta cámara
            },
            {
                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' });
    }
});

// Endpoint de URI de medios (el GET_MEDIA_URIS_PATH del navegador)
app.post('/api/media-uris', 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');
});

Opciones de configuración del reproductor

El reproductor DashJS se puede personalizar con varios ajustes optimizados para distintos casos de uso:

liveCatchup.enabledbooleanpathpredeterminado: true

Habilita la recuperación automática hasta el borde en vivo cuando la reproducción se queda atrás

liveCatchup.maxDriftnumberpathpredeterminado: 10

Deriva máxima permitida desde el borde en vivo, en segundos, antes de que se active la recuperación

buffer.stableBufferTimenumberpathpredeterminado: 12

Tamaño de búfer objetivo para una reproducción estable, en segundos

buffer.fastSwitchEnabledbooleanpathpredeterminado: false

Deshabilita el cambio rápido de calidad para mantener una calidad de video consistente

gaps.jumpGapsbooleanpathpredeterminado: true

Omite automáticamente los pequeños huecos en el stream

stallThresholdnumberpathpredeterminado: 0.5

Tiempo en segundos antes de considerar que la reproducción está estancada

Mejores prácticas de seguridad

Advertencia

Requisitos de seguridad críticos

Sigue estas prácticas de seguridad para proteger tus credenciales de la API y garantizar un streaming seguro:

Nunca expongas las claves de API
  • Almacena las claves de API solo en tu servidor backend
  • Usa variables de entorno para las credenciales sensibles
  • Nunca subas claves de API al control de versiones
  • Rota las claves con regularidad
Implementa controles de acceso
  • Autentica a los usuarios antes de otorgar acceso al stream
  • Implementa límites de tasa en tus endpoints de proxy
  • Registra todas las solicitudes de acceso para las pistas de auditoría
  • Usa tokens federados de corta duración limitados a una cámara (deviceUUid) y genera uno nuevo antes de que expire
Valida todas las solicitudes
  • Sanea las entradas del UUID de la cámara
  • Valida la expiración del token
  • Verifica los permisos del usuario antes de hacer de proxy de las solicitudes
  • Implementa protección CSRF
Usa HTTPS en todas partes
  • Aplica HTTPS para todas las comunicaciones
  • Implementa certificados SSL/TLS adecuados
  • Habilita los encabezados HSTS
  • Valida las cadenas de certificados

Solución de problemas

El video no carga

Verifica estos problemas comunes:

  1. Verifica que tu servidor backend esté en ejecución y sea accesible
  2. Confirma que el UUID de la cámara sea correcto (revisa la Consola de Rhombus)
  3. Revisa la consola del navegador en busca de mensajes de error
  4. Verifica que la clave de API tenga los permisos adecuados
  5. Asegúrate de que CORS esté configurado correctamente en tu servidor

Prueba tus endpoints:

bash
# Prueba la generación de token
curl -X POST "https://api2.rhombussystems.com/api/org/generateFederatedSessionToken" \
  -H "x-auth-scheme: api-token" \
  -H "x-auth-apikey: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"durationSec": 3600}'

# Prueba la obtención de la URI de medios
curl -X POST "https://api2.rhombussystems.com/api/camera/getMediaUris" \
  -H "x-auth-scheme: api-token" \
  -H "x-auth-apikey: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"cameraUuid": "YOUR_CAMERA_UUID"}'
Problemas de reproducción

Almacenamiento en búfer o tartamudeo:

  • Reduce bufferTimeAtTopQuality para un inicio más rápido
  • Aumenta stableBufferTime para una reproducción más fluida
  • Verifica el ancho de banda y la latencia de la red
  • Verifica la conectividad del CDN

Desconexiones del stream:

  • Verifica la expiración del token: dura el durationSec que pidió tu backend (no hay valor predeterminado). Genera uno nuevo antes de que expire
  • Verifica que la conectividad de red sea continua
  • Revisa las reglas del firewall para el acceso al CDN
  • Revisa la consola del navegador en busca de errores
Errores de autenticación

La generación del token falla:

  • Verifica que la clave de API exista y pertenezca a la región de tu organización (las claves no expiran, pero las eliminadas dejan de funcionar)
  • Revisa los permisos de la clave de API en la Consola de Rhombus
  • Asegúrate de que los encabezados tengan el formato correcto
  • Verifica que la cuenta esté activa

El token se rechaza durante la reproducción:

  • Verifica el tiempo de expiración del token
  • Verifica que los parámetros de la URL se agreguen correctamente
  • Asegúrate de que el token se agregue a cada solicitud, incluido el manifiesto (consulta el Paso 5)
  • Revisa si hay caracteres especiales en el token

Funciones avanzadas

Cuadrícula multicámara

Muestra varios feeds de cámara simultáneamente:

Multi-Camera Setupjavascript
class MultiCameraPlayer {
    constructor() {
        this.players = new Map();
    }

    async addCamera(cameraUuid, containerId) {
        const token = await getFederatedSessionToken(cameraUuid);  // un token por cámara
        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.addRequestInterceptor((request) => {
            request.url = this.withAuth(request.url, token);
            return Promise.resolve(request);
        });

        player.initialize(video, mediaUrl, true);
        this.players.set(cameraUuid, player);
    }

    withAuth(url, token) {
        if (url.includes('x-auth-ft=')) return url;
        const auth = `x-auth-scheme=federated-token&x-auth-ft=${encodeURIComponent(token)}`;
        return `${url}${url.includes('?') ? '&' : '?'}${auth}`;
    }
}

// Uso
const multiPlayer = new MultiCameraPlayer();
await multiPlayer.addCamera('camera-uuid-1', 'video1');
await multiPlayer.addCamera('camera-uuid-2', 'video2');

Controles personalizados

Crea controles de video personalizados:

Custom Controlsjavascript
class VideoControls {
    constructor(player) {
        this.player = player;
        this.setupEventListeners();
    }

    setupEventListeners() {
        // Control de volumen
        document.querySelector('#volume-slider').addEventListener('input', (e) => {
            this.player.setVolume(e.target.value / 100);
        });

        // Selección de calidad
        document.querySelector('#quality-select').addEventListener('change', (e) => {
            const quality = parseInt(e.target.value);
            this.player.setQualityFor('video', quality);
        });

        // Alternar pantalla completa
        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);
        });
    }
}

Próximos pasos

Soporte

¿Necesitas ayuda con tu implementación?