> ## Índice de la documentación
> Consulta el índice completo de la documentación en: https://developer.rhombus.com/llms.txt
> Úsalo para descubrir todas las páginas disponibles antes de seguir explorando.

# 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.

> **Note**: Esta página se tradujo automáticamente. Si encuentras errores o tienes sugerencias, [escríbenos](mailto:support@rhombus.com).

> **Info**: **Ejemplo completo disponible**
>
> La implementación completa y funcional está disponible en GitHub: [RhombusSystems/player-example](https://github.com/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:

```mermaid
graph LR
    A[Web Browser] --> B[Your Backend Server]
    B --> C[Rhombus API]
    C --> B
    B --> A
    A --> D[Rhombus Media CDN]
```

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](https://console.rhombussystems.com/settings/integrations/api-tokens/). Tu servidor backend usará esta clave para autenticarse con la API de Rhombus.

> **Warning**: 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:

```html index.html
<!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:

```javascript player.js
// 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:

```javascript Token Management
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:

```javascript Media URI Request
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:

```javascript Player Initialization
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](https://developer.rhombus.com/es/authentication/media#dash).

### Paso 6: Agrega controles de reproducción

Implementa la funcionalidad de reproducir/pausar:

```javascript Playback Controls
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:

**Node.js/Express**

```javascript server.js
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');
});
```

**Python/Flask**

```python app.py
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/federated-token', methods=['POST'])
def get_federated_token():
    try:
        data = request.get_json()

        response = requests.post(
            f'{RHOMBUS_BASE_URL}/org/generateFederatedSessionToken',
            json={
                'durationSec': 3600,                   # 1 hora; no hay valor predeterminado
                'deviceUUid': data.get('cameraUuid'),  # solo los medios de esta cámara
            },
            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/media-uris', 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)
```

## Opciones de configuración del reproductor

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

- **`liveCatchup.enabled` (path, boolean, default: true)**: Habilita la recuperación automática hasta el borde en vivo cuando la reproducción se queda atrás
- **`liveCatchup.maxDrift` (path, number, default: 10)**: Deriva máxima permitida desde el borde en vivo, en segundos, antes de que se active la recuperación
- **`buffer.stableBufferTime` (path, number, default: 12)**: Tamaño de búfer objetivo para una reproducción estable, en segundos
- **`buffer.fastSwitchEnabled` (path, boolean, default: false)**: Deshabilita el cambio rápido de calidad para mantener una calidad de video consistente
- **`gaps.jumpGaps` (path, boolean, default: true)**: Omite automáticamente los pequeños huecos en el stream
- **`stallThreshold` (path, number, default: 0.5)**: Tiempo en segundos antes de considerar que la reproducción está estancada

## Mejores prácticas de seguridad

> **Warning**: **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:

```javascript Multi-Camera Setup
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:

```javascript Custom Controls
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

- **[Ejemplo en GitHub](https://github.com/RhombusSystems/player-example)**: Clona el ejemplo completo y funcional
- **[Documentación de DashJS](https://github.com/Dash-Industry-Forum/dash.js/wiki)**: Aprende más sobre la configuración de DashJS
- **[Soporte de Rhombus](mailto:support@rhombus.com)**: Contacta al Soporte de Rhombus para obtener ayuda con tu integración.

## Soporte

¿Necesitas ayuda con tu implementación?

- Correo electrónico: <support@rhombus.com>

> **Note**: Esta guía de implementación se basa en el [ejemplo de reproductor de Rhombus](https://github.com/RhombusSystems/player-example) oficial y se actualiza periódicamente para reflejar las mejores prácticas.
