> ## Documentation Index
> Fetch the complete documentation index at: https://api-docs.rhombus.community/llms.txt
> Use this file to discover all available pages before exploring further.

# 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 fue traducida automáticamente. Si encuentra errores o tiene sugerencias, [contáctenos](mailto:support@rhombus.com).
</Note>

<Info>
  **Ejemplo completo disponible**

  La implementación completa y funcional está disponible en GitHub: [RhombusSystems/player-example](https://github.com/RhombusSystems/player-example)
</Info>

## Descripción general

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

<CardGroup cols={2}>
  <Card title="Transmitir video en vivo" icon="video">
    Muestra feeds de cámara en tiempo real con streaming de tasa de bits adaptativa
  </Card>

  <Card title="Autenticación segura" icon="shield-check">
    Usa tokens de sesión federados para un acceso seguro y por tiempo limitado
  </Card>

  <Card title="Reproducción optimizada" icon="gauge-high">
    Ajustes preconfigurados y optimizados para grabaciones de cámaras de seguridad
  </Card>

  <Card title="Integración sencilla" icon="code">
    Solución lista para usar con una configuración mínima
  </Card>
</CardGroup>

## Arquitectura

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

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

<Steps>
  <Step title="El frontend solicita un token">
    Tu aplicación web solicita la autenticación a tu servidor backend
  </Step>

  <Step title="El backend hace de proxy hacia Rhombus">
    Tu servidor se autentica con la API de Rhombus usando tu API key y devuelve un token federado
  </Step>

  <Step title="El frontend obtiene la URI de medios">
    Usando el token, tu frontend solicita la URI de streaming de la cámara
  </Step>

  <Step title="Reproducción directa del stream">
    El reproductor DashJS se conecta directamente al CDN de Rhombus usando la URI autenticada
  </Step>
</Steps>

## Requisitos previos

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

<AccordionGroup>
  <Accordion title="Credenciales de la API de Rhombus" icon="key">
    Genera una API key desde tu [Consola de Rhombus](https://console.rhombussystems.com/settings/api-management/). Tu servidor backend usará esta clave para autenticarse con la API de Rhombus.

    <Warning>
      Nunca expongas tu API key en el código del frontend. Usa siempre un servidor proxy backend.
    </Warning>
  </Accordion>

  <Accordion title="UUID de la cámara" icon="camera">
    Obtén el UUID de tu cámara desde la Consola de Rhombus o mediante la API:

    ```bash theme={null}
    curl -X POST "https://api2.rhombussystems.com/api/camera/getMinimalCameraStateList" \
      -H "x-auth-apikey: YOUR_API_KEY" \
      -H "x-auth-scheme: api-token"
    ```
  </Accordion>

  <Accordion title="Servidor backend" icon="server">
    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`)
  </Accordion>
</AccordionGroup>

## 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 theme={null}
<!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 theme={null}
// 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 theme={null}
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 horas
            })
        });

        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) {
    // Agrega los parámetros de autenticación a todas las solicitudes del stream
    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();
}
```

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

Implementa la obtención de la URI de medios:

```javascript Media URI Request theme={null}
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 theme={null}
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
            }
        });

        // Configura el modificador de URL para la autenticación
        player.on(dashjs.MediaPlayer.events.FRAGMENT_LOADING_STARTED,
            modifyRequestURL);

        // 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.');
    }
}
```

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

Implementa la funcionalidad de reproducir/pausar:

```javascript Playback Controls theme={null}
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:

<Tabs>
  <Tab title="Node.js/Express">
    ```javascript server.js theme={null}
    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: 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' });
        }
    });

    // 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');
    });
    ```
  </Tab>

  <Tab title="Python/Flask">
    ```python app.py theme={null}
    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()
            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/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)
    ```
  </Tab>
</Tabs>

## Opciones de configuración del reproductor

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

<ParamField path="liveCatchup.enabled" default="true" type="boolean">
  Habilita la recuperación automática hasta el borde en vivo cuando la reproducción se queda atrás
</ParamField>

<ParamField path="liveCatchup.maxDrift" default="10" type="number">
  Deriva máxima permitida desde el borde en vivo, en segundos, antes de que se active la recuperación
</ParamField>

<ParamField path="buffer.stableBufferTime" default="12" type="number">
  Tamaño de búfer objetivo para una reproducción estable, en segundos
</ParamField>

<ParamField path="buffer.fastSwitchEnabled" default="false" type="boolean">
  Deshabilita el cambio rápido de calidad para mantener una calidad de video consistente
</ParamField>

<ParamField path="gaps.jumpGaps" default="true" type="boolean">
  Omite automáticamente los pequeños huecos en el stream
</ParamField>

<ParamField path="stallThreshold" default="0.5" type="number">
  Tiempo en segundos antes de considerar que la reproducción está estancada
</ParamField>

## 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:
</Warning>

<AccordionGroup>
  <Accordion title="Nunca expongas las API keys" icon="triangle-exclamation">
    * Almacena las API keys solo en tu servidor backend
    * Usa variables de entorno para las credenciales sensibles
    * Nunca subas API keys al control de versiones
    * Rota las claves con regularidad
  </Accordion>

  <Accordion title="Implementa controles de acceso" icon="lock">
    * 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 (24 horas o menos)
  </Accordion>

  <Accordion title="Valida todas las solicitudes" icon="shield">
    * 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
  </Accordion>

  <Accordion title="Usa HTTPS en todas partes" icon="globe">
    * Aplica HTTPS para todas las comunicaciones
    * Implementa certificados SSL/TLS adecuados
    * Habilita los encabezados HSTS
    * Valida las cadenas de certificados
  </Accordion>
</AccordionGroup>

## Solución de problemas

<AccordionGroup>
  <Accordion title="El video no carga" icon="circle-xmark">
    **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 API key tenga los permisos adecuados
    5. Asegúrate de que CORS esté configurado correctamente en tu servidor

    **Prueba tus endpoints:**

    ```bash theme={null}
    # 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": 86400}'

    # 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"}'
    ```
  </Accordion>

  <Accordion title="Problemas de reproducción" icon="circle-pause">
    **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 (24 horas por defecto)
    * 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
  </Accordion>

  <Accordion title="Errores de autenticación" icon="key">
    **La generación del token falla:**

    * Verifica que la API key sea válida y no haya expirado
    * Revisa los permisos de la API key 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 pase correctamente a todas las solicitudes
    * Revisa si hay caracteres especiales en el token
  </Accordion>
</AccordionGroup>

## Funciones avanzadas

### Cuadrícula multicámara

Muestra varios feeds de cámara simultáneamente:

```javascript Multi-Camera Setup theme={null}
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();
    }
}

// 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 theme={null}
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

<CardGroup cols={2}>
  <Card title="Ejemplo en GitHub" icon="github" href="https://github.com/RhombusSystems/player-example">
    Clona el ejemplo completo y funcional
  </Card>

  <Card title="Documentación de DashJS" icon="file-code" href="https://github.com/Dash-Industry-Forum/dash.js/wiki">
    Aprende más sobre la configuración de DashJS
  </Card>

  <Card title="Obtén soporte" icon="life-ring" href="https://rhombus.community">
    Únete a la comunidad de desarrolladores para obtener ayuda
  </Card>
</CardGroup>

## Soporte

¿Necesitas ayuda con tu implementación?

* Correo electrónico: [support@rhombus.com](mailto:support@rhombus.com)
* Comunidad: [rhombus.community](https://rhombus.community)

<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.
</Note>
